|
1 | 1 | use crate::sym;
|
2 |
| -use rustc_ast::Attribute; |
3 | 2 | use rustc_ast::attr::AttributeExt;
|
| 3 | +use rustc_ast::{Attribute, LitKind, MetaItem, MetaItemInner}; |
4 | 4 | use rustc_attr_data_structures::RustcVersion;
|
5 | 5 | use rustc_attr_parsing::parse_version;
|
6 | 6 | use rustc_lint::LateContext;
|
@@ -186,27 +186,67 @@ impl MsrvStack {
|
186 | 186 | }
|
187 | 187 |
|
188 | 188 | fn 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); |
| 189 | + let mut first_clippy_attr = None; |
| 190 | + let mut clippy_msrv = None; |
| 191 | + let mut cfg_version: Option<RustcVersion> = None; |
| 192 | + for attr in attrs { |
| 193 | + if attr.path_matches(&[sym::clippy, sym::msrv]) { |
| 194 | + match first_clippy_attr { |
| 195 | + None => first_clippy_attr = Some(attr), |
| 196 | + Some(first) => { |
| 197 | + sess.dcx() |
| 198 | + .struct_span_err(attr.span(), "`clippy::msrv` is defined multiple times") |
| 199 | + .with_span_note(first.span(), "first definition found here") |
| 200 | + .emit(); |
| 201 | + }, |
202 | 202 | }
|
203 | 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"); |
| 204 | + if let Some(msrv) = attr.value_str() { |
| 205 | + if let Some(version) = parse_version(msrv) { |
| 206 | + clippy_msrv = Some(version); |
| 207 | + } else { |
| 208 | + sess.dcx() |
| 209 | + .span_err(attr.span(), format!("`{msrv}` is not a valid Rust version")); |
| 210 | + } |
| 211 | + } else { |
| 212 | + sess.dcx().span_err(attr.span(), "bad clippy attribute"); |
| 213 | + } |
| 214 | + } else if matches!(attr.name(), Some(sym::cfg | sym::cfg_trace)) // cfg in early passes, cfg_trace in late |
| 215 | + && let Some(list) = attr.meta_item_list() |
| 216 | + && let [MetaItemInner::MetaItem(meta_item)] = list.as_slice() |
| 217 | + { |
| 218 | + parse_cfg_version(&mut cfg_version, meta_item, false); |
208 | 219 | }
|
209 | 220 | }
|
210 | 221 |
|
211 |
| - None |
| 222 | + clippy_msrv.or(cfg_version) |
| 223 | +} |
| 224 | + |
| 225 | +fn parse_cfg_version(current: &mut Option<RustcVersion>, meta_item: &MetaItem, mut negated: bool) { |
| 226 | + let Some(name) = meta_item.name() else { return }; |
| 227 | + match name { |
| 228 | + sym::version => { |
| 229 | + if !negated |
| 230 | + && let Some([MetaItemInner::Lit(lit)]) = meta_item.meta_item_list() |
| 231 | + && let LitKind::Str(s, _) = lit.kind |
| 232 | + && let Some(version) = parse_version(s) |
| 233 | + { |
| 234 | + match current { |
| 235 | + Some(current) => *current = version.min(*current), |
| 236 | + None => *current = Some(version), |
| 237 | + } |
| 238 | + } |
| 239 | + }, |
| 240 | + sym::any | sym::all | sym::not => { |
| 241 | + if name == sym::not { |
| 242 | + negated = !negated; |
| 243 | + } |
| 244 | + for inner in meta_item.meta_item_list().into_iter().flatten() { |
| 245 | + if let Some(inner_meta_item) = inner.meta_item() { |
| 246 | + parse_cfg_version(current, inner_meta_item, negated); |
| 247 | + } |
| 248 | + } |
| 249 | + }, |
| 250 | + _ => {}, |
| 251 | + } |
212 | 252 | }
|
0 commit comments