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