Overhaul and document clippy_utils::attrs (#15763)
For https://github.com/rust-lang/rust-clippy/issues/15569 changelog: none
This commit is contained in:
commit
7562179364
7 changed files with 107 additions and 126 deletions
|
|
@ -4,7 +4,7 @@ use crate::FxHashSet;
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{first_line_of_span, indent_of, snippet};
|
||||
use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
|
||||
use clippy_utils::{get_attr, is_lint_allowed, sym};
|
||||
use clippy_utils::{get_builtin_attr, is_lint_allowed, sym};
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_data_structures::fx::FxIndexSet;
|
||||
|
|
@ -183,7 +183,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
|
|||
|
||||
fn has_sig_drop_attr_impl(&mut self, ty: Ty<'tcx>) -> bool {
|
||||
if let Some(adt) = ty.ty_adt_def()
|
||||
&& get_attr(
|
||||
&& get_builtin_attr(
|
||||
self.cx.sess(),
|
||||
self.cx.tcx.get_all_attrs(adt.did()),
|
||||
sym::has_significant_drop,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::res::MaybeResPath;
|
||||
use clippy_utils::source::{indent_of, snippet};
|
||||
use clippy_utils::{expr_or_init, get_attr, peel_hir_expr_unary, sym};
|
||||
use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
|
|
@ -167,7 +167,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> {
|
|||
|
||||
fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>, depth: usize) -> bool {
|
||||
if let Some(adt) = ty.ty_adt_def() {
|
||||
let mut iter = get_attr(
|
||||
let mut iter = get_builtin_attr(
|
||||
self.cx.sess(),
|
||||
self.cx.tcx.get_all_attrs(adt.did()),
|
||||
sym::has_significant_drop,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::res::MaybeQPath;
|
||||
use clippy_utils::{get_attr, higher, sym};
|
||||
use clippy_utils::{get_builtin_attr, higher, sym};
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::LitIntType;
|
||||
use rustc_ast::ast::{LitFloatType, LitKind};
|
||||
|
|
@ -854,5 +854,5 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
|
|||
|
||||
fn has_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
|
||||
let attrs = cx.tcx.hir_attrs(hir_id);
|
||||
get_attr(cx.sess(), attrs, sym::author).count() > 0
|
||||
get_builtin_attr(cx.sess(), attrs, sym::author).count() > 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use clippy_utils::{get_attr, sym};
|
||||
use clippy_utils::{get_builtin_attr, sym};
|
||||
use hir::TraitItem;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
|
@ -60,5 +60,5 @@ impl<'tcx> LateLintPass<'tcx> for DumpHir {
|
|||
|
||||
fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool {
|
||||
let attrs = cx.tcx.hir_attrs(hir_id);
|
||||
get_attr(cx.sess(), attrs, sym::dump).count() > 0
|
||||
get_builtin_attr(cx.sess(), attrs, sym::dump).count() > 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
//! Utility functions for attributes, including Clippy's built-in ones
|
||||
|
||||
use crate::source::SpanRangeExt;
|
||||
use crate::{sym, tokenize_with_text};
|
||||
use rustc_ast::attr;
|
||||
|
|
@ -12,131 +14,59 @@ use rustc_session::Session;
|
|||
use rustc_span::{Span, Symbol};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Deprecation status of attributes known by Clippy.
|
||||
pub enum DeprecationStatus {
|
||||
/// Attribute is deprecated
|
||||
Deprecated,
|
||||
/// Attribute is deprecated and was replaced by the named attribute
|
||||
Replaced(&'static str),
|
||||
None,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const BUILTIN_ATTRIBUTES: &[(Symbol, DeprecationStatus)] = &[
|
||||
(sym::author, DeprecationStatus::None),
|
||||
(sym::version, DeprecationStatus::None),
|
||||
(sym::cognitive_complexity, DeprecationStatus::None),
|
||||
(sym::cyclomatic_complexity, DeprecationStatus::Replaced("cognitive_complexity")),
|
||||
(sym::dump, DeprecationStatus::None),
|
||||
(sym::msrv, DeprecationStatus::None),
|
||||
// The following attributes are for the 3rd party crate authors.
|
||||
// See book/src/attribs.md
|
||||
(sym::has_significant_drop, DeprecationStatus::None),
|
||||
(sym::format_args, DeprecationStatus::None),
|
||||
];
|
||||
|
||||
pub struct LimitStack {
|
||||
stack: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Drop for LimitStack {
|
||||
fn drop(&mut self) {
|
||||
assert_eq!(self.stack.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl LimitStack {
|
||||
#[must_use]
|
||||
pub fn new(limit: u64) -> Self {
|
||||
Self { stack: vec![limit] }
|
||||
}
|
||||
pub fn limit(&self) -> u64 {
|
||||
*self.stack.last().expect("there should always be a value in the stack")
|
||||
}
|
||||
pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
|
||||
let stack = &mut self.stack;
|
||||
parse_attrs(sess, attrs, name, |val| stack.push(val));
|
||||
}
|
||||
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
|
||||
let stack = &mut self.stack;
|
||||
parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_attr<'a, A: AttributeExt + 'a>(
|
||||
/// Given `attrs`, extract all the instances of a built-in Clippy attribute called `name`
|
||||
pub fn get_builtin_attr<'a, A: AttributeExt + 'a>(
|
||||
sess: &'a Session,
|
||||
attrs: &'a [A],
|
||||
name: Symbol,
|
||||
) -> impl Iterator<Item = &'a A> {
|
||||
attrs.iter().filter(move |attr| {
|
||||
let Some(attr_segments) = attr.ident_path() else {
|
||||
return false;
|
||||
};
|
||||
if let Some([clippy, segment2]) = attr.ident_path().as_deref()
|
||||
&& clippy.name == sym::clippy
|
||||
{
|
||||
let new_name = match segment2.name {
|
||||
sym::cyclomatic_complexity => Some("cognitive_complexity"),
|
||||
sym::author
|
||||
| sym::version
|
||||
| sym::cognitive_complexity
|
||||
| sym::dump
|
||||
| sym::msrv
|
||||
// The following attributes are for the 3rd party crate authors.
|
||||
// See book/src/attribs.md
|
||||
| sym::has_significant_drop
|
||||
| sym::format_args => None,
|
||||
_ => {
|
||||
sess.dcx().span_err(segment2.span, "usage of unknown attribute");
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
if attr_segments.len() == 2 && attr_segments[0].name == sym::clippy {
|
||||
BUILTIN_ATTRIBUTES
|
||||
.iter()
|
||||
.find_map(|(builtin_name, deprecation_status)| {
|
||||
if attr_segments[1].name == *builtin_name {
|
||||
Some(deprecation_status)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map_or_else(
|
||||
|| {
|
||||
sess.dcx().span_err(attr_segments[1].span, "usage of unknown attribute");
|
||||
false
|
||||
},
|
||||
|deprecation_status| {
|
||||
let mut diag = sess
|
||||
.dcx()
|
||||
.struct_span_err(attr_segments[1].span, "usage of deprecated attribute");
|
||||
match *deprecation_status {
|
||||
DeprecationStatus::Deprecated => {
|
||||
diag.emit();
|
||||
false
|
||||
},
|
||||
DeprecationStatus::Replaced(new_name) => {
|
||||
diag.span_suggestion(
|
||||
attr_segments[1].span,
|
||||
"consider using",
|
||||
new_name,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
diag.emit();
|
||||
false
|
||||
},
|
||||
DeprecationStatus::None => {
|
||||
diag.cancel();
|
||||
attr_segments[1].name == name
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
match new_name {
|
||||
Some(new_name) => {
|
||||
sess.dcx()
|
||||
.struct_span_err(segment2.span, "usage of deprecated attribute")
|
||||
.with_span_suggestion(
|
||||
segment2.span,
|
||||
"consider using",
|
||||
new_name,
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
.emit();
|
||||
false
|
||||
},
|
||||
None => segment2.name == name,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) {
|
||||
for attr in get_attr(sess, attrs, name) {
|
||||
if let Some(value) = attr.value_str() {
|
||||
if let Ok(value) = FromStr::from_str(value.as_str()) {
|
||||
f(value);
|
||||
} else {
|
||||
sess.dcx().span_err(attr.span(), "not a number");
|
||||
}
|
||||
} else {
|
||||
sess.dcx().span_err(attr.span(), "bad clippy attribute");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> {
|
||||
/// If `attrs` contain exactly one instance of a built-in Clippy attribute called `name`,
|
||||
/// returns that attribute, and `None` otherwise
|
||||
pub fn get_unique_builtin_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> {
|
||||
let mut unique_attr: Option<&A> = None;
|
||||
for attr in get_attr(sess, attrs, name) {
|
||||
for attr in get_builtin_attr(sess, attrs, name) {
|
||||
if let Some(duplicate) = unique_attr {
|
||||
sess.dcx()
|
||||
.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
|
|||
unique_attr
|
||||
}
|
||||
|
||||
/// Returns true if the attributes contain any of `proc_macro`,
|
||||
/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
|
||||
/// Checks whether `attrs` contain any of `proc_macro`, `proc_macro_derive` or
|
||||
/// `proc_macro_attribute`
|
||||
pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool {
|
||||
attrs.iter().any(AttributeExt::is_proc_macro_attr)
|
||||
}
|
||||
|
||||
/// Returns true if the attributes contain `#[doc(hidden)]`
|
||||
/// Checks whether `attrs` contain `#[doc(hidden)]`
|
||||
pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool {
|
||||
attrs
|
||||
.iter()
|
||||
|
|
@ -164,6 +94,7 @@ pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool {
|
|||
.any(|l| attr::list_contains_name(&l, sym::hidden))
|
||||
}
|
||||
|
||||
/// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]`
|
||||
pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
|
||||
adt.is_variant_list_non_exhaustive()
|
||||
|| 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 {
|
|||
.any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..)))
|
||||
}
|
||||
|
||||
/// Checks if the given span contains a `#[cfg(..)]` attribute
|
||||
/// Checks whether the given span contains a `#[cfg(..)]` attribute
|
||||
pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
|
||||
s.check_source_text(cx, |src| {
|
||||
let mut iter = tokenize_with_text(src);
|
||||
|
|
@ -198,3 +129,52 @@ pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
|
|||
false
|
||||
})
|
||||
}
|
||||
|
||||
/// Currently used to keep track of the current value of `#[clippy::cognitive_complexity(N)]`
|
||||
pub struct LimitStack {
|
||||
default: u64,
|
||||
stack: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Drop for LimitStack {
|
||||
fn drop(&mut self) {
|
||||
debug_assert_eq!(self.stack, Vec::<u64>::new()); // avoid `.is_empty()`, for a nicer error message
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(missing_docs, reason = "they're all trivial...")]
|
||||
impl LimitStack {
|
||||
#[must_use]
|
||||
/// Initialize the stack starting with a default value, which usually comes from configuration
|
||||
pub fn new(limit: u64) -> Self {
|
||||
Self {
|
||||
default: limit,
|
||||
stack: vec![],
|
||||
}
|
||||
}
|
||||
pub fn limit(&self) -> u64 {
|
||||
self.stack.last().copied().unwrap_or(self.default)
|
||||
}
|
||||
pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
|
||||
let stack = &mut self.stack;
|
||||
parse_attrs(sess, attrs, name, |val| stack.push(val));
|
||||
}
|
||||
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
|
||||
let stack = &mut self.stack;
|
||||
parse_attrs(sess, attrs, name, |val| debug_assert_eq!(stack.pop(), Some(val)));
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) {
|
||||
for attr in get_builtin_attr(sess, attrs, name) {
|
||||
let Some(value) = attr.value_str() else {
|
||||
sess.dcx().span_err(attr.span(), "bad clippy attribute");
|
||||
continue;
|
||||
};
|
||||
let Ok(value) = u64::from_str(value.as_str()) else {
|
||||
sess.dcx().span_err(attr.span(), "not a number");
|
||||
continue;
|
||||
};
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ extern crate rustc_span;
|
|||
extern crate rustc_trait_selection;
|
||||
|
||||
pub mod ast_utils;
|
||||
#[deny(missing_docs)]
|
||||
pub mod attrs;
|
||||
mod check_proc_macro;
|
||||
pub mod comparisons;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use crate::visitors::{Descend, for_each_expr_without_closures};
|
||||
use crate::{get_unique_attr, sym};
|
||||
use crate::{get_unique_builtin_attr, sym};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
|
||||
|
|
@ -42,7 +42,7 @@ pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
|
|||
} else {
|
||||
// Allow users to tag any macro as being format!-like
|
||||
// TODO: consider deleting FORMAT_MACRO_DIAG_ITEMS and using just this method
|
||||
get_unique_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some()
|
||||
get_unique_builtin_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue