]> git.lizzy.rs Git - rust.git/blobdiff - src/tools/clippy/clippy_utils/src/msrvs.rs
Rollup merge of #105161 - cassaundra:numeric-literal-error, r=nnethercote
[rust.git] / src / tools / clippy / clippy_utils / src / msrvs.rs
index 79b19e6fb3eb051eff7a865ac44ea944e4d9cd88..12a512f78a699eb30c92ac178b17663db424d090 100644 (file)
@@ -1,4 +1,11 @@
+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 {
@@ -40,3 +47,97 @@ macro_rules! msrv_aliases {
     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();
+        }
+    }
+}