Skip to content

Commit 7562179

Browse files
authored
Overhaul and document clippy_utils::attrs (#15763)
For #15569 changelog: none
2 parents a2c13c1 + e55b435 commit 7562179

File tree

7 files changed

+107
-126
lines changed

7 files changed

+107
-126
lines changed

clippy_lints/src/matches/significant_drop_in_scrutinee.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::FxHashSet;
44
use clippy_utils::diagnostics::span_lint_and_then;
55
use clippy_utils::source::{first_line_of_span, indent_of, snippet};
66
use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
7-
use clippy_utils::{get_attr, is_lint_allowed, sym};
7+
use clippy_utils::{get_builtin_attr, is_lint_allowed, sym};
88
use itertools::Itertools;
99
use rustc_ast::Mutability;
1010
use rustc_data_structures::fx::FxIndexSet;
@@ -183,7 +183,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
183183

184184
fn has_sig_drop_attr_impl(&mut self, ty: Ty<'tcx>) -> bool {
185185
if let Some(adt) = ty.ty_adt_def()
186-
&& get_attr(
186+
&& get_builtin_attr(
187187
self.cx.sess(),
188188
self.cx.tcx.get_all_attrs(adt.did()),
189189
sym::has_significant_drop,

clippy_lints/src/significant_drop_tightening.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::res::MaybeResPath;
33
use clippy_utils::source::{indent_of, snippet};
4-
use clippy_utils::{expr_or_init, get_attr, peel_hir_expr_unary, sym};
4+
use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym};
55
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
66
use rustc_errors::Applicability;
77
use rustc_hir::def::{DefKind, Res};
@@ -167,7 +167,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> {
167167

168168
fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>, depth: usize) -> bool {
169169
if let Some(adt) = ty.ty_adt_def() {
170-
let mut iter = get_attr(
170+
let mut iter = get_builtin_attr(
171171
self.cx.sess(),
172172
self.cx.tcx.get_all_attrs(adt.did()),
173173
sym::has_significant_drop,

clippy_lints/src/utils/author.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::res::MaybeQPath;
2-
use clippy_utils::{get_attr, higher, sym};
2+
use clippy_utils::{get_builtin_attr, higher, sym};
33
use itertools::Itertools;
44
use rustc_ast::LitIntType;
55
use rustc_ast::ast::{LitFloatType, LitKind};
@@ -854,5 +854,5 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
854854

855855
fn has_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
856856
let attrs = cx.tcx.hir_attrs(hir_id);
857-
get_attr(cx.sess(), attrs, sym::author).count() > 0
857+
get_builtin_attr(cx.sess(), attrs, sym::author).count() > 0
858858
}

clippy_lints/src/utils/dump_hir.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use clippy_utils::{get_attr, sym};
1+
use clippy_utils::{get_builtin_attr, sym};
22
use hir::TraitItem;
33
use rustc_hir as hir;
44
use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -60,5 +60,5 @@ impl<'tcx> LateLintPass<'tcx> for DumpHir {
6060

6161
fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool {
6262
let attrs = cx.tcx.hir_attrs(hir_id);
63-
get_attr(cx.sess(), attrs, sym::dump).count() > 0
63+
get_builtin_attr(cx.sess(), attrs, sym::dump).count() > 0
6464
}

clippy_utils/src/attrs.rs

Lines changed: 96 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! Utility functions for attributes, including Clippy's built-in ones
2+
13
use crate::source::SpanRangeExt;
24
use crate::{sym, tokenize_with_text};
35
use rustc_ast::attr;
@@ -12,131 +14,59 @@ use rustc_session::Session;
1214
use rustc_span::{Span, Symbol};
1315
use std::str::FromStr;
1416

15-
/// Deprecation status of attributes known by Clippy.
16-
pub enum DeprecationStatus {
17-
/// Attribute is deprecated
18-
Deprecated,
19-
/// Attribute is deprecated and was replaced by the named attribute
20-
Replaced(&'static str),
21-
None,
22-
}
23-
24-
#[rustfmt::skip]
25-
pub const BUILTIN_ATTRIBUTES: &[(Symbol, DeprecationStatus)] = &[
26-
(sym::author, DeprecationStatus::None),
27-
(sym::version, DeprecationStatus::None),
28-
(sym::cognitive_complexity, DeprecationStatus::None),
29-
(sym::cyclomatic_complexity, DeprecationStatus::Replaced("cognitive_complexity")),
30-
(sym::dump, DeprecationStatus::None),
31-
(sym::msrv, DeprecationStatus::None),
32-
// The following attributes are for the 3rd party crate authors.
33-
// See book/src/attribs.md
34-
(sym::has_significant_drop, DeprecationStatus::None),
35-
(sym::format_args, DeprecationStatus::None),
36-
];
37-
38-
pub struct LimitStack {
39-
stack: Vec<u64>,
40-
}
41-
42-
impl Drop for LimitStack {
43-
fn drop(&mut self) {
44-
assert_eq!(self.stack.len(), 1);
45-
}
46-
}
47-
48-
impl LimitStack {
49-
#[must_use]
50-
pub fn new(limit: u64) -> Self {
51-
Self { stack: vec![limit] }
52-
}
53-
pub fn limit(&self) -> u64 {
54-
*self.stack.last().expect("there should always be a value in the stack")
55-
}
56-
pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
57-
let stack = &mut self.stack;
58-
parse_attrs(sess, attrs, name, |val| stack.push(val));
59-
}
60-
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
61-
let stack = &mut self.stack;
62-
parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
63-
}
64-
}
65-
66-
pub fn get_attr<'a, A: AttributeExt + 'a>(
17+
/// Given `attrs`, extract all the instances of a built-in Clippy attribute called `name`
18+
pub fn get_builtin_attr<'a, A: AttributeExt + 'a>(
6719
sess: &'a Session,
6820
attrs: &'a [A],
6921
name: Symbol,
7022
) -> impl Iterator<Item = &'a A> {
7123
attrs.iter().filter(move |attr| {
72-
let Some(attr_segments) = attr.ident_path() else {
73-
return false;
74-
};
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+
};
7543

76-
if attr_segments.len() == 2 && attr_segments[0].name == sym::clippy {
77-
BUILTIN_ATTRIBUTES
78-
.iter()
79-
.find_map(|(builtin_name, deprecation_status)| {
80-
if attr_segments[1].name == *builtin_name {
81-
Some(deprecation_status)
82-
} else {
83-
None
84-
}
85-
})
86-
.map_or_else(
87-
|| {
88-
sess.dcx().span_err(attr_segments[1].span, "usage of unknown attribute");
89-
false
90-
},
91-
|deprecation_status| {
92-
let mut diag = sess
93-
.dcx()
94-
.struct_span_err(attr_segments[1].span, "usage of deprecated attribute");
95-
match *deprecation_status {
96-
DeprecationStatus::Deprecated => {
97-
diag.emit();
98-
false
99-
},
100-
DeprecationStatus::Replaced(new_name) => {
101-
diag.span_suggestion(
102-
attr_segments[1].span,
103-
"consider using",
104-
new_name,
105-
Applicability::MachineApplicable,
106-
);
107-
diag.emit();
108-
false
109-
},
110-
DeprecationStatus::None => {
111-
diag.cancel();
112-
attr_segments[1].name == name
113-
},
114-
}
115-
},
116-
)
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+
}
11759
} else {
11860
false
11961
}
12062
})
12163
}
12264

123-
fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) {
124-
for attr in get_attr(sess, attrs, name) {
125-
if let Some(value) = attr.value_str() {
126-
if let Ok(value) = FromStr::from_str(value.as_str()) {
127-
f(value);
128-
} else {
129-
sess.dcx().span_err(attr.span(), "not a number");
130-
}
131-
} else {
132-
sess.dcx().span_err(attr.span(), "bad clippy attribute");
133-
}
134-
}
135-
}
136-
137-
pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> {
65+
/// If `attrs` contain exactly one instance of a built-in Clippy attribute called `name`,
66+
/// returns that attribute, and `None` otherwise
67+
pub fn get_unique_builtin_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> {
13868
let mut unique_attr: Option<&A> = None;
139-
for attr in get_attr(sess, attrs, name) {
69+
for attr in get_builtin_attr(sess, attrs, name) {
14070
if let Some(duplicate) = unique_attr {
14171
sess.dcx()
14272
.struct_span_err(attr.span(), format!("`{name}` is defined multiple times"))
@@ -149,13 +79,13 @@ pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], n
14979
unique_attr
15080
}
15181

152-
/// Returns true if the attributes contain any of `proc_macro`,
153-
/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
82+
/// Checks whether `attrs` contain any of `proc_macro`, `proc_macro_derive` or
83+
/// `proc_macro_attribute`
15484
pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool {
15585
attrs.iter().any(AttributeExt::is_proc_macro_attr)
15686
}
15787

158-
/// Returns true if the attributes contain `#[doc(hidden)]`
88+
/// Checks whether `attrs` contain `#[doc(hidden)]`
15989
pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool {
16090
attrs
16191
.iter()
@@ -164,6 +94,7 @@ pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool {
16494
.any(|l| attr::list_contains_name(&l, sym::hidden))
16595
}
16696

97+
/// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]`
16798
pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
16899
adt.is_variant_list_non_exhaustive()
169100
|| find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..))
@@ -176,7 +107,7 @@ pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
176107
.any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..)))
177108
}
178109

179-
/// Checks if the given span contains a `#[cfg(..)]` attribute
110+
/// Checks whether the given span contains a `#[cfg(..)]` attribute
180111
pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
181112
s.check_source_text(cx, |src| {
182113
let mut iter = tokenize_with_text(src);
@@ -198,3 +129,52 @@ pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
198129
false
199130
})
200131
}
132+
133+
/// Currently used to keep track of the current value of `#[clippy::cognitive_complexity(N)]`
134+
pub struct LimitStack {
135+
default: u64,
136+
stack: Vec<u64>,
137+
}
138+
139+
impl 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...")]
146+
impl 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+
168+
fn 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+
}

clippy_utils/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ extern crate rustc_span;
5050
extern crate rustc_trait_selection;
5151

5252
pub mod ast_utils;
53+
#[deny(missing_docs)]
5354
pub mod attrs;
5455
mod check_proc_macro;
5556
pub mod comparisons;

clippy_utils/src/macros.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::sync::{Arc, OnceLock};
44

55
use crate::visitors::{Descend, for_each_expr_without_closures};
6-
use crate::{get_unique_attr, sym};
6+
use crate::{get_unique_builtin_attr, sym};
77

88
use arrayvec::ArrayVec;
99
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
@@ -42,7 +42,7 @@ pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
4242
} else {
4343
// Allow users to tag any macro as being format!-like
4444
// TODO: consider deleting FORMAT_MACRO_DIAG_ITEMS and using just this method
45-
get_unique_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some()
45+
get_unique_builtin_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some()
4646
}
4747
}
4848

0 commit comments

Comments
 (0)