1use crate::source::SpanExt as _;
4use crate::{sym, tokenize_with_text};
5use rustc_ast::attr::AttributeExt;
6use rustc_attr_ir::{AttrArgs, find_attr};
7use rustc_errors::Applicability;
8use rustc_hir::def_id::DefId;
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 as _;
15
16pub fn check_clippy_attr<A: AttributeExt>(sess: &Session, attr: &A) {
18 if let [clippy, segment2] = &*attr.path()
19 && *clippy == sym::clippy
20 {
21 let path_span = attr
22 .path_span()
23 .expect("Clippy attributes are tool attributes, so they have a span");
24
25 match *segment2 {
26 sym::cyclomatic_complexity => {
27 sess.dcx()
28 .struct_span_err(path_span, "usage of deprecated attribute")
29 .with_span_suggestion(
30 path_span,
31 "consider using",
32 "clippy::cognitive_complexity",
33 Applicability::MachineApplicable,
34 )
35 .emit();
36 },
37 sym::author
38 | sym::version
39 | sym::cognitive_complexity
40 | sym::dump
41 | sym::msrv
42 | sym::has_significant_drop
43 | sym::format_args => {},
44 _ => {
45 sess.dcx().span_err(path_span, "usage of unknown attribute");
46 },
47 }
48 }
49}
50
51pub fn span_of_clippy_attr(attr: &(impl AttributeExt + 'static)) -> Span {
56 let a: &dyn core::any::Any = attr;
57 if let Some(a) = a.downcast_ref::<rustc_ast::ast::Attribute>() {
58 a.span
59 } else if let Some(rustc_attr_ir::Attribute::Tool(u)) = a.downcast_ref::<rustc_attr_ir::Attribute>() {
60 u.span
61 } else {
62 panic!("attribute {attr:?} is not a clippy attribute")
63 }
64}
65
66pub fn get_clippy_attr(tcx: TyCtxt<'_>, did: impl Into<DefId>, name: Symbol) -> Option<&rustc_attr_ir::AttrItem> {
70 let attr_name = [sym::clippy, name];
71 let mut attrs = tcx.tool_attrs(did, &attr_name);
72 let first = attrs.next()?;
73
74 for attr in attrs {
76 tcx.dcx()
77 .struct_span_err(attr.span, format!("`{name}` is defined multiple times"))
78 .with_span_note(first.span, "first definition found here")
79 .emit();
80 }
81
82 Some(first)
83}
84
85pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool {
88 attrs.iter().any(AttributeExt::is_proc_macro_attr)
89}
90
91pub fn is_doc_hidden(attrs: &[rustc_attr_ir::Attribute]) -> bool {
93 find_attr!(
94 attrs,
95 Doc(rustc_attr_ir::DocAttribute { hidden: Some(_), .. })
96 )
97}
98
99pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
101 adt.is_variant_list_non_exhaustive()
102 || find_attr!(tcx, adt.did(), NonExhaustive(..))
103 || adt.variants().iter().any(|variant_def| {
104 variant_def.is_field_list_non_exhaustive() || find_attr!(tcx, variant_def.def_id, NonExhaustive(..))
105 })
106 || adt
107 .all_fields()
108 .any(|field_def| find_attr!(tcx, field_def.did, NonExhaustive(..)))
109}
110
111fn span_contains_attr_idents(cx: &LateContext<'_>, s: Span, idents: &[&str]) -> bool {
112 s.check_text(cx, |src| {
113 if !src.contains('#') {
115 return false;
116 }
117
118 let mut iter = tokenize_with_text(src);
119
120 while iter.any(|(t, ..)| matches!(t, TokenKind::Pound)) {
122 let mut iter = iter.by_ref().skip_while(|(t, ..)| {
123 matches!(
124 t,
125 TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
126 )
127 });
128 if matches!(iter.next(), Some((TokenKind::OpenBracket, ..)))
129 && matches!(iter.next(), Some((TokenKind::Ident, name, _)) if idents.contains(&name))
130 {
131 return true;
132 }
133 }
134 false
135 })
136}
137
138pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
140 span_contains_attr_idents(cx, s, &["cfg"])
141}
142
143pub fn span_contains_cfg_or_cfg_attr(cx: &LateContext<'_>, s: Span) -> bool {
145 span_contains_attr_idents(cx, s, &["cfg", "cfg_attr"])
146}
147
148pub struct LimitStack {
150 default: u64,
151 stack: Vec<u64>,
152}
153
154impl Drop for LimitStack {
155 fn drop(&mut self) {
156 debug_assert_eq!(self.stack, Vec::<u64>::new()); }
158}
159
160#[expect(missing_docs, reason = "they're all trivial...")]
161impl LimitStack {
162 #[must_use]
163 pub fn new(limit: u64) -> Self {
165 Self {
166 default: limit,
167 stack: vec![],
168 }
169 }
170 pub fn limit(&self) -> u64 {
171 self.stack.last().copied().unwrap_or(self.default)
172 }
173 pub fn push_attrs(&mut self, sess: &Session, attrs: &[rustc_attr_ir::Attribute], name: Symbol) {
174 let stack = &mut self.stack;
175 parse_attrs(sess, attrs, name, |val| stack.push(val));
176 }
177 pub fn pop_attrs(&mut self, sess: &Session, attrs: &[rustc_attr_ir::Attribute], name: Symbol) {
178 let stack = &mut self.stack;
179 parse_attrs(sess, attrs, name, |val| {
180 let popped = stack.pop();
181 debug_assert_eq!(popped, Some(val));
182 });
183 }
184}
185
186fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[rustc_attr_ir::Attribute], name: Symbol, mut f: F) {
187 let attrs = attrs.iter().filter_map(move |attr| {
188 if let rustc_attr_ir::Attribute::Tool(t) = attr
189 && *t.path.segments == [sym::clippy, name]
190 {
191 Some(&**t)
192 } else {
193 None
194 }
195 });
196 for attr in attrs {
197 let AttrArgs::Eq { expr, .. } = attr.args else {
198 sess.dcx().span_err(attr.span, "bad clippy attribute");
199 continue;
200 };
201 let Some(value) = expr.value_as_str() else {
202 sess.dcx().span_err(attr.span, "not a number");
203 continue;
204 };
205 let Ok(value) = u64::from_str(value.as_str()) else {
206 sess.dcx().span_err(attr.span, "not a number");
207 continue;
208 };
209 f(value);
210 }
211}