From 258d788b5bfe03b885244b3192801672c2f4afc5 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 15:31:39 +0100 Subject: [PATCH 01/13] add variant `ExpectedNameValueAsLastArgument` to `AttributeParseErrorReason` --- compiler/rustc_attr_parsing/src/context.rs | 16 ++++++++++++++++ .../src/session_diagnostics.rs | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index b82607e7c450d..aad734c8c66ed 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -654,6 +654,22 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { ) } + pub(crate) fn expected_nv_as_last_argument( + &self, + span: Span, + attr_name: Symbol, + name_value_key: Symbol, + ) -> ErrorGuaranteed { + self.emit_parse_error( + span, + AttributeParseErrorReason::ExpectedNameValueAsLastArgument { + span, + attr_name, + 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 e98969dda300b..8f64a7cc2a2a9 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -759,6 +759,18 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { AttributeParseErrorReason::ExpectedIdentifier => { diag.span_label(self.span, "expected a valid identifier here"); } + AttributeParseErrorReason::ExpectedNameValueAsLastArgument { + span, + attr_name, + name_value_key, + } => { + diag.span_label( + span, + format!( + "the name value key `{name_value_key}` must come last in `#[{attr_name}]`" + ), + ); + } } if let Some(link) = self.template.docs { From 4e5f99e425f07d7866b67ff55e8fc68ad346f8ed Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 15:50:10 +0100 Subject: [PATCH 02/13] use full `RegisteredTools` in tools field on `AttributeParser` --- compiler/rustc_ast_lowering/src/delegation.rs | 1 + compiler/rustc_ast_lowering/src/lib.rs | 2 +- .../src/attributes/codegen_attrs.rs | 3 ++- compiler/rustc_attr_parsing/src/interface.rs | 16 +++++++++++----- compiler/rustc_expand/src/expand.rs | 1 + compiler/rustc_resolve/src/def_collector.rs | 2 +- 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index f20b979588c57..d8b72997c729c 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -300,6 +300,7 @@ impl<'hir> LoweringContext<'_, 'hir> { DUMMY_NODE_ID, Some(self.tcx.features()), ShouldEmit::Nothing, + self.tcx.registered_tools(()).to_owned(), )); } } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 24a7215ddb385..2a362ab164412 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -150,7 +150,7 @@ struct LoweringContext<'a, 'hir> { impl<'a, 'hir> LoweringContext<'a, 'hir> { fn new(tcx: TyCtxt<'hir>, resolver: &'a mut ResolverAstLowering) -> Self { - let registered_tools = tcx.registered_tools(()).iter().map(|x| x.name).collect(); + let registered_tools = tcx.registered_tools(()).to_owned(); Self { // Pseudo-globals. tcx, diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 4909e0d35173c..11aae5ce00d04 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -294,10 +294,11 @@ impl AttributeParser for NakedParser { let span = self.span?; + let tools = cx.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 f75f63a0e811a..c10ffcb73cd04 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: 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, + RegisteredTools::default(), ); 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: 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,7 +199,7 @@ impl<'sess> AttributeParser<'sess, Early> { ) -> T { let mut parser = Self { features, - tools: Vec::new(), + tools: RegisteredTools::default(), parse_only: None, sess, stage: Early { emit_errors }, @@ -231,7 +237,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { pub fn new( sess: &'sess Session, features: &'sess Features, - tools: Vec, + tools: RegisteredTools, stage: S, ) -> Self { Self { features: Some(features), tools, parse_only: None, sess, stage } diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 76a9a6f9d03d9..419fb5dcf2b4c 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -2172,6 +2172,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { self.cx.current_expansion.lint_node_id, Some(self.cx.ecfg.features), ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, + self.cx.resolver.registered_tools().to_owned(), ); 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 e2fea9146c798..c9d1bdd48d1ef 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -142,7 +142,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(()).to_owned(), Early { emit_errors: ShouldEmit::Nothing }, ); let attrs = parser.parse_attribute_list( From 3c10a85925d2f5e70f4540071a2e1168175bb907 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 18:25:27 +0100 Subject: [PATCH 03/13] 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 06d6928e1dfd5..ff1e1cd63ba97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3547,6 +3547,7 @@ dependencies = [ "rustc_hir", "rustc_lexer", "rustc_macros", + "rustc_middle", "rustc_parse", "rustc_parse_format", "rustc_session", @@ -4147,7 +4148,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 aad734c8c66ed..025e962d9d5a4 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 @@ -433,6 +433,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 c10ffcb73cd04..beb14d5578421 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -215,6 +215,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, @@ -228,6 +229,7 @@ impl<'sess> AttributeParser<'sess, Early> { parsed_description, template, attr_path, + attr_id, }; parse_fn(&mut cx, args) } @@ -387,6 +389,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 cfc18324b9aa1da21f4bdcfdbeb815e26b9d2495 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 18:48:30 +0100 Subject: [PATCH 04/13] 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 561bdd1a2db67..27b87afb1c0b6 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -23,7 +23,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}; @@ -68,26 +70,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 { @@ -102,29 +97,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 { @@ -303,6 +275,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 a3376ad967e06..f17ce279ab0cc 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -20,13 +20,15 @@ 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 {rustc_ast as ast, rustc_hir as hir}; 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 cd0d8765dd933..7678edc52999e 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -130,7 +130,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::{decorate_attribute_lint, decorate_builtin_lint}; pub use early::{EarlyCheckNode, check_ast_node}; pub use late::{check_crate, late_lint_mod, unerased_lint_store}; @@ -138,7 +138,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 1492df50a418a..a600cd1b09d26 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 { @@ -863,6 +883,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 dc18b05c75763..8f3e9bbd9da17 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 1a7a14f2345cb43500e0628fb6a2e5b308d02aa6 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 26 Feb 2026 14:48:23 +0100 Subject: [PATCH 05/13] Port `#[allow]`, `#[deny]`, `#[expect]`, `#[forbid]`, `#[warn]` to attr parser --- .../rustc_attr_parsing/src/attributes/lint.rs | 362 ++++++++++++++++++ .../rustc_attr_parsing/src/attributes/mod.rs | 1 + compiler/rustc_attr_parsing/src/context.rs | 6 + compiler/rustc_attr_parsing/src/interface.rs | 27 ++ .../src/session_diagnostics.rs | 16 + .../rustc_hir/src/attrs/data_structures.rs | 119 +++++- .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + .../rustc_hir/src/attrs/pretty_printing.rs | 3 +- compiler/rustc_hir/src/hir.rs | 3 + compiler/rustc_hir_typeck/src/expr.rs | 19 +- compiler/rustc_lint/src/context.rs | 21 +- compiler/rustc_lint/src/early.rs | 212 ++++++---- compiler/rustc_lint/src/early/diagnostics.rs | 20 + compiler/rustc_lint/src/errors.rs | 30 -- compiler/rustc_lint/src/lints.rs | 28 +- compiler/rustc_lint_defs/Cargo.toml | 1 - compiler/rustc_lint_defs/src/lib.rs | 19 + compiler/rustc_mir_build/src/builder/scope.rs | 11 +- compiler/rustc_passes/src/check_attr.rs | 157 ++++---- compiler/rustc_passes/src/errors.rs | 2 - 20 files changed, 843 insertions(+), 215 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..5892014500055 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/lint.rs @@ -0,0 +1,362 @@ +use std::marker::PhantomData; + +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::lint::CheckLintNameResult; +use rustc_session::lint::builtin::{RENAMED_AND_REMOVED_LINTS, UNKNOWN_LINTS}; + +use super::prelude::*; +use crate::session_diagnostics::UnknownToolInScopedLint; + +pub(crate) trait Lint { + const KIND: LintAttributeKind; + const ATTR_SYMBOL: Symbol; +} +#[derive(Default)] +pub(crate) struct Expect; +pub(crate) type ExpectParser = LintParser; + +impl Lint for Expect { + const KIND: LintAttributeKind = LintAttributeKind::Expect; + const ATTR_SYMBOL: Symbol = sym::expect; +} + +#[derive(Default)] +pub(crate) struct Allow; +pub(crate) type AllowParser = LintParser; + +impl Lint for Allow { + const KIND: LintAttributeKind = LintAttributeKind::Allow; + const ATTR_SYMBOL: Symbol = sym::allow; +} +#[derive(Default)] +pub(crate) struct Warn; +pub(crate) type WarnParser = LintParser; + +impl Lint for Warn { + const KIND: LintAttributeKind = LintAttributeKind::Warn; + const ATTR_SYMBOL: Symbol = sym::warn; +} + +#[derive(Default)] +pub(crate) struct Deny; +pub(crate) type DenyParser = LintParser; + +impl Lint for Deny { + const KIND: LintAttributeKind = LintAttributeKind::Deny; + const ATTR_SYMBOL: Symbol = sym::deny; +} + +#[derive(Default)] +pub(crate) struct Forbid; +pub(crate) type ForbidParser = LintParser; + +impl Lint for Forbid { + const KIND: LintAttributeKind = LintAttributeKind::Forbid; + const ATTR_SYMBOL: Symbol = sym::forbid; +} + +#[derive(Default)] +pub(crate) struct LintParser { + lint_attrs: ThinVec, + attr_index: usize, + phantom: PhantomData, +} + +impl AttributeParser for LintParser { + const ATTRIBUTES: AcceptMapping = &[( + &[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, T::ATTR_SYMBOL, this.attr_index) { + this.lint_attrs.push(lint_attr); + } + this.attr_index += 1; + }, + )]; + + 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 }), + Warn(Target::MacroCall), + ]) + }; + + fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { + if !self.lint_attrs.is_empty() { + Some(AttributeKind::LintAttribute { sub_attrs: self.lint_attrs, kind: T::KIND }) + } else { + None + } + } +} + +#[inline(always)] +fn validate_lint_attr( + cx: &mut AcceptContext<'_, '_, S>, + args: &ArgParser, + attr_name: Symbol, + attr_index: usize, +) -> Option { + let Some(list) = args.list() else { + cx.expected_list(cx.inner_span, args); + return None; + }; + let mut list = list.mixed().peekable(); + + let mut skip_reason_check = false; + let mut errored = false; + let mut reason = None; + let mut lint_instances = ThinVec::new(); + let mut lint_index = 0; + + 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(), attr_name, 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 => { + 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) + }; + + if let Some(ids) = check_lint( + cx, + Symbol::intern(&name), + tool_name, + tool_span, + meta_item.span(), + lint_index, + ) { + lint_instances.extend(ids); + skip_reason_check = true + } else { + skip_reason_check = true + } + lint_index += 1; + } + } + } + if !skip_reason_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 }, + attr_index, + }) +} + +fn check_lint( + cx: &mut AcceptContext<'_, '_, S>, + original_name: Symbol, + tool_name: Option, + tool_span: Option, + span: Span, + lint_index: usize, +) -> Option> { + let Some(lint_store) = &cx.sess.lint_store else { + bug!("lint_store required while parsing attributes"); + }; + let full_name = tool_name + .map(|tool| Symbol::intern(&format!("{tool}::{}", &original_name))) + .unwrap_or(original_name); + let mut lint_ids = Vec::new(); + match lint_store.check_lint_name(original_name.as_str(), tool_name, &cx.tools) { + CheckLintNameResult::Ok(ids) => { + for id in ids { + lint_ids.push(LintInstance::new(full_name, id.to_string(), span, lint_index)); + } + } + 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 + } + }; + for id in ids { + lint_ids.push(LintInstance::new(full_name, id.to_string(), span, lint_index)); + } + } + + 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. + return None; + } + + CheckLintNameResult::NoTool => { + if cx.tools.is_empty() { + bug!("tools should never be empty") + } + 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(), + }); + return None; + } + + CheckLintNameResult::Renamed(replace) => { + cx.emit_lint( + RENAMED_AND_REMOVED_LINTS, + AttributeLintKind::RenamedLint { name: full_name, replace, suggestion: span }, + span, + ); + + // 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 ids = match lint_store.check_lint_name(replace.as_str(), None, &cx.tools) { + CheckLintNameResult::Ok(ids) => ids, + _ => panic!("renamed lint does not exist: {replace}"), + }; + + for id in ids { + lint_ids.push(LintInstance::new(full_name, id.to_string(), span, lint_index)); + } + } + + CheckLintNameResult::RenamedToolLint(new_name) => { + cx.emit_lint( + RENAMED_AND_REMOVED_LINTS, + AttributeLintKind::RenamedLint { + name: full_name, + replace: new_name, + suggestion: span, + }, + span, + ); + return None; + } + + CheckLintNameResult::Removed(reason) => { + cx.emit_lint( + RENAMED_AND_REMOVED_LINTS, + AttributeLintKind::RemovedLint { name: full_name, reason }, + span, + ); + return None; + } + + CheckLintNameResult::NoLint(suggestion) => { + cx.emit_lint( + UNKNOWN_LINTS, + AttributeLintKind::UnknownLint { name: full_name, suggestion, span }, + span, + ); + return None; + } + } + Some(lint_ids) +} diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 8ee453d7f4649..c6e33b859395c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -45,6 +45,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 025e962d9d5a4..5dbd567eb9d40 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -34,6 +34,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::*; @@ -141,10 +142,14 @@ macro_rules! attribute_parsers { attribute_parsers!( pub(crate) static ATTRIBUTE_PARSERS = [ // tidy-alphabetical-start + AllowParser, BodyStabilityParser, ConfusablesParser, ConstStabilityParser, + DenyParser, DocParser, + ExpectParser, + ForbidParser, MacroUseParser, NakedParser, OnConstParser, @@ -154,6 +159,7 @@ attribute_parsers!( RustcCguTestAttributeParser, StabilityParser, UsedParser, + WarnParser, // tidy-alphabetical-end // tidy-alphabetical-start diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index beb14d5578421..e84dcc165e457 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -133,6 +133,33 @@ 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( + sess: &'sess Session, + mut attrs: Vec, + filter: &[Symbol], + target: Target, + target_span: Span, + target_node_id: NodeId, + features: Option<&'sess Features>, + emit_errors: ShouldEmit, + tools: RegisteredTools, + ) -> Vec { + attrs.retain(|attr| attr.has_any_name(filter)); + Self::parse_limited_all( + sess, + &attrs, + None, + target, + target_span, + target_node_id, + features, + emit_errors, + 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( diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 8f64a7cc2a2a9..b6ba738974030 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -579,6 +579,11 @@ pub(crate) enum AttributeParseErrorReason<'a> { list: bool, }, ExpectedIdentifier, + ExpectedNameValueAsLastArgument { + span: Span, + attr_name: Symbol, + name_value_key: Symbol, + }, } /// A description of a thing that can be parsed using an attribute parser. @@ -1042,3 +1047,14 @@ pub(crate) struct UnsupportedInstructionSet<'a> { pub instruction_set: Symbol, pub current_target: &'a TargetTuple, } + +#[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 68f5bb94c3fe8..8494d9eeca97c 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -19,7 +19,9 @@ 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 { @@ -794,6 +796,116 @@ pub struct RustcCleanQueries { pub span: Span, } +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub struct LintAttribute { + pub lint_instances: ThinVec, + pub attr_span: Span, + pub reason: Option, + pub attr_style: AttrStyle, + pub attr_id: HashIgnoredAttrId, + pub attr_index: usize, +} + +#[derive(Debug, Clone, PrintAttribute, Encodable, Decodable, HashStable_Generic)] +pub struct LintInstance { + span: Span, + lint_name: Symbol, + original_name: Option, + lint_index: usize, + kind: LintAttrTool, +} + +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)] +pub enum LintAttributeKind { + Allow, + Warn, + Deny, + Forbid, + Expect, +} + /// Represents parsed *built-in* inert attributes. /// /// ## Overview @@ -997,6 +1109,11 @@ pub enum AttributeKind { /// Represents `#[linkage]`. Linkage(Linkage, Span), + /// Represents `#[allow]`, `#[expect]`, `#[warn]`, `#[deny]`, `#[forbid]` + LintAttribute { + kind: LintAttributeKind, + sub_attrs: 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 c50d38b6d673a..a6647992e724d 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, + LintAttribute { .. } => 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 8fce529010150..3cebe4e7dfe1b 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -16,6 +16,7 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use rustc_target::spec::SanitizerSet; use thin_vec::ThinVec; +use crate::HashIgnoredAttrId; use crate::limit::Limit; /// This trait is used to print attributes in `rustc_hir_pretty`. @@ -190,7 +191,7 @@ macro_rules! print_tup { } print_tup!(A B C D E F G H); -print_skip!(Span, (), ErrorGuaranteed, AttrId); +print_skip!(Span, (), ErrorGuaranteed, AttrId, HashIgnoredAttrId); print_disp!(u8, u16, u128, usize, bool, NonZero, Limit); print_debug!( Symbol, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 373d61978439e..2cee404f75689 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1380,6 +1380,9 @@ 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::LintAttribute { 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 f3ca04c6bbe27..33e52ad1e1300 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -15,10 +15,11 @@ use rustc_errors::{ Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, listify, pluralize, struct_span_code_err, }; +use rustc_hir::attrs::AttributeKind; 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::{Attribute, ExprKind, HirId, QPath, find_attr, is_range_literal}; use rustc_hir_analysis::NoVariantNamed; use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer as _; use rustc_infer::infer::{self, DefineOpaqueTypes, InferOk, RegionVariableOrigin}; @@ -70,7 +71,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // // let y: u32 = (x?).try_into().unwrap(); // + +++++++++++++++++++++ - if attr.span().desugaring_kind().is_none() { + let span = match attr { + Attribute::Unparsed(attr) => attr.span, + Attribute::Parsed(AttributeKind::LintAttribute { sub_attrs, .. }) => { + for attr in sub_attrs { + if attr.attr_span.desugaring_kind().is_none() { + return true; + } + } + return false; + } + Attribute::Parsed(AttributeKind::Deprecated { span, .. }) => *span, + Attribute::Parsed(attr) => bug!("can't get span of parsed attr: {:?}", attr), + }; + + if span.desugaring_kind().is_none() { return true; } } diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 27b87afb1c0b6..6b6d8e5f3777f 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -13,10 +13,11 @@ use rustc_data_structures::sync; use rustc_data_structures::unord::UnordMap; use rustc_errors::{Diag, LintBuffer, LintDiagnostic, MultiSpan}; use rustc_feature::Features; +use rustc_hir::attrs::AttributeKind; use rustc_hir::def::Res; use rustc_hir::def_id::{CrateNum, DefId}; use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; -use rustc_hir::{Pat, PatKind}; +use rustc_hir::{Attribute, Pat, PatKind}; use rustc_middle::bug; use rustc_middle::lint::LevelAndSource; use rustc_middle::middle::privacy::EffectiveVisibilities; @@ -368,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::` @@ -861,7 +862,21 @@ impl<'tcx> LateContext<'tcx> { 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() { + let span = match attr { + Attribute::Unparsed(attr) => attr.span, + Attribute::Parsed(AttributeKind::LintAttribute { sub_attrs, .. }) => { + for attr in sub_attrs { + if attr.attr_span.desugaring_kind().is_none() { + return true; + } + } + return false; + } + Attribute::Parsed(AttributeKind::Deprecated { span, .. }) => *span, + Attribute::Parsed(attr) => bug!("can't get span of parsed attr: {:?}", attr), + }; + + if span.desugaring_kind().is_none() { return true; } } diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 0295df2feca56..d7d872a1d4e48 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::context::{EarlyContext, LintContext, LintStore}; @@ -48,13 +48,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); @@ -73,24 +77,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) { @@ -100,23 +119,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) { @@ -128,10 +162,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) { @@ -140,16 +179,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) { @@ -162,10 +211,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) { @@ -174,10 +228,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) { @@ -186,10 +245,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) { @@ -209,25 +273,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) { @@ -356,7 +425,12 @@ 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 5aee3f382ff3c..d7a650518785f 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -473,5 +473,25 @@ pub fn decorate_attribute_lint( &AttributeLintKind::MissingOptionsForOnConst => { lints::MissingOptionsForOnConstAttr.decorate_lint(diag) } + + &AttributeLintKind::RenamedLint { name, replace, suggestion } => lints::RenamedLint { + name, + replace, + suggestion: lints::RenamedLintSuggestion::WithSpan { suggestion, replace }, + } + .decorate_lint(diag), + &AttributeLintKind::DeprecatedLintName { name, suggestion, replace } => { + lints::DeprecatedLintName { name, suggestion, replace }.decorate_lint(diag) + } + &AttributeLintKind::RemovedLint { name, ref reason } => { + lints::RemovedLint { name, reason }.decorate_lint(diag) + } + &AttributeLintKind::UnknownLint { name, span, suggestion } => lints::UnknownLint { + name, + suggestion: suggestion.map(|(replace, from_rustc)| { + lints::UnknownLintSuggestion::WithSpan { suggestion: span, replace, from_rustc } + }), + } + .decorate_lint(diag), } } 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/lints.rs b/compiler/rustc_lint/src/lints.rs index 4b7e102e239ec..1e7e10f5a4790 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1226,11 +1226,11 @@ pub(crate) struct OverruledAttributeLint<'a> { #[derive(LintDiagnostic)] #[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(LintDiagnostic)] @@ -1245,32 +1245,32 @@ pub(crate) struct DeprecatedLintNameFromCommandLine<'a> { #[derive(LintDiagnostic)] #[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(LintDiagnostic)] #[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>, } @@ -1278,7 +1278,7 @@ pub(crate) struct RenamedLintFromCommandLine<'a> { #[derive(LintDiagnostic)] #[diag("lint `{$name}` has been removed: {$reason}")] pub(crate) struct RemovedLint<'a> { - pub name: &'a str, + pub name: Symbol, pub reason: &'a str, } @@ -1294,7 +1294,7 @@ pub(crate) struct RemovedLintFromCommandLine<'a> { #[derive(LintDiagnostic)] #[diag("unknown lint: `{$name}`")] pub(crate) struct UnknownLint { - pub name: String, + pub name: Symbol, #[subdiagnostic] pub suggestion: Option, } 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 a600cd1b09d26..45d2543d9f225 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -875,6 +875,25 @@ pub enum AttributeLintKind { }, MissingOptionsForOnUnimplemented, MissingOptionsForOnConst, + 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)>, + }, } #[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 b10df60e0f75b..a5c7bda961646 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::source_map::Spanned; use rustc_span::{DUMMY_SP, Span}; use tracing::{debug, instrument}; @@ -1299,7 +1299,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::LintAttribute { .. }))) + { // 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 89bd39b77e64b..263f7619767e5 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::{Align, ExternAbi, Size}; -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; @@ -22,8 +22,8 @@ use rustc_feature::{ }; use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::{ - AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr, - MirDialect, MirPhase, ReprAttr, SanitizerSet, + AttributeKind, CrateType, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, + InlineAttr, LintAttribute, MirDialect, MirPhase, ReprAttr, SanitizerSet, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalModDefId; @@ -41,7 +41,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, @@ -142,7 +141,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) @@ -234,6 +232,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Parsed(AttributeKind::DoNotRecommend{attr_span}) => {self.check_do_not_recommend(*attr_span, hir_id, target, item)}, Attribute::Parsed(AttributeKind::OnUnimplemented{span, directive}) => {self.check_diagnostic_on_unimplemented(*span, hir_id, target,directive.as_deref())}, Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)} + Attribute::Parsed(AttributeKind::LintAttribute { sub_attrs, .. }) => self.check_lint_attr(hir_id, sub_attrs), Attribute::Parsed( // tidy-alphabetical-start AttributeKind::RustcAllowIncoherentImpl(..) @@ -387,19 +386,12 @@ 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() { [sym::autodiff_forward, ..] | [sym::autodiff_reverse, ..] => { self.check_autodiff(hir_id, attr, span, target) } [ - // ok - sym::allow - | sym::expect - | sym::warn - | sym::deny - | sym::forbid // internal | sym::rustc_on_unimplemented | sym::rustc_layout @@ -489,8 +481,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); @@ -1601,86 +1592,74 @@ 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) - }) - { - 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) { + return; + }; + 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 - } else { + ast::AttrStyle::Inner => 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; + } }; + 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 b9ada150d0301..acd8ec0a0a607 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -313,8 +313,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 b5614221d0bd095214e99576d69aaf38a45ff384 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 19:36:26 +0100 Subject: [PATCH 06/13] make lint_index not optional 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 | 45 +++++------------------------ 2 files changed, 10 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 45d2543d9f225..e243501d628c8 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}; 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) } @@ -247,35 +245,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 bb42d99c2c5aa580cb0830a1cb98ea2f7c82e9b8 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 19:47:27 +0100 Subject: [PATCH 07/13] integrate parsed lint attrs into lint level system 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 | 5 + compiler/rustc_attr_parsing/src/interface.rs | 21 +- compiler/rustc_lint/src/levels.rs | 355 ++++++------------ 3 files changed, 132 insertions(+), 249 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/lint.rs b/compiler/rustc_attr_parsing/src/attributes/lint.rs index 5892014500055..134773e13f94a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/lint.rs +++ b/compiler/rustc_attr_parsing/src/attributes/lint.rs @@ -124,6 +124,11 @@ impl AttributeParser for LintParser 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), ]) }; diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index e84dcc165e457..e6535657b4ba8 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, @@ -117,7 +117,7 @@ impl<'sess> AttributeParser<'sess, Early> { ) -> Vec { let mut p = Self { features, tools, parse_only, sess, stage: Early { emit_errors } }; p.parse_attribute_list( - attrs, + attrs.into_iter(), target_span, target, OmitDoc::Skip, @@ -135,9 +135,9 @@ 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( + pub fn parse_limited_all_filtered<'a>( sess: &'sess Session, - mut attrs: Vec, + attrs: impl IntoIterator, filter: &[Symbol], target: Target, target_span: Span, @@ -146,10 +146,9 @@ impl<'sess> AttributeParser<'sess, Early> { emit_errors: ShouldEmit, tools: RegisteredTools, ) -> Vec { - attrs.retain(|attr| attr.has_any_name(filter)); Self::parse_limited_all( sess, - &attrs, + attrs.into_iter().filter(|attr| attr.has_any_name(filter)), None, target, target_span, @@ -292,9 +291,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, @@ -305,9 +304,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) { diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index f17ce279ab0cc..03758d0a3af5f 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -1,11 +1,13 @@ 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, LintDiagnostic, MultiSpan, msg}; use rustc_feature::{Features, GateIssue}; -use rustc_hir::HirId; +use rustc_hir::attrs::{AttributeKind, LintAttribute, LintAttributeKind, LintInstance}; use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{Attribute, HirId, Target}; use rustc_index::IndexVec; use rustc_middle::bug; use rustc_middle::hir::nested_filter; @@ -30,17 +32,26 @@ use {rustc_ast as ast, rustc_hir as hir}; 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, - UnknownLintSuggestion, + DeprecatedLintNameFromCommandLine, IgnoredUnlessCrateSpecified, 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. @@ -391,7 +402,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.to_owned(), + ); + + builder.add(&parsed_crate_attrs, true, None); builder } @@ -421,14 +444,27 @@ 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 }); + 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.clone(), + ); + + let is_crate_node = node_id == ast::CRATE_NODE_ID; - self.add(attrs, is_crate_node, source_hir_id); + self.add(&attrs, is_crate_node, None); if self.provider.current_specs().is_empty() { self.provider.sets.list.pop(); @@ -479,7 +515,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 }; @@ -639,14 +675,33 @@ 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 this function returns none, it means the attribute parser has already emitted appropriate errors + + let src = + LintLevelSource::Node { name: lint.original_lint_name(), span: lint.span(), reason }; + + 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, + ), + }; + + if self.check_gated_lint(*id, lint.span(), false) { + self.insert_spec(*id, LevelAndSource { level, lint_id: expect_lint_id, src }); + } + } + + fn add(&mut self, attrs: &[hir::Attribute], is_crate_node: bool, source_hir_id: Option) { + for attr in attrs { if attr.is_automatically_derived_attr() { self.insert( LintId::of(SINGLE_USE_LIFETIMES), @@ -672,233 +727,57 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { continue; } - 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 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()]` + let Attribute::Parsed(AttributeKind::LintAttribute { kind, sub_attrs }) = attr else { continue; }; - // Before processing the lint names, look for a reason (RFC 2383) - // at the end. - let mut reason = None; - if let Some(item) = tail_li.meta_item() { - match item.kind { - ast::MetaItemKind::Word => {} // actual lint names handled later - ast::MetaItemKind::NameValue(ref name_value) => { - if item.path == sym::reason { - if let ast::LitKind::Str(rationale, _) = name_value.kind { - reason = Some(rationale); - } else { - sess.dcx().emit_err(MalformedAttribute { - span: name_value.span, - sub: MalformedAttributeSub::ReasonMustBeStringLiteral( - name_value.span, - ), - }); - } - // found reason, reslice meta list to exclude it - metas.pop().unwrap(); - } else { - sess.dcx().emit_err(MalformedAttribute { - span: item.span, - sub: MalformedAttributeSub::BadAttributeArgument(item.span), - }); - } - } - ast::MetaItemKind::List(_) => { - sess.dcx().emit_err(MalformedAttribute { - span: item.span, - sub: MalformedAttributeSub::BadAttributeArgument(item.span), - }); - } - } - } - - for (lint_index, li) in metas.iter_mut().enumerate() { - let mut lint_id = lint_id; - if let Some(id) = &mut lint_id { - id.set_lint_index(Some(lint_index as u16)); - } - - let sp = li.span(); - let meta_item = match li { - ast::MetaItemInner::MetaItem(meta_item) if meta_item.is_word() => meta_item, - _ => { - let sub = if let Some(item) = li.meta_item() - && let ast::MetaItemKind::NameValue(_) = item.kind - && item.path == sym::reason - { - MalformedAttributeSub::ReasonMustComeLast(sp) - } else { - MalformedAttributeSub::BadAttributeArgument(sp) - }; - - sess.dcx().emit_err(MalformedAttribute { span: sp, sub }); - continue; - } - }; - let tool_ident = if meta_item.path.segments.len() > 1 { - Some(meta_item.path.segments.remove(0).ident) - } else { - None - }; - let tool_name = tool_ident.map(|ident| ident.name); - let name = pprust::path_to_string(&meta_item.path); - let lint_result = - self.store.check_lint_name(&name, tool_name, self.registered_tools); - - let (ids, name) = match lint_result { - CheckLintNameResult::Ok(ids) => { - let name = - meta_item.path.segments.last().expect("empty lint name").ident.name; - (ids, name) - } - - CheckLintNameResult::Tool(ids, new_lint_name) => { - let name = match new_lint_name { - None => { - let complete_name = - &format!("{}::{}", tool_ident.unwrap().name, name); - Symbol::intern(complete_name) - } - Some(new_lint_name) => { - self.emit_span_lint( - builtin::RENAMED_AND_REMOVED_LINTS, - sp.into(), - DeprecatedLintName { - name, - suggestion: sp, - replace: &new_lint_name, - }, - ); - Symbol::intern(&new_lint_name) - } - }; - (ids, name) - } - - CheckLintNameResult::MissingTool => { - // If `MissingTool` is returned, then either the lint does not - // exist in the tool or the code was not compiled with the tool and - // therefore the lint was never added to the `LintStore`. To detect - // this is the responsibility of the lint tool. - continue; - } - - CheckLintNameResult::NoTool => { - sess.dcx().emit_err(UnknownToolInScopedLint { - span: tool_ident.map(|ident| ident.span), - tool_name: tool_name.unwrap(), - lint_name: pprust::path_to_string(&meta_item.path), - is_nightly_build: sess.is_nightly_build(), - }); - continue; - } - - CheckLintNameResult::Renamed(ref replace) => { - if self.lint_added_lints { - let suggestion = - RenamedLintSuggestion::WithSpan { suggestion: sp, replace }; - let name = - tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); - let lint = RenamedLint { name: name.as_str(), replace, suggestion }; - self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint); + for LintAttribute { reason, lint_instances, attr_id, attr_index, .. } in sub_attrs { + 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 => { + let reason = reason.to_owned(); + for lint in lint_instances { + let lint_index = lint.lint_index().try_into().unwrap(); + let attr_index = (*attr_index).try_into().unwrap(); + let expectation_id = match source_hir_id { + None => LintExpectationId::Unstable { attr_id, lint_index }, + Some(hir_id) => LintExpectationId::Stable { + hir_id, + attr_id, + lint_index, + attr_index, + }, + }; + + self.simple_add( + Level::Expect, + lint, + reason.to_owned(), + 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(), + ), + ); } - // If this lint was renamed, apply the new lint instead of ignoring the - // attribute. Ignore any errors or warnings that happen because the new - // name is inaccurate. - // NOTE: `new_name` already includes the tool name, so we don't - // have to add it again. - let CheckLintNameResult::Ok(ids) = - self.store.check_lint_name(replace, None, self.registered_tools) - else { - panic!("renamed lint does not exist: {replace}"); - }; - - (ids, Symbol::intern(&replace)) - } - - CheckLintNameResult::Removed(ref reason) => { - if self.lint_added_lints { - let name = - tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); - let lint = RemovedLint { name: name.as_str(), reason }; - self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint); - } - 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, - } - }); - let lint = UnknownLint { name, suggestion }; - self.emit_span_lint(UNKNOWN_LINTS, sp.into(), lint); - } 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, - ), - ); + for lint in lint_instances { + self.simple_add(level, lint, reason.to_owned(), None); } } } From 4cd7b64fd73343a61a4006bfaefffd5ac1fe710e Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 22 Feb 2026 19:50:23 +0100 Subject: [PATCH 08/13] integrate new parsed lint attrs into clippy --- .../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 | 8 ++-- .../clippy/clippy_lints/src/collapsible_if.rs | 42 ++++++++++++------- .../src/returns/needless_return.rs | 32 +++++++------- src/tools/clippy/clippy_utils/src/sym.rs | 1 - 7 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/attrs/mod.rs b/src/tools/clippy/clippy_lints/src/attrs/mod.rs index fa2951d919346..9654a5cba58a4 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/mod.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/mod.rs @@ -586,7 +586,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..c025b47dc787d 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(|name| is_lint_level(name)) { 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..a43ecab5d1b25 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/utils.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/utils.rs @@ -1,9 +1,9 @@ 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, }; -use rustc_lint::{LateContext, Level}; +use rustc_lint::{LateContext}; use rustc_middle::ty; use rustc_span::sym; use rustc_span::symbol::Symbol; @@ -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 { + [sym::allow, sym::deny, sym::expect, sym::forbid, sym::warn].contains(&symbol) } 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 17e11b8b281d9..ab4e3b719ee5b 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::{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::source_map::SourceMap; use rustc_span::{BytePos, Span, Symbol}; @@ -238,19 +239,28 @@ 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::LintAttribute { + kind: LintAttributeKind::Expect, + sub_attrs, + }), + ] => { + sub_attrs + .into_iter() + .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..216b9f9b8d398 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,18 +180,20 @@ 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::LintAttribute { + kind: LintAttributeKind::Expect, + sub_attrs, + }), + ] => { + let lint = &sub_attrs[0].lint_instances[0]; + if !lint.tool_is_named(sym::clippy) { + return; + } + if 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; diff --git a/src/tools/clippy/clippy_utils/src/sym.rs b/src/tools/clippy/clippy_utils/src/sym.rs index 250e73afd4f00..87f60c60adcbd 100644 --- a/src/tools/clippy/clippy_utils/src/sym.rs +++ b/src/tools/clippy/clippy_utils/src/sym.rs @@ -640,7 +640,6 @@ generate! { visit_str, visit_string, wake, - warnings, wildcard_imports, with_capacity, wrapping_offset, From e8ce2025b70f3bc3718ae2656fadf2723292e9d4 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 26 Feb 2026 14:52:18 +0100 Subject: [PATCH 09/13] 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 --- compiler/rustc_attr_parsing/src/interface.rs | 2 +- .../src/error_codes/E0452.md | 3 +- compiler/rustc_lint/src/early.rs | 7 +- .../tests/ui/unknown_clippy_lints.stderr | 30 +-- tests/pretty/delegation-inherit-attributes.pp | 12 +- tests/pretty/delegation-inline-attribute.pp | 12 +- tests/pretty/hir-delegation.pp | 12 +- tests/pretty/hir-lifetimes.pp | 44 +++- tests/pretty/pin-ergonomics-hir.pp | 13 +- .../rustdoc-ui/doctest/dead-code-items.stdout | 47 +--- .../lints/renamed-lint-still-applies-2.rs | 12 + .../lints/renamed-lint-still-applies-2.stderr | 34 +++ .../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 +++--- .../diagnostics/liveness.rs | 2 + .../diagnostics/liveness.stderr | 57 +++-- tests/ui/coverage-attr/bad-syntax.rs | 37 ---- tests/ui/coverage-attr/bad-syntax.stderr | 158 +------------- ...deduplicate-diagnostics.deduplicate.stderr | 29 ++- .../deduplicate-diagnostics.duplicate.stderr | 35 ++- .../deduplicate-diagnostics.rs | 4 +- tests/ui/empty/empty-attributes.stderr | 48 ++-- 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 +- .../cstring-as-ptr.stderr | 1 + 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 +- tests/ui/lint/renamed-lints-still-apply.rs | 1 + .../ui/lint/renamed-lints-still-apply.stderr | 17 +- .../expect_lint_from_macro.rs | 4 +- .../expect_lint_from_macro.stderr | 9 +- .../expect_multiple_lints.rs | 1 + .../expect_multiple_lints.stderr | 16 +- .../force_warn_expected_lints_unfulfilled.rs | 1 + ...rce_warn_expected_lints_unfulfilled.stderr | 10 +- .../lint-attribute-only-with-reason.stderr | 10 +- .../multiple_expect_attrs.rs | 5 +- .../semicolon-in-expressions-from-macros.rs | 3 +- ...emicolon-in-expressions-from-macros.stderr | 9 +- 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 | 71 ++++-- ...ct-exprs-tuple-call-pretty-printing.stdout | 14 +- tests/ui/unpretty/unpretty-expr-fn-arg.stdout | 10 +- .../ui/where-clauses/unsupported_attribute.rs | 4 +- .../unsupported_attribute.stderr | 32 +-- 64 files changed, 814 insertions(+), 947 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_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index e6535657b4ba8..b795bc4019420 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -133,7 +133,7 @@ impl<'sess> AttributeParser<'sess, Early> { ) } - /// This method provides the same functionality as [`parse_limited_all`](Self::parse_limited_all) except filtered, + /// 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, 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/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index d7d872a1d4e48..547518d8a115a 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -425,12 +425,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), - DUMMY_SP - ); + 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/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 72493f557dfc2..84e3a8c67af8b 100644 --- a/tests/pretty/delegation-inherit-attributes.pp +++ b/tests/pretty/delegation-inherit-attributes.pp @@ -1,14 +1,16 @@ +#![attr = LintAttribute {kind: Allow, +sub_attrs: [LintAttribute {lint_instances: [LintInstance {lint_name: "incomplete_features", +lint_index: 0, kind: NoTool}], attr_style: Inner, attr_index: 0}]}] +#![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..9e046b8609b2e 100644 --- a/tests/pretty/delegation-inline-attribute.pp +++ b/tests/pretty/delegation-inline-attribute.pp @@ -1,12 +1,14 @@ -//@ pretty-compare-only -//@ pretty-mode:hir -//@ pp-exact:delegation-inline-attribute.pp - -#![allow(incomplete_features)] +#![attr = LintAttribute {kind: Allow, +sub_attrs: [LintAttribute {lint_instances: [LintInstance {lint_name: "incomplete_features", +lint_index: 0, kind: NoTool}], attr_style: Inner, attr_index: 0}]}] #![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 efa643313e6b4..e343cf8d5ca4f 100644 --- a/tests/pretty/hir-delegation.pp +++ b/tests/pretty/hir-delegation.pp @@ -1,12 +1,14 @@ -//@ pretty-compare-only -//@ pretty-mode:hir -//@ pp-exact:hir-delegation.pp - -#![allow(incomplete_features)] +#![attr = LintAttribute {kind: Allow, +sub_attrs: [LintAttribute {lint_instances: [LintInstance {lint_name: "incomplete_features", +lint_index: 0, kind: NoTool}], attr_style: Inner, attr_index: 0}]}] #![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..5fd9076f38f7f 100644 --- a/tests/pretty/hir-lifetimes.pp +++ b/tests/pretty/hir-lifetimes.pp @@ -1,13 +1,49 @@ +#![attr = LintAttribute {kind: Allow, +sub_attrs: [LintAttribute {lint_instances: [LintInstance {lint_name: "unused_imports", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unused_variables", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "unused_visibilities", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unused_assignments", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "dead_code", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unused_mut", original_name: "unused", lint_index: 0, +kind: NoTool}, LintInstance {lint_name: "unreachable_code", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unreachable_patterns", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "unused_must_use", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unused_unsafe", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "path_statements", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unused_attributes", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "unused_macros", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unused_macro_rules", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "unused_allocation", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unused_doc_comments", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "unused_extern_crates", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unused_features", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "unused_labels", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "unused_parens", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "unused_braces", +original_name: "unused", lint_index: 0, kind: NoTool}, +LintInstance {lint_name: "redundant_semicolons", original_name: "unused", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "map_unit_fn", +original_name: "unused", lint_index: 0, kind: NoTool}], attr_style: Inner, +attr_index: 0}]}] +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..8d768ae952449 100644 --- a/tests/pretty/pin-ergonomics-hir.pp +++ b/tests/pretty/pin-ergonomics-hir.pp @@ -1,12 +1,15 @@ -//@ pretty-compare-only -//@ pretty-mode:hir -//@ pp-exact:pin-ergonomics-hir.pp - -#![allow(dead_code, incomplete_features)] #![attr = Feature([pin_ergonomics#0])] +#![attr = LintAttribute {kind: Allow, +sub_attrs: [LintAttribute {lint_instances: [LintInstance {lint_name: "dead_code", +lint_index: 0, kind: NoTool}, LintInstance {lint_name: "incomplete_features", +lint_index: 1, kind: NoTool}], attr_style: Inner, attr_index: 0}]}] 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/doctest/dead-code-items.stdout b/tests/rustdoc-ui/doctest/dead-code-items.stdout index ecfe09f09ce26..099056df72b29 100644 --- a/tests/rustdoc-ui/doctest/dead-code-items.stdout +++ b/tests/rustdoc-ui/doctest/dead-code-items.stdout @@ -1,8 +1,14 @@ -running 13 tests +running 2 tests +test $DIR/dead-code-items.rs - A::field (line 41) - compile ... ok +test $DIR/dead-code-items.rs - U::field (line 57) - compile ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 11 tests test $DIR/dead-code-items.rs - A (line 34) - compile ... ok test $DIR/dead-code-items.rs - A (line 90) - compile ... ok -test $DIR/dead-code-items.rs - A::field (line 41) - compile ... FAILED test $DIR/dead-code-items.rs - A::method (line 96) - compile ... ok test $DIR/dead-code-items.rs - C (line 24) - compile ... FAILED test $DIR/dead-code-items.rs - Enum (line 72) - compile ... FAILED @@ -11,27 +17,10 @@ test $DIR/dead-code-items.rs - MyTrait (line 105) - compile ... FAILED test $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 112) - compile ... FAILED test $DIR/dead-code-items.rs - S (line 16) - compile ... ok test $DIR/dead-code-items.rs - U (line 50) - compile ... ok -test $DIR/dead-code-items.rs - U::field (line 57) - compile ... FAILED test $DIR/dead-code-items.rs - U::field2 (line 63) - compile ... ok failures: ----- $DIR/dead-code-items.rs - A::field (line 41) stdout ---- -error: trait `DeadCodeInField` is never used - --> $DIR/dead-code-items.rs:42:7 - | -LL | trait DeadCodeInField {} - | ^^^^^^^^^^^^^^^ - | -note: the lint level is defined here - --> $DIR/dead-code-items.rs:40:9 - | -LL | #![deny(dead_code)] - | ^^^^^^^^^ - -error: aborting due to 1 previous error - -Couldn't compile the test. ---- $DIR/dead-code-items.rs - C (line 24) stdout ---- error: unused variable: `unused_error` --> $DIR/dead-code-items.rs:25:5 @@ -115,33 +104,15 @@ LL | #![deny(warnings)] error: aborting due to 1 previous error -Couldn't compile the test. ----- $DIR/dead-code-items.rs - U::field (line 57) stdout ---- -error: trait `DeadCodeInUnionField` is never used - --> $DIR/dead-code-items.rs:58:7 - | -LL | trait DeadCodeInUnionField {} - | ^^^^^^^^^^^^^^^^^^^^ - | -note: the lint level is defined here - --> $DIR/dead-code-items.rs:56:9 - | -LL | #![deny(dead_code)] - | ^^^^^^^^^ - -error: aborting due to 1 previous error - Couldn't compile the test. failures: - $DIR/dead-code-items.rs - A::field (line 41) $DIR/dead-code-items.rs - C (line 24) $DIR/dead-code-items.rs - Enum (line 72) $DIR/dead-code-items.rs - Enum::Variant1 (line 79) $DIR/dead-code-items.rs - MyTrait (line 105) $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 112) - $DIR/dead-code-items.rs - U::field (line 57) -test result: FAILED. 6 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME +test result: FAILED. 6 passed; 5 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME all doctests ran in $TIME; merged doctests compilation took $TIME 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..1d7a974e24b0c --- /dev/null +++ b/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.stderr @@ -0,0 +1,34 @@ +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)] + | ^^^^^^^^^^^^^^^^^^^^^^ + = note: `#[deny(rustdoc::broken_intra_doc_links)]` implied by `#[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)] + | ^^^^^^^^^^^^^^^^^^^^^^ + = note: `#[deny(rustdoc::bare_urls)]` implied by `#[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 @@ - + , decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ); @@ -503,7 +503,7 @@ pub trait LintContext { /// typically generated by `#[derive(LintDiagnostic)]`). fn emit_span_lint>( &self, - lint: &'static Lint, + lint: &'static LintImpl, span: S, decorator: impl for<'a> LintDiagnostic<'a, ()>, ) { @@ -516,7 +516,7 @@ pub trait LintContext { /// `LintDiagnostic`, typically generated by `#[derive(LintDiagnostic)]`). fn emit_span_lint_lazy, L: for<'a> LintDiagnostic<'a, ()>>( &self, - lint: &'static Lint, + lint: &'static LintImpl, span: S, decorator: impl FnOnce() -> L, ) { @@ -532,7 +532,7 @@ pub trait LintContext { #[track_caller] fn span_lint>( &self, - lint: &'static Lint, + lint: &'static LintImpl, span: S, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { @@ -541,7 +541,7 @@ pub trait LintContext { /// Emit a lint from a lint struct (some type that implements `LintDiagnostic`, typically /// generated by `#[derive(LintDiagnostic)]`). - fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> LintDiagnostic<'a, ()>) { + fn emit_lint(&self, lint: &'static LintImpl, decorator: impl for<'a> LintDiagnostic<'a, ()>) { self.opt_span_lint(lint, None as Option, |lint| { decorator.decorate_lint(lint); }); @@ -550,12 +550,12 @@ pub trait LintContext { /// Emit a lint at the appropriate level, with no associated span. /// /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature - fn lint(&self, lint: &'static Lint, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)) { + fn lint(&self, lint: &'static LintImpl, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)) { self.opt_span_lint(lint, None as Option, decorate); } /// This returns the lint level for the given lint at the current location. - fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource; + fn get_lint_level(&self, lint: &'static LintImpl) -> LevelAndSource; /// This function can be used to manually fulfill an expectation. This can /// be used for lints which contain several spans, and should be suppressed, @@ -609,7 +609,7 @@ impl<'tcx> LintContext for LateContext<'tcx> { fn opt_span_lint>( &self, - lint: &'static Lint, + lint: &'static LintImpl, span: Option, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { @@ -621,7 +621,7 @@ impl<'tcx> LintContext for LateContext<'tcx> { } } - fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource { + fn get_lint_level(&self, lint: &'static LintImpl) -> LevelAndSource { self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs) } } @@ -634,14 +634,14 @@ impl LintContext for EarlyContext<'_> { fn opt_span_lint>( &self, - lint: &'static Lint, + lint: &'static LintImpl, span: Option, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { self.builder.opt_span_lint(lint, span.map(|s| s.into()), decorate) } - fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource { + fn get_lint_level(&self, lint: &'static LintImpl) -> LevelAndSource { self.builder.lint_level(lint) } } diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index a2ed2810cf157..f5927149fd0a5 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -424,7 +424,6 @@ fn check_ast_node_inner<'a, T: EarlyLintPass>( pass: T, ) { let mut cx = EarlyContextAndPass { context, tcx, pass }; - 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. diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index d7a650518785f..fec5cd534a03d 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -483,7 +483,7 @@ pub fn decorate_attribute_lint( &AttributeLintKind::DeprecatedLintName { name, suggestion, replace } => { lints::DeprecatedLintName { name, suggestion, replace }.decorate_lint(diag) } - &AttributeLintKind::RemovedLint { name, ref reason } => { + &AttributeLintKind::RemovedLint { name, reason } => { lints::RemovedLint { name, reason }.decorate_lint(diag) } &AttributeLintKind::UnknownLint { name, span, suggestion } => lints::UnknownLint { diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs index 33ba7f6edbda6..f465106d5adcb 100644 --- a/compiler/rustc_lint/src/errors.rs +++ b/compiler/rustc_lint/src/errors.rs @@ -18,7 +18,7 @@ pub(crate) struct OverruledAttribute<'a> { } pub(crate) enum OverruledAttributeSub { - DefaultSource { id: String }, + DefaultSource { id: Symbol }, NodeSource { span: Span, reason: Option }, CommandLineSource { id: Symbol }, } @@ -56,26 +56,26 @@ pub(crate) struct BuiltinEllipsisInclusiveRangePatterns { applicability = "machine-applicable" )] pub suggestion: Span, - pub replace: String, + pub replace: Symbol, } #[derive(Subdiagnostic)] #[note("requested on the command line with `{$level} {$lint_name}`")] -pub(crate) struct RequestedLevel<'a> { +pub(crate) struct RequestedLevel { pub level: Level, - pub lint_name: &'a str, + pub lint_name: Symbol, } #[derive(Diagnostic)] #[diag("`{$lint_group}` lint group is not supported with ´--force-warn´", code = E0602)] pub(crate) struct UnsupportedGroup { - pub lint_group: String, + pub lint_group: Symbol, } #[derive(Diagnostic)] #[diag("unknown lint tool: `{$tool_name}`", code = E0602)] -pub(crate) struct CheckNameUnknownTool<'a> { +pub(crate) struct CheckNameUnknownTool { pub tool_name: Symbol, #[subdiagnostic] - pub sub: RequestedLevel<'a>, + pub sub: RequestedLevel, } diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 03758d0a3af5f..5d458ef140624 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -23,7 +23,7 @@ use rustc_session::lint::builtin::{ UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES, }; use rustc_session::lint::{ - CheckLintNameResult, Level, Lint, LintExpectationId, LintId, TargetLint, + CheckLintNameResult, Level, Lint, LintExpectationId, LintId, LintImpl, TargetLint }; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use tracing::{debug, instrument}; @@ -90,7 +90,7 @@ impl LintLevelSets { fn get_lint_level( &self, - lint: &'static Lint, + lint: &'static LintImpl, idx: LintStackIndex, aux: Option<&FxIndexMap>, sess: &Session, @@ -231,7 +231,7 @@ pub struct TopDown { pub trait LintLevelsProvider { fn current_specs(&self) -> &FxIndexMap; fn insert(&mut self, id: LintId, lvl: LevelAndSource); - fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource; + fn get_lint_level(&self, lint: &'static LintImpl, sess: &Session) -> LevelAndSource; fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation); } @@ -244,7 +244,7 @@ impl LintLevelsProvider for TopDown { self.sets.list[self.cur].specs.insert(id, lvl); } - fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource { + fn get_lint_level(&self, lint: &'static LintImpl, sess: &Session) -> LevelAndSource { self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), sess) } @@ -267,7 +267,7 @@ impl LintLevelsProvider for LintLevelQueryMap<'_> { fn insert(&mut self, id: LintId, lvl: LevelAndSource) { self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl); } - fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource { + fn get_lint_level(&self, lint: &'static LintImpl, _: &Session) -> LevelAndSource { self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur) } fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) { @@ -506,27 +506,27 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { } fn add_command_line(&mut self) { - for &(ref lint_name, level) in &self.sess.opts.lint_opts { + for (lint_name, level) in &self.sess.opts.lint_opts { + let lint_name = *lint_name; + let level = *level; // Checks the validity of lint names derived from the command line. - let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name); + let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name.as_str()); if lint_name_only == crate::WARNINGS.name_lower() && matches!(level, Level::ForceWarn) { self.sess .dcx() .emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() }); } - match self.store.check_lint_name(lint_name_only, tool_name, self.registered_tools) { + match self.store.check_lint_name(tool_name, lint_name, self.registered_tools) { CheckLintNameResult::Renamed(replace) => { - let name = lint_name.as_str(); let suggestion = RenamedLintSuggestion::WithoutSpan { replace }; let requested_level = RequestedLevel { level, lint_name }; let lint = - RenamedLintFromCommandLine { name, replace, suggestion, requested_level }; + RenamedLintFromCommandLine { name: lint_name, replace, suggestion, requested_level }; self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); } - CheckLintNameResult::Removed(ref reason) => { - let name = lint_name.as_str(); + CheckLintNameResult::Removed(reason) => { let requested_level = RequestedLevel { level, lint_name }; - let lint = RemovedLintFromCommandLine { name, reason, requested_level }; + let lint = RemovedLintFromCommandLine { name: lint_name, reason, requested_level }; self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); } CheckLintNameResult::NoLint(suggestion) => { @@ -538,7 +538,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { let lint = UnknownLintFromCommandLine { name, suggestion, requested_level }; self.emit_lint(UNKNOWN_LINTS, lint); } - CheckLintNameResult::Tool(_, Some(ref replace)) => { + CheckLintNameResult::Tool(_, Some(replace)) => { let name = lint_name.clone(); let requested_level = RequestedLevel { level, lint_name }; let lint = DeprecatedLintNameFromCommandLine { name, replace, requested_level }; @@ -553,7 +553,6 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { _ => {} }; - let lint_flag_val = Symbol::intern(lint_name); let Some(ids) = self.store.find_lints(lint_name) else { // errors already handled above @@ -568,7 +567,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { } if self.check_gated_lint(id, DUMMY_SP, true) { - let src = LintLevelSource::CommandLine(lint_flag_val, level); + let src = LintLevelSource::CommandLine(lint_name, level); self.insert(id, LevelAndSource { level, lint_id: None, src }); } } @@ -613,7 +612,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { ); let sub = match old_src { LintLevelSource::Default => { - OverruledAttributeSub::DefaultSource { id: id.to_string() } + OverruledAttributeSub::DefaultSource { id: id.lint.name } } LintLevelSource::Node { span, reason, .. } => { OverruledAttributeSub::NodeSource { span, reason } @@ -687,7 +686,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { let src = LintLevelSource::Node { name: lint.original_lint_name(), span: lint.span(), reason }; - let id = match self.store.get_lint_by_name(lint.full_lint().as_str()) { + let id = match self.store.get_lint_by_name(lint.full_lint()) { Some(TargetLint::Id(id)) => id, None | Some(_) => bug!( "guaranteed to find id due to previous parsing, happened while parsing {:?}", @@ -760,7 +759,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { Some(expectation_id), ); - let is_unfulfilled_lint_expectations = lint.lint_name().as_str() + let is_unfulfilled_lint_expectations = lint.lint_name() == UNFULFILLED_LINT_EXPECTATIONS.name_lower(); self.provider.push_expectation( expectation_id, @@ -842,7 +841,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { } /// Find the lint level for a lint. - pub fn lint_level(&self, lint: &'static Lint) -> LevelAndSource { + pub fn lint_level(&self, lint: &'static LintImpl) -> LevelAndSource { self.provider.get_lint_level(lint, self.sess) } @@ -853,7 +852,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { #[track_caller] pub(crate) fn opt_span_lint( &self, - lint: &'static Lint, + lint: &'static LintImpl, span: Option, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { @@ -887,13 +886,13 @@ pub(crate) fn provide(providers: &mut Providers) { *providers = Providers { shallow_lint_levels_on, lints_that_dont_need_to_run, ..*providers }; } -pub(crate) fn parse_lint_and_tool_name(lint_name: &str) -> (Option, &str) { +pub(crate) fn parse_lint_and_tool_name(lint_name: &str) -> (Option, Symbol) { match lint_name.split_once("::") { Some((tool_name, lint_name)) => { let tool_name = Symbol::intern(tool_name); - (Some(tool_name), lint_name) + (Some(tool_name), Symbol::intern(lint_name)) } - None => (None, lint_name), + None => (None, Symbol::intern(lint_name)), } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 1e7e10f5a4790..33125099016a7 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -455,7 +455,7 @@ pub(crate) enum BuiltinEllipsisInclusiveRangePatternsLint { applicability = "machine-applicable" )] suggestion: Span, - replace: String, + replace: Symbol, }, #[diag("`...` range patterns are deprecated")] NonParenthesise { @@ -1236,11 +1236,11 @@ pub(crate) struct DeprecatedLintName { #[derive(LintDiagnostic)] #[diag("lint name `{$name}` is deprecated and may not have an effect in the future")] #[help("change it to {$replace}")] -pub(crate) struct DeprecatedLintNameFromCommandLine<'a> { - pub name: String, - pub replace: &'a str, +pub(crate) struct DeprecatedLintNameFromCommandLine { + pub name: Symbol, + pub replace: Symbol, #[subdiagnostic] - pub requested_level: RequestedLevel<'a>, + pub requested_level: RequestedLevel, } #[derive(LintDiagnostic)] @@ -1266,29 +1266,29 @@ pub(crate) enum RenamedLintSuggestion { #[derive(LintDiagnostic)] #[diag("lint `{$name}` has been renamed to `{$replace}`")] -pub(crate) struct RenamedLintFromCommandLine<'a> { - pub name: &'a str, +pub(crate) struct RenamedLintFromCommandLine { + pub name: Symbol, pub replace: Symbol, #[subdiagnostic] pub suggestion: RenamedLintSuggestion, #[subdiagnostic] - pub requested_level: RequestedLevel<'a>, + pub requested_level: RequestedLevel, } #[derive(LintDiagnostic)] #[diag("lint `{$name}` has been removed: {$reason}")] -pub(crate) struct RemovedLint<'a> { +pub(crate) struct RemovedLint { pub name: Symbol, - pub reason: &'a str, + pub reason: Symbol, } #[derive(LintDiagnostic)] #[diag("lint `{$name}` has been removed: {$reason}")] -pub(crate) struct RemovedLintFromCommandLine<'a> { - pub name: &'a str, - pub reason: &'a str, +pub(crate) struct RemovedLintFromCommandLine { + pub name: Symbol, + pub reason: Symbol, #[subdiagnostic] - pub requested_level: RequestedLevel<'a>, + pub requested_level: RequestedLevel, } #[derive(LintDiagnostic)] @@ -1326,12 +1326,12 @@ pub(crate) enum UnknownLintSuggestion { #[derive(LintDiagnostic)] #[diag("unknown lint: `{$name}`", code = E0602)] -pub(crate) struct UnknownLintFromCommandLine<'a> { - pub name: String, +pub(crate) struct UnknownLintFromCommandLine { + pub name: Symbol, #[subdiagnostic] pub suggestion: Option, #[subdiagnostic] - pub requested_level: RequestedLevel<'a>, + pub requested_level: RequestedLevel, } #[derive(LintDiagnostic)] diff --git a/compiler/rustc_lint/src/tests.rs b/compiler/rustc_lint/src/tests.rs index b3c91583914a1..38583efbd0ee9 100644 --- a/compiler/rustc_lint/src/tests.rs +++ b/compiler/rustc_lint/src/tests.rs @@ -1,6 +1,6 @@ #![allow(rustc::symbol_intern_string_literal)] -use rustc_span::{Symbol, create_default_session_globals_then}; +use rustc_span::{Symbol, create_default_session_globals_then, sym}; use crate::builtin::is_hexagon_register_span; use crate::levels::parse_lint_and_tool_name; @@ -8,14 +8,17 @@ use crate::levels::parse_lint_and_tool_name; #[test] fn parse_lint_no_tool() { create_default_session_globals_then(|| { - assert_eq!(parse_lint_and_tool_name("foo"), (None, "foo")) + assert_eq!(parse_lint_and_tool_name("foo"), (None, Symbol::intern("foo"))) }); } #[test] fn parse_lint_with_tool() { create_default_session_globals_then(|| { - assert_eq!(parse_lint_and_tool_name("clippy::foo"), (Some(Symbol::intern("clippy")), "foo")) + assert_eq!( + parse_lint_and_tool_name("clippy::foo"), + (Some(sym::clippy), Symbol::intern("foo")) + ) }); } @@ -24,7 +27,7 @@ fn parse_lint_multiple_path() { create_default_session_globals_then(|| { assert_eq!( parse_lint_and_tool_name("clippy::foo::bar"), - (Some(Symbol::intern("clippy")), "foo::bar") + (Some(sym::clippy), Symbol::intern("foo::bar")) ) }); } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index c20c9dca346fd..05193fb025d41 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -7,6 +7,7 @@ //! When removing a lint, make sure to also add a call to `register_removed` in //! compiler/rustc_lint/src/lib.rs. + use crate::{declare_lint, declare_lint_pass, fcw}; declare_lint_pass! { @@ -3072,6 +3073,7 @@ declare_lint! { "detects builtin cfgs set via the `--cfg`" } + declare_lint! { /// The `repr_transparent_non_zst_fields` lint /// detects types marked `#[repr(transparent)]` that (transitively) diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index e243501d628c8..94ac0aa0d216c 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::fmt::Display; +use std::sync::LazyLock; use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::stable_hasher::{ @@ -274,7 +275,7 @@ impl IntoDiagArg for Level { /// Specification of a single lint. #[derive(Copy, Clone, Debug)] -pub struct Lint { +pub struct LintImpl { /// A string identifier for the lint. /// /// This identifies the lint in attributes and in command-line arguments. @@ -288,7 +289,9 @@ pub struct Lint { /// /// See /// for naming guidelines. - pub name: &'static str, + pub name: Symbol, + + pub name_lower: Symbol, /// Default level for the lint. /// @@ -322,6 +325,7 @@ pub struct Lint { /// (e.g. the unknown_attributes lint) pub eval_always: bool, } +pub type Lint = LazyLock; /// Extra information for a future incompatibility lint. #[derive(Copy, Clone, Debug)] @@ -523,10 +527,11 @@ impl Display for EditionFcw { } } -impl Lint { +impl LintImpl { pub const fn default_fields_for_macro() -> Self { - Lint { - name: "", + LintImpl { + name: rustc_span::sym::empty, + name_lower: rustc_span::sym::empty, default_level: Level::Forbid, desc: "", edition_lint_opts: None, @@ -540,8 +545,8 @@ impl Lint { } /// Gets the lint's name, with ASCII letters converted to lowercase. - pub fn name_lower(&self) -> String { - self.name.to_ascii_lowercase() + pub fn name_lower(&self) -> Symbol { + self.name_lower } pub fn default_level(&self, edition: Edition) -> Level { @@ -559,11 +564,11 @@ pub enum TargetLint { Id(LintId), /// Temporary renaming, used for easing migration pain; see #16545 - Renamed(String, LintId), + Renamed(Symbol, LintId), /// Lint with this name existed previously, but has been removed/deprecated. /// The string argument is the reason for removal. - Removed(String), + Removed(Symbol), /// A lint name that should give no warnings and have no effect. /// @@ -576,7 +581,7 @@ pub enum TargetLint { #[derive(Clone, Copy, Debug)] pub struct LintId { // Identity is based on pointer equality of this field. - pub lint: &'static Lint, + pub lint: &'static LintImpl, } impl PartialEq for LintId { @@ -589,24 +594,26 @@ impl Eq for LintId {} impl std::hash::Hash for LintId { fn hash(&self, state: &mut H) { - let ptr = self.lint as *const Lint; + let ptr = self.lint as *const LintImpl; ptr.hash(state); } } + + impl LintId { /// Gets the `LintId` for a `Lint`. - pub fn of(lint: &'static Lint) -> LintId { - LintId { lint } + pub fn of(lint: &'static LintImpl) -> LintId { + LintId { lint: lint } } - pub fn lint_name_raw(&self) -> &'static str { + pub fn lint_name_raw(&self) -> Symbol { self.lint.name } /// Gets the name of the lint. pub fn to_string(&self) -> String { - self.lint.name_lower() + self.lint.name_lower().to_string() } } @@ -618,10 +625,10 @@ impl HashStable for LintId { } impl ToStableHashKey for LintId { - type KeyType = &'static str; + type KeyType = Symbol; #[inline] - fn to_stable_hash_key(&self, _: &HCX) -> &'static str { + fn to_stable_hash_key(&self, _: &HCX) -> Symbol { self.lint_name_raw() } } @@ -856,7 +863,7 @@ pub enum AttributeLintKind { }, RemovedLint { name: Symbol, - reason: String, + reason: Symbol, }, UnknownLint { name: Symbol, @@ -883,12 +890,12 @@ pub enum CheckLintNameResult<'a> { /// 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), + Removed(Symbol), /// 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), + 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 @@ -972,8 +979,10 @@ macro_rules! declare_lint { $(@edition $lint_edition:ident => $edition_level:ident;)? $($v:ident),*) => ( $(#[$attr])* - $vis static $NAME: &$crate::Lint = &$crate::Lint { - name: stringify!($NAME), + $vis static $NAME: &'static ::std::sync::LazyLock<$crate::LintImpl> = { + static HIDDEN: ::std::sync::LazyLock<$crate::LintImpl> = ::std::sync::LazyLock::new(|| $crate::LintImpl { + name: rustc_span::Symbol::intern(&stringify!($NAME)), + name_lower: rustc_span::Symbol::intern(&stringify!($NAME).to_ascii_lowercase()), default_level: $crate::$Level, desc: $desc, is_externally_loaded: false, @@ -986,9 +995,12 @@ macro_rules! declare_lint { }),)? $(edition_lint_opts: Some(($crate::Edition::$lint_edition, $crate::$edition_level)),)? $(eval_always: $eval_always,)? - ..$crate::Lint::default_fields_for_macro() + ..$crate::LintImpl::default_fields_for_macro() + }); + &HIDDEN }; - ); + + ) } #[macro_export] @@ -1015,8 +1027,12 @@ macro_rules! declare_tool_lint { $(, @feature_gate = $gate:ident;)? ) => ( $(#[$attr])* - $vis static $NAME: &$crate::Lint = &$crate::Lint { - name: &concat!(stringify!($tool), "::", stringify!($NAME)), + $vis static $NAME: &$crate::Lint = {static HIDDEN: $crate::Lint = ::std::sync::LazyLock::new(||{ + let name = concat!(stringify!($tool), "::", stringify!($NAME)); + + $crate::LintImpl{ + name: rustc_span::Symbol::intern(&name), + name_lower: rustc_span::Symbol::intern(&name.to_ascii_lowercase()), default_level: $crate::$Level, desc: $desc, edition_lint_opts: None, @@ -1026,7 +1042,9 @@ macro_rules! declare_tool_lint { $(feature_gate: Some(rustc_span::sym::$gate),)? crate_level_only: false, $(eval_always: $eval_always,)? - ..$crate::Lint::default_fields_for_macro() + ..$crate::LintImpl::default_fields_for_macro() + }}); + &HIDDEN }; ); } @@ -1044,11 +1062,11 @@ macro_rules! impl_lint_pass { ($ty:ty => [$($lint:expr),* $(,)?]) => { impl $crate::LintPass for $ty { fn name(&self) -> &'static str { stringify!($ty) } - fn get_lints(&self) -> $crate::LintVec { vec![$($lint),*] } + fn get_lints(&self) -> $crate::LintVec { vec![$(&$lint),*] } } impl $ty { #[allow(unused)] - pub fn lint_vec() -> $crate::LintVec { vec![$($lint),*] } + pub fn lint_vec() -> $crate::LintVec { vec![$(&$lint),*] } } }; } diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index 08ccc4a0fca41..5e7b2c84387bf 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -4,7 +4,7 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::sorted_map::SortedMap; use rustc_errors::{Diag, Diagnostic, MultiSpan}; use rustc_hir::{HirId, ItemLocalId}; -use rustc_lint_defs::EditionFcw; +use rustc_lint_defs::{EditionFcw, LintImpl}; use rustc_macros::{Decodable, Encodable, HashStable}; use rustc_session::Session; use rustc_session::lint::builtin::{self, FORBIDDEN_LINT_GROUPS}; @@ -175,7 +175,7 @@ impl ShallowLintLevelMap { impl TyCtxt<'_> { /// Fetch and return the user-visible lint level for the given lint at the given HirId. - pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> LevelAndSource { + pub fn lint_level_at_node(self, lint: &'static LintImpl, id: HirId) -> LevelAndSource { self.shallow_lint_levels_on(id.owner).lint_level_id_at_node(self, LintId::of(lint), id) } } @@ -213,7 +213,7 @@ impl LintExpectation { fn explain_lint_level_source( sess: &Session, - lint: &'static Lint, + lint: &'static LintImpl, level: Level, src: LintLevelSource, err: &mut Diag<'_, ()>, @@ -254,8 +254,8 @@ fn explain_lint_level_source( } LintLevelSource::CommandLine(lint_flag_val, orig_level) => { let flag = orig_level.to_cmd_flag(); - let hyphen_case_lint_name = name.replace('_', "-"); - if lint_flag_val.as_str() == name { + let hyphen_case_lint_name = name.as_str().replace('_', "-"); + if lint_flag_val == name { err.note_once(format!( "requested on the command line with `{flag} {hyphen_case_lint_name}`" )); @@ -265,7 +265,7 @@ fn explain_lint_level_source( "`{flag} {hyphen_case_lint_name}` implied by `{flag} {hyphen_case_flag_val}`" )); if matches!(orig_level, Level::Warn | Level::Deny) { - let help = if name == "dead_code" { + let help = if name.as_str() == "dead_code" { format!( "to override `{flag} {hyphen_case_flag_val}` add `#[expect({name})]` or `#[allow({name})]`" ) @@ -283,7 +283,7 @@ fn explain_lint_level_source( err.note(rationale.to_string()); } err.span_note_once(span, "the lint level is defined here"); - if lint_attr_name.as_str() != name { + if lint_attr_name != name { let level_str = level.as_str(); err.note_once(format!( "`#[{level_str}({name})]` implied by `#[{level_str}({lint_attr_name})]`" @@ -309,7 +309,7 @@ fn explain_lint_level_source( #[track_caller] pub fn lint_level( sess: &Session, - lint: &'static Lint, + lint: &'static LintImpl, level: LevelAndSource, span: Option, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), @@ -319,7 +319,7 @@ pub fn lint_level( #[track_caller] fn lint_level_impl( sess: &Session, - lint: &'static Lint, + lint: &'static LintImpl, level: LevelAndSource, span: Option, decorate: Box FnOnce(&'b mut Diag<'a, ()>)>, diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs index ff6baabbb29a0..446d927a88c0d 100644 --- a/compiler/rustc_middle/src/middle/stability.rs +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -9,6 +9,7 @@ use rustc_feature::GateIssue; use rustc_hir::attrs::{DeprecatedSince, Deprecation}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{self as hir, ConstStability, DefaultBodyStability, HirId, Stability}; +use rustc_lint_defs::LintImpl; use rustc_macros::{Decodable, Encodable, HashStable, Subdiagnostic}; use rustc_session::Session; use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE}; @@ -70,7 +71,7 @@ pub fn report_unstable( suggestion: Option<(Span, String, String, Applicability)>, is_soft: bool, span: Span, - soft_handler: impl FnOnce(&'static Lint, Span, String), + soft_handler: impl FnOnce(&'static LintImpl, Span, String), kind: UnstableKind, ) { let qual = match kind { diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index d21f07a23e328..59eb27398efed 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -38,6 +38,7 @@ use rustc_hir::lang_items::LangItem; use rustc_hir::limit::Limit; use rustc_hir::{self as hir, HirId, Node, TraitCandidate, find_attr}; use rustc_index::IndexVec; +use rustc_lint_defs::LintImpl; use rustc_serialize::opaque::{FileEncodeResult, FileEncoder}; use rustc_session::Session; use rustc_session::config::CrateType; @@ -2515,7 +2516,7 @@ impl<'tcx> TyCtxt<'tcx> { #[track_caller] pub fn node_span_lint( self, - lint: &'static Lint, + lint: &'static LintImpl, hir_id: HirId, span: impl Into, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), @@ -2559,7 +2560,7 @@ impl<'tcx> TyCtxt<'tcx> { #[track_caller] pub fn emit_node_lint( self, - lint: &'static Lint, + lint: &'static LintImpl, id: HirId, decorator: impl for<'a> LintDiagnostic<'a, ()>, ) { @@ -2574,7 +2575,7 @@ impl<'tcx> TyCtxt<'tcx> { #[track_caller] pub fn node_lint( self, - lint: &'static Lint, + lint: &'static LintImpl, id: HirId, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index b278a6179fe7f..d218eb00effed 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1410,6 +1410,7 @@ impl Default for Options { optimize: OptLevel::No, debuginfo: DebugInfo::None, lint_opts: Vec::new(), + cmd_line_lints: Vec::new(), lint_cap: None, describe_lints: false, output_types: OutputTypes(BTreeMap::new()), @@ -2473,7 +2474,7 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M let mut target_modifiers = BTreeMap::::new(); let mut unstable_opts = UnstableOptions::build(early_dcx, matches, &mut target_modifiers); - let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); + let (cmd_line_lints, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); if !unstable_opts.unstable_options && json_timings { early_dcx.early_fatal("--json=timings is unstable and requires using `-Zunstable-options`"); @@ -2763,7 +2764,8 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M crate_types, optimize: opt_level, debuginfo, - lint_opts, + cmd_line_lints, + lint_opts: Vec::new(), lint_cap, describe_lints, output_types, @@ -3064,7 +3066,7 @@ pub(crate) mod dep_tracking { use rustc_hashes::Hash64; use rustc_hir::attrs::CollapseMacroDebuginfo; use rustc_span::edition::Edition; - use rustc_span::{RealFileName, RemapPathScopeComponents}; + use rustc_span::{RealFileName, RemapPathScopeComponents, Symbol}; use rustc_target::spec::{ CodeModel, FramePointer, MergeFunctions, OnBrokenPipe, PanicStrategy, RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, SymbolVisibility, TargetTuple, @@ -3181,6 +3183,7 @@ pub(crate) mod dep_tracking { InliningThreshold, FunctionReturn, Align, + Symbol, ); impl DepTrackingHash for (T1, T2) diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 74b3aa11d0d80..5ef31fbc31ce2 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -13,7 +13,7 @@ use rustc_hashes::Hash64; use rustc_hir::attrs::CollapseMacroDebuginfo; use rustc_macros::{BlobDecodable, Encodable}; use rustc_span::edition::Edition; -use rustc_span::{RealFileName, RemapPathScopeComponents, SourceFileHashAlgorithm}; +use rustc_span::{RealFileName, RemapPathScopeComponents, SourceFileHashAlgorithm, Symbol}; use rustc_target::spec::{ CodeModel, FramePointer, LinkerFlavorCli, MergeFunctions, OnBrokenPipe, PanicStrategy, RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, SymbolVisibility, @@ -398,7 +398,8 @@ top_level_options!( /// can influence whether overflow checks are done or not. debug_assertions: bool [TRACKED], debuginfo: DebugInfo [TRACKED], - lint_opts: Vec<(String, lint::Level)> [TRACKED_NO_CRATE_HASH], + cmd_line_lints: Vec<(String, lint::Level)> [TRACKED_NO_CRATE_HASH], + lint_opts: Vec<(Symbol, lint::Level)> [TRACKED_NO_CRATE_HASH], lint_cap: Option [TRACKED_NO_CRATE_HASH], describe_lints: bool [UNTRACKED], output_types: OutputTypes [TRACKED], diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 9c12c480cacdd..5696a6043262b 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -2,7 +2,7 @@ //! It also serves as an input to the parser itself. use std::str; -use std::sync::Arc; +use std::sync::{Arc}; use rustc_ast::attr::AttrIdGenerator; use rustc_ast::node_id::NodeId; @@ -15,6 +15,7 @@ use rustc_errors::{ DiagMessage, EmissionGuarantee, MultiSpan, StashKey, }; use rustc_feature::{GateIssue, UnstableFeatures, find_feature_issue}; +use rustc_lint_defs::LintImpl; use rustc_span::edition::Edition; use rustc_span::hygiene::ExpnId; use rustc_span::source_map::{FilePathMapping, SourceMap}; @@ -27,7 +28,7 @@ use crate::errors::{ FeatureDiagnosticSuggestion, FeatureGateError, SuggestUpgradeCompiler, }; use crate::lint::builtin::UNSTABLE_SYNTAX_PRE_EXPANSION; -use crate::lint::{Lint, LintId}; +use crate::lint::{ LintId}; /// Collected spans during parsing for places where a certain feature was /// used and should be feature gated accordingly in `check_crate`. @@ -144,7 +145,7 @@ pub fn feature_warn_issue( add_feature_diagnostics_for_issue(&mut err, sess, feature, issue, false, None); // Decorate this as a future-incompatibility lint as in rustc_middle::lint::lint_level - let lint = UNSTABLE_SYNTAX_PRE_EXPANSION; + let lint = &UNSTABLE_SYNTAX_PRE_EXPANSION; let future_incompatible = lint.future_incompatible.as_ref().unwrap(); err.is_lint(lint.name_lower(), /* has_future_breakage */ false); err.warn(lint.desc); @@ -329,7 +330,7 @@ impl ParseSess { pub fn buffer_lint( &self, - lint: &'static Lint, + lint:&'static LintImpl, span: impl Into, node_id: NodeId, diagnostic: impl Into, @@ -339,7 +340,7 @@ impl ParseSess { pub(crate) fn opt_span_buffer_lint( &self, - lint: &'static Lint, + lint: &'static LintImpl, span: Option, node_id: NodeId, diagnostic: DecorateDiagCompat, @@ -348,7 +349,7 @@ impl ParseSess { buffered_lints.push(BufferedEarlyLint { span, node_id, - lint_id: LintId::of(lint), + lint_id: LintId::of(&*lint), diagnostic, }); }); diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 8f3e9bbd9da17..af50ccef66991 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -25,7 +25,7 @@ use rustc_macros::HashStable_Generic; pub use rustc_span::def_id::StableCrateId; use rustc_span::edition::Edition; use rustc_span::source_map::{FilePathMapping, SourceMap}; -use rustc_span::{RealFileName, Span, Symbol}; +use rustc_span::{RealFileName, Span, Symbol, sym}; use rustc_target::asm::InlineAsmArch; use rustc_target::spec::{ Arch, CodeModel, DebuginfoKind, Os, PanicStrategy, RelocModel, RelroLevel, SanitizerSet, @@ -84,12 +84,12 @@ pub trait DynLintStore: Any + DynSync + DynSend { fn check_lint_name( &self, - lint_name: &str, tool_name: Option, + complete_name: Symbol, registered_tools: &RegisteredTools, ) -> CheckLintNameResult<'_>; - fn find_lints(&self, lint_name: &str) -> Option<&[LintId]>; + fn find_lints(&self, lint_name: Symbol) -> Option<&[LintId]>; } /// Represents the data associated with a compilation @@ -177,6 +177,7 @@ pub struct Session { pub mir_opt_bisect_eval_count: AtomicUsize, } + #[derive(Clone, Copy)] pub enum CodegenUnits { /// Specified by the user. In this case we try fairly hard to produce the @@ -199,7 +200,7 @@ impl CodegenUnits { } pub struct LintGroup { - pub name: &'static str, + pub name: Symbol, pub lints: Vec, pub is_externally_loaded: bool, } @@ -981,7 +982,7 @@ fn default_emitter(sopts: &config::Options, source_map: Arc) -> Box, target: Target, @@ -989,13 +990,16 @@ pub fn build_session( ice_file: Option, using_internal_features: &'static AtomicBool, ) -> Session { + sopts + .lint_opts + .extend(sopts.cmd_line_lints.iter().map(|(lint, level)| (Symbol::intern(lint), *level))); // FIXME: This is not general enough to make the warning lint completely override // normal diagnostic warnings, since the warning lint can also be denied and changed // later via the source code. let warnings_allow = sopts .lint_opts .iter() - .rfind(|&(key, _)| *key == "warnings") + .rfind(|&(key, _)| *key == sym::warnings) .is_some_and(|&(_, level)| level == lint::Allow); let cap_lints_allow = sopts.lint_cap.is_some_and(|cap| cap == lint::Allow); let can_emit_warnings = !(warnings_allow || cap_lints_allow); diff --git a/compiler/rustc_span/src/edition.rs b/compiler/rustc_span/src/edition.rs index a27c5f62c1e5d..c33ea8be28d94 100644 --- a/compiler/rustc_span/src/edition.rs +++ b/compiler/rustc_span/src/edition.rs @@ -3,6 +3,8 @@ use std::str::FromStr; use rustc_macros::{BlobDecodable, Encodable, HashStable_Generic}; +use crate::{Symbol, sym}; + /// The edition of the compiler. (See [RFC 2052](https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md).) #[derive(Clone, Copy, Hash, PartialEq, PartialOrd, Debug, Encodable, BlobDecodable, Eq)] #[derive(HashStable_Generic)] @@ -65,13 +67,13 @@ impl fmt::Display for Edition { } impl Edition { - pub fn lint_name(self) -> &'static str { + pub fn lint_name(self) -> Symbol { match self { - Edition::Edition2015 => "rust_2015_compatibility", - Edition::Edition2018 => "rust_2018_compatibility", - Edition::Edition2021 => "rust_2021_compatibility", - Edition::Edition2024 => "rust_2024_compatibility", - Edition::EditionFuture => "edition_future_compatibility", + Edition::Edition2015 => sym::rust_2015_compatibility, + Edition::Edition2018 => sym::rust_2018_compatibility, + Edition::Edition2021 => sym::rust_2021_compatibility, + Edition::Edition2024 => sym::rust_2024_compatibility, + Edition::EditionFuture => sym::edition_future_compatibility, } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3d40b7317459b..b19ffbf697cbc 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -847,6 +847,7 @@ symbols! { dynamic_no_pic: "dynamic-no-pic", e, edition_panic, + edition_future_compatibility, effective_target_features, effects, eh_catch_typeinfo, @@ -1002,6 +1003,7 @@ symbols! { full, fundamental, fused_iterator, + future_incompatible, future_output, future_trait, fxsr, @@ -1657,10 +1659,14 @@ symbols! { runtime, rust, rust_2015, + rust_2015_compatibility, rust_2018, + rust_2018_compatibility, rust_2018_preview, rust_2021, + rust_2021_compatibility, rust_2024, + rust_2024_compatibility, rust_analyzer, rust_begin_unwind, rust_cold_cc, @@ -1719,6 +1725,7 @@ symbols! { rustc_force_inline, rustc_has_incoherent_inherent_impls, rustc_hidden_type_of_opaques, + warnings, rustc_if_this_changed, rustc_inherit_overflow_checks, rustc_insignificant_dtor, @@ -2191,6 +2198,7 @@ symbols! { variant_count, variants, vec, + gr_than_or_eq: "..=", vector, verbatim, version, diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 3d4b4e969157c..ebc363df53734 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -15,7 +15,7 @@ use rustc_session::config::{ use rustc_session::lint::Level; use rustc_session::search_paths::SearchPath; use rustc_session::{EarlyDiagCtxt, getopts}; -use rustc_span::FileName; +use rustc_span::{FileName, Symbol}; use rustc_span::edition::Edition; use rustc_target::spec::TargetTuple; @@ -114,7 +114,7 @@ pub(crate) struct Options { /// The path to the sysroot. Used during the compilation process. pub(crate) sysroot: Sysroot, /// Lint information passed over the command-line. - pub(crate) lint_opts: Vec<(String, Level)>, + pub(crate) lint_opts: Vec<(Symbol, Level)>, /// Whether to ask rustc to describe the lints it knows. pub(crate) describe_lints: bool, /// What level to cap lints at. @@ -550,7 +550,7 @@ impl Options { } let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); - + let lint_opts = lint_opts.into_iter().map(|(lint,level)|(Symbol::intern(&lint), level)).collect(); let input = if describe_lints { InputMode::HasFile(make_input(early_dcx, "")) } else { diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 375f8338319b6..336ed62eedd95 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -226,16 +226,16 @@ pub(crate) fn create_config( // Specifically unblock lints relevant to documentation or the lint machinery itself. let mut lints_to_show = vec![ // it's unclear whether these should be part of rustdoc directly (#77364) - rustc_lint::builtin::MISSING_DOCS.name.to_string(), - rustc_lint::builtin::INVALID_DOC_ATTRIBUTES.name.to_string(), + rustc_lint::builtin::MISSING_DOCS.name, + rustc_lint::builtin::INVALID_DOC_ATTRIBUTES.name, // these are definitely not part of rustdoc, but we want to warn on them anyway. - rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name.to_string(), - rustc_lint::builtin::UNKNOWN_LINTS.name.to_string(), - rustc_lint::builtin::UNEXPECTED_CFGS.name.to_string(), + rustc_lint::builtin::RENAMED_AND_REMOVED_LINTS.name, + rustc_lint::builtin::UNKNOWN_LINTS.name, + rustc_lint::builtin::UNEXPECTED_CFGS.name, // this lint is needed to support `#[expect]` attributes - rustc_lint::builtin::UNFULFILLED_LINT_EXPECTATIONS.name.to_string(), + rustc_lint::builtin::UNFULFILLED_LINT_EXPECTATIONS.name, ]; - lints_to_show.extend(crate::lint::RUSTDOC_LINTS.iter().map(|lint| lint.name.to_string())); + lints_to_show.extend(crate::lint::RUSTDOC_LINTS.iter().map(|lint| lint.name)); let (lint_opts, lint_caps) = crate::lint::init_lints(lints_to_show, lint_opts, |lint| { Some((lint.name_lower(), lint::Allow)) diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 5d3715c70e087..937e7236e10a8 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -139,9 +139,9 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions // See core::create_config for what's going on here. let allowed_lints = vec![ - invalid_codeblock_attributes_name.to_owned(), - lint::builtin::UNKNOWN_LINTS.name.to_owned(), - lint::builtin::RENAMED_AND_REMOVED_LINTS.name.to_owned(), + invalid_codeblock_attributes_name, + lint::builtin::UNKNOWN_LINTS.name, + lint::builtin::RENAMED_AND_REMOVED_LINTS.name, ]; let (lint_opts, lint_caps) = init_lints(allowed_lints, options.lint_opts.clone(), |lint| { diff --git a/src/librustdoc/lint.rs b/src/librustdoc/lint.rs index b09ea05688595..d11f3e5af09a8 100644 --- a/src/librustdoc/lint.rs +++ b/src/librustdoc/lint.rs @@ -4,6 +4,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_lint::LintStore; use rustc_lint_defs::{Lint, LintId, declare_tool_lint}; use rustc_session::{Session, lint}; +use rustc_span::Symbol; /// This function is used to setup the lint initialization. By default, in rustdoc, everything /// is "allowed". Depending if we run in test mode or not, we want some of them to be at their @@ -18,16 +19,16 @@ use rustc_session::{Session, lint}; /// * Vector of tuples of lints' name and their associated "max" level /// * HashMap of lint id with their associated "max" level pub(crate) fn init_lints( - mut allowed_lints: Vec, - lint_opts: Vec<(String, lint::Level)>, + mut allowed_lints: Vec, + lint_opts: Vec<(Symbol, lint::Level)>, filter_call: F, -) -> (Vec<(String, lint::Level)>, FxHashMap) +) -> (Vec<(Symbol, lint::Level)>, FxHashMap) where - F: Fn(&lint::Lint) -> Option<(String, lint::Level)>, + F: Fn(&lint::LintImpl) -> Option<(Symbol, lint::Level)>, { let warnings_lint_name = lint::builtin::WARNINGS.name; - allowed_lints.push(warnings_lint_name.to_owned()); + allowed_lints.push(warnings_lint_name); allowed_lints.extend(lint_opts.iter().map(|(lint, _)| lint).cloned()); let lints = || { @@ -40,7 +41,7 @@ where .filter_map(|lint| { // Permit feature-gated lints to avoid feature errors when trying to // allow all lints. - if lint.feature_gate.is_some() || allowed_lints.iter().any(|l| lint.name == l) { + if lint.feature_gate.is_some() || allowed_lints.iter().any(|l| lint.name == *l) { None } else { filter_call(lint) @@ -53,7 +54,7 @@ where .filter_map(|lint| { // We don't want to allow *all* lints so let's ignore // those ones. - if allowed_lints.iter().any(|l| lint.name == l) { + if allowed_lints.iter().any(|l| lint.name == *l) { None } else { Some((lint::LintId::of(lint), lint::Allow)) @@ -225,7 +226,7 @@ pub(crate) fn register_lints(_sess: &Session, lint_store: &mut LintStore) { .collect(), ); for lint in &*RUSTDOC_LINTS { - let name = lint.name_lower(); + let name = lint.name_lower().to_string(); lint_store.register_renamed(&name.replace("rustdoc::", ""), &name); } lint_store diff --git a/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs b/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs index 4d64eec25d273..34cfcb1130d3f 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs @@ -24,7 +24,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, name: Symbol, items: &[MetaItemInner] pub(super) fn check_command_line(cx: &EarlyContext<'_>) { for (name, level) in &cx.sess().opts.lint_opts { - if name == "clippy::restriction" && *level > Level::Allow { + if name.as_str() == "clippy::restriction" && *level > Level::Allow { span_lint_and_then( cx, BLANKET_CLIPPY_RESTRICTION_LINTS, diff --git a/src/tools/clippy/clippy_lints/src/cargo/lint_groups_priority.rs b/src/tools/clippy/clippy_lints/src/cargo/lint_groups_priority.rs index fbf5f72c53615..f43d2b1a0adce 100644 --- a/src/tools/clippy/clippy_lints/src/cargo/lint_groups_priority.rs +++ b/src/tools/clippy/clippy_lints/src/cargo/lint_groups_priority.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_lint::{LateContext, unerased_lint_store}; -use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext}; +use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext}; use std::ops::Range; use std::path::Path; use toml::Spanned; @@ -52,7 +52,7 @@ impl<'a> LintConfig<'a> { } } -fn check_table(cx: &LateContext<'_>, table: &DeTable<'_>, known_groups: &FxHashSet<&str>, file: &SourceFile) { +fn check_table(cx: &LateContext<'_>, table: &DeTable<'_>, known_groups: &FxHashSet, file: &SourceFile) { let mut lints = Vec::new(); let mut groups = Vec::new(); for (name, config) in table { @@ -136,12 +136,13 @@ pub fn check(cx: &LateContext<'_>) { let mut rustc_groups = FxHashSet::default(); let mut clippy_groups = FxHashSet::default(); for group in unerased_lint_store(cx.tcx.sess).get_all_group_names() { + let group = group.as_str(); match group.split_once("::") { None => { - rustc_groups.insert(group); + rustc_groups.insert(group.to_string()); }, Some(("clippy", group)) => { - clippy_groups.insert(group); + clippy_groups.insert(group.to_string()); }, _ => {}, } diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 4dca8dfe94d09..4be96ed960c54 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -415,7 +415,7 @@ use utils::attr_collector::{AttrCollector, AttrStorage}; pub fn explain(name: &str) -> i32 { let target = format!("clippy::{}", name.to_ascii_uppercase()); - if let Some(info) = declared_lints::LINTS.iter().find(|info| info.lint.name == target) { + if let Some(info) = declared_lints::LINTS.iter().find(|info| info.lint.name.as_str() == target) { println!("{}", sanitize_explanation(info.explanation)); // Check if the lint has configuration let mut mdconf = get_configuration_metadata(); diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs index 81b06ea0c539b..f24a12c3674ef 100644 --- a/src/tools/clippy/clippy_utils/src/diagnostics.rs +++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs @@ -18,7 +18,7 @@ use std::env; fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) { if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() - && let Some(lint) = lint.name_lower().strip_prefix("clippy::") + && let Some(lint) = lint.name_lower().as_str().strip_prefix("clippy::") { diag.help(format!( "for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{lint}", diff --git a/src/tools/clippy/declare_clippy_lint/src/lib.rs b/src/tools/clippy/declare_clippy_lint/src/lib.rs index f7d9c64bfbd0d..f7c007963d616 100644 --- a/src/tools/clippy/declare_clippy_lint/src/lib.rs +++ b/src/tools/clippy/declare_clippy_lint/src/lib.rs @@ -114,7 +114,7 @@ impl LintInfo { #[must_use] #[expect(clippy::missing_panics_doc)] pub fn name_lower(&self) -> String { - self.lint.name.strip_prefix("clippy::").unwrap().to_ascii_lowercase() + self.lint.name.as_str().strip_prefix("clippy::").unwrap().to_ascii_lowercase() } } diff --git a/src/tools/clippy/tests/config-consistency.rs b/src/tools/clippy/tests/config-consistency.rs index 9e7ca26c7d400..db9cb6dde9949 100644 --- a/src/tools/clippy/tests/config-consistency.rs +++ b/src/tools/clippy/tests/config-consistency.rs @@ -16,7 +16,7 @@ fn config_consistency() { let lint_names: HashSet = clippy_lints::declared_lints::LINTS .iter() - .map(|lint_info| lint_info.lint.name.strip_prefix("clippy::").unwrap().to_lowercase()) + .map(|lint_info| lint_info.lint.name.as_str().strip_prefix("clippy::").unwrap().to_lowercase()) .collect(); for conf in clippy_config::get_configuration_metadata() { for lint in conf.lints { From ce145ee94e2e24934e2afd0f367dc4981cc5cc47 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Wed, 25 Feb 2026 17:20:45 +0100 Subject: [PATCH 12/13] for integrate parsed lint attrs into lint level system --- compiler/rustc_attr_parsing/src/interface.rs | 2 +- compiler/rustc_lint/src/levels.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index b1cc6eca2da55..667a9a03894aa 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -117,7 +117,7 @@ impl<'sess> AttributeParser<'sess, Early> { ) -> Vec { let mut p = Self { features, tools, parse_only, sess, stage: Early { emit_errors } }; p.parse_attribute_list( - attrs.into_iter(), + attrs, target_span, target, OmitDoc::Skip, diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 5d458ef140624..e69cc040207ef 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -450,7 +450,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { let prev = self.provider.cur; self.provider.cur = self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev }); - let attrs = AttributeParser::parse_limited_all_filtered( + let attrs = if !attrs.is_empty() {AttributeParser::parse_limited_all_filtered( self.sess, attrs, ALLOW_LISTED_ATTRS, @@ -460,7 +460,9 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { Some(self.features), rustc_attr_parsing::ShouldEmit::Nothing, self.registered_tools.clone(), - ); + ) } else { + vec![] + }; let is_crate_node = node_id == ast::CRATE_NODE_ID; From ec8992c243c22b718404b37438280ef50ab8c887 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 26 Feb 2026 16:39:35 +0100 Subject: [PATCH 13/13] tidy --- compiler/rustc_driver_impl/src/lib.rs | 4 +-- compiler/rustc_errors/src/decorate_diag.rs | 3 +- compiler/rustc_interface/src/passes.rs | 2 +- compiler/rustc_lint/src/context.rs | 19 +++++++--- compiler/rustc_lint/src/levels.rs | 42 ++++++++++++---------- compiler/rustc_lint_defs/src/builtin.rs | 2 -- compiler/rustc_lint_defs/src/lib.rs | 8 ++--- compiler/rustc_session/src/parse.rs | 6 ++-- compiler/rustc_session/src/session.rs | 1 - compiler/rustc_span/src/symbol.rs | 6 ++-- src/librustdoc/config.rs | 5 +-- 11 files changed, 53 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 3a167b42ef94b..0c8c9db470360 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -973,9 +973,7 @@ Available lint options: lints } - fn sort_lint_groups( - lints: Vec<(Symbol, Vec, bool)>, - ) -> Vec<(Symbol, Vec)> { + fn sort_lint_groups(lints: Vec<(Symbol, Vec, bool)>) -> Vec<(Symbol, Vec)> { let mut lints: Vec<_> = lints.into_iter().map(|(x, y, _)| (x, y)).collect(); lints.sort_by_key(|l| l.0); lints diff --git a/compiler/rustc_errors/src/decorate_diag.rs b/compiler/rustc_errors/src/decorate_diag.rs index c04541caa6a6d..b2516162463b5 100644 --- a/compiler/rustc_errors/src/decorate_diag.rs +++ b/compiler/rustc_errors/src/decorate_diag.rs @@ -1,9 +1,8 @@ - /// This module provides types and traits for buffering lints until later in compilation. use rustc_ast::node_id::NodeId; use rustc_data_structures::fx::FxIndexMap; use rustc_error_messages::MultiSpan; -use rustc_lint_defs::{BuiltinLintDiag, LintId, LintImpl}; +use rustc_lint_defs::{BuiltinLintDiag, LintId, LintImpl}; use crate::{DynSend, LintDiagnostic, LintDiagnosticBox}; diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index ac000a55368d1..f364b5a7caed4 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -119,7 +119,7 @@ impl LintStoreExpand for LintStoreExpandImpl<'_> { items: &[Box], name: Symbol, ) { - pre_expansion_lint(sess, features, self.0, registered_tools, (node_id,items), name); + pre_expansion_lint(sess, features, self.0, registered_tools, (node_id, items), name); } } diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 8fe4bbbdc11da..d28d9081d3ef7 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -25,7 +25,8 @@ 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::{ - CheckLintNameResult, FutureIncompatibleInfo, Lint, LintExpectationId, LintId, LintImpl, TargetLint + CheckLintNameResult, FutureIncompatibleInfo, Lint, LintExpectationId, LintId, LintImpl, + TargetLint, }; use rustc_session::{DynLintStore, Session}; use rustc_span::edit_distance::find_best_match_for_names; @@ -348,7 +349,9 @@ impl LintStore { // 1. The tool is currently running, so this lint really doesn't exist. // FIXME: should this handle tools that never register a lint, like rustfmt? debug!("lints={:?}", self.by_name); - return if self.by_name.keys().any(|lint| lint.as_str().trim_start_matches(tool_name.as_str()).starts_with("::")) { + return if self.by_name.keys().any(|lint| { + lint.as_str().trim_start_matches(tool_name.as_str()).starts_with("::") + }) { self.no_lint_suggestion(complete_name, tool_name) } else { // 2. The tool isn't currently running, so no lints will be registered. @@ -391,9 +394,11 @@ impl LintStore { } } - fn no_lint_suggestion(&self, lint_name: Symbol,tool_name: Symbol,) -> CheckLintNameResult<'_> { + fn no_lint_suggestion(&self, lint_name: Symbol, tool_name: Symbol) -> CheckLintNameResult<'_> { let name_lower = Symbol::intern(&lint_name.as_str().to_lowercase()); - if lint_name.as_str().chars().any(char::is_uppercase) && self.find_lints(name_lower).is_some() { + if lint_name.as_str().chars().any(char::is_uppercase) + && self.find_lints(name_lower).is_some() + { // First check if the lint name is (partly) in upper case instead of lower case... return CheckLintNameResult::NoLint(Some((name_lower, false))); } @@ -550,7 +555,11 @@ pub trait LintContext { /// Emit a lint at the appropriate level, with no associated span. /// /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature - fn lint(&self, lint: &'static LintImpl, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)) { + fn lint( + &self, + lint: &'static LintImpl, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), + ) { self.opt_span_lint(lint, None as Option, decorate); } diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index e69cc040207ef..5da53a199000a 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -23,7 +23,7 @@ use rustc_session::lint::builtin::{ UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES, }; use rustc_session::lint::{ - CheckLintNameResult, Level, Lint, LintExpectationId, LintId, LintImpl, TargetLint + CheckLintNameResult, Level, Lint, LintExpectationId, LintId, LintImpl, TargetLint, }; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use tracing::{debug, instrument}; @@ -450,17 +450,19 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { let prev = self.provider.cur; self.provider.cur = self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev }); - let attrs = if !attrs.is_empty() {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.clone(), - ) } else { + let attrs = if !attrs.is_empty() { + 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.clone(), + ) + } else { vec![] }; @@ -522,13 +524,18 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { CheckLintNameResult::Renamed(replace) => { let suggestion = RenamedLintSuggestion::WithoutSpan { replace }; let requested_level = RequestedLevel { level, lint_name }; - let lint = - RenamedLintFromCommandLine { name: lint_name, replace, suggestion, requested_level }; + let lint = RenamedLintFromCommandLine { + name: lint_name, + replace, + suggestion, + requested_level, + }; self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); } CheckLintNameResult::Removed(reason) => { let requested_level = RequestedLevel { level, lint_name }; - let lint = RemovedLintFromCommandLine { name: lint_name, reason, requested_level }; + let lint = + RemovedLintFromCommandLine { name: lint_name, reason, requested_level }; self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); } CheckLintNameResult::NoLint(suggestion) => { @@ -555,7 +562,6 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { _ => {} }; - let Some(ids) = self.store.find_lints(lint_name) else { // errors already handled above continue; @@ -761,8 +767,8 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { Some(expectation_id), ); - let is_unfulfilled_lint_expectations = lint.lint_name() - == UNFULFILLED_LINT_EXPECTATIONS.name_lower(); + let is_unfulfilled_lint_expectations = + lint.lint_name() == UNFULFILLED_LINT_EXPECTATIONS.name_lower(); self.provider.push_expectation( expectation_id, LintExpectation::new( diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 05193fb025d41..c20c9dca346fd 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -7,7 +7,6 @@ //! When removing a lint, make sure to also add a call to `register_removed` in //! compiler/rustc_lint/src/lib.rs. - use crate::{declare_lint, declare_lint_pass, fcw}; declare_lint_pass! { @@ -3073,7 +3072,6 @@ declare_lint! { "detects builtin cfgs set via the `--cfg`" } - declare_lint! { /// The `repr_transparent_non_zst_fields` lint /// detects types marked `#[repr(transparent)]` that (transitively) diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 94ac0aa0d216c..31ae97b12f703 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -599,12 +599,10 @@ impl std::hash::Hash for LintId { } } - - impl LintId { /// Gets the `LintId` for a `Lint`. pub fn of(lint: &'static LintImpl) -> LintId { - LintId { lint: lint } + LintId { lint } } pub fn lint_name_raw(&self) -> Symbol { @@ -1029,7 +1027,7 @@ macro_rules! declare_tool_lint { $(#[$attr])* $vis static $NAME: &$crate::Lint = {static HIDDEN: $crate::Lint = ::std::sync::LazyLock::new(||{ let name = concat!(stringify!($tool), "::", stringify!($NAME)); - + $crate::LintImpl{ name: rustc_span::Symbol::intern(&name), name_lower: rustc_span::Symbol::intern(&name.to_ascii_lowercase()), @@ -1044,7 +1042,7 @@ macro_rules! declare_tool_lint { $(eval_always: $eval_always,)? ..$crate::LintImpl::default_fields_for_macro() }}); - &HIDDEN + &HIDDEN }; ); } diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 5696a6043262b..ac5cd4ab9ce74 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -2,7 +2,7 @@ //! It also serves as an input to the parser itself. use std::str; -use std::sync::{Arc}; +use std::sync::Arc; use rustc_ast::attr::AttrIdGenerator; use rustc_ast::node_id::NodeId; @@ -27,8 +27,8 @@ use crate::errors::{ CliFeatureDiagnosticHelp, FeatureDiagnosticForIssue, FeatureDiagnosticHelp, FeatureDiagnosticSuggestion, FeatureGateError, SuggestUpgradeCompiler, }; +use crate::lint::LintId; use crate::lint::builtin::UNSTABLE_SYNTAX_PRE_EXPANSION; -use crate::lint::{ LintId}; /// Collected spans during parsing for places where a certain feature was /// used and should be feature gated accordingly in `check_crate`. @@ -330,7 +330,7 @@ impl ParseSess { pub fn buffer_lint( &self, - lint:&'static LintImpl, + lint: &'static LintImpl, span: impl Into, node_id: NodeId, diagnostic: impl Into, diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index af50ccef66991..393872e0ab4a3 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -177,7 +177,6 @@ pub struct Session { pub mir_opt_bisect_eval_count: AtomicUsize, } - #[derive(Clone, Copy)] pub enum CodegenUnits { /// Specified by the user. In this case we try fairly hard to produce the diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b19ffbf697cbc..6580a72a61b50 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -846,8 +846,8 @@ symbols! { dyn_trait, dynamic_no_pic: "dynamic-no-pic", e, - edition_panic, edition_future_compatibility, + edition_panic, effective_target_features, effects, eh_catch_typeinfo, @@ -1029,6 +1029,7 @@ symbols! { global_asm, global_registration, globs, + gr_than_or_eq: "..=", gt, guard_patterns, half_open_range_patterns, @@ -1725,7 +1726,6 @@ symbols! { rustc_force_inline, rustc_has_incoherent_inherent_impls, rustc_hidden_type_of_opaques, - warnings, rustc_if_this_changed, rustc_inherit_overflow_checks, rustc_insignificant_dtor, @@ -2198,7 +2198,6 @@ symbols! { variant_count, variants, vec, - gr_than_or_eq: "..=", vector, verbatim, version, @@ -2218,6 +2217,7 @@ symbols! { vtable_align, vtable_size, warn, + warnings, wasip2, wasm32, wasm64, diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index ebc363df53734..2a70d62891e34 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -15,8 +15,8 @@ use rustc_session::config::{ use rustc_session::lint::Level; use rustc_session::search_paths::SearchPath; use rustc_session::{EarlyDiagCtxt, getopts}; -use rustc_span::{FileName, Symbol}; use rustc_span::edition::Edition; +use rustc_span::{FileName, Symbol}; use rustc_target::spec::TargetTuple; use crate::core::new_dcx; @@ -550,7 +550,8 @@ impl Options { } let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); - let lint_opts = lint_opts.into_iter().map(|(lint,level)|(Symbol::intern(&lint), level)).collect(); + let lint_opts = + lint_opts.into_iter().map(|(lint, level)| (Symbol::intern(&lint), level)).collect(); let input = if describe_lints { InputMode::HasFile(make_input(early_dcx, "")) } else {