rust/clippy_utils/src/attrs.rs

162 lines
5.6 KiB
Rust
Raw Normal View History

use rustc_ast::{ast, attr};
use rustc_errors::Applicability;
use rustc_session::Session;
use rustc_span::sym;
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: &[(&str, DeprecationStatus)] = &[
("author", DeprecationStatus::None),
("version", DeprecationStatus::None),
("cognitive_complexity", DeprecationStatus::None),
("cyclomatic_complexity", DeprecationStatus::Replaced("cognitive_complexity")),
("dump", DeprecationStatus::None),
("msrv", DeprecationStatus::None),
("has_significant_drop", 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: &[ast::Attribute], name: &'static str) {
let stack = &mut self.stack;
parse_attrs(sess, attrs, name, |val| stack.push(val));
}
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
let stack = &mut self.stack;
parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
}
}
pub fn get_attr<'a>(
sess: &'a Session,
attrs: &'a [ast::Attribute],
name: &'static str,
) -> impl Iterator<Item = &'a ast::Attribute> {
attrs.iter().filter(move |attr| {
2022-08-11 06:06:11 -05:00
let attr = if let ast::AttrKind::Normal(ref normal) = attr.kind {
&normal.item
} else {
return false;
};
let attr_segments = &attr.path.segments;
if attr_segments.len() == 2 && attr_segments[0].ident.name == sym::clippy {
BUILTIN_ATTRIBUTES
.iter()
.find_map(|&(builtin_name, ref deprecation_status)| {
if attr_segments[1].ident.name.as_str() == builtin_name {
Some(deprecation_status)
} else {
None
}
})
.map_or_else(
|| {
sess.dcx()
.span_err(attr_segments[1].ident.span, "usage of unknown attribute");
false
},
|deprecation_status| {
let mut diag = sess
.dcx()
.struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute");
match *deprecation_status {
DeprecationStatus::Deprecated => {
diag.emit();
false
},
DeprecationStatus::Replaced(new_name) => {
diag.span_suggestion(
attr_segments[1].ident.span,
"consider using",
new_name,
Applicability::MachineApplicable,
);
diag.emit();
false
},
DeprecationStatus::None => {
diag.cancel();
attr_segments[1].ident.name.as_str() == name
},
}
},
)
} else {
false
}
})
}
fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
for attr in get_attr(sess, attrs, name) {
if let Some(ref 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>(
sess: &'a Session,
attrs: &'a [ast::Attribute],
name: &'static str,
) -> Option<&'a ast::Attribute> {
let mut unique_attr: Option<&ast::Attribute> = None;
for attr in get_attr(sess, attrs, name) {
if let Some(duplicate) = unique_attr {
sess.dcx()
.struct_span_err(attr.span, format!("`{name}` is defined multiple times"))
Make `DiagnosticBuilder::emit` consuming. This works for most of its call sites. This is nice, because `emit` very much makes sense as a consuming operation -- indeed, `DiagnosticBuilderState` exists to ensure no diagnostic is emitted twice, but it uses runtime checks. For the small number of call sites where a consuming emit doesn't work, the commit adds `DiagnosticBuilder::emit_without_consuming`. (This will be removed in subsequent commits.) Likewise, `emit_unless` becomes consuming. And `delay_as_bug` becomes consuming, while `delay_as_bug_without_consuming` is added (which will also be removed in subsequent commits.) All this requires significant changes to `DiagnosticBuilder`'s chaining methods. Currently `DiagnosticBuilder` method chaining uses a non-consuming `&mut self -> &mut Self` style, which allows chaining to be used when the chain ends in `emit()`, like so: ``` struct_err(msg).span(span).emit(); ``` But it doesn't work when producing a `DiagnosticBuilder` value, requiring this: ``` let mut err = self.struct_err(msg); err.span(span); err ``` This style of chaining won't work with consuming `emit` though. For that, we need to use to a `self -> Self` style. That also would allow `DiagnosticBuilder` production to be chained, e.g.: ``` self.struct_err(msg).span(span) ``` However, removing the `&mut self -> &mut Self` style would require that individual modifications of a `DiagnosticBuilder` go from this: ``` err.span(span); ``` to this: ``` err = err.span(span); ``` There are *many* such places. I have a high tolerance for tedious refactorings, but even I gave up after a long time trying to convert them all. Instead, this commit has it both ways: the existing `&mut self -> Self` chaining methods are kept, and new `self -> Self` chaining methods are added, all of which have a `_mv` suffix (short for "move"). Changes to the existing `forward!` macro lets this happen with very little additional boilerplate code. I chose to add the suffix to the new chaining methods rather than the existing ones, because the number of changes required is much smaller that way. This doubled chainging is a bit clumsy, but I think it is worthwhile because it allows a *lot* of good things to subsequently happen. In this commit, there are many `mut` qualifiers removed in places where diagnostics are emitted without being modified. In subsequent commits: - chaining can be used more, making the code more concise; - more use of chaining also permits the removal of redundant diagnostic APIs like `struct_err_with_code`, which can be replaced easily with `struct_err` + `code_mv`; - `emit_without_diagnostic` can be removed, which simplifies a lot of machinery, removing the need for `DiagnosticBuilderState`.
2024-01-02 19:17:35 -06:00
.span_note_mv(duplicate.span, "first definition found here")
.emit();
} else {
unique_attr = Some(attr);
}
}
unique_attr
}
/// Returns true if the attributes contain any of `proc_macro`,
/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
pub fn is_proc_macro(attrs: &[ast::Attribute]) -> bool {
attrs.iter().any(rustc_ast::Attribute::is_proc_macro_attr)
}
/// Returns true if the attributes contain `#[doc(hidden)]`
pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
attrs
.iter()
.filter(|attr| attr.has_name(sym::doc))
.filter_map(ast::Attribute::meta_item_list)
.any(|l| attr::list_contains_name(&l, sym::hidden))
}