Skip to main content

clippy_utils/
attrs.rs

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