From 0f15999777bdd76edc9c34441bf6601a392c2b4e Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 15:31:39 +0100 Subject: [PATCH 01/10] add variant `ExpectedNameValueAsLastArgument` to `AttributeParseErrorReason` --- compiler/rustc_attr_parsing/src/context.rs | 11 +++++++++++ .../rustc_attr_parsing/src/session_diagnostics.rs | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 259a73de59853..0d4f2c7531717 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -658,6 +658,17 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { ) } + pub(crate) fn expected_nv_as_last_argument( + &self, + span: Span, + name_value_key: Symbol, + ) -> ErrorGuaranteed { + self.emit_parse_error( + span, + AttributeParseErrorReason::ExpectedNameValueAsLastArgument { span, name_value_key }, + ) + } + pub(crate) fn warn_empty_attribute(&mut self, span: Span) { let attr_path = self.attr_path.clone().to_string(); let valid_without_list = self.template.word; diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index bab830098f1a8..cea0251abebb3 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -573,6 +573,10 @@ pub(crate) enum AttributeParseErrorReason<'a> { list: bool, }, ExpectedIdentifier, + ExpectedNameValueAsLastArgument { + span: Span, + name_value_key: Symbol, + }, } /// A description of a thing that can be parsed using an attribute parser. @@ -753,6 +757,12 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { AttributeParseErrorReason::ExpectedIdentifier => { diag.span_label(self.span, "expected a valid identifier here"); } + AttributeParseErrorReason::ExpectedNameValueAsLastArgument { span, name_value_key } => { + diag.span_label( + span, + format!("expected {name_value_key} = \"...\" to be the last argument"), + ); + } } if let Some(link) = self.template.docs { From 8671c708108a6ec2dcf611519328a718e6582218 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Wed, 4 Mar 2026 11:06:15 +0100 Subject: [PATCH 02/10] make tools on `AttributeParser` hold reference to `RegisteredTools` --- compiler/rustc_ast_lowering/src/lib.rs | 2 +- .../src/attributes/codegen_attrs.rs | 8 ++++- compiler/rustc_attr_parsing/src/interface.rs | 31 +++++++++---------- compiler/rustc_expand/src/expand.rs | 1 + compiler/rustc_resolve/src/def_collector.rs | 2 +- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index a2be51cfa31b4..50b2ec4bac8ba 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -154,7 +154,7 @@ struct LoweringContext<'a, 'hir, R> { impl<'a, 'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'a, 'hir, R> { fn new(tcx: TyCtxt<'hir>, resolver: &'a mut R) -> Self { - let registered_tools = tcx.registered_tools(()).iter().map(|x| x.name).collect(); + let registered_tools = tcx.registered_tools(()); Self { tcx, resolver, diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 7a748d7627cb2..417a2cce77a66 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -1,4 +1,5 @@ use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, RtsanSetting, SanitizerSet, UsedBy}; +use rustc_middle::bug; use rustc_session::parse::feature_err; use super::prelude::*; @@ -289,10 +290,15 @@ impl AttributeParser for NakedParser { let span = self.span?; + let Some(tools) = cx.tools else { + bug!("tools required while parsing attributes"); + }; + + let tools = tools.iter().map(|tool| tool.name).collect::>(); // only if we found a naked attribute do we do the somewhat expensive check 'outer: for other_attr in cx.all_attrs { for allowed_attr in ALLOW_LIST { - if other_attr.segments().next().is_some_and(|i| cx.tools.contains(&i.name)) { + if other_attr.segments().next().is_some_and(|i| tools.contains(&i.name)) { // effectively skips the error message being emitted below // if it's a tool attribute continue 'outer; diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 7305c4b7c2fa8..adf8e97ef6093 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -8,6 +8,7 @@ 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}; @@ -21,7 +22,7 @@ use crate::{Early, Late, OmitDoc, ShouldEmit}; /// 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: Vec, + pub(crate) tools: Option<&'sess RegisteredTools>, pub(crate) features: Option<&'sess Features>, pub(crate) sess: &'sess Session, pub(crate) stage: S, @@ -47,6 +48,8 @@ impl<'sess> AttributeParser<'sess, Early> { /// No diagnostics will be emitted when parsing limited. Lints are not emitted at all, while /// 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 pub fn parse_limited( sess: &'sess Session, attrs: &[ast::Attribute], @@ -68,6 +71,8 @@ impl<'sess> AttributeParser<'sess, Early> { /// 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 pub fn parse_limited_should_emit( sess: &'sess Session, attrs: &[ast::Attribute], @@ -86,6 +91,7 @@ impl<'sess> AttributeParser<'sess, Early> { target_node_id, features, should_emit, + None, ); assert!(parsed.len() <= 1); parsed.pop() @@ -107,9 +113,9 @@ impl<'sess> AttributeParser<'sess, Early> { target_node_id: NodeId, features: Option<&'sess Features>, emit_errors: ShouldEmit, + tools: Option<&'sess RegisteredTools>, ) -> Vec { - let mut p = - Self { features, tools: Vec::new(), parse_only, sess, stage: Early { emit_errors } }; + let mut p = Self { features, tools, parse_only, sess, stage: Early { emit_errors } }; p.parse_attribute_list( attrs, target_span, @@ -193,13 +199,8 @@ impl<'sess> AttributeParser<'sess, Early> { parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &I) -> T, template: &AttributeTemplate, ) -> T { - let mut parser = Self { - features, - tools: Vec::new(), - parse_only: None, - sess, - stage: Early { emit_errors }, - }; + let mut parser = + Self { features, tools: None, parse_only: None, sess, stage: Early { emit_errors } }; let mut emit_lint = |lint_id: LintId, span: Span, kind: AttributeLintKind| { sess.psess.buffer_lint( lint_id.lint, @@ -233,10 +234,10 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { pub fn new( sess: &'sess Session, features: &'sess Features, - tools: Vec, + tools: &'sess RegisteredTools, stage: S, ) -> Self { - Self { features: Some(features), tools, parse_only: None, sess, stage } + Self { features: Some(features), tools: Some(tools), parse_only: None, sess, stage } } pub(crate) fn sess(&self) -> &'sess Session { @@ -415,11 +416,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { let attr = Attribute::Unparsed(Box::new(attr)); - if self.tools.contains(&parts[0]) - // FIXME: this can be removed once #152369 has been merged. - // https://github.com/rust-lang/rust/pull/152369 - || [sym::allow, sym::deny, sym::expect, sym::forbid, sym::warn] - .contains(&parts[0]) + if self.tools.is_some_and(|tools|tools.iter().any(|tool|tool.name == parts[0])) { attributes.push(attr); } else { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 640d0746fe1a2..6cd58c3155275 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -2173,6 +2173,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { self.cx.current_expansion.lint_node_id, Some(self.cx.ecfg.features), ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, + Some(self.cx.resolver.registered_tools()), ); let current_span = if let Some(sp) = span { sp.to(attr.span) } else { attr.span }; diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index de36f01b6d0e5..c6a421802aa21 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -146,7 +146,7 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { let mut parser = AttributeParser::<'_, Early>::new( &self.resolver.tcx.sess, self.resolver.tcx.features(), - Vec::new(), + self.resolver.tcx().registered_tools(()), Early { emit_errors: ShouldEmit::Nothing }, ); let attrs = parser.parse_attribute_list( From ba752ac3fb23ea4f0ec00b127ac5a2b5b242e2c2 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 18:25:27 +0100 Subject: [PATCH 03/10] add field `attr_id` to attr parser's `AcceptContext` --- Cargo.lock | 2 +- compiler/rustc_attr_parsing/Cargo.toml | 1 + compiler/rustc_attr_parsing/src/context.rs | 4 +++- compiler/rustc_attr_parsing/src/interface.rs | 3 +++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85997e1886f1e..b226c0fa69552 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3624,6 +3624,7 @@ dependencies = [ "rustc_hir", "rustc_lexer", "rustc_macros", + "rustc_middle", "rustc_parse", "rustc_parse_format", "rustc_session", @@ -4224,7 +4225,6 @@ dependencies = [ name = "rustc_lint_defs" version = "0.0.0" dependencies = [ - "rustc_ast", "rustc_data_structures", "rustc_error_messages", "rustc_hir_id", diff --git a/compiler/rustc_attr_parsing/Cargo.toml b/compiler/rustc_attr_parsing/Cargo.toml index 886df58e8d6f0..8c9d7d9df4ede 100644 --- a/compiler/rustc_attr_parsing/Cargo.toml +++ b/compiler/rustc_attr_parsing/Cargo.toml @@ -14,6 +14,7 @@ rustc_feature = { path = "../rustc_feature" } rustc_hir = { path = "../rustc_hir" } rustc_lexer = { path = "../rustc_lexer" } rustc_macros = { path = "../rustc_macros" } +rustc_middle = {path = "../rustc_middle"} rustc_parse = { path = "../rustc_parse" } rustc_parse_format = { path = "../rustc_parse_format" } rustc_session = { path = "../rustc_session" } diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 0d4f2c7531717..c8440ffdba462 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -14,7 +14,7 @@ use rustc_hir::{AttrPath, HirId}; use rustc_parse::parser::Recovery; use rustc_session::Session; use rustc_session::lint::{Lint, LintId}; -use rustc_span::{ErrorGuaranteed, Span, Symbol}; +use rustc_span::{AttrId, ErrorGuaranteed, Span, Symbol}; use crate::AttributeParser; // Glob imports to avoid big, bitrotty import lists @@ -437,6 +437,8 @@ pub struct AcceptContext<'f, 'sess, S: Stage> { /// The name of the attribute we're currently accepting. pub(crate) attr_path: AttrPath, + + pub(crate) attr_id: AttrId, } impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> { diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index adf8e97ef6093..82cfe19147cc5 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -212,6 +212,7 @@ impl<'sess> AttributeParser<'sess, Early> { if let Some(safety) = attr_safety { parser.check_attribute_safety(&attr_path, inner_span, safety, &mut emit_lint) } + let attr_id = sess.psess.attr_id_generator.mk_attr_id(); let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext { shared: SharedContext { cx: &mut parser, @@ -225,6 +226,7 @@ impl<'sess> AttributeParser<'sess, Early> { parsed_description, template, attr_path, + attr_id, }; parse_fn(&mut cx, args) } @@ -390,6 +392,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { parsed_description: ParsedDescription::Attribute, template: &accept.template, attr_path: attr_path.clone(), + attr_id: attr.id, }; (accept.accept_fn)(&mut cx, &args); From b24a6b3aa02a9679ce06bad5e92a78de2765e36d Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Wed, 4 Mar 2026 11:07:04 +0100 Subject: [PATCH 04/10] Decouple `CheckLintNameResult` from `rustc_lint` --- compiler/rustc_lint/src/context.rs | 60 ++++++++------------------- compiler/rustc_lint/src/levels.rs | 6 ++- compiler/rustc_lint/src/lib.rs | 6 ++- compiler/rustc_lint_defs/src/lib.rs | 45 ++++++++++++++++++++ compiler/rustc_session/src/session.rs | 11 ++++- 5 files changed, 81 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 752c2220d4147..32c1d5db1e04d 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -25,7 +25,9 @@ use rustc_middle::middle::privacy::EffectiveVisibilities; use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; use rustc_middle::ty::print::{PrintError, PrintTraitRefExt as _, Printer, with_no_trimmed_paths}; use rustc_middle::ty::{self, GenericArg, RegisteredTools, Ty, TyCtxt, TypingEnv, TypingMode}; -use rustc_session::lint::{FutureIncompatibleInfo, Lint, LintExpectationId, LintId}; +use rustc_session::lint::{ + CheckLintNameResult, FutureIncompatibleInfo, Lint, LintExpectationId, LintId, TargetLint, +}; use rustc_session::{DynLintStore, Session}; use rustc_span::edit_distance::find_best_match_for_names; use rustc_span::{Ident, Span, Symbol, sym}; @@ -69,26 +71,19 @@ impl DynLintStore for LintStore { rustc_session::LintGroup { name, lints, is_externally_loaded } })) } -} - -/// The target of the `by_name` map, which accounts for renaming/deprecation. -#[derive(Debug)] -enum TargetLint { - /// A direct lint target - Id(LintId), - /// Temporary renaming, used for easing migration pain; see #16545 - Renamed(String, LintId), - - /// Lint with this name existed previously, but has been removed/deprecated. - /// The string argument is the reason for removal. - Removed(String), + fn check_lint_name( + &self, + lint_name: &str, + tool_name: Option, + registered_tools: &RegisteredTools, + ) -> CheckLintNameResult<'_> { + self.check_lint_name(lint_name, tool_name, registered_tools) + } - /// A lint name that should give no warnings and have no effect. - /// - /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers - /// them as tool lints. - Ignored, + fn find_lints(&self, lint_name: &str) -> Option<&[LintId]> { + self.find_lints(lint_name) + } } struct LintAlias { @@ -103,29 +98,6 @@ struct LintGroup { depr: Option, } -#[derive(Debug)] -pub enum CheckLintNameResult<'a> { - Ok(&'a [LintId]), - /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name. - NoLint(Option<(Symbol, bool)>), - /// The lint refers to a tool that has not been registered. - NoTool, - /// The lint has been renamed to a new name. - Renamed(String), - /// The lint has been removed due to the given reason. - Removed(String), - - /// The lint is from a tool. The `LintId` will be returned as if it were a - /// rustc lint. The `Option` indicates if the lint has been - /// renamed. - Tool(&'a [LintId], Option), - - /// The lint is from a tool. 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`. - MissingTool, -} - impl LintStore { pub fn new() -> LintStore { LintStore { @@ -304,6 +276,10 @@ impl LintStore { self.by_name.insert(name.into(), Removed(reason.into())); } + pub fn get_lint_by_name(&self, lint_name: &str) -> Option<&TargetLint> { + self.by_name.get(lint_name) + } + pub fn find_lints(&self, lint_name: &str) -> Option<&[LintId]> { match self.by_name.get(lint_name) { Some(Id(lint_id)) => Some(slice::from_ref(lint_id)), diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 2b859b65c9f8f..64303de60f8b0 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -22,12 +22,14 @@ use rustc_session::lint::builtin::{ self, FORBIDDEN_LINT_GROUPS, RENAMED_AND_REMOVED_LINTS, SINGLE_USE_LIFETIMES, UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES, }; -use rustc_session::lint::{Level, Lint, LintExpectationId, LintId}; +use rustc_session::lint::{ + CheckLintNameResult, Level, Lint, LintExpectationId, LintId, TargetLint, +}; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use tracing::{debug, instrument}; use crate::builtin::MISSING_DOCS; -use crate::context::{CheckLintNameResult, LintStore}; +use crate::context::LintStore; use crate::errors::{ CheckNameUnknownTool, MalformedAttribute, MalformedAttributeSub, OverruledAttribute, OverruledAttributeSub, RequestedLevel, UnknownToolInScopedLint, UnsupportedGroup, diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 2f773b9e166c7..e4106cbdfb797 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -128,7 +128,7 @@ use unused::*; #[rustfmt::skip] pub use builtin::{MissingDoc, SoftLints}; -pub use context::{CheckLintNameResult, EarlyContext, LateContext, LintContext, LintStore}; +pub use context::{EarlyContext, LateContext, LintContext, LintStore}; pub use early::diagnostics::{DecorateAttrLint, DecorateBuiltinLint}; pub use early::{EarlyCheckNode, check_ast_node}; pub use late::{check_crate, late_lint_mod, unerased_lint_store}; @@ -136,7 +136,9 @@ pub use levels::LintLevelsBuilder; pub use passes::{EarlyLintPass, LateLintPass}; pub use rustc_errors::BufferedEarlyLint; pub use rustc_session::lint::Level::{self, *}; -pub use rustc_session::lint::{FutureIncompatibleInfo, Lint, LintId, LintPass, LintVec}; +pub use rustc_session::lint::{ + CheckLintNameResult, FutureIncompatibleInfo, Lint, LintId, LintPass, LintVec, +}; pub fn provide(providers: &mut Providers) { levels::provide(providers); diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 0faa504429614..b5e918f2cde84 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -583,6 +583,26 @@ impl Lint { } } +/// The target of the `by_name` map, which accounts for renaming/deprecation. +#[derive(Debug)] +pub enum TargetLint { + /// A direct lint target + Id(LintId), + + /// Temporary renaming, used for easing migration pain; see #16545 + Renamed(String, LintId), + + /// Lint with this name existed previously, but has been removed/deprecated. + /// The string argument is the reason for removal. + Removed(String), + + /// A lint name that should give no warnings and have no effect. + /// + /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers + /// them as tool lints. + Ignored, +} + /// Identifies a lint known to the compiler. #[derive(Clone, Copy, Debug)] pub struct LintId { @@ -855,6 +875,31 @@ pub enum FormatWarning { InvalidSpecifier { name: String, span: Span }, } +#[derive(Debug)] +pub enum CheckLintNameResult<'a> { + Ok(&'a [LintId]), + /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name. + NoLint(Option<(Symbol, bool)>), + /// The lint refers to a tool that has not been registered. + NoTool, + /// The lint has been renamed to a new name. + Renamed(Symbol), + /// Lint that previously was part of rustc, but now is part of external lint tool + RenamedToolLint(Symbol), + /// The lint has been removed due to the given reason. + Removed(String), + + /// The lint is from a tool. The `LintId` will be returned as if it were a + /// rustc lint. The `Option` indicates if the lint has been + /// renamed. + Tool(&'a [LintId], Option), + + /// The lint is from a tool. 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`. + MissingTool, +} + pub type RegisteredTools = FxIndexSet; /// Declares a static item of type `&'static Lint`. diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index fece46dd9711d..607db13b3f6f6 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -40,7 +40,7 @@ use crate::config::{ Input, InstrumentCoverage, OptLevel, OutFileName, OutputType, SwitchWithOptPath, }; use crate::filesearch::FileSearch; -use crate::lint::LintId; +use crate::lint::{CheckLintNameResult, LintId, RegisteredTools}; use crate::parse::{ParseSess, add_feature_diagnostics}; use crate::search_paths::SearchPath; use crate::{errors, filesearch, lint}; @@ -81,6 +81,15 @@ pub struct CompilerIO { pub trait DynLintStore: Any + DynSync + DynSend { /// Provides a way to access lint groups without depending on `rustc_lint` fn lint_groups_iter(&self) -> Box + '_>; + + fn check_lint_name( + &self, + lint_name: &str, + tool_name: Option, + registered_tools: &RegisteredTools, + ) -> CheckLintNameResult<'_>; + + fn find_lints(&self, lint_name: &str) -> Option<&[LintId]>; } /// Represents the data associated with a compilation From bd82b04c79f715ebb6431c54d135015d10c8ac3c Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 19:36:26 +0100 Subject: [PATCH 05/10] make lint_index mandatory in `LintExpectationId` also add attr_id to `Stable` variant directly, instead of having to iterate over all the attrs on the hir_id to find it --- compiler/rustc_lint/src/expect.rs | 16 ++------- compiler/rustc_lint_defs/src/lib.rs | 56 ++++++++++------------------- 2 files changed, 21 insertions(+), 51 deletions(-) diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs index 481e116d06e01..28dceb0c5c069 100644 --- a/compiler/rustc_lint/src/expect.rs +++ b/compiler/rustc_lint/src/expect.rs @@ -30,19 +30,9 @@ fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option) { let fulfilled_expectations = tcx.dcx().steal_fulfilled_expectation_ids(); // Turn a `LintExpectationId` into a `(AttrId, lint_index)` pair. - let canonicalize_id = |expect_id: &LintExpectationId| { - match *expect_id { - LintExpectationId::Unstable { attr_id, lint_index: Some(lint_index) } => { - (attr_id, lint_index) - } - LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => { - // We are an `eval_always` query, so looking at the attribute's `AttrId` is ok. - let attr_id = tcx.hir_attrs(hir_id)[attr_index as usize].id(); - - (attr_id, lint_index) - } - _ => panic!("fulfilled expectations must have a lint index"), - } + let canonicalize_id = |expect_id: &LintExpectationId| match *expect_id { + LintExpectationId::Unstable { attr_id, lint_index, .. } => (attr_id, lint_index), + LintExpectationId::Stable { attr_id, lint_index, .. } => (attr_id, lint_index), }; let fulfilled_expectations: FxHashSet<_> = diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index b5e918f2cde84..0e50d29f26edb 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -1,8 +1,6 @@ use std::borrow::Cow; use std::fmt::Display; -use rustc_ast::AttrId; -use rustc_ast::attr::AttributeExt; use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::stable_hasher::{ HashStable, StableCompare, StableHasher, ToStableHashKey, @@ -12,7 +10,7 @@ use rustc_hir_id::{HashStableContext, HirId, ItemLocalId}; use rustc_macros::{Decodable, Encodable, HashStable_Generic}; use rustc_span::def_id::DefPathHash; pub use rustc_span::edition::Edition; -use rustc_span::{Ident, Span, Symbol, sym}; +use rustc_span::{AttrId, Ident, Span, Symbol, sym}; use serde::{Deserialize, Serialize}; pub use self::Level::*; @@ -107,12 +105,12 @@ pub enum Applicability { pub enum LintExpectationId { /// Used for lints emitted during the `EarlyLintPass`. This id is not /// hash stable and should not be cached. - Unstable { attr_id: AttrId, lint_index: Option }, + Unstable { attr_id: AttrId, lint_index: u16 }, /// The [`HirId`] that the lint expectation is attached to. This id is /// stable and can be cached. The additional index ensures that nodes with /// several expectations can correctly match diagnostics to the individual /// expectation. - Stable { hir_id: HirId, attr_index: u16, lint_index: Option }, + Stable { hir_id: HirId, attr_id: AttrId, attr_index: u16, lint_index: u16 }, } impl LintExpectationId { @@ -123,14 +121,14 @@ impl LintExpectationId { } } - pub fn get_lint_index(&self) -> Option { + pub fn get_lint_index(&self) -> u16 { let (LintExpectationId::Unstable { lint_index, .. } | LintExpectationId::Stable { lint_index, .. }) = self; *lint_index } - pub fn set_lint_index(&mut self, new_lint_index: Option) { + pub fn set_lint_index(&mut self, new_lint_index: u16) { let (LintExpectationId::Unstable { lint_index, .. } | LintExpectationId::Stable { lint_index, .. }) = self; @@ -142,7 +140,7 @@ impl HashStable for LintExpectationId { #[inline] fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) { match self { - LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => { + LintExpectationId::Stable { hir_id, attr_index, lint_index, .. } => { hir_id.hash_stable(hcx, hasher); attr_index.hash_stable(hcx, hasher); lint_index.hash_stable(hcx, hasher); @@ -162,7 +160,7 @@ impl ToStableHashKey for LintExpectationId { #[inline] fn to_stable_hash_key(&self, hcx: &HCX) -> Self::KeyType { match self { - LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => { + LintExpectationId::Stable { hir_id, attr_index, lint_index, .. } => { let (def_path_hash, lint_idx) = hir_id.to_stable_hash_key(hcx); (def_path_hash, lint_idx, *attr_index, *lint_index) } @@ -235,6 +233,17 @@ impl Level { } } + pub fn from_symbol(x: Symbol) -> Option { + match x { + sym::allow => Some(Level::Allow), + sym::deny => Some(Level::Deny), + sym::expect => Some(Level::Expect), + sym::forbid => Some(Level::Forbid), + sym::warn => Some(Level::Warn), + _ => None, + } + } + /// Converts a lower-case string to a level. This will never construct the expect /// level as that would require a [`LintExpectationId`]. pub fn from_str(x: &str) -> Option { @@ -247,35 +256,6 @@ impl Level { } } - /// Converts an `Attribute` to a level. - pub fn from_attr(attr: &impl AttributeExt) -> Option<(Self, Option)> { - attr.name().and_then(|name| Self::from_symbol(name, || Some(attr.id()))) - } - - /// Converts a `Symbol` to a level. - pub fn from_symbol( - s: Symbol, - id: impl FnOnce() -> Option, - ) -> Option<(Self, Option)> { - match s { - sym::allow => Some((Level::Allow, None)), - sym::expect => { - if let Some(attr_id) = id() { - Some(( - Level::Expect, - Some(LintExpectationId::Unstable { attr_id, lint_index: None }), - )) - } else { - None - } - } - sym::warn => Some((Level::Warn, None)), - sym::deny => Some((Level::Deny, None)), - sym::forbid => Some((Level::Forbid, None)), - _ => None, - } - } - pub fn to_cmd_flag(self) -> &'static str { match self { Level::Warn => "-W", From 179a63c9883f52a01cbdc30274cd3d817bf1c290 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Tue, 10 Mar 2026 16:49:53 +0100 Subject: [PATCH 06/10] 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 --- .../rustc_attr_parsing/src/attributes/lint.rs | 370 +++++++++++++++ .../rustc_attr_parsing/src/attributes/mod.rs | 1 + compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_attr_parsing/src/interface.rs | 42 +- .../src/session_diagnostics.rs | 11 + .../rustc_hir/src/attrs/data_structures.rs | 146 +++++- .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + .../rustc_hir/src/attrs/pretty_printing.rs | 6 +- compiler/rustc_hir/src/hir.rs | 14 + compiler/rustc_hir_typeck/src/expr.rs | 36 +- compiler/rustc_lint/src/context.rs | 9 +- compiler/rustc_lint/src/early.rs | 207 ++++++--- compiler/rustc_lint/src/early/diagnostics.rs | 22 + compiler/rustc_lint/src/errors.rs | 30 -- compiler/rustc_lint/src/levels.rs | 435 ++++++------------ compiler/rustc_lint/src/lints.rs | 32 +- compiler/rustc_lint_defs/Cargo.toml | 1 - compiler/rustc_lint_defs/src/lib.rs | 23 + compiler/rustc_mir_build/src/builder/scope.rs | 11 +- compiler/rustc_passes/src/check_attr.rs | 164 +++---- compiler/rustc_passes/src/errors.rs | 2 - 21 files changed, 1011 insertions(+), 554 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/lint.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/lint.rs b/compiler/rustc_attr_parsing/src/attributes/lint.rs new file mode 100644 index 0000000000000..f8b093273d4fb --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/lint.rs @@ -0,0 +1,370 @@ +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_middle::bug; +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, +} + +trait Mapping { + const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn); +} +impl Mapping for T { + const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn) = ( + &[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::(cx, args) { + this.lint_attrs.push(lint_attr); + } + }, + ); +} + +impl AttributeParser for LintParser { + const ATTRIBUTES: AcceptMapping = + &[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 { + 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( + cx: &mut AcceptContext<'_, '_, S>, + args: &ArgParser, +) -> Option { + let Some(lint_store) = cx.sess.lint_store.as_ref().map(|store| store.to_owned()) else { + bug!("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 { + bug!("first segment should always exist"); + }; + + let rest = segments.collect::>(); + let (tool_name, tool_span, name): (Option, Option, _) = + 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::>() + .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, + tool_span: Option, + span: Span, +) -> Option<&'a [LintId]> { + let Some(tools) = cx.tools else { + bug!("tools required while parsing attributes"); + }; + if tools.is_empty() { + bug!("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 + } + } +} diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 67147642921c2..b1abdd2a8872b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -46,6 +46,7 @@ pub(crate) mod dummy; 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; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index c8440ffdba462..19d9e55f3c53a 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -36,6 +36,7 @@ use crate::attributes::dummy::*; 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::*; @@ -147,6 +148,7 @@ attribute_parsers!( ConfusablesParser, ConstStabilityParser, DocParser, + LintParser, MacroUseParser, NakedParser, OnConstParser, diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 82cfe19147cc5..e57d0e46d7729 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -104,9 +104,9 @@ impl<'sess> AttributeParser<'sess, Early> { /// `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, parse_only: Option, target: Target, target_span: Span, @@ -133,6 +133,32 @@ impl<'sess> AttributeParser<'sess, Early> { ) } + /// 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, + filter: &[Symbol], + target: Target, + target_span: Span, + target_node_id: NodeId, + features: Option<&'sess Features>, + emit_errors: ShouldEmit, + tools: &'sess RegisteredTools, + ) -> Vec { + 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( @@ -262,9 +288,9 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { /// /// `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, target_span: Span, target: Target, omit_doc: OmitDoc, @@ -280,9 +306,9 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { let mut attr_paths: Vec> = Vec::new(); let mut early_parsed_state = EarlyParsedState::default(); - let mut finalizers: Vec<&FinalizeFn> = Vec::with_capacity(attrs.len()); + let mut finalizers: Vec<&FinalizeFn> = 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 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { 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 { diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index cea0251abebb3..1f7cafdbc6781 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -1067,3 +1067,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, + pub tool_name: Symbol, + pub full_lint_name: Symbol, + #[help("add `#![register_tool({$tool_name})]` to the crate root")] + pub is_nightly_build: bool, +} diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index a18ddff947099..f18d5a1f190a2 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -15,14 +15,16 @@ use rustc_hir::LangItem; 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 @@ impl fmt::Display for AutoDiffItem { } } +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub struct LintAttribute { + /// See RFC #2383 + pub reason: Option, + 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, +} + +#[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, + /// 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 { + 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), + /// Represents `#[loop_match]`. LoopMatch(Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index c19fc6976c6e6..6612ebd6135b8 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -56,6 +56,7 @@ impl AttributeKind { LinkOrdinal { .. } => No, LinkSection { .. } => Yes, // Needed for rustdoc Linkage(..) => No, + LintAttributes { .. } => No, LoopMatch(..) => No, MacroEscape(..) => No, MacroExport { .. } => Yes, diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index 9d14f9de3078d..811b250b6fcd6 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -17,6 +17,8 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; 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 @@ macro_rules! print_tup { } print_tup!(A B C D E F G H); -print_skip!(Span, (), ErrorGuaranteed, AttrId); -print_disp!(u8, u16, u32, u128, usize, bool, NonZero, Limit); +print_skip!(Span, (), ErrorGuaranteed, AttrId, HashIgnoredAttrId); +print_disp!(u8, u16, u32, u128, usize, bool, NonZero, Limit, LintInstance); print_debug!( Symbol, Ident, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 71424f7275c94..ce49253421638 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1311,6 +1311,19 @@ impl Attribute { 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 { @@ -1385,6 +1398,7 @@ impl AttributeExt for Attribute { 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:?}"), } } diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 6b77169994a03..ac693efc66d65 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -16,11 +16,10 @@ use rustc_errors::{ 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 @@ use crate::{ 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, diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 32c1d5db1e04d..b027100c51362 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -369,7 +369,7 @@ impl LintStore { } } 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 @@ impl<'tcx> LateContext<'tcx> { /// 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) } diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index c0eca3b5197b6..ee0979ec85423 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -12,7 +12,7 @@ use rustc_feature::Features; 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 @@ impl<'ecx, 'tcx, T: EarlyLintPass> EarlyContextAndPass<'ecx, 'tcx, T> { /// 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(&mut self, id: ast::NodeId, attrs: &'_ [ast::Attribute], f: F) - where + fn with_lint_attrs( + &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 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } fn visit_param(&mut self, param: &'ast ast::Param) { - self.with_lint_attrs(param.id, ¶m.attrs, |cx| { - lint_callback!(cx, check_param, param); - ast_visit::walk_param(cx, param); - }); + self.with_lint_attrs( + param.id, + ¶m.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 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } 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 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> // // 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 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } 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 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } 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 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } 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 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } fn visit_generic_param(&mut self, param: &'ast ast::GenericParam) { - self.with_lint_attrs(param.id, ¶m.attrs, |cx| { - lint_callback!(cx, check_generic_param, param); - ast_visit::walk_generic_param(cx, param); - }); + self.with_lint_attrs( + param.id, + ¶m.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 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } 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 diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 43bb3971d7ac4..2aa7ce3074fe2 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -433,6 +433,28 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &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) + } } } } diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs index 8fec30816bd13..33ba7f6edbda6 100644 --- a/compiler/rustc_lint/src/errors.rs +++ b/compiler/rustc_lint/src/errors.rs @@ -44,36 +44,6 @@ impl Subdiagnostic for OverruledAttributeSub { } } -#[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, - 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 { diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 64303de60f8b0..12c5748d5ecdf 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -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_middle::ty::{RegisteredTools, TyCtxt}; 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 tracing::{debug, instrument}; 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 @@ impl LintLevelsProvider for LintLevelQueryMap<'_> { 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 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { 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 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { pub(crate) fn push( &mut self, attrs: &[ast::Attribute], - is_crate_node: bool, - source_hir_id: Option, + 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 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { .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 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { }; } - fn add( + fn simple_add( &mut self, - attrs: &[impl AttributeExt], - is_crate_node: bool, - source_hir_id: Option, + level: Level, + lint: &LintInstance, + reason: Option, + expect_lint_id: Option, ) { - 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; - } - - // `#[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; - } + // If this function returns none, it means the attribute parser has already emitted appropriate errors - 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 stable_id = LintExpectationId::Stable { - hir_id, - attr_index: attr_index.try_into().unwrap(), - lint_index: None, - }; - - (Level::Expect, Some(stable_id)) - } - Some((lvl, id)) => (lvl, id), - }; + let src = + LintLevelSource::Node { name: lint.original_lint_name(), span: lint.span(), reason }; - 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) - } + 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, + ), + }; - 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) - } + if self.check_gated_lint(*id, lint.span(), false) { + self.insert_spec(*id, LevelAndSource { level, lint_id: expect_lint_id, src }); + } + } - 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; - } + fn add(&mut self, attrs: &[hir::Attribute], source_hir_id: Option) { + 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, + }, + ); + } - 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; - } + let Some(attrs) = find_attr!(attrs, LintAttributes(sub_attrs) => sub_attrs.into_iter()) + else { + return; + }; - 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}"); + 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, + }, }; - (ids, Symbol::intern(&replace)) + 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(), + ), + ); } - 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, - ), - ); - } - } - } - - 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 { 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); } } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index c7c92356c3782..8201545563e7e 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -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, } @@ -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, } diff --git a/compiler/rustc_lint_defs/Cargo.toml b/compiler/rustc_lint_defs/Cargo.toml index c8201d5ea8ccc..2ca62f7fa8cdc 100644 --- a/compiler/rustc_lint_defs/Cargo.toml +++ b/compiler/rustc_lint_defs/Cargo.toml @@ -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" } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 0e50d29f26edb..f1900fdcf3f23 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -847,6 +847,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)] diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index 91610e768d012..ab63685705143 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -85,7 +85,8 @@ use std::mem; 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::thir::{AdtExpr, AdtExprBase, ArmId, ExprId, ExprKind}; 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 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { 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; diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 26ba2b0e8f42d..71b02490f4eac 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -10,7 +10,7 @@ use std::collections::hash_map::Entry; 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_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, msg}; 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::traits::ObligationCause; 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 @@ impl<'tcx> CheckAttrVisitor<'tcx> { 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 @@ impl<'tcx> CheckAttrVisitor<'tcx> { 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 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | 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 @@ impl<'tcx> CheckAttrVisitor<'tcx> { &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); @@ -1594,88 +1582,76 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute, style: Option) { - // 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(); - - 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( + 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::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, diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index f9dc696f320e3..628d0b0c961a1 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -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( From c65afff8aca736b239690da73ceddc412bea6f87 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 19:50:23 +0100 Subject: [PATCH 07/10] integrate parsed lint attrs into clippy --- compiler/rustc_lint/src/context.rs | 2 + .../clippy/clippy_lints/src/attrs/mod.rs | 2 +- .../src/attrs/unnecessary_clippy_cfg.rs | 7 +++- .../src/attrs/useless_attribute.rs | 2 +- .../clippy/clippy_lints/src/attrs/utils.rs | 6 +-- .../clippy/clippy_lints/src/collapsible_if.rs | 40 +++++++++++-------- .../src/returns/needless_return.rs | 30 +++++++------- 7 files changed, 50 insertions(+), 39 deletions(-) diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index b027100c51362..ae90c74c7e06b 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -578,6 +578,8 @@ impl<'tcx> LintContext for LateContext<'tcx> { } } + /// Only appropriate for use inside of the compiler + /// since the compiler doesn't track levels of tool lints fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource { self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs) } diff --git a/src/tools/clippy/clippy_lints/src/attrs/mod.rs b/src/tools/clippy/clippy_lints/src/attrs/mod.rs index c15a378053e39..372defbb4d7e2 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/mod.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/mod.rs @@ -583,7 +583,7 @@ impl EarlyLintPass for PostExpansionEarlyAttributes { if matches!(name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) { allow_attributes_without_reason::check(cx, name, items, attr); } - if is_lint_level(name, attr.id) { + if is_lint_level(name) { blanket_clippy_restriction_lints::check(cx, name, items); } if items.is_empty() || !attr.has_name(sym::deprecated) { diff --git a/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs b/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs index 6ee3290fa761d..5d095c9b27ade 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs @@ -1,10 +1,12 @@ +use crate::attrs::is_lint_level; + use super::{Attribute, UNNECESSARY_CLIPPY_CFG}; use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::source::SpanRangeExt; use itertools::Itertools; use rustc_ast::AttrStyle; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, Level}; +use rustc_lint::{EarlyContext}; use rustc_span::sym; pub(super) fn check( @@ -13,9 +15,10 @@ pub(super) fn check( behind_cfg_attr: &rustc_ast::MetaItem, attr: &Attribute, ) { + // FIXME use proper attr parsing here if cfg_attr.has_name(sym::clippy) && let Some(ident) = behind_cfg_attr.ident() - && Level::from_symbol(ident.name, || Some(attr.id)).is_some() + && is_lint_level(ident.name) && let Some(items) = behind_cfg_attr.meta_item_list() { let nb_items = items.len(); diff --git a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs index 9a1e315ae5306..2d56086a96024 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs @@ -15,7 +15,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) { return; } if let Some(lint_list) = &attr.meta_item_list() - && attr.name().is_some_and(|name| is_lint_level(name, attr.id)) + && attr.name().is_some_and(is_lint_level) { for lint in lint_list { match item.kind { diff --git a/src/tools/clippy/clippy_lints/src/attrs/utils.rs b/src/tools/clippy/clippy_lints/src/attrs/utils.rs index 7b66f91f6c073..512f961228b15 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/utils.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/utils.rs @@ -1,5 +1,5 @@ use clippy_utils::macros::{is_panic, macro_backtrace}; -use rustc_ast::{AttrId, MetaItemInner}; +use rustc_ast::{MetaItemInner}; use rustc_hir::{ Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind, }; @@ -16,8 +16,8 @@ pub(super) fn is_word(nmi: &MetaItemInner, expected: Symbol) -> bool { } } -pub(super) fn is_lint_level(symbol: Symbol, attr_id: AttrId) -> bool { - Level::from_symbol(symbol, || Some(attr_id)).is_some() +pub(super) fn is_lint_level(symbol: Symbol) -> bool { + Level::from_symbol(symbol).is_some() } pub(super) fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool { diff --git a/src/tools/clippy/clippy_lints/src/collapsible_if.rs b/src/tools/clippy/clippy_lints/src/collapsible_if.rs index 3850c55c49f8a..7f5bc520dc4d3 100644 --- a/src/tools/clippy/clippy_lints/src/collapsible_if.rs +++ b/src/tools/clippy/clippy_lints/src/collapsible_if.rs @@ -3,11 +3,12 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::source::{HasSession, IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability}; use clippy_utils::{can_use_if_let_chains, span_contains_non_whitespace, sym, tokenize_with_text}; -use rustc_ast::{BinOpKind, MetaItemInner}; +use rustc_ast::BinOpKind; use rustc_errors::Applicability; -use rustc_hir::{Block, Expr, ExprKind, StmtKind}; +use rustc_hir::attrs::{AttributeKind, LintAttributeKind}; +use rustc_hir::{Attribute, Block, Expr, ExprKind, StmtKind}; use rustc_lexer::TokenKind; -use rustc_lint::{LateContext, LateLintPass, Level}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::{BytePos, Span, Symbol}; @@ -237,19 +238,26 @@ impl CollapsibleIf { !span_contains_non_whitespace(cx, span, self.lint_commented_code) }, - [attr] - if matches!(Level::from_attr(attr), Some((Level::Expect, _))) - && let Some(metas) = attr.meta_item_list() - && let Some(MetaItemInner::MetaItem(meta_item)) = metas.first() - && let [tool, lint_name] = meta_item.path.segments.as_slice() - && tool.ident.name == sym::clippy - && [expected_lint_name, sym::style, sym::all].contains(&lint_name.ident.name) => - { - // There is an `expect` attribute -- check that there is no _other_ significant text - let span_before_attr = inner_if.span.split_at(1).1.until(attr.span()); - let span_after_attr = attr.span().between(inner_if_expr.span); - !span_contains_non_whitespace(cx, span_before_attr, self.lint_commented_code) - && !span_contains_non_whitespace(cx, span_after_attr, self.lint_commented_code) + [ + Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs)), + ] => { + sub_attrs + .into_iter() + .filter(|attr|attr.kind == LintAttributeKind::Expect) + .flat_map(|attr| attr.lint_instances.iter().map(|group| (attr.attr_span, group))) + .filter(|(_, lint_id)| { + lint_id.tool_is_named(sym::clippy) + && (expected_lint_name == lint_id.lint_name() + || [expected_lint_name, sym::style, sym::all] + .contains(&lint_id.original_name_without_tool())) + }) + .any(|(attr_span, _)| { + // There is an `expect` attribute -- check that there is no _other_ significant text + let span_before_attr = inner_if.span.split_at(1).1.until(attr_span); + let span_after_attr = attr_span.between(inner_if_expr.span); + !span_contains_non_whitespace(cx, span_before_attr, self.lint_commented_code) + && !span_contains_non_whitespace(cx, span_after_attr, self.lint_commented_code) + }) }, // There are other attributes, which are significant tokens -- check failed diff --git a/src/tools/clippy/clippy_lints/src/returns/needless_return.rs b/src/tools/clippy/clippy_lints/src/returns/needless_return.rs index 04e4f379e37c1..aab6adf5d19a4 100644 --- a/src/tools/clippy/clippy_lints/src/returns/needless_return.rs +++ b/src/tools/clippy/clippy_lints/src/returns/needless_return.rs @@ -4,11 +4,11 @@ use clippy_utils::{ binary_expr_needs_parentheses, is_from_proc_macro, leaks_droppable_temporary_with_limited_lifetime, span_contains_cfg, span_find_starting_semi, sym, }; -use rustc_ast::MetaItemInner; use rustc_errors::Applicability; +use rustc_hir::attrs::{AttributeKind, LintAttributeKind}; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, Expr, ExprKind, HirId, LangItem, MatchSource, StmtKind}; -use rustc_lint::{LateContext, Level, LintContext}; +use rustc_hir::{Attribute, Body, Expr, ExprKind, HirId, LangItem, MatchSource, StmtKind}; +use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::{self, Ty}; use rustc_span::{BytePos, Pos, Span}; use std::borrow::Cow; @@ -180,20 +180,18 @@ fn check_final_expr<'tcx>( // actually fulfill the expectation (clippy::#12998) match cx.tcx.hir_attrs(expr.hir_id) { [] => {}, - [attr] => { - if matches!(Level::from_attr(attr), Some((Level::Expect, _))) - && let metas = attr.meta_item_list() - && let Some(lst) = metas - && let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice() - && let [tool, lint_name] = meta_item.path.segments.as_slice() - && tool.ident.name == sym::clippy - && matches!( - lint_name.ident.name, - sym::needless_return | sym::style | sym::all | sym::warnings - ) + [Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs))] => { + if !sub_attrs + .into_iter() + .filter(|attr| attr.kind == LintAttributeKind::Expect) + .flat_map(|attr| &attr.lint_instances) + .any(|lint| { + matches!( + lint.original_name_without_tool(), + sym::needless_return | sym::style | sym::all | sym::warnings + ) + }) { - // This is an expectation of the `needless_return` lint - } else { return; } }, From 486dada6ce45b99e537063f32cc90de41caf3372 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Tue, 10 Mar 2026 16:54:27 +0100 Subject: [PATCH 08/10] bless tests and tidy also removes E0452 and splits `tests/rustdoc-ui/lints/renamed-lint-still-applies` into 2 tests this is because of delayed warn lint being lost on compiler aborting on error --- .../src/error_codes/E0452.md | 3 +- .../tests/ui/unknown_clippy_lints.stderr | 30 +- tests/pretty/delegation-inherit-attributes.pp | 11 +- tests/pretty/delegation-inline-attribute.pp | 11 +- tests/pretty/hir-delegation.pp | 11 +- tests/pretty/hir-lifetimes.pp | 14 +- tests/pretty/pin-ergonomics-hir.pp | 11 +- .../lints/renamed-lint-still-applies-2.rs | 12 + .../lints/renamed-lint-still-applies-2.stderr | 32 ++ .../lints/renamed-lint-still-applies.rs | 5 +- .../lints/renamed-lint-still-applies.stderr | 36 +- ...-highlight-span-extra-arguments-147070.svg | 2 +- tests/ui/attributes/malformed-attrs.stderr | 170 +++++---- .../unsafe/proc-unsafe-attributes.rs | 9 +- .../unsafe/proc-unsafe-attributes.stderr | 62 ++- ...deduplicate-diagnostics.deduplicate.stderr | 29 +- .../deduplicate-diagnostics.duplicate.stderr | 35 +- .../deduplicate-diagnostics.rs | 4 +- tests/ui/error-codes/E0452.rs | 8 - tests/ui/error-codes/E0452.stderr | 49 --- ...issue-43106-gating-of-builtin-attrs.stderr | 206 +++++----- ...between-expected-trait-and-found-trait.svg | 2 +- tests/ui/lint/empty-lint-attributes.stderr | 8 +- tests/ui/lint/inert-attr-macro.rs | 6 +- tests/ui/lint/inert-attr-macro.stderr | 9 +- tests/ui/lint/issue-97094.stderr | 36 +- tests/ui/lint/lint-malformed.rs | 8 +- tests/ui/lint/lint-malformed.stderr | 63 +-- tests/ui/lint/reasons-erroneous.rs | 34 +- tests/ui/lint/reasons-erroneous.stderr | 141 +++++-- tests/ui/lint/register-tool-lint.rs | 2 - tests/ui/lint/register-tool-lint.stderr | 11 +- .../ui/lint/renamed-lints-still-apply.stderr | 16 +- .../expect_lint_from_macro.rs | 4 +- .../expect_lint_from_macro.stderr | 9 +- .../expect_multiple_lints.rs | 44 +++ .../expect_multiple_lints.stderr | 178 ++++++++- .../force_warn_expected_lints_unfulfilled.rs | 90 +++++ ...rce_warn_expected_lints_unfulfilled.stderr | 358 +++++++++++++++++- .../lint-attribute-only-with-reason.stderr | 10 +- .../multiple_expect_attrs.rs | 1 - .../multiple_expect_attrs.stderr | 2 +- .../semicolon-in-expressions-from-macros.rs | 3 +- ...emicolon-in-expressions-from-macros.stderr | 9 +- tests/ui/lint/unused/empty-attributes.stderr | 48 +-- tests/ui/parser/issues/issue-104620.rs | 4 +- tests/ui/proc-macro/cfg-eval.stderr | 2 +- tests/ui/tool-attributes/tool_lints.rs | 2 - tests/ui/tool-attributes/tool_lints.stderr | 20 +- .../tool-attributes/unknown-lint-tool-name.rs | 10 +- .../unknown-lint-tool-name.stderr | 40 +- tests/ui/unpretty/exhaustive.hir.stdout | 56 ++- ...ct-exprs-tuple-call-pretty-printing.stdout | 11 +- tests/ui/unpretty/unpretty-expr-fn-arg.stdout | 9 +- .../ui/where-clauses/unsupported_attribute.rs | 4 +- .../unsupported_attribute.stderr | 8 +- 56 files changed, 1338 insertions(+), 670 deletions(-) create mode 100644 tests/rustdoc-ui/lints/renamed-lint-still-applies-2.rs create mode 100644 tests/rustdoc-ui/lints/renamed-lint-still-applies-2.stderr delete mode 100644 tests/ui/error-codes/E0452.rs delete mode 100644 tests/ui/error-codes/E0452.stderr diff --git a/compiler/rustc_error_codes/src/error_codes/E0452.md b/compiler/rustc_error_codes/src/error_codes/E0452.md index 429813a7cdd4e..a2471ec78eed5 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0452.md +++ b/compiler/rustc_error_codes/src/error_codes/E0452.md @@ -1,8 +1,9 @@ +#### Note: this error code is no longer emitted by the compiler An invalid lint attribute has been given. Erroneous code example: -```compile_fail,E0452 +```compile_fail #![allow(foo = "")] // error: malformed lint attribute ``` diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr index 592fdfbebd43a..974c24bdc3bf3 100644 --- a/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr +++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr @@ -1,23 +1,11 @@ -error: unknown lint: `clippy::All` - --> tests/ui/unknown_clippy_lints.rs:3:10 - | -LL | #![allow(clippy::All)] - | ^^^^^^^^^^^ help: did you mean: `clippy::all` - | - = note: `-D unknown-lints` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(unknown_lints)]` - -error: unknown lint: `clippy::CMP_OWNED` - --> tests/ui/unknown_clippy_lints.rs:5:9 - | -LL | #![warn(clippy::CMP_OWNED)] - | ^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::cmp_owned` - error: unknown lint: `clippy::if_not_els` --> tests/ui/unknown_clippy_lints.rs:9:8 | LL | #[warn(clippy::if_not_els)] | ^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::if_not_else` + | + = note: `-D unknown-lints` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unknown_lints)]` error: unknown lint: `clippy::UNNecsaRy_cAst` --> tests/ui/unknown_clippy_lints.rs:11:8 @@ -67,5 +55,17 @@ LL - #[warn(clippy::missing_docs)] LL + #[warn(missing_docs)] | +error: unknown lint: `clippy::All` + --> tests/ui/unknown_clippy_lints.rs:3:10 + | +LL | #![allow(clippy::All)] + | ^^^^^^^^^^^ help: did you mean: `clippy::all` + +error: unknown lint: `clippy::CMP_OWNED` + --> tests/ui/unknown_clippy_lints.rs:5:9 + | +LL | #![warn(clippy::CMP_OWNED)] + | ^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::cmp_owned` + error: aborting due to 9 previous errors diff --git a/tests/pretty/delegation-inherit-attributes.pp b/tests/pretty/delegation-inherit-attributes.pp index 242e7161aa84c..2db27f3598cdb 100644 --- a/tests/pretty/delegation-inherit-attributes.pp +++ b/tests/pretty/delegation-inherit-attributes.pp @@ -1,14 +1,15 @@ +#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, +lint_instances: [incomplete_features]}])] +#![attr = Feature([fn_delegation#0])] +extern crate std; +#[attr = PreludeImport] +use std::prelude::rust_2021::*; //@ edition:2021 //@ aux-crate:to_reuse_functions=to-reuse-functions.rs //@ pretty-mode:hir //@ pretty-compare-only //@ pp-exact:delegation-inherit-attributes.pp -#![allow(incomplete_features)] -#![attr = Feature([fn_delegation#0])] -extern crate std; -#[attr = PreludeImport] -use std::prelude::rust_2021::*; extern crate to_reuse_functions; diff --git a/tests/pretty/delegation-inline-attribute.pp b/tests/pretty/delegation-inline-attribute.pp index 125ed1c298262..4828f2e6c80f6 100644 --- a/tests/pretty/delegation-inline-attribute.pp +++ b/tests/pretty/delegation-inline-attribute.pp @@ -1,12 +1,13 @@ -//@ pretty-compare-only -//@ pretty-mode:hir -//@ pp-exact:delegation-inline-attribute.pp - -#![allow(incomplete_features)] +#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, +lint_instances: [incomplete_features]}])] #![attr = Feature([fn_delegation#0])] extern crate std; #[attr = PreludeImport] use ::std::prelude::rust_2015::*; +//@ pretty-compare-only +//@ pretty-mode:hir +//@ pp-exact:delegation-inline-attribute.pp + mod to_reuse { fn foo(x: usize) -> usize { x } diff --git a/tests/pretty/hir-delegation.pp b/tests/pretty/hir-delegation.pp index 28bb49458ce1d..5337dd2e96dfa 100644 --- a/tests/pretty/hir-delegation.pp +++ b/tests/pretty/hir-delegation.pp @@ -1,12 +1,13 @@ -//@ pretty-compare-only -//@ pretty-mode:hir -//@ pp-exact:hir-delegation.pp - -#![allow(incomplete_features)] +#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, +lint_instances: [incomplete_features]}])] #![attr = Feature([fn_delegation#0])] extern crate std; #[attr = PreludeImport] use ::std::prelude::rust_2015::*; +//@ pretty-compare-only +//@ pretty-mode:hir +//@ pp-exact:hir-delegation.pp + fn b(e: C) { } diff --git a/tests/pretty/hir-lifetimes.pp b/tests/pretty/hir-lifetimes.pp index c35a40eed0c50..07ac4ccfdd450 100644 --- a/tests/pretty/hir-lifetimes.pp +++ b/tests/pretty/hir-lifetimes.pp @@ -1,13 +1,19 @@ +#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, +lint_instances: [unused_imports, unused_variables, unused_visibilities, +unused_assignments, dead_code, unused_mut, unreachable_code, +unreachable_patterns, unused_must_use, unused_unsafe, path_statements, +unused_attributes, unused_macros, unused_macro_rules, unused_allocation, +unused_doc_comments, unused_extern_crates, unused_features, unused_labels, +unused_parens, unused_braces, redundant_semicolons, map_unit_fn]}])] +extern crate std; +#[attr = PreludeImport] +use ::std::prelude::rust_2015::*; //@ pretty-compare-only //@ pretty-mode:hir //@ pp-exact:hir-lifetimes.pp // This tests the pretty-printing of lifetimes in lots of ways. -#![allow(unused)] -extern crate std; -#[attr = PreludeImport] -use ::std::prelude::rust_2015::*; struct Foo<'a> { x: &'a u32, diff --git a/tests/pretty/pin-ergonomics-hir.pp b/tests/pretty/pin-ergonomics-hir.pp index 6c9dec2bfb1fb..5b024bfff3b59 100644 --- a/tests/pretty/pin-ergonomics-hir.pp +++ b/tests/pretty/pin-ergonomics-hir.pp @@ -1,12 +1,13 @@ -//@ pretty-compare-only -//@ pretty-mode:hir -//@ pp-exact:pin-ergonomics-hir.pp - -#![allow(dead_code, incomplete_features)] #![attr = Feature([pin_ergonomics#0])] +#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, +lint_instances: [dead_code, incomplete_features]}])] extern crate std; #[attr = PreludeImport] use ::std::prelude::rust_2015::*; +//@ pretty-compare-only +//@ pretty-mode:hir +//@ pp-exact:pin-ergonomics-hir.pp + use std::pin::Pin; diff --git a/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.rs b/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.rs new file mode 100644 index 0000000000000..6fe663518ad68 --- /dev/null +++ b/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.rs @@ -0,0 +1,12 @@ +// compile-args: --crate-type lib + +// This file does not emit the rename warnings +// due to compilation aborting before we emit delayed lints + +#![deny(broken_intra_doc_links)] +//! [x] +//~^ ERROR unresolved link + +#![deny(rustdoc::non_autolinks)] +//! http://example.com +//~^ ERROR not a hyperlink diff --git a/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.stderr b/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.stderr new file mode 100644 index 0000000000000..484566587d9e2 --- /dev/null +++ b/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.stderr @@ -0,0 +1,32 @@ +error: unresolved link to `x` + --> $DIR/renamed-lint-still-applies-2.rs:7:6 + | +LL | //! [x] + | ^ no item named `x` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` +note: the lint level is defined here + --> $DIR/renamed-lint-still-applies-2.rs:6:9 + | +LL | #![deny(broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: this URL is not a hyperlink + --> $DIR/renamed-lint-still-applies-2.rs:11:5 + | +LL | //! http://example.com + | ^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +note: the lint level is defined here + --> $DIR/renamed-lint-still-applies-2.rs:10:9 + | +LL | #![deny(rustdoc::non_autolinks)] + | ^^^^^^^^^^^^^^^^^^^^^^ +help: use an automatic link instead + | +LL | //! + | + + + +error: aborting due to 2 previous errors + diff --git a/tests/rustdoc-ui/lints/renamed-lint-still-applies.rs b/tests/rustdoc-ui/lints/renamed-lint-still-applies.rs index a4d3a4b497117..8dded5460f124 100644 --- a/tests/rustdoc-ui/lints/renamed-lint-still-applies.rs +++ b/tests/rustdoc-ui/lints/renamed-lint-still-applies.rs @@ -1,10 +1,7 @@ +//@ check-pass // compile-args: --crate-type lib #![deny(broken_intra_doc_links)] //~^ WARNING renamed to `rustdoc::broken_intra_doc_links` -//! [x] -//~^ ERROR unresolved link #![deny(rustdoc::non_autolinks)] //~^ WARNING renamed to `rustdoc::bare_urls` -//! http://example.com -//~^ ERROR not a hyperlink diff --git a/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr b/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr index 88807dfb495d0..b9dde5fbc7fee 100644 --- a/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr +++ b/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr @@ -1,5 +1,5 @@ warning: lint `broken_intra_doc_links` has been renamed to `rustdoc::broken_intra_doc_links` - --> $DIR/renamed-lint-still-applies.rs:2:9 + --> $DIR/renamed-lint-still-applies.rs:3:9 | LL | #![deny(broken_intra_doc_links)] | ^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `rustdoc::broken_intra_doc_links` @@ -7,40 +7,10 @@ LL | #![deny(broken_intra_doc_links)] = note: `#[warn(renamed_and_removed_lints)]` on by default warning: lint `rustdoc::non_autolinks` has been renamed to `rustdoc::bare_urls` - --> $DIR/renamed-lint-still-applies.rs:7:9 + --> $DIR/renamed-lint-still-applies.rs:6:9 | LL | #![deny(rustdoc::non_autolinks)] | ^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `rustdoc::bare_urls` -error: unresolved link to `x` - --> $DIR/renamed-lint-still-applies.rs:4:6 - | -LL | //! [x] - | ^ no item named `x` in scope - | - = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` -note: the lint level is defined here - --> $DIR/renamed-lint-still-applies.rs:2:9 - | -LL | #![deny(broken_intra_doc_links)] - | ^^^^^^^^^^^^^^^^^^^^^^ - -error: this URL is not a hyperlink - --> $DIR/renamed-lint-still-applies.rs:9:5 - | -LL | //! http://example.com - | ^^^^^^^^^^^^^^^^^^ - | - = note: bare URLs are not automatically turned into clickable links -note: the lint level is defined here - --> $DIR/renamed-lint-still-applies.rs:7:9 - | -LL | #![deny(rustdoc::non_autolinks)] - | ^^^^^^^^^^^^^^^^^^^^^^ -help: use an automatic link instead - | -LL | //! - | + + - -error: aborting due to 2 previous errors; 2 warnings emitted +warning: 2 warnings emitted diff --git a/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.svg b/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.svg index 549acee7cee54..08239ac686a67 100644 --- a/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.svg +++ b/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.svg @@ -1,4 +1,4 @@ - +