clippy_utils/
attrs.rs

1use rustc_ast::attr;
2use rustc_ast::attr::AttributeExt;
3use rustc_errors::Applicability;
4use rustc_lexer::TokenKind;
5use rustc_lint::LateContext;
6use rustc_middle::ty::{AdtDef, TyCtxt};
7use rustc_session::Session;
8use rustc_span::{Span, Symbol};
9use std::str::FromStr;
10use rustc_attr_data_structures::find_attr;
11use crate::source::SpanRangeExt;
12use crate::{sym, tokenize_with_text};
13use rustc_attr_data_structures::AttributeKind;
14
15/// Deprecation status of attributes known by Clippy.
16pub enum DeprecationStatus {
17    /// Attribute is deprecated
18    Deprecated,
19    /// Attribute is deprecated and was replaced by the named attribute
20    Replaced(&'static str),
21    None,
22}
23
24#[rustfmt::skip]
25pub const BUILTIN_ATTRIBUTES: &[(Symbol, DeprecationStatus)] = &[
26    (sym::author,                DeprecationStatus::None),
27    (sym::version,               DeprecationStatus::None),
28    (sym::cognitive_complexity,  DeprecationStatus::None),
29    (sym::cyclomatic_complexity, DeprecationStatus::Replaced("cognitive_complexity")),
30    (sym::dump,                  DeprecationStatus::None),
31    (sym::msrv,                  DeprecationStatus::None),
32    // The following attributes are for the 3rd party crate authors.
33    // See book/src/attribs.md
34    (sym::has_significant_drop,  DeprecationStatus::None),
35    (sym::format_args,           DeprecationStatus::None),
36];
37
38pub struct LimitStack {
39    stack: Vec<u64>,
40}
41
42impl Drop for LimitStack {
43    fn drop(&mut self) {
44        assert_eq!(self.stack.len(), 1);
45    }
46}
47
48impl LimitStack {
49    #[must_use]
50    pub fn new(limit: u64) -> Self {
51        Self { stack: vec![limit] }
52    }
53    pub fn limit(&self) -> u64 {
54        *self.stack.last().expect("there should always be a value in the stack")
55    }
56    pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
57        let stack = &mut self.stack;
58        parse_attrs(sess, attrs, name, |val| stack.push(val));
59    }
60    pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
61        let stack = &mut self.stack;
62        parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
63    }
64}
65
66pub fn get_attr<'a, A: AttributeExt + 'a>(
67    sess: &'a Session,
68    attrs: &'a [A],
69    name: Symbol,
70) -> impl Iterator<Item = &'a A> {
71    attrs.iter().filter(move |attr| {
72        let Some(attr_segments) = attr.ident_path() else {
73            return false;
74        };
75
76        if attr_segments.len() == 2 && attr_segments[0].name == sym::clippy {
77            BUILTIN_ATTRIBUTES
78                .iter()
79                .find_map(|(builtin_name, deprecation_status)| {
80                    if attr_segments[1].name == *builtin_name {
81                        Some(deprecation_status)
82                    } else {
83                        None
84                    }
85                })
86                .map_or_else(
87                    || {
88                        sess.dcx().span_err(attr_segments[1].span, "usage of unknown attribute");
89                        false
90                    },
91                    |deprecation_status| {
92                        let mut diag = sess
93                            .dcx()
94                            .struct_span_err(attr_segments[1].span, "usage of deprecated attribute");
95                        match *deprecation_status {
96                            DeprecationStatus::Deprecated => {
97                                diag.emit();
98                                false
99                            },
100                            DeprecationStatus::Replaced(new_name) => {
101                                diag.span_suggestion(
102                                    attr_segments[1].span,
103                                    "consider using",
104                                    new_name,
105                                    Applicability::MachineApplicable,
106                                );
107                                diag.emit();
108                                false
109                            },
110                            DeprecationStatus::None => {
111                                diag.cancel();
112                                attr_segments[1].name == name
113                            },
114                        }
115                    },
116                )
117        } else {
118            false
119        }
120    })
121}
122
123fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) {
124    for attr in get_attr(sess, attrs, name) {
125        if let Some(value) = attr.value_str() {
126            if let Ok(value) = FromStr::from_str(value.as_str()) {
127                f(value);
128            } else {
129                sess.dcx().span_err(attr.span(), "not a number");
130            }
131        } else {
132            sess.dcx().span_err(attr.span(), "bad clippy attribute");
133        }
134    }
135}
136
137pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> {
138    let mut unique_attr: Option<&A> = None;
139    for attr in get_attr(sess, attrs, name) {
140        if let Some(duplicate) = unique_attr {
141            sess.dcx()
142                .struct_span_err(attr.span(), format!("`{name}` is defined multiple times"))
143                .with_span_note(duplicate.span(), "first definition found here")
144                .emit();
145        } else {
146            unique_attr = Some(attr);
147        }
148    }
149    unique_attr
150}
151
152/// Returns true if the attributes contain any of `proc_macro`,
153/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
154pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool {
155    attrs.iter().any(AttributeExt::is_proc_macro_attr)
156}
157
158/// Returns true if the attributes contain `#[doc(hidden)]`
159pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool {
160    attrs
161        .iter()
162        .filter(|attr| attr.has_name(sym::doc))
163        .filter_map(AttributeExt::meta_item_list)
164        .any(|l| attr::list_contains_name(&l, sym::hidden))
165}
166
167pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
168    adt.is_variant_list_non_exhaustive()
169        || find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..))
170        || adt.variants().iter().any(|variant_def| {
171            variant_def.is_field_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(variant_def.def_id), AttributeKind::NonExhaustive(..))
172        })
173        || adt
174            .all_fields()
175            .any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..)))
176}
177
178/// Checks if the given span contains a `#[cfg(..)]` attribute
179pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
180    s.check_source_text(cx, |src| {
181        let mut iter = tokenize_with_text(src);
182
183        // Search for the token sequence [`#`, `[`, `cfg`]
184        while iter.any(|(t, ..)| matches!(t, TokenKind::Pound)) {
185            let mut iter = iter.by_ref().skip_while(|(t, ..)| {
186                matches!(
187                    t,
188                    TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
189                )
190            });
191            if matches!(iter.next(), Some((TokenKind::OpenBracket, ..)))
192                && matches!(iter.next(), Some((TokenKind::Ident, "cfg", _)))
193            {
194                return true;
195            }
196        }
197        false
198    })
199}