clippy_utils/
attrs.rs

1//! Utility functions for attributes, including Clippy's built-in ones
2
3use crate::source::SpanRangeExt;
4use crate::{sym, tokenize_with_text};
5use rustc_ast::attr;
6use rustc_ast::attr::AttributeExt;
7use rustc_errors::Applicability;
8use rustc_hir::attrs::AttributeKind;
9use rustc_hir::find_attr;
10use rustc_lexer::TokenKind;
11use rustc_lint::LateContext;
12use rustc_middle::ty::{AdtDef, TyCtxt};
13use rustc_session::Session;
14use rustc_span::{Span, Symbol};
15use std::str::FromStr;
16
17/// Given `attrs`, extract all the instances of a built-in Clippy attribute called `name`
18pub fn get_builtin_attr<'a, A: AttributeExt + 'a>(
19    sess: &'a Session,
20    attrs: &'a [A],
21    name: Symbol,
22) -> impl Iterator<Item = &'a A> {
23    attrs.iter().filter(move |attr| {
24        if let Some([clippy, segment2]) = attr.ident_path().as_deref()
25            && clippy.name == sym::clippy
26        {
27            let new_name = match segment2.name {
28                sym::cyclomatic_complexity => Some("cognitive_complexity"),
29                sym::author
30                | sym::version
31                | sym::cognitive_complexity
32                | sym::dump
33                | sym::msrv
34                // The following attributes are for the 3rd party crate authors.
35                // See book/src/attribs.md
36                | sym::has_significant_drop
37                | sym::format_args => None,
38                _ => {
39                    sess.dcx().span_err(segment2.span, "usage of unknown attribute");
40                    return false;
41                },
42            };
43
44            match new_name {
45                Some(new_name) => {
46                    sess.dcx()
47                        .struct_span_err(segment2.span, "usage of deprecated attribute")
48                        .with_span_suggestion(
49                            segment2.span,
50                            "consider using",
51                            new_name,
52                            Applicability::MachineApplicable,
53                        )
54                        .emit();
55                    false
56                },
57                None => segment2.name == name,
58            }
59        } else {
60            false
61        }
62    })
63}
64
65/// If `attrs` contain exactly one instance of a built-in Clippy attribute called `name`,
66/// returns that attribute, and `None` otherwise
67pub fn get_unique_builtin_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> {
68    let mut unique_attr: Option<&A> = None;
69    for attr in get_builtin_attr(sess, attrs, name) {
70        if let Some(duplicate) = unique_attr {
71            sess.dcx()
72                .struct_span_err(attr.span(), format!("`{name}` is defined multiple times"))
73                .with_span_note(duplicate.span(), "first definition found here")
74                .emit();
75        } else {
76            unique_attr = Some(attr);
77        }
78    }
79    unique_attr
80}
81
82/// Checks whether `attrs` contain any of `proc_macro`, `proc_macro_derive` or
83/// `proc_macro_attribute`
84pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool {
85    attrs.iter().any(AttributeExt::is_proc_macro_attr)
86}
87
88/// Checks whether `attrs` contain `#[doc(hidden)]`
89pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool {
90    attrs
91        .iter()
92        .filter(|attr| attr.has_name(sym::doc))
93        .filter_map(AttributeExt::meta_item_list)
94        .any(|l| attr::list_contains_name(&l, sym::hidden))
95}
96
97/// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]`
98pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
99    adt.is_variant_list_non_exhaustive()
100        || find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..))
101        || adt.variants().iter().any(|variant_def| {
102            variant_def.is_field_list_non_exhaustive()
103                || find_attr!(tcx.get_all_attrs(variant_def.def_id), AttributeKind::NonExhaustive(..))
104        })
105        || adt
106            .all_fields()
107            .any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..)))
108}
109
110/// Checks whether the given span contains a `#[cfg(..)]` attribute
111pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
112    s.check_source_text(cx, |src| {
113        let mut iter = tokenize_with_text(src);
114
115        // Search for the token sequence [`#`, `[`, `cfg`]
116        while iter.any(|(t, ..)| matches!(t, TokenKind::Pound)) {
117            let mut iter = iter.by_ref().skip_while(|(t, ..)| {
118                matches!(
119                    t,
120                    TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
121                )
122            });
123            if matches!(iter.next(), Some((TokenKind::OpenBracket, ..)))
124                && matches!(iter.next(), Some((TokenKind::Ident, "cfg", _)))
125            {
126                return true;
127            }
128        }
129        false
130    })
131}
132
133/// Currently used to keep track of the current value of `#[clippy::cognitive_complexity(N)]`
134pub struct LimitStack {
135    default: u64,
136    stack: Vec<u64>,
137}
138
139impl Drop for LimitStack {
140    fn drop(&mut self) {
141        debug_assert_eq!(self.stack, Vec::<u64>::new()); // avoid `.is_empty()`, for a nicer error message
142    }
143}
144
145#[expect(missing_docs, reason = "they're all trivial...")]
146impl LimitStack {
147    #[must_use]
148    /// Initialize the stack starting with a default value, which usually comes from configuration
149    pub fn new(limit: u64) -> Self {
150        Self {
151            default: limit,
152            stack: vec![],
153        }
154    }
155    pub fn limit(&self) -> u64 {
156        self.stack.last().copied().unwrap_or(self.default)
157    }
158    pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
159        let stack = &mut self.stack;
160        parse_attrs(sess, attrs, name, |val| stack.push(val));
161    }
162    pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
163        let stack = &mut self.stack;
164        parse_attrs(sess, attrs, name, |val| debug_assert_eq!(stack.pop(), Some(val)));
165    }
166}
167
168fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) {
169    for attr in get_builtin_attr(sess, attrs, name) {
170        let Some(value) = attr.value_str() else {
171            sess.dcx().span_err(attr.span(), "bad clippy attribute");
172            continue;
173        };
174        let Ok(value) = u64::from_str(value.as_str()) else {
175            sess.dcx().span_err(attr.span(), "not a number");
176            continue;
177        };
178        f(value);
179    }
180}