clippy_utils/
msrvs.rs

1use rustc_ast::attr::AttributeExt;
2
3use rustc_attr_parsing::{RustcVersion, parse_version};
4use rustc_session::Session;
5use rustc_span::{Symbol, sym};
6use serde::Deserialize;
7use smallvec::{SmallVec, smallvec};
8use std::fmt;
9
10macro_rules! msrv_aliases {
11    ($($major:literal,$minor:literal,$patch:literal {
12        $($name:ident),* $(,)?
13    })*) => {
14        $($(
15        pub const $name: RustcVersion = RustcVersion { major: $major, minor :$minor, patch: $patch };
16        )*)*
17    };
18}
19
20// names may refer to stabilized feature flags or library items
21msrv_aliases! {
22    1,84,0 { CONST_OPTION_AS_SLICE }
23    1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
24    1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP }
25    1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION }
26    1,80,0 { BOX_INTO_ITER, LAZY_CELL }
27    1,77,0 { C_STR_LITERALS }
28    1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
29    1,75,0 { OPTION_AS_SLICE }
30    1,74,0 { REPR_RUST }
31    1,73,0 { MANUAL_DIV_CEIL }
32    1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
33    1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
34    1,68,0 { PATH_MAIN_SEPARATOR_STR }
35    1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
36    1,63,0 { CLONE_INTO }
37    1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
38    1,59,0 { THREAD_LOCAL_CONST_INIT }
39    1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
40    1,56,0 { CONST_FN_UNION }
41    1,55,0 { SEEK_REWIND }
42    1,54,0 { INTO_KEYS }
43    1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
44    1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
45    1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
46    1,50,0 { BOOL_THEN, CLAMP, SLICE_FILL }
47    1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST }
48    1,46,0 { CONST_IF_MATCH }
49    1,45,0 { STR_STRIP_PREFIX }
50    1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS }
51    1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
52    1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
53    1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
54    1,38,0 { POINTER_CAST, REM_EUCLID }
55    1,37,0 { TYPE_ALIAS_ENUM_VARIANTS }
56    1,36,0 { ITERATOR_COPIED }
57    1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
58    1,34,0 { TRY_FROM }
59    1,33,0 { UNDERSCORE_IMPORTS }
60    1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
61    1,29,0 { ITER_FLATTEN }
62    1,28,0 { FROM_BOOL, REPEAT_WITH }
63    1,27,0 { ITERATOR_TRY_FOLD }
64    1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
65    1,24,0 { IS_ASCII_DIGIT }
66    1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
67    1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
68    1,16,0 { STR_REPEAT }
69    1,15,0 { MAYBE_BOUND_IN_WHERE }
70}
71
72/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]`
73#[derive(Debug, Clone)]
74pub struct Msrv {
75    stack: SmallVec<[RustcVersion; 2]>,
76}
77
78impl fmt::Display for Msrv {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        if let Some(msrv) = self.current() {
81            write!(f, "{msrv}")
82        } else {
83            f.write_str("1.0.0")
84        }
85    }
86}
87
88impl<'de> Deserialize<'de> for Msrv {
89    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
90    where
91        D: serde::Deserializer<'de>,
92    {
93        let v = String::deserialize(deserializer)?;
94        parse_version(Symbol::intern(&v))
95            .map(|v| Msrv { stack: smallvec![v] })
96            .ok_or_else(|| serde::de::Error::custom("not a valid Rust version"))
97    }
98}
99
100impl Msrv {
101    pub fn empty() -> Msrv {
102        Msrv { stack: SmallVec::new() }
103    }
104
105    pub fn read_cargo(&mut self, sess: &Session) {
106        let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
107            .ok()
108            .and_then(|v| parse_version(Symbol::intern(&v)));
109
110        match (self.current(), cargo_msrv) {
111            (None, Some(cargo_msrv)) => self.stack = smallvec![cargo_msrv],
112            (Some(clippy_msrv), Some(cargo_msrv)) => {
113                if clippy_msrv != cargo_msrv {
114                    sess.dcx().warn(format!(
115                        "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
116                    ));
117                }
118            },
119            _ => {},
120        }
121    }
122
123    pub fn current(&self) -> Option<RustcVersion> {
124        self.stack.last().copied()
125    }
126
127    pub fn meets(&self, required: RustcVersion) -> bool {
128        self.current().is_none_or(|msrv| msrv >= required)
129    }
130
131    fn parse_attr(sess: &Session, attrs: &[impl AttributeExt]) -> Option<RustcVersion> {
132        let sym_msrv = Symbol::intern("msrv");
133        let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym_msrv]));
134
135        if let Some(msrv_attr) = msrv_attrs.next() {
136            if let Some(duplicate) = msrv_attrs.next_back() {
137                sess.dcx()
138                    .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
139                    .with_span_note(msrv_attr.span(), "first definition found here")
140                    .emit();
141            }
142
143            if let Some(msrv) = msrv_attr.value_str() {
144                if let Some(version) = parse_version(msrv) {
145                    return Some(version);
146                }
147
148                sess.dcx()
149                    .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
150            } else {
151                sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
152            }
153        }
154
155        None
156    }
157
158    pub fn check_attributes(&mut self, sess: &Session, attrs: &[impl AttributeExt]) {
159        if let Some(version) = Self::parse_attr(sess, attrs) {
160            self.stack.push(version);
161        }
162    }
163
164    pub fn check_attributes_post(&mut self, sess: &Session, attrs: &[impl AttributeExt]) {
165        if Self::parse_attr(sess, attrs).is_some() {
166            self.stack.pop();
167        }
168    }
169}