1use crate::sym;
2use rustc_ast::Attribute;
3use rustc_ast::attr::AttributeExt;
4use rustc_attr_data_structures::RustcVersion;
5use rustc_attr_parsing::parse_version;
6use rustc_lint::LateContext;
7use rustc_session::Session;
8use rustc_span::Symbol;
9use serde::Deserialize;
10use smallvec::SmallVec;
11use std::iter::once;
12use std::sync::atomic::{AtomicBool, Ordering};
13
14macro_rules! msrv_aliases {
15 ($($major:literal,$minor:literal,$patch:literal {
16 $($name:ident),* $(,)?
17 })*) => {
18 $($(
19 pub const $name: RustcVersion = RustcVersion { major: $major, minor :$minor, patch: $patch };
20 )*)*
21 };
22}
23
24msrv_aliases! {
26 1,88,0 { LET_CHAINS }
27 1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT }
28 1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL }
29 1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
30 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
31 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP }
32 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION, DURATION_ABS_DIFF }
33 1,80,0 { BOX_INTO_ITER, LAZY_CELL }
34 1,77,0 { C_STR_LITERALS }
35 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
36 1,75,0 { OPTION_AS_SLICE }
37 1,74,0 { REPR_RUST, IO_ERROR_OTHER }
38 1,73,0 { DIV_CEIL }
39 1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
40 1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
41 1,68,0 { PATH_MAIN_SEPARATOR_STR }
42 1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
43 1,63,0 { CLONE_INTO, CONST_SLICE_FROM_REF }
44 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
45 1,60,0 { ABS_DIFF }
46 1,59,0 { THREAD_LOCAL_CONST_INIT }
47 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
48 1,57,0 { MAP_WHILE }
49 1,56,0 { CONST_FN_UNION }
50 1,55,0 { SEEK_REWIND }
51 1,54,0 { INTO_KEYS }
52 1,53,0 { OR_PATTERNS, INTEGER_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
53 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
54 1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
55 1,50,0 { BOOL_THEN, CLAMP, SLICE_FILL }
56 1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST }
57 1,46,0 { CONST_IF_MATCH }
58 1,45,0 { STR_STRIP_PREFIX }
59 1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS }
60 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
61 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
62 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
63 1,38,0 { POINTER_CAST, REM_EUCLID }
64 1,37,0 { TYPE_ALIAS_ENUM_VARIANTS }
65 1,36,0 { ITERATOR_COPIED }
66 1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
67 1,34,0 { TRY_FROM }
68 1,33,0 { UNDERSCORE_IMPORTS }
69 1,32,0 { CONST_IS_POWER_OF_TWO }
70 1,31,0 { OPTION_REPLACE }
71 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
72 1,29,0 { ITER_FLATTEN }
73 1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF }
74 1,27,0 { ITERATOR_TRY_FOLD }
75 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
76 1,24,0 { IS_ASCII_DIGIT }
77 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
78 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
79 1,16,0 { STR_REPEAT }
80 1,15,0 { MAYBE_BOUND_IN_WHERE }
81 1,13,0 { QUESTION_MARK_OPERATOR }
82}
83
84static SEEN_MSRV_ATTR: AtomicBool = AtomicBool::new(false);
88
89#[derive(Copy, Clone, Debug, Default)]
92pub struct Msrv(Option<RustcVersion>);
93
94impl<'de> Deserialize<'de> for Msrv {
95 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96 where
97 D: serde::Deserializer<'de>,
98 {
99 let v = String::deserialize(deserializer)?;
100 parse_version(Symbol::intern(&v))
101 .map(|v| Self(Some(v)))
102 .ok_or_else(|| serde::de::Error::custom("not a valid Rust version"))
103 }
104}
105
106impl Msrv {
107 pub fn current(self, cx: &LateContext<'_>) -> Option<RustcVersion> {
112 if SEEN_MSRV_ATTR.load(Ordering::Relaxed) {
113 let start = cx.last_node_with_lint_attrs;
114 if let Some(msrv_attr) = once(start)
115 .chain(cx.tcx.hir_parent_id_iter(start))
116 .find_map(|id| parse_attrs(cx.tcx.sess, cx.tcx.hir_attrs(id)))
117 {
118 return Some(msrv_attr);
119 }
120 }
121
122 self.0
123 }
124
125 pub fn meets(self, cx: &LateContext<'_>, required: RustcVersion) -> bool {
130 self.current(cx).is_none_or(|msrv| msrv >= required)
131 }
132
133 pub fn read_cargo(&mut self, sess: &Session) {
134 let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
135 .ok()
136 .and_then(|v| parse_version(Symbol::intern(&v)));
137
138 match (self.0, cargo_msrv) {
139 (None, Some(cargo_msrv)) => self.0 = Some(cargo_msrv),
140 (Some(clippy_msrv), Some(cargo_msrv)) => {
141 if clippy_msrv != cargo_msrv {
142 sess.dcx().warn(format!(
143 "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
144 ));
145 }
146 },
147 _ => {},
148 }
149 }
150}
151
152#[derive(Debug, Clone)]
155pub struct MsrvStack {
156 stack: SmallVec<[RustcVersion; 2]>,
157}
158
159impl MsrvStack {
160 pub fn new(initial: Msrv) -> Self {
161 Self {
162 stack: SmallVec::from_iter(initial.0),
163 }
164 }
165
166 pub fn current(&self) -> Option<RustcVersion> {
167 self.stack.last().copied()
168 }
169
170 pub fn meets(&self, required: RustcVersion) -> bool {
171 self.current().is_none_or(|msrv| msrv >= required)
172 }
173
174 pub fn check_attributes(&mut self, sess: &Session, attrs: &[Attribute]) {
175 if let Some(version) = parse_attrs(sess, attrs) {
176 SEEN_MSRV_ATTR.store(true, Ordering::Relaxed);
177 self.stack.push(version);
178 }
179 }
180
181 pub fn check_attributes_post(&mut self, sess: &Session, attrs: &[Attribute]) {
182 if parse_attrs(sess, attrs).is_some() {
183 self.stack.pop();
184 }
185 }
186}
187
188fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt]) -> Option<RustcVersion> {
189 let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym::msrv]));
190
191 if let Some(msrv_attr) = msrv_attrs.next() {
192 if let Some(duplicate) = msrv_attrs.next_back() {
193 sess.dcx()
194 .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
195 .with_span_note(msrv_attr.span(), "first definition found here")
196 .emit();
197 }
198
199 if let Some(msrv) = msrv_attr.value_str() {
200 if let Some(version) = parse_version(msrv) {
201 return Some(version);
202 }
203
204 sess.dcx()
205 .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
206 } else {
207 sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
208 }
209 }
210
211 None
212}