Skip to main content

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