Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion compiler/rustc_ast/src/attr/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
// 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 `<major>.<minor>.<patch>`, with no suffix.
pub fn parse_str_strict(value: &str) -> Option<Self> {
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<RustcVersion> = OnceLock::new();
Expand Down
12 changes: 12 additions & 0 deletions compiler/rustc_errors/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -256,6 +257,8 @@ pub struct DiagInner {
/// With `-Ztrack_diagnostics` enabled,
/// we print where in rustc this error was emitted.
pub emitted_at: DiagLocation,
/// Used to avoid lints which would affect MSRV
pub rust_version: Option<RustcVersion>,
}

impl DiagInner {
Expand All @@ -279,6 +282,7 @@ impl DiagInner {
is_lint: None,
long_ty_path: None,
emitted_at: DiagLocation::caller(),
rust_version: None,
}
}

Expand Down Expand Up @@ -372,6 +376,7 @@ impl DiagInner {
// omit self.sort_span
&self.is_lint,
// omit self.emitted_at
// omit self.rust_version
)
}
}
Expand Down Expand Up @@ -1164,6 +1169,13 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
self
} }

with_fn! { with_rust_version,
/// Add a minimum Rust version.
pub fn rust_version(&mut self, major: u16, minor: u16, patch: u16) -> &mut Self {
self.rust_version = Some(RustcVersion { major, minor, patch });
self
} }

with_fn! { with_lint_id,
/// Add an argument.
pub fn lint_id(
Expand Down
17 changes: 17 additions & 0 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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;
Expand Down Expand Up @@ -363,6 +364,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<PathBuf>,

/// Controlled by `-Z lint-rust-version`; this allows avoiding emitting lints which would raise MSRV.
msrv: Option<RustcVersion>,
}

/// A key denoting where from a diagnostic was stashed.
Expand Down Expand Up @@ -477,6 +481,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<DynEmitter>) -> Self {
Self { inner: Lock::new(DiagCtxtInner::new(emitter)) }
}
Expand Down Expand Up @@ -524,6 +533,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
Expand Down Expand Up @@ -1170,6 +1180,7 @@ impl DiagCtxtInner {
future_breakage_diagnostics: Vec::new(),
fulfilled_expectations: Default::default(),
ice_file: None,
msrv: None,
}
}

Expand Down Expand Up @@ -1284,6 +1295,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);
Expand Down
27 changes: 26 additions & 1 deletion compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ where
message: None,
code: None,
used_fields: HashSet::new(),
msrv: None,
};
f(builder, variant)
});
Expand Down Expand Up @@ -87,6 +88,10 @@ pub(crate) struct DiagnosticDeriveVariantBuilder {
/// multiple specifications.
pub code: SpannedOption<()>,

/// MSRV is an optional part of the struct attribute - this is only set to detect
/// multiple specifications.
pub msrv: SpannedOption<()>,

pub used_fields: HashSet<proc_macro2::Ident>,
}

Expand Down Expand Up @@ -233,9 +238,29 @@ impl DiagnosticDeriveVariantBuilder {
diag.code(#arg_value);
});
}
"msrv" => {
let arg_span = arg_name.span().unwrap();
self.msrv.set_once((), arg_span);
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(arg_value),
..
}) = arg_value
&& let arg_value = arg_value.value()
&& let mut components = arg_value.splitn(3, '.')
&& let Some(major) = components.next().and_then(|c| c.parse::<u16>().ok())
&& let Some(minor) = components.next().and_then(|c| c.parse::<u16>().ok())
&& let Some(patch) = components.next().and_then(|c| c.parse::<u16>().ok())
{
tokens.extend(quote! {
diag.rust_version(#major, #minor, #patch);
});
} else {
span_err(arg_span, "expected a string containing a rust version (xx.yy.zz)").emit();
}
}
_ => {
span_err(arg_name.span().unwrap(), "unknown argument")
.note("only the `code` parameter is valid after the message")
.note("only the `code` or `msrv` parameters are valid after the message")
.emit();
}
}
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3047,6 +3047,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;
Expand Down Expand Up @@ -3172,7 +3173,8 @@ pub(crate) mod dep_tracking {
InliningThreshold,
FunctionReturn,
Align,
CodegenRetagOptions
CodegenRetagOptions,
RustcVersion,
);

impl<T1, T2> DepTrackingHash for (T1, T2)
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -2054,6 +2056,18 @@ pub mod parse {
};
true
}

pub(crate) fn parse_rust_version(slot: &mut Option<RustcVersion>, 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! {
Expand Down Expand Up @@ -2446,6 +2460,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<RustcVersion> = (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<String> = (Vec::new(), parse_list, [TRACKED],
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,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)
Expand Down
19 changes: 19 additions & 0 deletions tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,3 +753,22 @@ struct VariableExists {
struct VariableDoesNotExist {
sub: String,
}

#[derive(Diagnostic)]
#[diag("this is an example message", msrv = "1.1.1")]
struct Msrv;

#[derive(Diagnostic)]
#[diag("this is an example message", msrv = 1.1.1)]
//~^ ERROR expected a string containing a rust version
struct MsrvNoString;

#[derive(Diagnostic)]
#[diag("this is an example message", msrv = "stable")]
//~^ ERROR expected a string containing a rust version
struct MsrvBadVersion;

#[derive(Diagnostic)]
#[diag("this is an example message", msrv = foo())]
//~^ ERROR expected a string containing a rust version
struct MsrvBadVersionExpr;
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ error: derive(Diagnostic): unknown argument
LL | #[diag("this is an example message", code = E0123, slug = "foo")]
| ^^^^
|
= note: only the `code` parameter is valid after the message
= note: only the `code` or `msrv` parameters are valid after the message

error: derive(Diagnostic): `#[suggestion = ...]` is not a valid attribute
--> $DIR/diagnostic-derive-inline.rs:94:5
Expand Down Expand Up @@ -494,6 +494,24 @@ LL | #[diag("does not exist: {$nosub}")]
|
= help: Available fields: "sub"

error: derive(Diagnostic): expected a string containing a rust version (xx.yy.zz)
--> $DIR/diagnostic-derive-inline.rs:762:38
|
LL | #[diag("this is an example message", msrv = 1.1.1)]
| ^^^^

error: derive(Diagnostic): expected a string containing a rust version (xx.yy.zz)
--> $DIR/diagnostic-derive-inline.rs:767:38
|
LL | #[diag("this is an example message", msrv = "stable")]
| ^^^^

error: derive(Diagnostic): expected a string containing a rust version (xx.yy.zz)
--> $DIR/diagnostic-derive-inline.rs:772:38
|
LL | #[diag("this is an example message", msrv = foo())]
| ^^^^

error: cannot find attribute `nonsense` in this scope
--> $DIR/diagnostic-derive-inline.rs:60:3
|
Expand Down Expand Up @@ -574,5 +592,5 @@ LL | #[multipart_suggestion("with a suggestion")]
|
= note: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute

error: aborting due to 75 previous errors
error: aborting due to 78 previous errors

Loading