+use std::sync::OnceLock;
+
+use rustc_ast::Attribute;
use rustc_semver::RustcVersion;
+use rustc_session::Session;
+use rustc_span::Span;
+
+use crate::attrs::get_unique_attr;
macro_rules! msrv_aliases {
($($major:literal,$minor:literal,$patch:literal {
1,16,0 { STR_REPEAT }
1,55,0 { SEEK_REWIND }
}
+
+fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
+ if let Ok(version) = RustcVersion::parse(msrv) {
+ return Some(version);
+ } else if let Some(sess) = sess {
+ if let Some(span) = span {
+ sess.span_err(span, format!("`{msrv}` is not a valid Rust version"));
+ }
+ }
+ None
+}
+
+/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]`
+#[derive(Debug, Clone, Default)]
+pub struct Msrv {
+ stack: Vec<RustcVersion>,
+}
+
+impl Msrv {
+ fn new(initial: Option<RustcVersion>) -> Self {
+ Self {
+ stack: Vec::from_iter(initial),
+ }
+ }
+
+ fn read_inner(conf_msrv: &Option<String>, sess: &Session) -> Self {
+ let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
+ .ok()
+ .and_then(|v| parse_msrv(&v, None, None));
+ let clippy_msrv = conf_msrv.as_ref().and_then(|s| {
+ parse_msrv(s, None, None).or_else(|| {
+ sess.err(format!(
+ "error reading Clippy's configuration file. `{s}` is not a valid Rust version"
+ ));
+ None
+ })
+ });
+
+ // if both files have an msrv, let's compare them and emit a warning if they differ
+ if let Some(cargo_msrv) = cargo_msrv
+ && let Some(clippy_msrv) = clippy_msrv
+ && clippy_msrv != cargo_msrv
+ {
+ sess.warn(format!(
+ "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
+ ));
+ }
+
+ Self::new(clippy_msrv.or(cargo_msrv))
+ }
+
+ /// Set the initial MSRV from the Clippy config file or from Cargo due to the `rust-version`
+ /// field in `Cargo.toml`
+ ///
+ /// Returns a `&'static Msrv` as `Copy` types are more easily passed to the
+ /// `register_{late,early}_pass` callbacks
+ pub fn read(conf_msrv: &Option<String>, sess: &Session) -> &'static Self {
+ static PARSED: OnceLock<Msrv> = OnceLock::new();
+
+ PARSED.get_or_init(|| Self::read_inner(conf_msrv, sess))
+ }
+
+ pub fn current(&self) -> Option<RustcVersion> {
+ self.stack.last().copied()
+ }
+
+ pub fn meets(&self, required: RustcVersion) -> bool {
+ self.current().map_or(true, |version| version.meets(required))
+ }
+
+ fn parse_attr(sess: &Session, attrs: &[Attribute]) -> Option<RustcVersion> {
+ if let Some(msrv_attr) = get_unique_attr(sess, attrs, "msrv") {
+ if let Some(msrv) = msrv_attr.value_str() {
+ return parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span));
+ }
+
+ sess.span_err(msrv_attr.span, "bad clippy attribute");
+ }
+
+ None
+ }
+
+ pub fn enter_lint_attrs(&mut self, sess: &Session, attrs: &[Attribute]) {
+ if let Some(version) = Self::parse_attr(sess, attrs) {
+ self.stack.push(version);
+ }
+ }
+
+ pub fn exit_lint_attrs(&mut self, sess: &Session, attrs: &[Attribute]) {
+ if Self::parse_attr(sess, attrs).is_some() {
+ self.stack.pop();
+ }
+ }
+}