1use crate::{is_in_const_context, sym};
2use rustc_ast::Attribute;
3use rustc_ast::attr::AttributeExt;
4use rustc_attr_ir::RustcVersion;
5use rustc_attr_parsing::parse_version;
6use rustc_data_structures::smallvec::SmallVec;
7use rustc_hir::def::DefKind;
8use rustc_hir::def_id::DefId;
9use rustc_hir::{Constness, HirId, StabilityLevel, StableSince};
10use rustc_lint::LateContext;
11use rustc_middle::ty::TyCtxt;
12use rustc_session::Session;
13use std::iter::once;
14use std::sync::atomic::{AtomicBool, Ordering};
15
16macro_rules! msrv_aliases {
17 ($($major:literal,$minor:literal,$patch:literal {
18 $($name:ident),* $(,)?
19 })*) => {
20 $($(
21 pub const $name: RustcVersion = RustcVersion { major: $major, minor :$minor, patch: $patch };
22 )*)*
23 };
24}
25
26msrv_aliases! {
28 1,98,0 { MAP_OR_DEFAULT }
29 1,97,0 { ISOLATE_LOWEST_ONE, BIT_WIDTH }
30 1,94,0 { EULER_GAMMA, GOLDEN_RATIO, MUL_ADD_CONST }
31 1,93,0 { VEC_DEQUE_POP_BACK_IF, VEC_DEQUE_POP_FRONT_IF }
32 1,91,0 { DURATION_FROM_MINUTES_HOURS }
33 1,89,0 { NONNULL_FROM_MUT }
34 1,88,0 { LET_CHAINS, AS_CHUNKS, RAW_PTR_DEFAULT }
35 1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST }
36 1,86,0 { VEC_POP_IF }
37 1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL, WAKER_NOOP, ABS_CONST, RADIANS_CONST, CONST_MEM_SWAP }
38 1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
39 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
40 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP, SPECIALIZED_TO_STRING_FOR_REFS }
41 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION, DURATION_ABS_DIFF }
42 1,80,0 { BOX_INTO_ITER, LAZY_CELL }
43 1,79,0 { CONST_BLOCKS, CSTR_COUNT_BYTES }
44 1,77,0 { C_STR_LITERALS }
45 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
46 1,75,0 { OPTION_AS_SLICE }
47 1,74,0 { REPR_RUST, IO_ERROR_OTHER }
48 1,73,0 { DIV_CEIL }
49 1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
50 1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
51 1,68,0 { PATH_MAIN_SEPARATOR_STR }
52 1,67,0 { ILOG2 }
53 1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
54 1,63,0 { CLONE_INTO, CONST_SLICE_FROM_REF }
55 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
56 1,61,0 { CONST_FN_TRAIT_BOUND }
57 1,60,0 { ABS_DIFF }
58 1,59,0 { THREAD_LOCAL_CONST_INIT }
59 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
60 1,57,0 { MAP_WHILE, CONST_PANIC }
61 1,56,0 { CONST_FN_UNION }
62 1,55,0 { SEEK_REWIND }
63 1,54,0 { INTO_KEYS }
64 1,53,0 { OR_PATTERNS, INTEGER_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
65 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
66 1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
67 1,50,0 { BOOL_THEN, CLAMP, SLICE_FILL }
68 1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST }
69 1,46,0 { CONST_IF_MATCH, OPTION_ZIP }
70 1,45,0 { STR_STRIP_PREFIX }
71 1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS }
72 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
73 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR, RESULT_MAP_OR_ELSE }
74 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
75 1,38,0 { POINTER_CAST, REM_EUCLID }
76 1,37,0 { TYPE_ALIAS_ENUM_VARIANTS }
77 1,36,0 { ITERATOR_COPIED }
78 1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
79 1,34,0 { TRY_FROM }
80 1,33,0 { UNDERSCORE_IMPORTS }
81 1,32,0 { CONST_IS_POWER_OF_TWO, CONST_DURATION_FROM_NANOS_MICROS_MILLIS_SECS }
82 1,31,0 { OPTION_REPLACE }
83 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
84 1,29,0 { ITER_FLATTEN }
85 1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF }
86 1,27,0 { ITERATOR_TRY_FOLD, DOUBLE_ENDED_ITERATOR_RFIND, DURATION_FROM_NANOS_MICROS }
87 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN, POINTER_ADD_SUB_METHODS, BOX_LEAK }
88 1,24,0 { IS_ASCII_DIGIT, PTR_NULL }
89 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
90 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
91 1,16,0 { STR_REPEAT, RESULT_UNWRAP_OR_DEFAULT }
92 1,15,0 { MAYBE_BOUND_IN_WHERE }
93 1,13,0 { QUESTION_MARK_OPERATOR }
94 1,3,0 { DURATION_FROM_MILLIS_SECS }
95}
96
97static SEEN_MSRV_ATTR: AtomicBool = AtomicBool::new(false);
101
102#[derive(Copy, Clone, Debug, Default)]
105pub struct Msrv(Option<RustcVersion>);
106
107impl From<Option<RustcVersion>> for Msrv {
108 #[inline]
109 fn from(value: Option<RustcVersion>) -> Self {
110 Self(value)
111 }
112}
113
114impl Msrv {
115 pub fn current(self, cx: &LateContext<'_>) -> Option<RustcVersion> {
120 if SEEN_MSRV_ATTR.load(Ordering::Relaxed) {
121 self.for_attrs(cx.tcx, cx.last_node_with_lint_attrs)
122 } else {
123 self.0
124 }
125 }
126
127 pub fn at(self, tcx: TyCtxt<'_>, node: HirId) -> Option<RustcVersion> {
132 if SEEN_MSRV_ATTR.load(Ordering::Relaxed) {
133 self.for_attrs(tcx, node)
134 } else {
135 self.0
136 }
137 }
138
139 fn for_attrs(self, tcx: TyCtxt<'_>, node: HirId) -> Option<RustcVersion> {
140 once(node)
141 .chain(tcx.hir_parent_id_iter(node))
142 .find_map(|id| parse_attrs(tcx.hir_attrs(id)))
143 .or(self.0)
144 }
145
146 pub fn meets(self, cx: &LateContext<'_>, required: RustcVersion) -> bool {
151 self.current(cx).is_none_or(|msrv| msrv >= required)
152 }
153 pub fn meets_at(self, tcx: TyCtxt<'_>, node: HirId, required: RustcVersion) -> bool {
158 self.at(tcx, node).is_none_or(|msrv| msrv >= required)
159 }
160
161 pub fn is_stable(self, cx: &LateContext<'_>, def_id: DefId) -> bool {
162 self.stability_met(cx, cx.tcx.lookup_stability(def_id).map(|stability| stability.level))
163 }
164
165 pub fn is_const_stable(self, cx: &LateContext<'_>, def_id: DefId) -> bool {
173 let constness = match cx.tcx.def_kind(def_id) {
174 DefKind::Impl { of_trait: true } => cx.tcx.impl_trait_header(def_id).constness,
177 _ => cx.tcx.constness(def_id),
178 };
179
180 matches!(constness, Constness::Const { .. })
181 && self.stability_met(
182 cx,
183 cx.tcx.lookup_const_stability(def_id).map(|stability| stability.level),
184 )
185 }
186
187 pub fn is_stable_or_const_stable(self, cx: &LateContext<'_>, def_id: DefId) -> bool {
192 if is_in_const_context(cx) {
193 self.is_const_stable(cx, def_id)
194 } else {
195 self.is_stable(cx, def_id)
196 }
197 }
198
199 fn stability_met(self, cx: &LateContext<'_>, level: Option<StabilityLevel>) -> bool {
200 level.is_none_or(|level| {
201 if let StabilityLevel::Stable { since, .. } = level {
202 let version = match since {
203 StableSince::Version(version) => version,
204 StableSince::Current => RustcVersion::CURRENT,
205 StableSince::Err(_) => return false,
206 };
207
208 self.meets(cx, version)
209 } else {
210 false
214 }
215 })
216 }
217}
218
219#[derive(Debug, Clone)]
222pub struct MsrvStack {
223 stack: SmallVec<[RustcVersion; 2]>,
224}
225
226impl From<Option<RustcVersion>> for MsrvStack {
227 #[inline]
228 fn from(value: Option<RustcVersion>) -> Self {
229 Self {
230 stack: SmallVec::from_iter(value),
231 }
232 }
233}
234
235impl MsrvStack {
236 pub fn current(&self) -> Option<RustcVersion> {
237 self.stack.last().copied()
238 }
239
240 pub fn meets(&self, required: RustcVersion) -> bool {
241 self.current().is_none_or(|msrv| msrv >= required)
242 }
243
244 pub fn check_attributes(&mut self, attrs: &[Attribute]) {
245 if let Some(version) = parse_attrs(attrs) {
246 SEEN_MSRV_ATTR.store(true, Ordering::Relaxed);
247 self.stack.push(version);
248 }
249 }
250
251 pub fn check_attributes_post(&mut self, attrs: &[Attribute]) {
252 if parse_attrs(attrs).is_some() {
253 self.stack.pop();
254 }
255 }
256}
257
258fn parse_attrs(attrs: &[impl AttributeExt]) -> Option<RustcVersion> {
259 let msrv_attr = attrs.iter().find(|attr| attr.path_matches(&[sym::clippy, sym::msrv]))?;
260
261 let msrv = msrv_attr.value_str()?;
262
263 parse_version(msrv)
264}
265
266pub fn check_attrs(sess: &Session, attrs: &[impl AttributeExt]) {
267 let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym::msrv]));
268
269 let Some(msrv_attr) = msrv_attrs.next() else {
270 return;
271 };
272
273 if let Some(duplicate) = msrv_attrs.next_back() {
274 sess.dcx()
275 .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
276 .with_span_note(msrv_attr.span(), "first definition found here")
277 .emit();
278 }
279
280 let Some(msrv) = msrv_attr.value_str() else {
281 sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
282 return;
283 };
284
285 if parse_version(msrv).is_none() {
286 sess.dcx()
287 .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
288 }
289}