Port #[allow], #[deny], #[expect], #[forbid], #[warn] to attr parser

also changes method `parse_limited_all` to take Iterator as an input,
to avoid needing to do expensive allocation
This commit is contained in:
Edvin Bryntesson
2026-03-10 16:49:53 +01:00
committed by Jonathan Brouwer
parent cd88c395c8
commit 345a3eb08b
21 changed files with 1020 additions and 564 deletions
@@ -0,0 +1,369 @@
use rustc_ast::LitKind;
use rustc_hir::HashIgnoredAttrId;
use rustc_hir::attrs::{LintAttribute, LintAttributeKind, LintInstance};
use rustc_hir::lints::AttributeLintKind;
use rustc_hir::target::GenericParamKind;
use rustc_session::DynLintStore;
use rustc_session::lint::builtin::{RENAMED_AND_REMOVED_LINTS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES};
use rustc_session::lint::{CheckLintNameResult, LintId};
use super::prelude::*;
use crate::attributes::AcceptFn;
use crate::session_diagnostics::UnknownToolInScopedLint;
pub(crate) trait Lint {
const KIND: LintAttributeKind;
const ATTR_SYMBOL: Symbol = Self::KIND.symbol();
}
pub(crate) struct Allow;
impl Lint for Allow {
const KIND: LintAttributeKind = LintAttributeKind::Allow;
}
pub(crate) struct Deny;
impl Lint for Deny {
const KIND: LintAttributeKind = LintAttributeKind::Deny;
}
pub(crate) struct Expect;
impl Lint for Expect {
const KIND: LintAttributeKind = LintAttributeKind::Expect;
}
pub(crate) struct Forbid;
impl Lint for Forbid {
const KIND: LintAttributeKind = LintAttributeKind::Forbid;
}
pub(crate) struct Warn;
impl Lint for Warn {
const KIND: LintAttributeKind = LintAttributeKind::Warn;
}
#[derive(Default)]
pub(crate) struct LintParser {
lint_attrs: ThinVec<LintAttribute>,
}
trait Mapping<S: Stage> {
const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn<LintParser, S>);
}
impl<S: Stage, T: Lint> Mapping<S> for T {
const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn<LintParser, S>) = (
&[T::ATTR_SYMBOL],
template!(
List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#],
"https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"
),
|this, cx, args| {
if let Some(lint_attr) = validate_lint_attr::<T, S>(cx, args) {
this.lint_attrs.push(lint_attr);
}
},
);
}
impl<S: Stage> AttributeParser<S> for LintParser {
const ATTRIBUTES: AcceptMapping<Self, S> =
&[Allow::MAPPING, Deny::MAPPING, Expect::MAPPING, Forbid::MAPPING, Warn::MAPPING];
const ALLOWED_TARGETS: AllowedTargets = {
use super::prelude::{Allow, Warn};
AllowedTargets::AllowList(&[
Allow(Target::ExternCrate),
Allow(Target::Use),
Allow(Target::Static),
Allow(Target::Const),
Allow(Target::Fn),
Allow(Target::Closure),
Allow(Target::Mod),
Allow(Target::ForeignMod),
Allow(Target::GlobalAsm),
Allow(Target::TyAlias),
Allow(Target::Enum),
Allow(Target::Variant),
Allow(Target::Struct),
Allow(Target::Field),
Allow(Target::Union),
Allow(Target::Trait),
Allow(Target::TraitAlias),
Allow(Target::Impl { of_trait: false }),
Allow(Target::Impl { of_trait: true }),
Allow(Target::Expression),
Allow(Target::Statement),
Allow(Target::Arm),
Allow(Target::AssocConst),
Allow(Target::Method(MethodKind::Inherent)),
Allow(Target::Method(MethodKind::Trait { body: false })),
Allow(Target::Method(MethodKind::Trait { body: true })),
Allow(Target::Method(MethodKind::TraitImpl)),
Allow(Target::AssocTy),
Allow(Target::ForeignFn),
Allow(Target::ForeignStatic),
Allow(Target::ForeignTy),
Allow(Target::MacroDef),
Allow(Target::Param),
Allow(Target::PatField),
Allow(Target::ExprField),
Allow(Target::Crate),
Allow(Target::Delegation { mac: false }),
Allow(Target::Delegation { mac: true }),
Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: false }),
Allow(Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: false }),
Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: false }),
Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: true }),
Allow(Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: true }),
Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: true }),
Warn(Target::MacroCall),
])
};
fn finalize(mut self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
if !self.lint_attrs.is_empty() {
// Sort to ensure correct order operations later
self.lint_attrs.sort_by(|a, b| a.attr_span.cmp(&b.attr_span));
Some(AttributeKind::LintAttributes(self.lint_attrs))
} else {
None
}
}
}
#[inline(always)]
fn validate_lint_attr<T: Lint, S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> Option<LintAttribute> {
let Some(lint_store) = cx.sess.lint_store.as_ref().map(|store| store.to_owned()) else {
unreachable!("lint_store required while parsing attributes");
};
let lint_store = lint_store.as_ref();
let Some(list) = args.list() else {
cx.expected_list(cx.inner_span, args);
return None;
};
let mut list = list.mixed().peekable();
let mut skip_unused_check = false;
let mut errored = false;
let mut reason = None;
let mut lint_instances = ThinVec::new();
let mut lint_index = 0;
let targeting_crate = matches!(cx.target, Target::Crate);
while let Some(item) = list.next() {
let Some(meta_item) = item.meta_item() else {
cx.expected_identifier(item.span());
errored = true;
continue;
};
match meta_item.args() {
ArgParser::NameValue(nv_parser) if meta_item.path().word_is(sym::reason) => {
//FIXME replace this with duplicate check?
if list.peek().is_some() {
cx.expected_nv_as_last_argument(meta_item.span(), sym::reason);
errored = true;
continue;
}
let val_lit = nv_parser.value_as_lit();
let LitKind::Str(reason_sym, _) = val_lit.kind else {
cx.expected_string_literal(nv_parser.value_span, Some(val_lit));
errored = true;
continue;
};
reason = Some(reason_sym);
}
ArgParser::NameValue(_) => {
cx.expected_specific_argument(meta_item.span(), &[sym::reason]);
errored = true;
}
ArgParser::List(list) => {
cx.expected_no_args(list.span);
errored = true;
}
ArgParser::NoArgs => {
skip_unused_check = true;
let mut segments = meta_item.path().segments();
let Some(tool_or_name) = segments.next() else {
unreachable!("first segment should always exist");
};
let rest = segments.collect::<Vec<_>>();
let (tool_name, tool_span, name): (Option<Symbol>, Option<Span>, _) =
if rest.is_empty() {
let name = tool_or_name.name;
(None, None, name.to_string())
} else {
let tool = tool_or_name;
let name = rest
.into_iter()
.map(|ident| ident.to_string())
.collect::<Vec<_>>()
.join("::");
(Some(tool.name), Some(tool.span), name)
};
let meta_item_span = meta_item.span();
let original_name = Symbol::intern(&name);
let mut full_name = tool_name
.map(|tool| Symbol::intern(&format!("{tool}::{}", original_name)))
.unwrap_or(original_name);
if let Some(ids) = check_lint(
cx,
lint_store,
original_name,
&mut full_name,
tool_name,
tool_span,
meta_item_span,
) {
if !targeting_crate && ids.iter().any(|lint_id| lint_id.lint.crate_level_only) {
cx.emit_lint(
UNUSED_ATTRIBUTES,
AttributeLintKind::IgnoredUnlessCrateSpecified {
level: T::ATTR_SYMBOL,
name: original_name,
},
meta_item_span,
);
}
lint_instances.extend(ids.into_iter().map(|id| {
LintInstance::new(full_name, id.to_string(), meta_item_span, lint_index)
}));
}
lint_index += 1;
}
}
}
if !skip_unused_check && !errored && lint_instances.is_empty() {
cx.warn_empty_attribute(cx.attr_span);
}
(!errored).then_some(LintAttribute {
reason,
lint_instances,
attr_span: cx.attr_span,
attr_style: cx.attr_style,
attr_id: HashIgnoredAttrId { attr_id: cx.attr_id },
kind: T::KIND,
})
}
fn check_lint<'a, S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
lint_store: &'a dyn DynLintStore,
original_name: Symbol,
full_name: &mut Symbol,
tool_name: Option<Symbol>,
tool_span: Option<Span>,
span: Span,
) -> Option<&'a [LintId]> {
let Some(tools) = cx.tools else {
unreachable!("tools required while parsing attributes");
};
if tools.is_empty() {
unreachable!("tools should never be empty")
}
match lint_store.check_lint_name(original_name.as_str(), tool_name, tools) {
CheckLintNameResult::Ok(ids) => Some(ids),
CheckLintNameResult::Tool(ids, new_lint_name) => {
let _name = match new_lint_name {
None => original_name,
Some(new_lint_name) => {
let new_lint_name = Symbol::intern(&new_lint_name);
cx.emit_lint(
RENAMED_AND_REMOVED_LINTS,
AttributeLintKind::DeprecatedLintName {
name: *full_name,
suggestion: span,
replace: new_lint_name,
},
span,
);
new_lint_name
}
};
Some(ids)
}
CheckLintNameResult::MissingTool => {
// If `MissingTool` is returned, then either the lint does not
// exist in the tool or the code was not compiled with the tool and
// therefore the lint was never added to the `LintStore`. To detect
// this is the responsibility of the lint tool.
None
}
CheckLintNameResult::NoTool => {
cx.emit_err(UnknownToolInScopedLint {
span: tool_span,
tool_name: tool_name.unwrap(),
full_lint_name: *full_name,
is_nightly_build: cx.sess.is_nightly_build(),
});
None
}
CheckLintNameResult::Renamed(replace) => {
cx.emit_lint(
RENAMED_AND_REMOVED_LINTS,
AttributeLintKind::RenamedLint { name: *full_name, replace, suggestion: span },
span,
);
// Since it was renamed, and we have emitted the warning
// we replace the "full_name", to ensure we don't get notes with:
// `#[allow(NEW_NAME)]` implied by `#[allow(OLD_NAME)]`
// Other lints still have access to the original name as the user wrote it,
// through `original_name`
*full_name = replace;
// If this lint was renamed, apply the new lint instead of ignoring the
// attribute. Ignore any errors or warnings that happen because the new
// name is inaccurate.
// NOTE: `new_name` already includes the tool name, so we don't
// have to add it again.
match lint_store.check_lint_name(replace.as_str(), None, tools) {
CheckLintNameResult::Ok(ids) => Some(ids),
_ => panic!("renamed lint does not exist: {replace}"),
}
}
CheckLintNameResult::RenamedToolLint(new_name) => {
cx.emit_lint(
RENAMED_AND_REMOVED_LINTS,
AttributeLintKind::RenamedLint {
name: *full_name,
replace: new_name,
suggestion: span,
},
span,
);
None
}
CheckLintNameResult::Removed(reason) => {
cx.emit_lint(
RENAMED_AND_REMOVED_LINTS,
AttributeLintKind::RemovedLint { name: *full_name, reason },
span,
);
None
}
CheckLintNameResult::NoLint(suggestion) => {
cx.emit_lint(
UNKNOWN_LINTS,
AttributeLintKind::UnknownLint { name: *full_name, suggestion, span },
span,
);
None
}
}
}
@@ -46,6 +46,7 @@
pub(crate) mod inline;
pub(crate) mod instruction_set;
pub(crate) mod link_attrs;
pub(crate) mod lint;
pub(crate) mod lint_helpers;
pub(crate) mod loop_match;
pub(crate) mod macro_attrs;
@@ -37,6 +37,7 @@
use crate::attributes::inline::*;
use crate::attributes::instruction_set::*;
use crate::attributes::link_attrs::*;
use crate::attributes::lint::*;
use crate::attributes::lint_helpers::*;
use crate::attributes::loop_match::*;
use crate::attributes::macro_attrs::*;
@@ -149,6 +150,7 @@ mod late {
ConfusablesParser,
ConstStabilityParser,
DocParser,
LintParser,
MacroUseParser,
NakedParser,
OnConstParser,
+42 -14
View File
@@ -3,15 +3,15 @@
use rustc_ast as ast;
use rustc_ast::token::DocFragmentKind;
use rustc_ast::{AttrItemKind, AttrStyle, NodeId, Safety};
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::DiagCtxtHandle;
use rustc_feature::{AttributeTemplate, Features};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::lints::AttributeLintKind;
use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, Target};
use rustc_middle::ty::RegisteredTools;
use rustc_session::Session;
use rustc_session::lint::{BuiltinLintDiag, LintId};
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
use crate::context::{AcceptContext, FinalizeContext, FinalizeFn, SharedContext, Stage};
use crate::early_parsed::{EARLY_PARSED_ATTRIBUTES, EarlyParsedState};
@@ -22,7 +22,7 @@
/// Context created once, for example as part of the ast lowering
/// context, through which all attributes can be lowered.
pub struct AttributeParser<'sess, S: Stage = Late> {
pub(crate) tools: Option<&'sess RegisteredTools>,
pub(crate) tools: Option<&'sess FxIndexSet<Ident>>,
pub(crate) features: Option<&'sess Features>,
pub(crate) sess: &'sess Session,
pub(crate) stage: S,
@@ -49,7 +49,7 @@ impl<'sess> AttributeParser<'sess, Early> {
/// errors will be emitted as a delayed bugs. in other words, we *expect* attributes parsed
/// with `parse_limited` to be reparsed later during ast lowering where we *do* emit the errors
///
/// Due to this function not taking in RegisteredTools, *do not* use this for parsing any lint attributes
/// Due to this function not taking in RegisteredTools (`FxIndexSet<Ident>`), *do not* use this for parsing any lint attributes
pub fn parse_limited(
sess: &'sess Session,
attrs: &[ast::Attribute],
@@ -72,7 +72,7 @@ pub fn parse_limited(
/// This does the same as `parse_limited`, except it has a `should_emit` parameter which allows it to emit errors.
/// Usually you want `parse_limited`, which emits no errors.
///
/// Due to this function not taking in RegisteredTools, *do not* use this for parsing any lint attributes
/// Due to this function not taking in RegisteredTools (`FxIndexSet<Ident>`), *do not* use this for parsing any lint attributes
pub fn parse_limited_should_emit(
sess: &'sess Session,
attrs: &[ast::Attribute],
@@ -104,16 +104,16 @@ pub fn parse_limited_should_emit(
/// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
/// crash if you tried to do so through [`parse_limited_all`](Self::parse_limited_all).
/// Therefore, if `parse_only` is None, then features *must* be provided.
pub fn parse_limited_all(
pub fn parse_limited_all<'a>(
sess: &'sess Session,
attrs: &[ast::Attribute],
attrs: impl IntoIterator<Item = &'a ast::Attribute>,
parse_only: Option<Symbol>,
target: Target,
target_span: Span,
target_node_id: NodeId,
features: Option<&'sess Features>,
emit_errors: ShouldEmit,
tools: Option<&'sess RegisteredTools>,
tools: Option<&'sess FxIndexSet<Ident>>,
) -> Vec<Attribute> {
let mut p = Self { features, tools, parse_only, sess, stage: Early { emit_errors } };
p.parse_attribute_list(
@@ -133,6 +133,32 @@ pub fn parse_limited_all(
)
}
/// This method provides the same functionality as [`parse_limited_all`](Self::parse_limited_all) except filtered,
/// making sure that only allow-listed symbols are parsed
pub fn parse_limited_all_filtered<'a>(
sess: &'sess Session,
attrs: impl IntoIterator<Item = &'a ast::Attribute>,
filter: &[Symbol],
target: Target,
target_span: Span,
target_node_id: NodeId,
features: Option<&'sess Features>,
emit_errors: ShouldEmit,
tools: &'sess FxIndexSet<Ident>,
) -> Vec<Attribute> {
Self::parse_limited_all(
sess,
attrs.into_iter().filter(|attr| attr.has_any_name(filter)),
None,
target,
target_span,
target_node_id,
features,
emit_errors,
Some(tools),
)
}
/// This method parses a single attribute, using `parse_fn`.
/// This is useful if you already know what exact attribute this is, and want to parse it.
pub fn parse_single<T>(
@@ -236,7 +262,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
pub fn new(
sess: &'sess Session,
features: &'sess Features,
tools: &'sess RegisteredTools,
tools: &'sess FxIndexSet<Ident>,
stage: S,
) -> Self {
Self { features: Some(features), tools: Some(tools), parse_only: None, sess, stage }
@@ -262,9 +288,9 @@ pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> {
///
/// `target_span` is the span of the thing this list of attributes is applied to,
/// and when `omit_doc` is set, doc attributes are filtered out.
pub fn parse_attribute_list(
pub fn parse_attribute_list<'a>(
&mut self,
attrs: &[ast::Attribute],
attrs: impl IntoIterator<Item = &'a ast::Attribute>,
target_span: Span,
target: Target,
omit_doc: OmitDoc,
@@ -280,9 +306,9 @@ pub fn parse_attribute_list(
let mut attr_paths: Vec<RefPathParser<'_>> = Vec::new();
let mut early_parsed_state = EarlyParsedState::default();
let mut finalizers: Vec<&FinalizeFn<S>> = Vec::with_capacity(attrs.len());
let mut finalizers: Vec<&FinalizeFn<S>> = Vec::new();
for attr in attrs {
for attr in attrs.into_iter() {
// If we're only looking for a single attribute, skip all the ones we don't care about.
if let Some(expected) = self.parse_only {
if !attr.has_name(expected) {
@@ -419,7 +445,9 @@ pub fn parse_attribute_list(
let attr = Attribute::Unparsed(Box::new(attr));
if self.tools.is_some_and(|tools|tools.iter().any(|tool|tool.name == parts[0]))
if self
.tools
.is_some_and(|tools| tools.iter().any(|tool| tool.name == parts[0]))
{
attributes.push(attr);
} else {
@@ -1138,3 +1138,14 @@ pub(crate) struct UnstableAttrForAlreadyStableFeature {
#[label("the stability attribute annotates this item")]
pub item_span: Span,
}
#[derive(Diagnostic)]
#[diag("unknown tool name `{$tool_name}` found in scoped lint: `{$full_lint_name}`", code = E0710)]
pub(crate) struct UnknownToolInScopedLint {
#[primary_span]
pub span: Option<Span>,
pub tool_name: Symbol,
pub full_lint_name: Symbol,
#[help("add `#![register_tool({$tool_name})]` to the crate root")]
pub is_nightly_build: bool,
}
+144 -2
View File
@@ -15,14 +15,16 @@
use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute};
use rustc_span::def_id::DefId;
use rustc_span::hygiene::Transparency;
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, sym};
pub use rustc_target::spec::SanitizerSet;
use thin_vec::ThinVec;
use crate::attrs::diagnostic::*;
use crate::attrs::pretty_printing::PrintAttribute;
use crate::limit::Limit;
use crate::{DefaultBodyStability, PartialConstStability, RustcVersion, Stability};
use crate::{
DefaultBodyStability, HashIgnoredAttrId, PartialConstStability, RustcVersion, Stability,
};
#[derive(Copy, Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub enum EiiImplResolution {
@@ -894,6 +896,143 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
}
}
#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub struct LintAttribute {
/// See RFC #2383
pub reason: Option<Symbol>,
pub kind: LintAttributeKind,
pub attr_style: AttrStyle,
pub attr_span: Span,
/// Needed by `LintExpectationId` to track fulfilled expectations
pub attr_id: HashIgnoredAttrId,
pub lint_instances: ThinVec<LintInstance>,
}
#[derive(Debug, Clone, Encodable, Decodable, HashStable_Generic)]
pub struct LintInstance {
/// The span of the `MetaItem` that produced this `LintInstance`
span: Span,
/// The fully resolved name of the lint
/// for renamed lints, this gets updated to match the new name
lint_name: Symbol,
/// The raw identifier for resolving this lint
/// if this is none, lint_name never diffed from the original
/// name after parsing, original_name.unwrap_or(self.lint_name)
original_name: Option<Symbol>,
/// Index of this lint, used to keep track of lint groups
lint_index: usize,
kind: LintAttrTool,
}
impl fmt::Display for LintInstance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.full_lint().fmt(f)
}
}
impl LintInstance {
pub fn new(
original_name: Symbol,
long_lint_name: String,
span: Span,
lint_index: usize,
) -> Self {
let original_name = (original_name.as_str() != long_lint_name).then_some(original_name);
let mut tool_name = None;
let lint_name = match long_lint_name.split_once("::") {
Some((new_tool_name, lint_name)) => {
tool_name = Some(Symbol::intern(new_tool_name));
Symbol::intern(lint_name)
}
None => Symbol::intern(&long_lint_name),
};
let kind = match tool_name {
Some(tool_name) => {
let full_lint = Symbol::intern(&format!("{tool_name}::{lint_name}",));
LintAttrTool::Present { tool_name, full_lint }
}
None => LintAttrTool::NoTool,
};
Self { original_name, span, lint_index, lint_name, kind }
}
pub fn full_lint(&self) -> Symbol {
match self.kind {
LintAttrTool::Present { full_lint, .. } => full_lint,
LintAttrTool::NoTool => self.lint_name,
}
}
pub fn span(&self) -> Span {
self.span
}
pub fn lint_index(&self) -> usize {
self.lint_index
}
pub fn lint_name(&self) -> Symbol {
self.lint_name
}
pub fn original_name_without_tool(&self) -> Symbol {
let full_original_lint_name = self.original_lint_name();
match self.kind {
LintAttrTool::Present { tool_name, .. } => Symbol::intern(
full_original_lint_name
.as_str()
.trim_start_matches(tool_name.as_str())
.trim_start_matches("::"),
),
LintAttrTool::NoTool => full_original_lint_name,
}
}
pub fn tool_name(&self) -> Option<Symbol> {
if let LintAttrTool::Present { tool_name, .. } = self.kind { Some(tool_name) } else { None }
}
pub fn tool_is_named(&self, other: Symbol) -> bool {
self.tool_name().is_some_and(|tool_name| tool_name == other)
}
pub fn original_lint_name(&self) -> Symbol {
match self.original_name {
Some(name) => name,
None => self.full_lint(),
}
}
}
#[derive(Debug, Clone, PrintAttribute, Encodable, Decodable, HashStable_Generic)]
enum LintAttrTool {
Present { tool_name: Symbol, full_lint: Symbol },
NoTool,
}
#[derive(Clone, Copy, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute, PartialEq)]
pub enum LintAttributeKind {
Allow,
Deny,
Expect,
Forbid,
Warn,
}
impl LintAttributeKind {
pub const fn symbol(&self) -> Symbol {
match self {
Self::Allow => sym::allow,
Self::Deny => sym::deny,
Self::Expect => sym::expect,
Self::Forbid => sym::forbid,
Self::Warn => sym::warn,
}
}
}
/// Represents parsed *built-in* inert attributes.
///
/// ## Overview
@@ -1097,6 +1236,9 @@ pub enum AttributeKind {
/// Represents `#[linkage]`.
Linkage(Linkage, Span),
/// Represents `#[allow]`, `#[expect]`, `#[warn]`, `#[deny]`, `#[forbid]`
LintAttributes(ThinVec<LintAttribute>),
/// Represents `#[loop_match]`.
LoopMatch(Span),
@@ -56,6 +56,7 @@ pub fn encode_cross_crate(&self) -> EncodeCrossCrate {
LinkOrdinal { .. } => No,
LinkSection { .. } => Yes, // Needed for rustdoc
Linkage(..) => No,
LintAttributes { .. } => No,
LoopMatch(..) => No,
MacroEscape(..) => No,
MacroExport { .. } => Yes,
@@ -17,6 +17,8 @@
use rustc_target::spec::SanitizerSet;
use thin_vec::ThinVec;
use crate::HashIgnoredAttrId;
use crate::attrs::LintInstance;
use crate::limit::Limit;
/// This trait is used to print attributes in `rustc_hir_pretty`.
@@ -191,8 +193,8 @@ fn print_attribute(&self, p: &mut Printer) {
}
print_tup!(A B C D E F G H);
print_skip!(Span, (), ErrorGuaranteed, AttrId);
print_disp!(u8, u16, u32, u128, usize, bool, NonZero<u32>, Limit);
print_skip!(Span, (), ErrorGuaranteed, AttrId, HashIgnoredAttrId);
print_disp!(u8, u16, u32, u128, usize, bool, NonZero<u32>, Limit, LintInstance);
print_debug!(
Symbol,
Ident,
+14
View File
@@ -1304,6 +1304,19 @@ pub fn is_parsed_attr(&self) -> bool {
Attribute::Unparsed(_) => false,
}
}
pub fn has_span_without_desugaring_kind(&self) -> bool {
let span = match self {
Attribute::Unparsed(attr) => attr.span,
Attribute::Parsed(AttributeKind::Deprecated { span, .. }) => *span,
Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs)) => {
return sub_attrs.iter().any(|attr| attr.attr_span.desugaring_kind().is_none());
}
Attribute::Parsed(attr) => panic!("can't get span of parsed attr: {:?}", attr),
};
span.desugaring_kind().is_none()
}
}
impl AttributeExt for Attribute {
@@ -1378,6 +1391,7 @@ fn span(&self) -> Span {
Attribute::Parsed(AttributeKind::DocComment { span, .. }) => *span,
Attribute::Parsed(AttributeKind::Deprecated { span, .. }) => *span,
Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) => cfgs[0].1,
Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs)) => sub_attrs[0].attr_span,
a => panic!("can't get the span of an arbitrary parsed attribute: {a:?}"),
}
}
+15 -21
View File
@@ -16,11 +16,10 @@
Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, listify, pluralize,
struct_span_code_err,
};
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, HirId, QPath, find_attr, is_range_literal};
use rustc_hir::{self as hir, Attribute, ExprKind, HirId, QPath, find_attr, is_range_literal};
use rustc_hir_analysis::NoVariantNamed;
use rustc_hir_analysis::errors::NoFieldOnType;
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer as _;
@@ -56,26 +55,21 @@
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub(crate) fn precedence(&self, expr: &hir::Expr<'_>) -> ExprPrecedence {
// For the purpose of rendering suggestions, disregard attributes
// that originate from desugaring of any kind. For example, `x?`
// desugars to `#[allow(unreachable_code)] match ...`. Failing to
// ignore the prefix attribute in the desugaring would cause this
// suggestion:
//
// let y: u32 = x?.try_into().unwrap();
// ++++++++++++++++++++
//
// to be rendered as:
//
// let y: u32 = (x?).try_into().unwrap();
// + +++++++++++++++++++++
let has_attr = |id: HirId| -> bool {
for attr in self.tcx.hir_attrs(id) {
// For the purpose of rendering suggestions, disregard attributes
// that originate from desugaring of any kind. For example, `x?`
// desugars to `#[allow(unreachable_code)] match ...`. Failing to
// ignore the prefix attribute in the desugaring would cause this
// suggestion:
//
// let y: u32 = x?.try_into().unwrap();
// ++++++++++++++++++++
//
// to be rendered as:
//
// let y: u32 = (x?).try_into().unwrap();
// + +++++++++++++++++++++
if attr.span().desugaring_kind().is_none() {
return true;
}
}
false
self.tcx.hir_attrs(id).iter().any(Attribute::has_span_without_desugaring_kind)
};
// Special case: range expressions are desugared to struct literals in HIR,
+2 -7
View File
@@ -369,7 +369,7 @@ pub fn check_lint_name(
}
}
match self.by_name.get(&complete_name) {
Some(Renamed(new_name, _)) => CheckLintNameResult::Renamed(new_name.to_string()),
Some(Renamed(new_name, _)) => CheckLintNameResult::Renamed(Symbol::intern(new_name)),
Some(Removed(reason)) => CheckLintNameResult::Removed(reason.to_string()),
None => match self.lint_groups.get(&*complete_name) {
// If neither the lint, nor the lint group exists check if there is a `clippy::`
@@ -818,12 +818,7 @@ pub fn get_associated_type(
/// be used for pretty-printing HIR by rustc_hir_pretty.
pub fn precedence(&self, expr: &hir::Expr<'_>) -> ExprPrecedence {
let has_attr = |id: hir::HirId| -> bool {
for attr in self.tcx.hir_attrs(id) {
if attr.span().desugaring_kind().is_none() {
return true;
}
}
false
self.tcx.hir_attrs(id).iter().any(hir::Attribute::has_span_without_desugaring_kind)
};
expr.precedence(&has_attr)
}
+138 -69
View File
@@ -12,7 +12,7 @@
use rustc_middle::ty::{RegisteredTools, TyCtxt};
use rustc_session::Session;
use rustc_session::lint::LintPass;
use rustc_span::{Ident, Span};
use rustc_span::{DUMMY_SP, Ident, Span};
use tracing::debug;
use crate::DecorateBuiltinLint;
@@ -59,13 +59,17 @@ fn check_id(&mut self, id: ast::NodeId) {
/// Merge the lints specified by any lint attributes into the
/// current lint context, call the provided function, then reset the
/// lints in effect to their previous state.
fn with_lint_attrs<F>(&mut self, id: ast::NodeId, attrs: &'_ [ast::Attribute], f: F)
where
fn with_lint_attrs<F>(
&mut self,
id: ast::NodeId,
attrs: &'_ [ast::Attribute],
f: F,
target_span: Span,
) where
F: FnOnce(&mut Self),
{
let is_crate_node = id == ast::CRATE_NODE_ID;
debug!(?id);
let push = self.context.builder.push(attrs, is_crate_node, None);
let push = self.context.builder.push(attrs, id, target_span);
debug!("early context: enter_attrs({:?})", attrs);
lint_callback!(self, check_attributes, attrs);
@@ -84,24 +88,39 @@ fn visit_id(&mut self, id: rustc_ast::NodeId) {
}
fn visit_param(&mut self, param: &'ast ast::Param) {
self.with_lint_attrs(param.id, &param.attrs, |cx| {
lint_callback!(cx, check_param, param);
ast_visit::walk_param(cx, param);
});
self.with_lint_attrs(
param.id,
&param.attrs,
|cx| {
lint_callback!(cx, check_param, param);
ast_visit::walk_param(cx, param);
},
param.span,
);
}
fn visit_item(&mut self, it: &'ast ast::Item) {
self.with_lint_attrs(it.id, &it.attrs, |cx| {
lint_callback!(cx, check_item, it);
ast_visit::walk_item(cx, it);
lint_callback!(cx, check_item_post, it);
})
self.with_lint_attrs(
it.id,
&it.attrs,
|cx| {
lint_callback!(cx, check_item, it);
ast_visit::walk_item(cx, it);
lint_callback!(cx, check_item_post, it);
},
it.span,
)
}
fn visit_foreign_item(&mut self, it: &'ast ast::ForeignItem) {
self.with_lint_attrs(it.id, &it.attrs, |cx| {
ast_visit::walk_item(cx, it);
})
self.with_lint_attrs(
it.id,
&it.attrs,
|cx| {
ast_visit::walk_item(cx, it);
},
it.span,
)
}
fn visit_pat(&mut self, p: &'ast ast::Pat) {
@@ -111,23 +130,38 @@ fn visit_pat(&mut self, p: &'ast ast::Pat) {
}
fn visit_pat_field(&mut self, field: &'ast ast::PatField) {
self.with_lint_attrs(field.id, &field.attrs, |cx| {
ast_visit::walk_pat_field(cx, field);
});
self.with_lint_attrs(
field.id,
&field.attrs,
|cx| {
ast_visit::walk_pat_field(cx, field);
},
field.span,
);
}
fn visit_expr(&mut self, e: &'ast ast::Expr) {
self.with_lint_attrs(e.id, &e.attrs, |cx| {
lint_callback!(cx, check_expr, e);
ast_visit::walk_expr(cx, e);
lint_callback!(cx, check_expr_post, e);
})
self.with_lint_attrs(
e.id,
&e.attrs,
|cx| {
lint_callback!(cx, check_expr, e);
ast_visit::walk_expr(cx, e);
lint_callback!(cx, check_expr_post, e);
},
e.span,
)
}
fn visit_expr_field(&mut self, f: &'ast ast::ExprField) {
self.with_lint_attrs(f.id, &f.attrs, |cx| {
ast_visit::walk_expr_field(cx, f);
})
self.with_lint_attrs(
f.id,
&f.attrs,
|cx| {
ast_visit::walk_expr_field(cx, f);
},
f.span,
)
}
fn visit_stmt(&mut self, s: &'ast ast::Stmt) {
@@ -139,10 +173,15 @@ fn visit_stmt(&mut self, s: &'ast ast::Stmt) {
//
// Note that statements get their attributes from
// the AST struct that they wrap (e.g. an item)
self.with_lint_attrs(s.id, s.attrs(), |cx| {
lint_callback!(cx, check_stmt, s);
ast_visit::walk_stmt(cx, s);
});
self.with_lint_attrs(
s.id,
s.attrs(),
|cx| {
lint_callback!(cx, check_stmt, s);
ast_visit::walk_stmt(cx, s);
},
s.span,
);
}
fn visit_fn(&mut self, fk: ast_visit::FnKind<'ast>, _: &AttrVec, span: Span, id: ast::NodeId) {
@@ -151,16 +190,26 @@ fn visit_fn(&mut self, fk: ast_visit::FnKind<'ast>, _: &AttrVec, span: Span, id:
}
fn visit_field_def(&mut self, s: &'ast ast::FieldDef) {
self.with_lint_attrs(s.id, &s.attrs, |cx| {
ast_visit::walk_field_def(cx, s);
})
self.with_lint_attrs(
s.id,
&s.attrs,
|cx| {
ast_visit::walk_field_def(cx, s);
},
s.span,
)
}
fn visit_variant(&mut self, v: &'ast ast::Variant) {
self.with_lint_attrs(v.id, &v.attrs, |cx| {
lint_callback!(cx, check_variant, v);
ast_visit::walk_variant(cx, v);
})
self.with_lint_attrs(
v.id,
&v.attrs,
|cx| {
lint_callback!(cx, check_variant, v);
ast_visit::walk_variant(cx, v);
},
v.span,
)
}
fn visit_ty(&mut self, t: &'ast ast::Ty) {
@@ -173,10 +222,15 @@ fn visit_ident(&mut self, ident: &Ident) {
}
fn visit_local(&mut self, l: &'ast ast::Local) {
self.with_lint_attrs(l.id, &l.attrs, |cx| {
lint_callback!(cx, check_local, l);
ast_visit::walk_local(cx, l);
})
self.with_lint_attrs(
l.id,
&l.attrs,
|cx| {
lint_callback!(cx, check_local, l);
ast_visit::walk_local(cx, l);
},
l.span,
)
}
fn visit_block(&mut self, b: &'ast ast::Block) {
@@ -185,10 +239,15 @@ fn visit_block(&mut self, b: &'ast ast::Block) {
}
fn visit_arm(&mut self, a: &'ast ast::Arm) {
self.with_lint_attrs(a.id, &a.attrs, |cx| {
lint_callback!(cx, check_arm, a);
ast_visit::walk_arm(cx, a);
})
self.with_lint_attrs(
a.id,
&a.attrs,
|cx| {
lint_callback!(cx, check_arm, a);
ast_visit::walk_arm(cx, a);
},
a.span,
)
}
fn visit_generic_arg(&mut self, arg: &'ast ast::GenericArg) {
@@ -197,10 +256,15 @@ fn visit_generic_arg(&mut self, arg: &'ast ast::GenericArg) {
}
fn visit_generic_param(&mut self, param: &'ast ast::GenericParam) {
self.with_lint_attrs(param.id, &param.attrs, |cx| {
lint_callback!(cx, check_generic_param, param);
ast_visit::walk_generic_param(cx, param);
});
self.with_lint_attrs(
param.id,
&param.attrs,
|cx| {
lint_callback!(cx, check_generic_param, param);
ast_visit::walk_generic_param(cx, param);
},
param.span(),
);
}
fn visit_generics(&mut self, g: &'ast ast::Generics) {
@@ -220,25 +284,30 @@ fn visit_poly_trait_ref(&mut self, t: &'ast ast::PolyTraitRef) {
}
fn visit_assoc_item(&mut self, item: &'ast ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
self.with_lint_attrs(item.id, &item.attrs, |cx| {
match ctxt {
ast_visit::AssocCtxt::Trait => {
lint_callback!(cx, check_trait_item, item);
self.with_lint_attrs(
item.id,
&item.attrs,
|cx| {
match ctxt {
ast_visit::AssocCtxt::Trait => {
lint_callback!(cx, check_trait_item, item);
}
ast_visit::AssocCtxt::Impl { .. } => {
lint_callback!(cx, check_impl_item, item);
}
}
ast_visit::AssocCtxt::Impl { .. } => {
lint_callback!(cx, check_impl_item, item);
ast_visit::walk_assoc_item(cx, item, ctxt);
match ctxt {
ast_visit::AssocCtxt::Trait => {
lint_callback!(cx, check_trait_item_post, item);
}
ast_visit::AssocCtxt::Impl { .. } => {
lint_callback!(cx, check_impl_item_post, item);
}
}
}
ast_visit::walk_assoc_item(cx, item, ctxt);
match ctxt {
ast_visit::AssocCtxt::Trait => {
lint_callback!(cx, check_trait_item_post, item);
}
ast_visit::AssocCtxt::Impl { .. } => {
lint_callback!(cx, check_impl_item_post, item);
}
}
});
},
item.span,
);
}
fn visit_attribute(&mut self, attr: &'ast ast::Attribute) {
@@ -367,7 +436,7 @@ fn check_ast_node_inner<'a, T: EarlyLintPass>(
) {
let mut cx = EarlyContextAndPass { context, tcx, pass };
cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx));
cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx), DUMMY_SP);
// All of the buffered lints should have been emitted at this point.
// If not, that means that we somehow buffered a lint for a node id
@@ -351,6 +351,28 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
&AttributeLintKind::MissingOptionsForOnMove => {
lints::MissingOptionsForOnMoveAttr.into_diag(dcx, level)
}
&AttributeLintKind::RenamedLint { name, replace, suggestion } => lints::RenamedLint {
name,
replace,
suggestion: lints::RenamedLintSuggestion::WithSpan { suggestion, replace },
}
.into_diag(dcx, level),
&AttributeLintKind::DeprecatedLintName { name, suggestion, replace } => {
lints::DeprecatedLintName { name, suggestion, replace }.into_diag(dcx, level)
}
&AttributeLintKind::RemovedLint { name, ref reason } => {
lints::RemovedLint { name, reason }.into_diag(dcx, level)
}
&AttributeLintKind::UnknownLint { name, span, suggestion } => lints::UnknownLint {
name,
suggestion: suggestion.map(|(replace, from_rustc)| {
lints::UnknownLintSuggestion::WithSpan { suggestion: span, replace, from_rustc }
}),
}
.into_diag(dcx, level),
&AttributeLintKind::IgnoredUnlessCrateSpecified { level: attr_level, name } => {
lints::IgnoredUnlessCrateSpecified { level: attr_level, name }.into_diag(dcx, level)
}
}
}
}
-30
View File
@@ -44,36 +44,6 @@ fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
}
}
#[derive(Diagnostic)]
#[diag("malformed lint attribute input", code = E0452)]
pub(crate) struct MalformedAttribute {
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub sub: MalformedAttributeSub,
}
#[derive(Subdiagnostic)]
pub(crate) enum MalformedAttributeSub {
#[label("bad attribute argument")]
BadAttributeArgument(#[primary_span] Span),
#[label("reason must be a string literal")]
ReasonMustBeStringLiteral(#[primary_span] Span),
#[label("reason in lint attribute must come last")]
ReasonMustComeLast(#[primary_span] Span),
}
#[derive(Diagnostic)]
#[diag("unknown tool name `{$tool_name}` found in scoped lint: `{$tool_name}::{$lint_name}`", code = E0710)]
pub(crate) struct UnknownToolInScopedLint {
#[primary_span]
pub span: Option<Span>,
pub tool_name: Symbol,
pub lint_name: String,
#[help("add `#![register_tool({$tool_name})]` to the crate root")]
pub is_nightly_build: bool,
}
#[derive(Diagnostic)]
#[diag("`...` range patterns are deprecated", code = E0783)]
pub(crate) struct BuiltinEllipsisInclusiveRangePatterns {
+139 -304
View File
@@ -1,13 +1,13 @@
use rustc_ast as ast;
use rustc_ast::attr::AttributeExt;
use rustc_ast_pretty::pprust;
use rustc_ast::{DUMMY_NODE_ID, NodeId};
use rustc_attr_parsing::AttributeParser;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_data_structures::unord::UnordSet;
use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, MultiSpan, msg};
use rustc_feature::{Features, GateIssue};
use rustc_hir as hir;
use rustc_hir::HirId;
use rustc_hir::attrs::{LintAttribute, LintAttributeKind, LintInstance};
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{self as hir, HirId, Target, find_attr};
use rustc_index::IndexVec;
use rustc_middle::bug;
use rustc_middle::hir::nested_filter;
@@ -20,7 +20,7 @@
use rustc_session::Session;
use rustc_session::lint::builtin::{
self, FORBIDDEN_LINT_GROUPS, RENAMED_AND_REMOVED_LINTS, SINGLE_USE_LIFETIMES,
UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES,
UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS,
};
use rustc_session::lint::{
CheckLintNameResult, Level, Lint, LintExpectationId, LintId, TargetLint,
@@ -31,17 +31,26 @@
use crate::builtin::MISSING_DOCS;
use crate::context::LintStore;
use crate::errors::{
CheckNameUnknownTool, MalformedAttribute, MalformedAttributeSub, OverruledAttribute,
OverruledAttributeSub, RequestedLevel, UnknownToolInScopedLint, UnsupportedGroup,
CheckNameUnknownTool, OverruledAttribute, OverruledAttributeSub, RequestedLevel,
UnsupportedGroup,
};
use crate::late::unerased_lint_store;
use crate::lints::{
DeprecatedLintName, DeprecatedLintNameFromCommandLine, IgnoredUnlessCrateSpecified,
OverruledAttributeLint, RemovedLint, RemovedLintFromCommandLine, RenamedLint,
RenamedLintFromCommandLine, RenamedLintSuggestion, UnknownLint, UnknownLintFromCommandLine,
DeprecatedLintNameFromCommandLine, OverruledAttributeLint, RemovedLintFromCommandLine,
RenamedLintFromCommandLine, RenamedLintSuggestion, UnknownLintFromCommandLine,
UnknownLintSuggestion,
};
const ALLOW_LISTED_ATTRS: &[Symbol] = &[
sym::allow,
sym::deny,
sym::expect,
sym::forbid,
sym::warn,
sym::automatically_derived,
sym::doc,
];
/// Collection of lint levels for the whole crate.
/// This is used by AST-based lints, which do not
/// wait until we have built HIR to be emitted.
@@ -268,11 +277,7 @@ fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectati
impl<'tcx> LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
fn add_id(&mut self, hir_id: HirId) {
self.provider.cur = hir_id;
self.add(
self.provider.attrs.get(hir_id.local_id),
hir_id == hir::CRATE_HIR_ID,
Some(hir_id),
);
self.add(self.provider.attrs.get(hir_id.local_id), Some(hir_id));
}
}
@@ -392,7 +397,19 @@ pub fn crate_root(
crate_attrs: &[ast::Attribute],
) -> Self {
let mut builder = Self::new(sess, features, lint_added_lints, store, registered_tools);
builder.add(crate_attrs, true, None);
let parsed_crate_attrs = AttributeParser::parse_limited_all_filtered(
sess,
crate_attrs,
ALLOW_LISTED_ATTRS,
Target::Crate,
DUMMY_SP,
DUMMY_NODE_ID,
Some(features),
rustc_attr_parsing::ShouldEmit::Nothing,
registered_tools,
);
builder.add(&parsed_crate_attrs, None);
builder
}
@@ -422,18 +439,31 @@ fn process_command_line(&mut self) {
pub(crate) fn push(
&mut self,
attrs: &[ast::Attribute],
is_crate_node: bool,
source_hir_id: Option<HirId>,
node_id: NodeId,
target_span: Span,
) -> BuilderPush {
let prev = self.provider.cur;
self.provider.cur =
self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev });
if !attrs.is_empty() {
let attrs = AttributeParser::parse_limited_all_filtered(
self.sess,
attrs,
ALLOW_LISTED_ATTRS,
Target::Fn,
target_span,
node_id,
Some(self.features),
rustc_attr_parsing::ShouldEmit::Nothing,
self.registered_tools,
);
self.add(attrs, is_crate_node, source_hir_id);
self.add(&attrs, None);
if self.provider.current_specs().is_empty() {
self.provider.sets.list.pop();
self.provider.cur = prev;
if self.provider.current_specs().is_empty() {
self.provider.sets.list.pop();
self.provider.cur = prev;
}
}
BuilderPush { prev }
@@ -480,7 +510,7 @@ fn add_command_line(&mut self) {
.emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() });
}
match self.store.check_lint_name(lint_name_only, tool_name, self.registered_tools) {
CheckLintNameResult::Renamed(ref replace) => {
CheckLintNameResult::Renamed(replace) => {
let name = lint_name.as_str();
let suggestion = RenamedLintSuggestion::WithoutSpan { replace };
let requested_level = RequestedLevel { level, lint_name };
@@ -640,297 +670,102 @@ fn insert_spec(&mut self, id: LintId, LevelAndSource { level, lint_id, src }: Le
};
}
fn add(
fn simple_add(
&mut self,
attrs: &[impl AttributeExt],
is_crate_node: bool,
source_hir_id: Option<HirId>,
level: Level,
lint: &LintInstance,
reason: Option<Symbol>,
expect_lint_id: Option<LintExpectationId>,
) {
let sess = self.sess;
for (attr_index, attr) in attrs.iter().enumerate() {
if attr.is_automatically_derived_attr() {
self.insert(
LintId::of(SINGLE_USE_LIFETIMES),
LevelAndSource {
level: Level::Allow,
lint_id: None,
src: LintLevelSource::Default,
},
);
continue;
}
// If this function returns none, it means the attribute parser has already emitted appropriate errors
// `#[doc(hidden)]` disables missing_docs check.
if attr.is_doc_hidden() {
self.insert(
LintId::of(MISSING_DOCS),
LevelAndSource {
level: Level::Allow,
lint_id: None,
src: LintLevelSource::Default,
},
);
continue;
}
let src =
LintLevelSource::Node { name: lint.original_lint_name(), span: lint.span(), reason };
let (level, lint_id) = match Level::from_attr(attr) {
None => continue,
// This is the only lint level with a `LintExpectationId` that can be created from
// an attribute.
Some((Level::Expect, Some(unstable_id))) if let Some(hir_id) = source_hir_id => {
let LintExpectationId::Unstable { lint_index: None, attr_id: _ } = unstable_id
else {
bug!("stable id Level::from_attr")
};
let id = match self.store.get_lint_by_name(lint.full_lint().as_str()) {
Some(TargetLint::Id(id)) => id,
None | Some(_) => bug!(
"guaranteed to find id due to previous parsing, happened while parsing {:?}",
lint,
),
};
let stable_id = LintExpectationId::Stable {
hir_id,
attr_index: attr_index.try_into().unwrap(),
lint_index: None,
};
if self.check_gated_lint(*id, lint.span(), false) {
self.insert_spec(*id, LevelAndSource { level, lint_id: expect_lint_id, src });
}
}
(Level::Expect, Some(stable_id))
}
Some((lvl, id)) => (lvl, id),
};
let Some(mut metas) = attr.meta_item_list() else { continue };
// Check whether `metas` is empty, and get its last element.
let Some(tail_li) = metas.last() else {
// This emits the unused_attributes lint for `#[level()]`
continue;
};
// Before processing the lint names, look for a reason (RFC 2383)
// at the end.
let mut reason = None;
if let Some(item) = tail_li.meta_item() {
match item.kind {
ast::MetaItemKind::Word => {} // actual lint names handled later
ast::MetaItemKind::NameValue(ref name_value) => {
if item.path == sym::reason {
if let ast::LitKind::Str(rationale, _) = name_value.kind {
reason = Some(rationale);
} else {
sess.dcx().emit_err(MalformedAttribute {
span: name_value.span,
sub: MalformedAttributeSub::ReasonMustBeStringLiteral(
name_value.span,
),
});
}
// found reason, reslice meta list to exclude it
metas.pop().unwrap();
} else {
sess.dcx().emit_err(MalformedAttribute {
span: item.span,
sub: MalformedAttributeSub::BadAttributeArgument(item.span),
});
}
}
ast::MetaItemKind::List(_) => {
sess.dcx().emit_err(MalformedAttribute {
span: item.span,
sub: MalformedAttributeSub::BadAttributeArgument(item.span),
});
}
}
}
for (lint_index, li) in metas.iter_mut().enumerate() {
let mut lint_id = lint_id;
if let Some(id) = &mut lint_id {
id.set_lint_index(Some(lint_index as u16));
}
let sp = li.span();
let meta_item = match li {
ast::MetaItemInner::MetaItem(meta_item) if meta_item.is_word() => meta_item,
_ => {
let sub = if let Some(item) = li.meta_item()
&& let ast::MetaItemKind::NameValue(_) = item.kind
&& item.path == sym::reason
{
MalformedAttributeSub::ReasonMustComeLast(sp)
} else {
MalformedAttributeSub::BadAttributeArgument(sp)
};
sess.dcx().emit_err(MalformedAttribute { span: sp, sub });
continue;
}
};
let tool_ident = if meta_item.path.segments.len() > 1 {
Some(meta_item.path.segments.remove(0).ident)
} else {
None
};
let tool_name = tool_ident.map(|ident| ident.name);
let name = pprust::path_to_string(&meta_item.path);
let lint_result =
self.store.check_lint_name(&name, tool_name, self.registered_tools);
let (ids, name) = match lint_result {
CheckLintNameResult::Ok(ids) => {
let name =
meta_item.path.segments.last().expect("empty lint name").ident.name;
(ids, name)
}
CheckLintNameResult::Tool(ids, new_lint_name) => {
let name = match new_lint_name {
None => {
let complete_name =
&format!("{}::{}", tool_ident.unwrap().name, name);
Symbol::intern(complete_name)
}
Some(new_lint_name) => {
self.emit_span_lint(
builtin::RENAMED_AND_REMOVED_LINTS,
sp.into(),
DeprecatedLintName {
name,
suggestion: sp,
replace: &new_lint_name,
},
);
Symbol::intern(&new_lint_name)
}
};
(ids, name)
}
CheckLintNameResult::MissingTool => {
// If `MissingTool` is returned, then either the lint does not
// exist in the tool or the code was not compiled with the tool and
// therefore the lint was never added to the `LintStore`. To detect
// this is the responsibility of the lint tool.
continue;
}
CheckLintNameResult::NoTool => {
sess.dcx().emit_err(UnknownToolInScopedLint {
span: tool_ident.map(|ident| ident.span),
tool_name: tool_name.unwrap(),
lint_name: pprust::path_to_string(&meta_item.path),
is_nightly_build: sess.is_nightly_build(),
});
continue;
}
CheckLintNameResult::Renamed(ref replace) => {
if self.lint_added_lints {
let suggestion =
RenamedLintSuggestion::WithSpan { suggestion: sp, replace };
let name =
tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
self.emit_span_lint(
RENAMED_AND_REMOVED_LINTS,
sp.into(),
RenamedLint { name: name.as_str(), replace, suggestion },
);
}
// If this lint was renamed, apply the new lint instead of ignoring the
// attribute. Ignore any errors or warnings that happen because the new
// name is inaccurate.
// NOTE: `new_name` already includes the tool name, so we don't
// have to add it again.
let CheckLintNameResult::Ok(ids) =
self.store.check_lint_name(replace, None, self.registered_tools)
else {
panic!("renamed lint does not exist: {replace}");
};
(ids, Symbol::intern(&replace))
}
CheckLintNameResult::Removed(ref reason) => {
if self.lint_added_lints {
let name =
tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
self.emit_span_lint(
RENAMED_AND_REMOVED_LINTS,
sp.into(),
RemovedLint { name: name.as_str(), reason },
);
}
continue;
}
CheckLintNameResult::NoLint(suggestion) => {
if self.lint_added_lints {
let name =
tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
let suggestion = suggestion.map(|(replace, from_rustc)| {
UnknownLintSuggestion::WithSpan {
suggestion: sp,
replace,
from_rustc,
}
});
self.emit_span_lint(
UNKNOWN_LINTS,
sp.into(),
UnknownLint { name, suggestion },
);
}
continue;
}
};
let src = LintLevelSource::Node { name, span: sp, reason };
for &id in ids {
if self.check_gated_lint(id, sp, false) {
self.insert_spec(id, LevelAndSource { level, lint_id, src });
}
}
// This checks for instances where the user writes
// `#[expect(unfulfilled_lint_expectations)]` in that case we want to avoid
// overriding the lint level but instead add an expectation that can't be
// fulfilled. The lint message will include an explanation, that the
// `unfulfilled_lint_expectations` lint can't be expected.
if let (Level::Expect, Some(expect_id)) = (level, lint_id) {
// The `unfulfilled_lint_expectations` lint is not part of any lint
// groups. Therefore. we only need to check the slice if it contains a
// single lint.
let is_unfulfilled_lint_expectations = match ids {
[lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS),
_ => false,
};
self.provider.push_expectation(
expect_id,
LintExpectation::new(
reason,
sp,
is_unfulfilled_lint_expectations,
tool_name,
),
);
}
}
fn add(&mut self, attrs: &[hir::Attribute], source_hir_id: Option<HirId>) {
if find_attr!(attrs, AutomaticallyDerived(..)) {
self.insert(
LintId::of(SINGLE_USE_LIFETIMES),
LevelAndSource {
level: Level::Allow,
lint_id: None,
src: LintLevelSource::Default,
},
);
}
// `#[doc(hidden)]` disables missing_docs check.
if find_attr!(attrs, Doc(d) if d.hidden.is_some()) {
self.insert(
LintId::of(MISSING_DOCS),
LevelAndSource {
level: Level::Allow,
lint_id: None,
src: LintLevelSource::Default,
},
);
}
if self.lint_added_lints && !is_crate_node {
for (id, &LevelAndSource { level, ref src, .. }) in self.current_specs().iter() {
if !id.lint.crate_level_only {
let Some(attrs) = find_attr!(attrs, LintAttributes(sub_attrs) => sub_attrs.into_iter())
else {
return;
};
for (attr_index, LintAttribute { reason, lint_instances, attr_id, kind, .. }) in
attrs.enumerate()
{
let attr_id = attr_id.attr_id;
let level = match kind {
LintAttributeKind::Allow => Level::Allow,
LintAttributeKind::Deny => Level::Deny,
LintAttributeKind::Forbid => Level::Forbid,
LintAttributeKind::Warn => Level::Warn,
LintAttributeKind::Expect => {
for lint in lint_instances {
let lint_index = lint.lint_index().try_into().unwrap();
let attr_index = attr_index.try_into().unwrap();
let expectation_id = match source_hir_id {
None => LintExpectationId::Unstable { attr_id, lint_index },
Some(hir_id) => LintExpectationId::Stable {
hir_id,
attr_id,
lint_index,
attr_index,
},
};
self.simple_add(Level::Expect, lint, *reason, Some(expectation_id));
let is_unfulfilled_lint_expectations =
lint.lint_name().as_str() == UNFULFILLED_LINT_EXPECTATIONS.name_lower();
self.provider.push_expectation(
expectation_id,
LintExpectation::new(
*reason,
lint.span(),
is_unfulfilled_lint_expectations,
lint.tool_name(),
),
);
}
continue;
}
let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src
else {
continue;
};
self.emit_span_lint(
UNUSED_ATTRIBUTES,
lint_attr_span.into(),
IgnoredUnlessCrateSpecified { level: level.as_str(), name: lint_attr_name },
);
// don't set a separate error for every lint in the group
break;
};
for lint in lint_instances {
self.simple_add(level, lint, *reason, None);
}
}
}
+16 -16
View File
@@ -1238,11 +1238,11 @@ pub(crate) struct OverruledAttributeLint<'a> {
#[derive(Diagnostic)]
#[diag("lint name `{$name}` is deprecated and may not have an effect in the future")]
pub(crate) struct DeprecatedLintName<'a> {
pub name: String,
pub(crate) struct DeprecatedLintName {
pub name: Symbol,
#[suggestion("change it to", code = "{replace}", applicability = "machine-applicable")]
pub suggestion: Span,
pub replace: &'a str,
pub replace: Symbol,
}
#[derive(Diagnostic)]
@@ -1257,32 +1257,32 @@ pub(crate) struct DeprecatedLintNameFromCommandLine<'a> {
#[derive(Diagnostic)]
#[diag("lint `{$name}` has been renamed to `{$replace}`")]
pub(crate) struct RenamedLint<'a> {
pub name: &'a str,
pub replace: &'a str,
pub(crate) struct RenamedLint {
pub name: Symbol,
pub replace: Symbol,
#[subdiagnostic]
pub suggestion: RenamedLintSuggestion<'a>,
pub suggestion: RenamedLintSuggestion,
}
#[derive(Subdiagnostic)]
pub(crate) enum RenamedLintSuggestion<'a> {
pub(crate) enum RenamedLintSuggestion {
#[suggestion("use the new name", code = "{replace}", applicability = "machine-applicable")]
WithSpan {
#[primary_span]
suggestion: Span,
replace: &'a str,
replace: Symbol,
},
#[help("use the new name `{$replace}`")]
WithoutSpan { replace: &'a str },
WithoutSpan { replace: Symbol },
}
#[derive(Diagnostic)]
#[diag("lint `{$name}` has been renamed to `{$replace}`")]
pub(crate) struct RenamedLintFromCommandLine<'a> {
pub name: &'a str,
pub replace: &'a str,
pub replace: Symbol,
#[subdiagnostic]
pub suggestion: RenamedLintSuggestion<'a>,
pub suggestion: RenamedLintSuggestion,
#[subdiagnostic]
pub requested_level: RequestedLevel<'a>,
}
@@ -1290,7 +1290,7 @@ pub(crate) struct RenamedLintFromCommandLine<'a> {
#[derive(Diagnostic)]
#[diag("lint `{$name}` has been removed: {$reason}")]
pub(crate) struct RemovedLint<'a> {
pub name: &'a str,
pub name: Symbol,
pub reason: &'a str,
}
@@ -1306,7 +1306,7 @@ pub(crate) struct RemovedLintFromCommandLine<'a> {
#[derive(Diagnostic)]
#[diag("unknown lint: `{$name}`")]
pub(crate) struct UnknownLint {
pub name: String,
pub name: Symbol,
#[subdiagnostic]
pub suggestion: Option<UnknownLintSuggestion>,
}
@@ -1348,8 +1348,8 @@ pub(crate) struct UnknownLintFromCommandLine<'a> {
#[derive(Diagnostic)]
#[diag("{$level}({$name}) is ignored unless specified at crate level")]
pub(crate) struct IgnoredUnlessCrateSpecified<'a> {
pub level: &'a str,
pub(crate) struct IgnoredUnlessCrateSpecified {
pub level: Symbol,
pub name: Symbol,
}
-1
View File
@@ -5,7 +5,6 @@ edition = "2024"
[dependencies]
# tidy-alphabetical-start
rustc_ast = { path = "../rustc_ast" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_error_messages = { path = "../rustc_error_messages" }
rustc_hir_id = { path = "../rustc_hir_id" }
+23
View File
@@ -802,6 +802,29 @@ pub enum AttributeLintKind {
name: Symbol,
},
OnMoveMalformedAttrExpectedLiteralOrDelimiter,
RenamedLint {
name: Symbol,
replace: Symbol,
suggestion: Span,
},
DeprecatedLintName {
name: Symbol,
suggestion: Span,
replace: Symbol,
},
RemovedLint {
name: Symbol,
reason: String,
},
UnknownLint {
name: Symbol,
span: Span,
suggestion: Option<(Symbol, bool)>,
},
IgnoredUnlessCrateSpecified {
level: Symbol,
name: Symbol,
},
}
#[derive(Debug, Clone, HashStable_Generic)]
@@ -85,7 +85,8 @@
use interpret::ErrorHandled;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::HirId;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{Attribute, HirId};
use rustc_index::{IndexSlice, IndexVec};
use rustc_middle::middle::region;
use rustc_middle::mir::{self, *};
@@ -93,7 +94,6 @@
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, ValTree};
use rustc_middle::{bug, span_bug};
use rustc_pattern_analysis::rustc::RustcPatCtxt;
use rustc_session::lint::Level;
use rustc_span::{DUMMY_SP, Span, Spanned};
use tracing::{debug, instrument};
@@ -1298,7 +1298,12 @@ fn maybe_lint_level_root_bounded(&mut self, orig_id: HirId) -> HirId {
break;
}
if self.tcx.hir_attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) {
if self
.tcx
.hir_attrs(id)
.iter()
.any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::LintAttributes { .. })))
{
// This is a rare case. It's for a node path that doesn't reach the root due to an
// intervening lint level attribute. This result doesn't get cached.
return id;
+69 -93
View File
@@ -10,7 +10,7 @@
use std::slice;
use rustc_abi::ExternAbi;
use rustc_ast::{AttrStyle, MetaItemKind, ast};
use rustc_ast::ast;
use rustc_attr_parsing::{AttributeParser, Late};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::thin_vec::ThinVec;
@@ -19,8 +19,8 @@
use rustc_feature::{AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
use rustc_hir::attrs::diagnostic::Directive;
use rustc_hir::attrs::{
AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr,
ReprAttr, SanitizerSet,
AttributeKind, CrateType, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution,
InlineAttr, LintAttribute, ReprAttr, SanitizerSet,
};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalModDefId;
@@ -37,7 +37,6 @@
use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::{self, TyCtxt, TypingMode};
use rustc_middle::{bug, span_bug};
use rustc_session::config::CrateType;
use rustc_session::lint;
use rustc_session::lint::builtin::{
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
@@ -141,7 +140,6 @@ fn check_attributes(
let mut seen = FxHashMap::default();
let attrs = self.tcx.hir_attrs(hir_id);
for attr in attrs {
let mut style = None;
match attr {
Attribute::Parsed(AttributeKind::ProcMacro(_)) => {
self.check_proc_macro(hir_id, target, ProcMacroKind::FunctionLike)
@@ -223,6 +221,7 @@ fn check_attributes(
Attribute::Parsed(AttributeKind::OnMove { span, directive }) => {
self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref())
},
Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs)) => self.check_lint_attr(hir_id, sub_attrs),
Attribute::Parsed(
// tidy-alphabetical-start
AttributeKind::RustcAllowIncoherentImpl(..)
@@ -380,18 +379,8 @@ fn check_attributes(
| AttributeKind::WindowsSubsystem(..)
// tidy-alphabetical-end
) => { /* do nothing */ }
Attribute::Unparsed(attr_item) => {
style = Some(attr_item.style);
Attribute::Unparsed(_) => {
match attr.path().as_slice() {
[
// ok
sym::allow
| sym::expect
| sym::warn
| sym::deny
| sym::forbid,
..
] => {}
[name, rest@..] => {
match BUILTIN_ATTRIBUTE_MAP.get(name) {
Some(_) => {
@@ -473,8 +462,7 @@ fn check_attributes(
&mut seen,
);
}
self.check_unused_attribute(hir_id, attr, style)
self.check_unused_attribute(hir_id, attr)
}
self.check_repr(attrs, span, target, item, hir_id);
@@ -1583,88 +1571,76 @@ fn check_macro_export(&self, hir_id: HirId, attr_span: Span, target: Target) {
}
}
fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute, style: Option<AttrStyle>) {
// Warn on useless empty attributes.
// FIXME(jdonszelmann): this lint should be moved to attribute parsing, see `AcceptContext::warn_empty_attribute`
let note =
if attr.has_any_name(&[sym::allow, sym::expect, sym::warn, sym::deny, sym::forbid])
&& attr.meta_item_list().is_some_and(|list| list.is_empty())
{
errors::UnusedNote::EmptyList { name: attr.name().unwrap() }
} else if attr.has_any_name(&[
sym::allow,
sym::warn,
sym::deny,
sym::forbid,
sym::expect,
]) && let Some(meta) = attr.meta_item_list()
&& let [meta] = meta.as_slice()
&& let Some(item) = meta.meta_item()
&& let MetaItemKind::NameValue(_) = &item.kind
&& item.path == sym::reason
{
errors::UnusedNote::NoLints { name: attr.name().unwrap() }
} else if attr.has_any_name(&[
sym::allow,
sym::warn,
sym::deny,
sym::forbid,
sym::expect,
]) && let Some(meta) = attr.meta_item_list()
&& meta.iter().any(|meta| {
meta.meta_item().map_or(false, |item| {
item.path == sym::linker_messages || item.path == sym::linker_info
})
})
{
if hir_id != CRATE_HIR_ID {
match style {
Some(ast::AttrStyle::Outer) => {
let attr_span = attr.span();
let bang_position = self
.tcx
.sess
.source_map()
.span_until_char(attr_span, '[')
.shrink_to_hi();
fn check_lint_attr(&self, hir_id: HirId, sub_attrs: &[LintAttribute]) {
for LintAttribute { attr_span, lint_instances, attr_style, .. } in sub_attrs {
if !lint_instances.iter().any(|id| {
id.lint_name() == sym::linker_messages || id.lint_name() == sym::linker_info
}) {
continue;
};
let note = if hir_id != CRATE_HIR_ID {
match attr_style {
ast::AttrStyle::Outer => {
let attr_span = attr_span;
let bang_position = self
.tcx
.sess
.source_map()
.span_until_char(*attr_span, '[')
.shrink_to_hi();
self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
attr_span,
errors::OuterCrateLevelAttr {
suggestion: errors::OuterCrateLevelAttrSuggestion {
bang_position,
},
},
)
}
Some(ast::AttrStyle::Inner) | None => self.tcx.emit_node_span_lint(
self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
attr.span(),
errors::InnerCrateLevelAttr,
),
};
return;
} else {
let never_needs_link = self
.tcx
.crate_types()
.iter()
.all(|kind| matches!(kind, CrateType::Rlib | CrateType::StaticLib));
if never_needs_link {
errors::UnusedNote::LinkerMessagesBinaryCrateOnly
} else {
return;
*attr_span,
errors::OuterCrateLevelAttr {
suggestion: errors::OuterCrateLevelAttrSuggestion { bang_position },
},
)
}
}
} else if attr.has_name(sym::default_method_body_is_const) {
errors::UnusedNote::DefaultMethodBodyConst
ast::AttrStyle::Inner => self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
*attr_span,
errors::InnerCrateLevelAttr,
),
};
continue;
} else {
return;
let never_needs_link = self
.tcx
.crate_types()
.iter()
.all(|kind| matches!(kind, CrateType::Rlib | CrateType::StaticLib));
if never_needs_link {
errors::UnusedNote::LinkerMessagesBinaryCrateOnly
} else {
continue;
}
};
self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
*attr_span,
errors::Unused { attr_span: *attr_span, note },
);
}
}
fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute) {
// Warn on useless empty attributes.
// FIXME(jdonszelmann): this lint should be moved to attribute parsing, see `AcceptContext::warn_empty_attribute`
let note = if attr.has_any_name(&[sym::feature])
&& attr.meta_item_list().is_some_and(|list| list.is_empty())
{
errors::UnusedNote::EmptyList { name: attr.name().unwrap() }
} else if attr.has_name(sym::default_method_body_is_const) {
errors::UnusedNote::DefaultMethodBodyConst
} else {
return;
};
self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
-2
View File
@@ -294,8 +294,6 @@ pub(crate) enum MacroExport {
pub(crate) enum UnusedNote {
#[note("attribute `{$name}` with an empty list has no effect")]
EmptyList { name: Symbol },
#[note("attribute `{$name}` without any lints has no effect")]
NoLints { name: Symbol },
#[note("`default_method_body_is_const` has been replaced with `const` on traits")]
DefaultMethodBodyConst,
#[note(