use crate::source::SpanRangeExt;
use crate::{sym, tokenize_with_text};
use rustc_ast::attr::AttributeExt;
use rustc_errors::Applicability;
use rustc_hir::find_attr;
use rustc_lexer::TokenKind;
use rustc_lint::LateContext;
use rustc_middle::ty::{AdtDef, TyCtxt};
use rustc_session::Session;
use rustc_span::{Span, Symbol};
use std::str::FromStr;
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| {
if let [clippy, segment2] = &*attr.path()
&& *clippy == sym::clippy
{
let path_span = attr
.path_span()
.expect("Clippy attributes are unparsed and have a span");
let new_name = match *segment2 {
sym::cyclomatic_complexity => Some("cognitive_complexity"),
sym::author
| sym::version
| sym::cognitive_complexity
| sym::dump
| sym::msrv
| sym::has_significant_drop
| sym::format_args => None,
_ => {
sess.dcx().span_err(path_span, "usage of unknown attribute");
return false;
},
};
match new_name {
Some(new_name) => {
sess.dcx()
.struct_span_err(path_span, "usage of deprecated attribute")
.with_span_suggestion(
path_span,
"consider using",
format!("clippy::{new_name}"),
Applicability::MachineApplicable,
)
.emit();
false
},
None => *segment2 == name,
}
} else {
false
}
})
}
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_builtin_attr(sess, attrs, name) {
if let Some(duplicate) = unique_attr {
sess.dcx()
.struct_span_err(attr.span(), format!("`{name}` is defined multiple times"))
.with_span_note(duplicate.span(), "first definition found here")
.emit();
} else {
unique_attr = Some(attr);
}
}
unique_attr
}
pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool {
attrs.iter().any(AttributeExt::is_proc_macro_attr)
}
pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool {
attrs.iter().any(AttributeExt::is_doc_hidden)
}
pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
adt.is_variant_list_non_exhaustive()
|| find_attr!(tcx, adt.did(), NonExhaustive(..))
|| adt.variants().iter().any(|variant_def| {
variant_def.is_field_list_non_exhaustive() || find_attr!(tcx, variant_def.def_id, NonExhaustive(..))
})
|| adt
.all_fields()
.any(|field_def| find_attr!(tcx, field_def.did, NonExhaustive(..)))
}
pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
s.check_source_text(cx, |src| {
let mut iter = tokenize_with_text(src);
while iter.any(|(t, ..)| matches!(t, TokenKind::Pound)) {
let mut iter = iter.by_ref().skip_while(|(t, ..)| {
matches!(
t,
TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
)
});
if matches!(iter.next(), Some((TokenKind::OpenBracket, ..)))
&& matches!(iter.next(), Some((TokenKind::Ident, "cfg", _)))
{
return true;
}
}
false
})
}
pub struct LimitStack {
default: u64,
stack: Vec<u64>,
}
impl Drop for LimitStack {
fn drop(&mut self) {
debug_assert_eq!(self.stack, Vec::<u64>::new()); }
}
#[expect(missing_docs, reason = "they're all trivial...")]
impl LimitStack {
#[must_use]
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| {
let popped = stack.pop();
debug_assert_eq!(popped, 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);
}
}