blob: c956738edf0eba997c116be0822b52b2719038ed [file] [log] [blame] [edit]
use super::DUPLICATED_ATTRIBUTES;
use clippy_utils::diagnostics::span_lint_and_then;
use itertools::Itertools;
use rustc_ast::{Attribute, MetaItem};
use rustc_ast_pretty::pprust::path_to_string;
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::EarlyContext;
use rustc_span::{Span, Symbol, sym};
use std::collections::hash_map::Entry;
fn emit_if_duplicated(
cx: &EarlyContext<'_>,
attr: &MetaItem,
attr_paths: &mut FxHashMap<String, Span>,
complete_path: String,
) {
match attr_paths.entry(complete_path) {
Entry::Vacant(v) => {
v.insert(attr.span);
},
Entry::Occupied(o) => {
span_lint_and_then(cx, DUPLICATED_ATTRIBUTES, attr.span, "duplicated attribute", |diag| {
diag.span_note(*o.get(), "first defined here");
diag.span_help(attr.span, "remove this attribute");
});
},
}
}
fn check_duplicated_attr(
cx: &EarlyContext<'_>,
attr: &MetaItem,
attr_paths: &mut FxHashMap<String, Span>,
parent: &mut Vec<Symbol>,
) {
if attr.span.from_expansion() {
return;
}
let attr_path = if let Some(ident) = attr.ident() {
ident.name
} else {
Symbol::intern(&path_to_string(&attr.path))
};
if let Some(ident) = attr.ident() {
let name = ident.name;
if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason
{
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.
return;
}
if let Some(direct_parent) = parent.last()
&& *direct_parent == sym::cfg_trace
&& [sym::all, sym::not, sym::any].contains(&name)
{
// FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one
// level `cfg`, we leave.
return;
}
}
if let Some(value) = attr.value_str() {
emit_if_duplicated(
cx,
attr,
attr_paths,
format!("{}:{attr_path}={value}", parent.iter().join(":")),
);
} else if let Some(sub_attrs) = attr.meta_item_list() {
parent.push(attr_path);
for sub_attr in sub_attrs {
if let Some(meta) = sub_attr.meta_item() {
check_duplicated_attr(cx, meta, attr_paths, parent);
}
}
parent.pop();
} else {
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{attr_path}", parent.iter().join(":")));
}
}
pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
let mut attr_paths = FxHashMap::default();
for attr in attrs {
if let Some(meta) = attr.meta() {
check_duplicated_attr(cx, &meta, &mut attr_paths, &mut Vec::new());
}
}
}