@@ -67,6 +67,7 @@ pub(crate) fn options() -> Result<Options, OptionError> {
6767 let mut rustc_quit_on_rmeta_raw = None ;
6868 let mut rustc_output_format_raw = None ;
6969 let mut flags = Flags :: new ( ) ;
70+ let mut require_explicit_unstable_features = None ;
7071 flags. define_repeated_flag ( "--subst" , "" , & mut subst_mapping_raw) ;
7172 flags. define_flag ( "--stable-status-file" , "" , & mut stable_status_file_raw) ;
7273 flags. define_flag ( "--volatile-status-file" , "" , & mut volatile_status_file_raw) ;
@@ -114,6 +115,12 @@ pub(crate) fn options() -> Result<Options, OptionError> {
114115 Default: `rendered`",
115116 & mut rustc_output_format_raw,
116117 ) ;
118+ flags. define_flag (
119+ "--require-explicit-unstable-features" ,
120+ "If set, an empty -Zallow-features= will be added to the rustc command line whenever no \
121+ other -Zallow-features= is present in the rustc flags.",
122+ & mut require_explicit_unstable_features,
123+ ) ;
117124
118125 let mut child_args = match flags
119126 . parse ( env:: args ( ) . collect ( ) )
@@ -191,9 +198,13 @@ pub(crate) fn options() -> Result<Options, OptionError> {
191198 & volatile_stamp_mappings,
192199 & subst_mappings,
193200 ) ;
201+
202+ let require_explicit_unstable_features =
203+ require_explicit_unstable_features. is_some_and ( |s| s == "true" ) ;
204+
194205 // Append all the arguments fetched from files to those provided via command line.
195206 child_args. append ( & mut file_arguments) ;
196- let child_args = prepare_args ( child_args, & subst_mappings) ?;
207+ let child_args = prepare_args ( child_args, & subst_mappings, require_explicit_unstable_features ) ?;
197208 // Split the executable path from the rest of the arguments.
198209 let ( exec_path, args) = child_args. split_first ( ) . ok_or_else ( || {
199210 OptionError :: Generic (
@@ -243,6 +254,10 @@ fn env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionE
243254 Ok ( env_vars)
244255}
245256
257+ fn is_allow_features_flag ( arg : & str ) -> bool {
258+ arg. starts_with ( "-Zallow-features=" ) || arg. starts_with ( "allow-features=" )
259+ }
260+
246261fn prepare_arg ( mut arg : String , subst_mappings : & [ ( String , String ) ] ) -> String {
247262 for ( f, replace_with) in subst_mappings {
248263 let from = format ! ( "${{{f}}}" ) ;
@@ -251,11 +266,12 @@ fn prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String {
251266 arg
252267}
253268
254- /// Apply substitutions to the given param file. Returns the new filename.
269+ /// Apply substitutions to the given param file. Returns the new filename and whether any
270+ /// allow-features flags were found.
255271fn prepare_param_file (
256272 filename : & str ,
257273 subst_mappings : & [ ( String , String ) ] ,
258- ) -> Result < String , OptionError > {
274+ ) -> Result < ( String , bool ) , OptionError > {
259275 let expanded_file = format ! ( "{filename}.expanded" ) ;
260276 let format_err = |err : io:: Error | {
261277 OptionError :: Generic ( format ! (
@@ -271,38 +287,48 @@ fn prepare_param_file(
271287 out : & mut io:: BufWriter < File > ,
272288 subst_mappings : & [ ( String , String ) ] ,
273289 format_err : & impl Fn ( io:: Error ) -> OptionError ,
274- ) -> Result < ( ) , OptionError > {
290+ ) -> Result < bool , OptionError > {
291+ let mut has_allow_features_flag = false ;
275292 for arg in read_file_to_array ( filename) . map_err ( OptionError :: Generic ) ? {
276293 let arg = prepare_arg ( arg, subst_mappings) ;
294+ has_allow_features_flag |= is_allow_features_flag ( & arg) ;
277295 if let Some ( arg_file) = arg. strip_prefix ( '@' ) {
278- process_file ( arg_file, out, subst_mappings, format_err) ?;
296+ has_allow_features_flag |= process_file ( arg_file, out, subst_mappings, format_err) ?;
279297 } else {
280298 writeln ! ( out, "{arg}" ) . map_err ( format_err) ?;
281299 }
282300 }
283- Ok ( ( ) )
301+ Ok ( has_allow_features_flag )
284302 }
285- process_file ( filename, & mut out, subst_mappings, & format_err) ?;
286- Ok ( expanded_file)
303+ let has_allow_features_flag = process_file ( filename, & mut out, subst_mappings, & format_err) ?;
304+ Ok ( ( expanded_file, has_allow_features_flag ) )
287305}
288306
289307/// Apply substitutions to the provided arguments, recursing into param files.
290308fn prepare_args (
291309 args : Vec < String > ,
292310 subst_mappings : & [ ( String , String ) ] ,
311+ require_explicit_unstable_features : bool ,
293312) -> Result < Vec < String > , OptionError > {
294- args. into_iter ( )
295- . map ( |arg| {
296- let arg = prepare_arg ( arg, subst_mappings) ;
297- if let Some ( param_file) = arg. strip_prefix ( '@' ) {
298- // Note that substitutions may also apply to the param file path!
299- prepare_param_file ( param_file, subst_mappings)
300- . map ( |filename| format ! ( "@{filename}" ) )
301- } else {
302- Ok ( arg)
303- }
304- } )
305- . collect ( )
313+ let mut allowed_features = false ;
314+ let mut processed_args = Vec :: < String > :: new ( ) ;
315+ for arg in args. into_iter ( ) {
316+ let arg = prepare_arg ( arg, subst_mappings) ;
317+ if let Some ( param_file) = arg. strip_prefix ( '@' ) {
318+ // Note that substitutions may also apply to the param file path!
319+ let ( file, allowed) = prepare_param_file ( param_file, subst_mappings)
320+ . map ( |( filename, af) | ( format ! ( "@{filename}" ) , af) ) ?;
321+ allowed_features |= allowed;
322+ processed_args. push ( file) ;
323+ } else {
324+ allowed_features |= is_allow_features_flag ( & arg) ;
325+ processed_args. push ( arg) ;
326+ }
327+ }
328+ if !allowed_features && require_explicit_unstable_features {
329+ processed_args. push ( "-Zallow-features=" . to_string ( ) ) ;
330+ }
331+ Ok ( processed_args)
306332}
307333
308334fn environment_block (
@@ -334,3 +360,33 @@ fn environment_block(
334360 }
335361 environment_variables
336362}
363+
364+ #[ cfg( test) ]
365+ mod test {
366+ use super :: * ;
367+
368+ #[ test]
369+ fn test_enforce_allow_features_flag_user_didnt_say ( ) {
370+ let args = vec ! [ "rustc" . to_string( ) ] ;
371+ let subst_mappings: Vec < ( String , String ) > = vec ! [ ] ;
372+ let args = prepare_args ( args, & subst_mappings) . unwrap ( ) ;
373+ assert_eq ! ( args, vec![ "rustc" . to_string( ) , "-Zallow-features=" . to_string( ) , ] ) ;
374+ }
375+
376+ #[ test]
377+ fn test_enforce_allow_features_flag_user_requested_something ( ) {
378+ let args = vec ! [
379+ "rustc" . to_string( ) ,
380+ "-Zallow-features=whitespace_instead_of_curly_braces" . to_string( ) ,
381+ ] ;
382+ let subst_mappings: Vec < ( String , String ) > = vec ! [ ] ;
383+ let args = prepare_args ( args, & subst_mappings) . unwrap ( ) ;
384+ assert_eq ! (
385+ args,
386+ vec![
387+ "rustc" . to_string( ) ,
388+ "-Zallow-features=whitespace_instead_of_curly_braces" . to_string( ) ,
389+ ]
390+ ) ;
391+ }
392+ }
0 commit comments