]>
Commit | Line | Data |
---|---|---|
487cf647 FG |
1 | use std::sync::OnceLock; |
2 | ||
3 | use rustc_ast::Attribute; | |
cdc7bbd5 | 4 | use rustc_semver::RustcVersion; |
487cf647 FG |
5 | use rustc_session::Session; |
6 | use rustc_span::Span; | |
7 | ||
8 | use crate::attrs::get_unique_attr; | |
cdc7bbd5 XL |
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! { | |
487cf647 | 22 | 1,65,0 { LET_ELSE } |
064997fb | 23 | 1,62,0 { BOOL_THEN_SOME } |
2b03887a | 24 | 1,58,0 { FORMAT_ARGS_CAPTURE } |
f2b60f7d | 25 | 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR } |
064997fb | 26 | 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST } |
487cf647 | 27 | 1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS } |
2b03887a | 28 | 1,50,0 { BOOL_THEN, CLAMP } |
487cf647 | 29 | 1,47,0 { TAU, IS_ASCII_DIGIT_CONST } |
cdc7bbd5 XL |
30 | 1,46,0 { CONST_IF_MATCH } |
31 | 1,45,0 { STR_STRIP_PREFIX } | |
c295e0f8 | 32 | 1,43,0 { LOG2_10, LOG10_2 } |
5e7ed085 | 33 | 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS } |
cdc7bbd5 XL |
34 | 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE } |
35 | 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF } | |
064997fb | 36 | 1,38,0 { POINTER_CAST, REM_EUCLID } |
cdc7bbd5 XL |
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 } | |
a2a8927a XL |
41 | 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } |
42 | 1,28,0 { FROM_BOOL } | |
064997fb | 43 | 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } |
f2b60f7d | 44 | 1,24,0 { IS_ASCII_DIGIT } |
064997fb | 45 | 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } |
04454e1e | 46 | 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } |
17df50a5 | 47 | 1,16,0 { STR_REPEAT } |
487cf647 FG |
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 | } | |
cdc7bbd5 | 143 | } |