]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_utils/src/msrvs.rs
Preparing for merge from rustc
[rust.git] / src / tools / clippy / clippy_utils / src / msrvs.rs
1 use std::sync::OnceLock;
2
3 use rustc_ast::Attribute;
4 use rustc_semver::RustcVersion;
5 use rustc_session::Session;
6 use rustc_span::Span;
7
8 use crate::attrs::get_unique_attr;
9
10 macro_rules! msrv_aliases {
11     ($($major:literal,$minor:literal,$patch:literal {
12         $($name:ident),* $(,)?
13     })*) => {
14         $($(
15         pub const $name: RustcVersion = RustcVersion::new($major, $minor, $patch);
16         )*)*
17     };
18 }
19
20 // names may refer to stabilized feature flags or library items
21 msrv_aliases! {
22     1,65,0 { LET_ELSE }
23     1,62,0 { BOOL_THEN_SOME }
24     1,58,0 { FORMAT_ARGS_CAPTURE }
25     1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
26     1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
27     1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
28     1,50,0 { BOOL_THEN, CLAMP }
29     1,47,0 { TAU, IS_ASCII_DIGIT_CONST }
30     1,46,0 { CONST_IF_MATCH }
31     1,45,0 { STR_STRIP_PREFIX }
32     1,43,0 { LOG2_10, LOG10_2 }
33     1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
34     1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
35     1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
36     1,38,0 { POINTER_CAST, REM_EUCLID }
37     1,37,0 { TYPE_ALIAS_ENUM_VARIANTS }
38     1,36,0 { ITERATOR_COPIED }
39     1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
40     1,34,0 { TRY_FROM }
41     1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
42     1,28,0 { FROM_BOOL }
43     1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
44     1,24,0 { IS_ASCII_DIGIT }
45     1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
46     1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
47     1,16,0 { STR_REPEAT }
48     1,55,0 { SEEK_REWIND }
49 }
50
51 fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
52     if let Ok(version) = RustcVersion::parse(msrv) {
53         return Some(version);
54     } else if let Some(sess) = sess {
55         if let Some(span) = span {
56             sess.span_err(span, format!("`{msrv}` is not a valid Rust version"));
57         }
58     }
59     None
60 }
61
62 /// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]`
63 #[derive(Debug, Clone, Default)]
64 pub struct Msrv {
65     stack: Vec<RustcVersion>,
66 }
67
68 impl Msrv {
69     fn new(initial: Option<RustcVersion>) -> Self {
70         Self {
71             stack: Vec::from_iter(initial),
72         }
73     }
74
75     fn read_inner(conf_msrv: &Option<String>, sess: &Session) -> Self {
76         let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
77             .ok()
78             .and_then(|v| parse_msrv(&v, None, None));
79         let clippy_msrv = conf_msrv.as_ref().and_then(|s| {
80             parse_msrv(s, None, None).or_else(|| {
81                 sess.err(format!(
82                     "error reading Clippy's configuration file. `{s}` is not a valid Rust version"
83                 ));
84                 None
85             })
86         });
87
88         // if both files have an msrv, let's compare them and emit a warning if they differ
89         if let Some(cargo_msrv) = cargo_msrv
90             && let Some(clippy_msrv) = clippy_msrv
91             && clippy_msrv != cargo_msrv
92         {
93             sess.warn(format!(
94                 "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
95             ));
96         }
97
98         Self::new(clippy_msrv.or(cargo_msrv))
99     }
100
101     /// Set the initial MSRV from the Clippy config file or from Cargo due to the `rust-version`
102     /// field in `Cargo.toml`
103     ///
104     /// Returns a `&'static Msrv` as `Copy` types are more easily passed to the
105     /// `register_{late,early}_pass` callbacks
106     pub fn read(conf_msrv: &Option<String>, sess: &Session) -> &'static Self {
107         static PARSED: OnceLock<Msrv> = OnceLock::new();
108
109         PARSED.get_or_init(|| Self::read_inner(conf_msrv, sess))
110     }
111
112     pub fn current(&self) -> Option<RustcVersion> {
113         self.stack.last().copied()
114     }
115
116     pub fn meets(&self, required: RustcVersion) -> bool {
117         self.current().map_or(true, |version| version.meets(required))
118     }
119
120     fn parse_attr(sess: &Session, attrs: &[Attribute]) -> Option<RustcVersion> {
121         if let Some(msrv_attr) = get_unique_attr(sess, attrs, "msrv") {
122             if let Some(msrv) = msrv_attr.value_str() {
123                 return parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span));
124             }
125
126             sess.span_err(msrv_attr.span, "bad clippy attribute");
127         }
128
129         None
130     }
131
132     pub fn enter_lint_attrs(&mut self, sess: &Session, attrs: &[Attribute]) {
133         if let Some(version) = Self::parse_attr(sess, attrs) {
134             self.stack.push(version);
135         }
136     }
137
138     pub fn exit_lint_attrs(&mut self, sess: &Session, attrs: &[Attribute]) {
139         if Self::parse_attr(sess, attrs).is_some() {
140             self.stack.pop();
141         }
142     }
143 }