diff --git a/Cargo.lock b/Cargo.lock index 7092e498e937e..b0d1c12ebae65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4298,6 +4298,7 @@ dependencies = [ name = "rustc_lint_defs" version = "0.0.0" dependencies = [ + "rustc_ast", "rustc_data_structures", "rustc_error_messages", "rustc_hir_id", diff --git a/compiler/rustc_ast/src/attr/version.rs b/compiler/rustc_ast/src/attr/version.rs index 506a7a291813c..62755a2456ae1 100644 --- a/compiler/rustc_ast/src/attr/version.rs +++ b/compiler/rustc_ast/src/attr/version.rs @@ -24,14 +24,24 @@ impl RustcVersion { } }) } + + /// Parse a [`RustcVersion`] with an optional patch version, ignoring suffixes such as `-dev` or `-nightly`. fn parse_str(value: &str) -> Option { - // Ignore any suffixes such as "-dev" or "-nightly". let mut components = value.split('-').next().unwrap().splitn(3, '.'); let major = components.next()?.parse().ok()?; let minor = components.next()?.parse().ok()?; let patch = components.next().unwrap_or("0").parse().ok()?; Some(RustcVersion { major, minor, patch }) } + + /// Parse a [`RustcVersion`] which is exactly `..`, with no suffix. + pub fn parse_str_strict(value: &str) -> Option { + let mut components = value.splitn(3, '.'); + let major = components.next()?.parse().ok()?; + let minor = components.next()?.parse().ok()?; + let patch = components.next()?.parse().ok()?; + Some(RustcVersion { major, minor, patch }) + } } static CURRENT_OVERRIDABLE: OnceLock = OnceLock::new(); diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 2fbb90b770261..9a3fedde3b6b4 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -7,6 +7,7 @@ use std::panic; use std::path::PathBuf; use std::thread::panicking; +use rustc_ast::attr::version::RustcVersion; use rustc_data_structures::sync::{DynSend, DynSync}; use rustc_error_messages::{DiagArgMap, DiagArgName, DiagArgValue, IntoDiagArg}; use rustc_lint_defs::{Applicability, LintExpectationId}; @@ -175,6 +176,8 @@ pub struct IsLint { pub(crate) name: String, /// Indicates whether this lint should show up in cargo's future breakage report. has_future_breakage: bool, + /// Indicates the minimum rust version this lint applies to + rust_version: Option, } #[derive(Debug, PartialEq, Eq)] @@ -308,6 +311,11 @@ impl DiagInner { matches!(self.is_lint, Some(IsLint { has_future_breakage: true, .. })) } + /// Indicates the minimum rust version this lint applies to. + pub(crate) fn rust_version(&self) -> Option { + self.is_lint.as_ref().and_then(|is| is.rust_version) + } + pub(crate) fn is_force_warn(&self) -> bool { match self.level { Level::ForceWarning => { @@ -1152,8 +1160,13 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> { self } } - pub fn is_lint(&mut self, name: String, has_future_breakage: bool) -> &mut Self { - self.is_lint = Some(IsLint { name, has_future_breakage }); + pub fn is_lint( + &mut self, + name: String, + has_future_breakage: bool, + rust_version: Option, + ) -> &mut Self { + self.is_lint = Some(IsLint { name, has_future_breakage, rust_version }); self } diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index c2d09da3b0e82..76cdb10f429c7 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -46,6 +46,7 @@ pub use diagnostic_impls::{ }; pub use emitter::ColorConfig; use emitter::{DynEmitter, Emitter}; +use rustc_ast::attr::version::RustcVersion; use rustc_data_structures::AtomicRef; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::stable_hash::StableHasher; @@ -365,6 +366,9 @@ struct DiagCtxtInner { /// The file where the ICE information is stored. This allows delayed_span_bug backtraces to be /// stored along side the main panic backtrace. ice_file: Option, + + /// Controlled by `-Z lint-rust-version`; this allows avoiding emitting lints which would raise MSRV. + msrv: Option, } /// A key denoting where from a diagnostic was stashed. @@ -479,6 +483,11 @@ impl DiagCtxt { self } + pub fn with_msrv(mut self, msrv: RustcVersion) -> Self { + self.inner.get_mut().msrv = Some(msrv); + self + } + pub fn new(emitter: Box) -> Self { Self { inner: Lock::new(DiagCtxtInner::new(emitter)) } } @@ -526,6 +535,7 @@ impl DiagCtxt { future_breakage_diagnostics, fulfilled_expectations, ice_file: _, + msrv: _, } = inner.deref_mut(); // For the `Vec`s and `HashMap`s, we overwrite with an empty container to free the @@ -1193,6 +1203,7 @@ impl DiagCtxtInner { future_breakage_diagnostics: Vec::new(), fulfilled_expectations: Default::default(), ice_file: None, + msrv: None, } } @@ -1307,6 +1318,12 @@ impl DiagCtxtInner { } } + if let (Some(msrv), Some(diag_msrv)) = (self.msrv, diagnostic.rust_version()) + && diag_msrv > msrv + { + return None; + } + TRACK_DIAGNOSTIC(diagnostic, &mut |mut diagnostic| { if let Some(code) = diagnostic.code { self.emitted_diagnostic_codes.insert(code); diff --git a/compiler/rustc_lint_defs/Cargo.toml b/compiler/rustc_lint_defs/Cargo.toml index 2ca62f7fa8cdc..c8201d5ea8ccc 100644 --- a/compiler/rustc_lint_defs/Cargo.toml +++ b/compiler/rustc_lint_defs/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] # tidy-alphabetical-start +rustc_ast = { path = "../rustc_ast" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_error_messages = { path = "../rustc_error_messages" } rustc_hir_id = { path = "../rustc_hir_id" } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 1edc12e1ca7eb..6949090781cf6 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::fmt::Display; +use rustc_ast::attr::version::RustcVersion; use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::stable_hash::{StableCompare, StableHash, StableHashCtxt, StableHasher}; use rustc_error_messages::{DiagArgValue, IntoDiagArg}; @@ -305,6 +306,9 @@ pub struct Lint { /// `true` if this lint is unaffected by `-D warnings` pub ignore_deny_warnings: bool, + + /// Used to avoid lints which would affect MSRV + pub rust_version: Option, } /// Extra information for a future incompatibility lint. @@ -521,7 +525,40 @@ impl Lint { crate_level_only: false, eval_always: false, ignore_deny_warnings: false, + rust_version: None, + } + } + + // FIXME(const-hack): This is used so that `declare_lint` can declare an MSRV statically. + // `RustcVersion::parse_str_strict` should ideally be used instead. + pub const fn parse_rust_version(version: &str) -> RustcVersion { + const fn parse_part(input: &mut &[u8]) -> u16 { + let mut val = 0; + let mut idx = 0; + while idx < input.len() { + let v = input[idx]; + match v { + b'0'..=b'9' => { + val = val * 10 + (v - b'0') as u16; + } + b'.' => { + idx += 1; + break; + } + _ => panic!("invalid character in version"), + } + idx += 1; + } + *input = input.split_at(idx).1; + val } + + let mut bytes = version.as_bytes(); + let major = parse_part(&mut bytes); + let minor = parse_part(&mut bytes); + let patch = parse_part(&mut bytes); + assert!(bytes.is_empty()); + RustcVersion { major, minor, patch } } /// Gets the lint's name, with ASCII letters converted to lowercase. @@ -671,6 +708,7 @@ macro_rules! declare_lint { $($field:ident : $val:expr),* $(,)* }; )? $(@edition $lint_edition:ident => $edition_level:ident;)? + $(@msrv = $msrv:literal;)? $($v:ident),*) => ( $(#[$attr])* $vis static $NAME: &$crate::Lint = &$crate::Lint { @@ -687,6 +725,7 @@ macro_rules! declare_lint { }),)? $(edition_lint_opts: Some(($crate::Edition::$lint_edition, $crate::$edition_level)),)? $(eval_always: $eval_always,)? + $(rust_version: Some($crate::Lint::parse_rust_version($msrv)),)? ..$crate::Lint::default_fields_for_macro() }; ); diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index aa4b8f1d12c9f..b852a107d3343 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -514,7 +514,7 @@ pub fn emit_lint_base<'a, D: Diagnostic<'a, ()> + 'a>( err.disable_suggestions(); } - err.is_lint(lint.name_lower(), has_future_breakage); + err.is_lint(lint.name_lower(), has_future_breakage, lint.rust_version); // Lint diagnostics that are covered by the expect level will not be emitted outside // the compiler. It is therefore not necessary to add any information for the user. // This will therefore directly call the decorate function which will in turn emit diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 003b813124eea..1dba13ce9b4bc 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -3059,6 +3059,7 @@ pub(crate) mod dep_tracking { use std::path::PathBuf; use rustc_abi::Align; + use rustc_ast::attr::version::RustcVersion; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::stable_hash::StableHasher; use rustc_errors::LanguageIdentifier; @@ -3184,7 +3185,8 @@ pub(crate) mod dep_tracking { InliningThreshold, FunctionReturn, Align, - CodegenRetagOptions + CodegenRetagOptions, + RustcVersion, ); impl DepTrackingHash for (T1, T2) diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index bc4c5e98282a5..41b35f5700eca 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -82,7 +82,11 @@ pub fn feature_warn_issue( // Decorate this as a future-incompatibility lint as in rustc_middle::lint::lint_level let lint = UNSTABLE_SYNTAX_PRE_EXPANSION; let future_incompatible = lint.future_incompatible.as_ref().unwrap(); - err.is_lint(lint.name_lower(), /* has_future_breakage */ false); + err.is_lint( + lint.name_lower(), + /* has_future_breakage */ false, + /* rust_version */ None, + ); err.warn(lint.desc); err.note(format!("for more information, see {}", future_incompatible.reason.reference())); diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 0582791dad005..75f9f73dee948 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use std::str; use rustc_abi::Align; +use rustc_ast::attr::version::RustcVersion; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::profiling::TimePassesFormat; use rustc_data_structures::stable_hash::StableHasher; @@ -846,6 +847,7 @@ mod desc { super::mitigation_coverage::DeniedPartialMitigationKind::KINDS; pub(crate) const parse_deny_partial_mitigations: &str = super::mitigation_coverage::DeniedPartialMitigationKind::KINDS; + pub(crate) const parse_rust_version: &str = "a rust version (`xx.yy.zz`)"; } pub mod parse { @@ -2054,6 +2056,18 @@ pub mod parse { }; true } + + pub(crate) fn parse_rust_version(slot: &mut Option, v: Option<&str>) -> bool { + let Some(v) = v else { + return false; + }; + if let Some(version) = RustcVersion::parse_str_strict(v) { + *slot = Some(version); + true + } else { + false + } + } } options! { @@ -2454,6 +2468,8 @@ options! { "lint LLVM IR (default: no)"), lint_mir: bool = (false, parse_bool, [UNTRACKED], "lint MIR before and after each transformation"), + lint_rust_version: Option = (None, parse_rust_version, [TRACKED], + "control the minimum rust version for lints"), llvm_module_flag: Vec<(String, u32, String)> = (Vec::new(), parse_llvm_module_flag, [TRACKED], "a list of module flags to pass to LLVM (space separated)"), llvm_plugins: Vec = (Vec::new(), parse_list, [TRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 9f88161073b60..c19a2d71e2078 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1040,6 +1040,10 @@ pub fn build_session( dcx = dcx.with_ice_file(ice_file); } + if let Some(msrv) = sopts.unstable_opts.lint_rust_version { + dcx = dcx.with_msrv(msrv); + } + let host_triple = TargetTuple::from_tuple(config::host_tuple()); let (host, target_warnings) = Target::search(&host_triple, sopts.sysroot.path(), sopts.unstable_opts.unstable_options)