diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index b6f7abed6f3c9..eaeadc03b5dd6 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -1211,9 +1211,7 @@ pub fn handle_options(early_dcx: &EarlyDiagCtxt, args: &[String]) -> Option Option) -> &'static Option) -> Self { - // `true` if this is a feature-staged build, i.e., on the beta or stable channel. + /// Determines whether this compiler allows unstable options/features, + /// according to whether it was built as a stable/beta compiler or a nightly + /// compiler, and taking `RUSTC_BOOTSTRAP` into account. + #[inline(never)] + pub fn from_environment() -> Self { + Self::from_environment_inner(|name| env::var(name)) + } + + /// Unit tests can pass a mock `std::env::var` instead of modifying the real environment. + fn from_environment_inner( + env_var: impl Fn(&str) -> Result, // std::env::var + ) -> Self { + // If `CFG_DISABLE_UNSTABLE_FEATURES` was true when this compiler was + // built, it is a stable/beta compiler that forbids unstable features. let disable_unstable_features = option_env!("CFG_DISABLE_UNSTABLE_FEATURES").is_some_and(|s| s != "0"); - // Returns whether `krate` should be counted as unstable - let is_unstable_crate = - |var: &str| krate.is_some_and(|name| var.split(',').any(|new_krate| new_krate == name)); - - let bootstrap = std::env::var("RUSTC_BOOTSTRAP").ok(); - if let Some(val) = bootstrap.as_deref() { - match val { - val if val == "1" || is_unstable_crate(val) => return UnstableFeatures::Cheat, - // Hypnotize ourselves so that we think we are a stable compiler and thus don't - // allow any unstable features. - "-1" => return UnstableFeatures::Disallow, - _ => {} - } + let default_answer = if disable_unstable_features { + UnstableFeatures::Disallow + } else { + UnstableFeatures::Allow + }; + + // Returns true if the given list of comma-separated crate names + // contains `CARGO_CRATE_NAME`. + // + // This is not actually used by bootstrap; it only exists so that when + // cargo sees a third-party crate trying to set `RUSTC_BOOTSTRAP=1` in + // build.rs, it can suggest a somewhat less horrifying alternative. + // + // See for context. + let includes_current_crate = |names: &str| -> bool { + let Ok(crate_name) = env_var("CARGO_CRATE_NAME") else { return false }; + // Normalize `-` in crate names to `_`. + let crate_name = crate_name.replace('-', "_"); + names.replace('-', "_").split(',').any(|name| name == crate_name) + }; + + match env_var("RUSTC_BOOTSTRAP").as_deref() { + // Force the compiler to act as nightly, even if it's stable. + Ok("1") => UnstableFeatures::Cheat, + // Force the compiler to act as stable, even if it's nightly. + Ok("-1") => UnstableFeatures::Disallow, + // Force nightly if `RUSTC_BOOTSTRAP` contains the current crate name. + Ok(names) if includes_current_crate(names) => UnstableFeatures::Cheat, + _ => default_answer, } - - if disable_unstable_features { UnstableFeatures::Disallow } else { UnstableFeatures::Allow } } pub fn is_nightly_build(&self) -> bool { diff --git a/compiler/rustc_feature/src/tests.rs b/compiler/rustc_feature/src/tests.rs index cc0e1f3120965..a105d358f8d38 100644 --- a/compiler/rustc_feature/src/tests.rs +++ b/compiler/rustc_feature/src/tests.rs @@ -1,10 +1,19 @@ +use std::env; + use super::UnstableFeatures; +fn unstable_features(rustc_bootstrap: &str, crate_name: Option<&str>) -> UnstableFeatures { + UnstableFeatures::from_environment_inner(|name| match name { + "RUSTC_BOOTSTRAP" => Ok(rustc_bootstrap.to_owned()), + "CARGO_CRATE_NAME" => crate_name.map(str::to_owned).ok_or(env::VarError::NotPresent), + _ => Err(env::VarError::NotPresent), + }) +} + #[test] fn rustc_bootstrap_parsing() { - let is_bootstrap = |env, krate| { - std::env::set_var("RUSTC_BOOTSTRAP", env); - matches!(UnstableFeatures::from_environment(krate), UnstableFeatures::Cheat) + let is_bootstrap = |rustc_bootstrap, crate_name| { + matches!(unstable_features(rustc_bootstrap, crate_name), UnstableFeatures::Cheat) }; assert!(is_bootstrap("1", None)); assert!(is_bootstrap("1", Some("x"))); @@ -13,6 +22,7 @@ fn rustc_bootstrap_parsing() { // RUSTC_BOOTSTRAP allows multiple comma-delimited crates assert!(is_bootstrap("x,y,z", Some("x"))); assert!(is_bootstrap("x,y,z", Some("y"))); + assert!(is_bootstrap("x,y-utils", Some("y_utils"))); // Crate that aren't specified do not get unstable features assert!(!is_bootstrap("x", Some("a"))); assert!(!is_bootstrap("x,y,z", Some("a"))); @@ -22,10 +32,8 @@ fn rustc_bootstrap_parsing() { assert!(!is_bootstrap("0", None)); // `RUSTC_BOOTSTRAP=-1` is force-stable, no unstable features allowed. - let is_force_stable = |krate| { - std::env::set_var("RUSTC_BOOTSTRAP", "-1"); - matches!(UnstableFeatures::from_environment(krate), UnstableFeatures::Disallow) - }; + let is_force_stable = + |crate_name| matches!(unstable_features("-1", crate_name), UnstableFeatures::Disallow); assert!(is_force_stable(None)); // Does not support specifying any crate. assert!(is_force_stable(Some("x"))); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index f6e6fd33c48ea..4b22ae7b5b439 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -40,6 +40,7 @@ use crate::{EarlyDiagCtxt, HashStableContext, Session, filesearch, lint}; mod cfg; mod native_libs; +pub mod nightly_options; pub mod sigpipe; /// The different settings that the `-C strip` flag can have. @@ -1824,7 +1825,7 @@ pub fn parse_crate_edition(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches }; if !edition.is_stable() && !nightly_options::is_unstable_enabled(matches) { - let is_nightly = nightly_options::match_is_nightly_build(matches); + let is_nightly = nightly_options::is_nightly_build(); let msg = if !is_nightly { format!( "the crate requires edition {edition}, but the latest edition supported by this Rust version is {LATEST_STABLE_EDITION}" @@ -2509,7 +2510,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M let debuginfo_compression = unstable_opts.debuginfo_compression; let crate_name = matches.opt_str("crate-name"); - let unstable_features = UnstableFeatures::from_environment(crate_name.as_deref()); + let unstable_features = UnstableFeatures::from_environment(); // Parse any `-l` flags, which link to native libraries. let libs = parse_native_libs(early_dcx, &unstable_opts, unstable_features, matches); @@ -2689,77 +2690,6 @@ pub fn parse_crate_types_from_list(list_list: Vec) -> Result bool { - match_is_nightly_build(matches) - && matches.opt_strs("Z").iter().any(|x| *x == "unstable-options") - } - - pub fn match_is_nightly_build(matches: &getopts::Matches) -> bool { - is_nightly_build(matches.opt_str("crate-name").as_deref()) - } - - fn is_nightly_build(krate: Option<&str>) -> bool { - UnstableFeatures::from_environment(krate).is_nightly_build() - } - - pub fn check_nightly_options( - early_dcx: &EarlyDiagCtxt, - matches: &getopts::Matches, - flags: &[RustcOptGroup], - ) { - let has_z_unstable_option = matches.opt_strs("Z").iter().any(|x| *x == "unstable-options"); - let really_allows_unstable_options = match_is_nightly_build(matches); - let mut nightly_options_on_stable = 0; - - for opt in flags.iter() { - if opt.stability == OptionStability::Stable { - continue; - } - if !matches.opt_present(opt.name) { - continue; - } - if opt.name != "Z" && !has_z_unstable_option { - early_dcx.early_fatal(format!( - "the `-Z unstable-options` flag must also be passed to enable \ - the flag `{}`", - opt.name - )); - } - if really_allows_unstable_options { - continue; - } - match opt.stability { - OptionStability::Unstable => { - nightly_options_on_stable += 1; - let msg = format!( - "the option `{}` is only accepted on the nightly compiler", - opt.name - ); - let _ = early_dcx.early_err(msg); - } - OptionStability::Stable => {} - } - } - if nightly_options_on_stable > 0 { - early_dcx - .early_help("consider switching to a nightly toolchain: `rustup default nightly`"); - early_dcx.early_note("selecting a toolchain with `+toolchain` arguments require a rustup proxy; see "); - early_dcx.early_note("for more information about Rust's stability policy, see "); - early_dcx.early_fatal(format!( - "{} nightly option{} were parsed", - nightly_options_on_stable, - if nightly_options_on_stable > 1 { "s" } else { "" } - )); - } - } -} - impl fmt::Display for CrateType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { diff --git a/compiler/rustc_session/src/config/nightly_options.rs b/compiler/rustc_session/src/config/nightly_options.rs new file mode 100644 index 0000000000000..b6ce00823d04b --- /dev/null +++ b/compiler/rustc_session/src/config/nightly_options.rs @@ -0,0 +1,59 @@ +use rustc_feature::UnstableFeatures; + +use crate::EarlyDiagCtxt; +use crate::config::{OptionStability, RustcOptGroup}; + +pub fn is_unstable_enabled(matches: &getopts::Matches) -> bool { + is_nightly_build() && matches.opt_strs("Z").iter().any(|x| *x == "unstable-options") +} + +pub fn is_nightly_build() -> bool { + UnstableFeatures::from_environment().is_nightly_build() +} + +pub fn check_nightly_options( + early_dcx: &EarlyDiagCtxt, + matches: &getopts::Matches, + flags: &[RustcOptGroup], +) { + let has_z_unstable_option = matches.opt_strs("Z").iter().any(|x| *x == "unstable-options"); + let really_allows_unstable_options = is_nightly_build(); + let mut nightly_options_on_stable = 0; + + for opt in flags.iter() { + if opt.stability == OptionStability::Stable { + continue; + } + if !matches.opt_present(opt.name) { + continue; + } + if opt.name != "Z" && !has_z_unstable_option { + early_dcx.early_fatal(format!( + "the `-Z unstable-options` flag must also be passed to enable the flag `{}`", + opt.name + )); + } + if really_allows_unstable_options { + continue; + } + match opt.stability { + OptionStability::Unstable => { + nightly_options_on_stable += 1; + let msg = + format!("the option `{}` is only accepted on the nightly compiler", opt.name); + let _ = early_dcx.early_err(msg); + } + OptionStability::Stable => {} + } + } + if nightly_options_on_stable > 0 { + early_dcx.early_help("consider switching to a nightly toolchain: `rustup default nightly`"); + early_dcx.early_note("selecting a toolchain with `+toolchain` arguments require a rustup proxy; see "); + early_dcx.early_note("for more information about Rust's stability policy, see "); + early_dcx.early_fatal(format!( + "{} nightly option{} were parsed", + nightly_options_on_stable, + if nightly_options_on_stable > 1 { "s" } else { "" } + )); + } +} diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 21c1165511099..e3616e53bb02d 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -251,7 +251,7 @@ impl ParseSess { pub fn with_dcx(dcx: DiagCtxt, source_map: Lrc) -> Self { Self { dcx, - unstable_features: UnstableFeatures::from_environment(None), + unstable_features: UnstableFeatures::from_environment(), config: Cfg::default(), check_config: CheckCfg::default(), edition: ExpnId::root().expn_data().edition, diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 5071ed1c47faa..db7753a80d8ac 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -409,7 +409,7 @@ impl Options { println_condition(p.condition); } - if nightly_options::match_is_nightly_build(matches) { + if nightly_options::is_nightly_build() { println!("\nPasses run with `--show-coverage`:"); for p in passes::COVERAGE_PASSES { print!("{:>20}", p.pass.name); @@ -653,7 +653,7 @@ impl Options { &matches.opt_strs("html-after-content"), &matches.opt_strs("markdown-before-content"), &matches.opt_strs("markdown-after-content"), - nightly_options::match_is_nightly_build(matches), + nightly_options::is_nightly_build(), dcx, &mut id_map, edition, @@ -775,8 +775,7 @@ impl Options { let with_examples = matches.opt_strs("with-examples"); let call_locations = crate::scrape_examples::load_call_locations(with_examples, dcx); - let unstable_features = - rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); + let unstable_features = rustc_feature::UnstableFeatures::from_environment(); let options = Options { bin_crate, proc_macro_crate, diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index a562a9eee717b..ffbb10aa3f2a2 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -260,7 +260,7 @@ pub(crate) fn create_config( cg: codegen_options, externs, target_triple: target, - unstable_features: UnstableFeatures::from_environment(crate_name.as_deref()), + unstable_features: UnstableFeatures::from_environment(), actually_rustdoc: true, resolve_doc_links, unstable_opts,