From 747ce1a484f7d1353b8ac066046e12641abd75a8 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 19 Jun 2026 15:24:21 +0100 Subject: [PATCH 1/8] Make `AttributeParser::parse_limited` accept filters instead of symbol The existing API is preserved as `parse_limited_sym` as a simple helper. Similarly, `parse_limited_should_emit` is preserved as `parse_limited_sym_should_emit`. I've noted that all users targets crate, so the `target_node_id` and `target` parameters are removed from it. --- compiler/rustc_ast_passes/src/feature_gate.rs | 2 +- compiler/rustc_attr_parsing/src/interface.rs | 70 +++++++++++++------ .../src/deriving/generic/mod.rs | 2 +- .../src/proc_macro_harness.rs | 2 +- compiler/rustc_builtin_macros/src/test.rs | 2 +- .../rustc_builtin_macros/src/test_harness.rs | 2 +- compiler/rustc_expand/src/config.rs | 2 +- compiler/rustc_interface/src/passes.rs | 16 ++--- compiler/rustc_lint/src/builtin.rs | 2 +- compiler/rustc_lint/src/nonstandard_style.rs | 2 +- .../rustc_passes/src/debugger_visualizer.rs | 2 +- .../rustc_resolve/src/build_reduced_graph.rs | 2 +- .../rustc_resolve/src/diagnostics/impls.rs | 2 +- compiler/rustc_resolve/src/macros.rs | 2 +- 14 files changed, 65 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index bec96621fbfed..d1c1d172bef9e 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -634,7 +634,7 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate) let mut errored = false; if let Some(Attribute::Parsed(AttributeKind::Feature(feature_idents, first_span))) = - AttributeParser::parse_limited(sess, &krate.attrs, &[sym::feature]) + AttributeParser::parse_limited_sym(sess, &krate.attrs, &[sym::feature]) { // `feature(...)` used on non-nightly. This is definitely an error. let mut err = diagnostics::FeatureOnNonNightly { diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 30ff962c8bded..11048a9daa262 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -43,10 +43,10 @@ pub struct AttributeParser<'sess> { pub(crate) sess: &'sess Session, pub(crate) should_emit: ShouldEmit, - /// *Only* parse attributes with this symbol. + /// *Only* parse attributes that passes this filter. /// - /// Used in cases where we want the lowering infrastructure for parse just a single attribute. - parse_only: Option<&'static [Symbol]>, + /// Used in cases where we want the lowering infrastructure for parse just limited attributes. + parse_filter: Option<&'sess dyn Fn(&ast::Attribute) -> bool>, } impl<'sess> AttributeParser<'sess> { @@ -58,8 +58,8 @@ impl<'sess> AttributeParser<'sess> { /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would /// crash if you tried to do so through [`parse_limited`](Self::parse_limited). /// - /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with - /// that symbol are picked out of the list of instructions and parsed. Those are returned. + /// To make sure use is limited, supply a filter. Only attributes that passes the filter are + /// picked out of the list of instructions and parsed. Those are returned. /// /// 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 @@ -69,21 +69,29 @@ impl<'sess> AttributeParser<'sess> { pub fn parse_limited( sess: &'sess Session, attrs: &[ast::Attribute], - sym: &'static [Symbol], + parse_filter: &dyn Fn(&ast::Attribute) -> bool, ) -> Option { Self::parse_limited_should_emit( sess, attrs, - sym, + parse_filter, // Because we're not emitting warnings/errors, the target should not matter DUMMY_SP, - CRATE_NODE_ID, - Target::Crate, None, ShouldEmit::Nothing, ) } + /// This does the same as `parse_limited`, except that it takes a fixed symbol instead of a + /// filter. + pub fn parse_limited_sym( + sess: &'sess Session, + attrs: &[ast::Attribute], + sym: &'static [Symbol], + ) -> Option { + Self::parse_limited(sess, attrs, &|attr| attr.path_matches(sym)) + } + /// 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. /// @@ -91,20 +99,18 @@ impl<'sess> AttributeParser<'sess> { pub fn parse_limited_should_emit( sess: &'sess Session, attrs: &[ast::Attribute], - sym: &'static [Symbol], + parse_filter: &dyn Fn(&ast::Attribute) -> bool, target_span: Span, - target_node_id: NodeId, - target: Target, features: Option<&'sess Features>, should_emit: ShouldEmit, ) -> Option { let mut parsed = Self::parse_limited_all( sess, attrs, - Some(sym), - target, + Some(parse_filter), + Target::Crate, target_span, - target_node_id, + CRATE_NODE_ID, features, should_emit, None, @@ -113,17 +119,37 @@ impl<'sess> AttributeParser<'sess> { parsed.pop() } + /// This does the same as `parse_limited_should_emit`, except that it takes a fixed symbol + /// instead of a filter. + pub fn parse_limited_sym_should_emit( + sess: &'sess Session, + attrs: &[ast::Attribute], + sym: &'static [Symbol], + target_span: Span, + features: Option<&'sess Features>, + should_emit: ShouldEmit, + ) -> Option { + Self::parse_limited_should_emit( + sess, + attrs, + &|attr| attr.path_matches(sym), + target_span, + features, + should_emit, + ) + } + /// This method allows you to parse a list of attributes *before* `rustc_ast_lowering`. /// This can be used for attributes that would be removed before `rustc_ast_lowering`, such as attributes on macro calls. /// /// Try to use this as little as possible. Attributes *should* be lowered during /// `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. + /// Therefore, if `parse_filter` is None, then features *must* be provided. pub fn parse_limited_all( sess: &'sess Session, attrs: &[ast::Attribute], - parse_only: Option<&'static [Symbol]>, + parse_filter: Option<&dyn Fn(&ast::Attribute) -> bool>, target: Target, target_span: Span, target_node_id: NodeId, @@ -131,7 +157,7 @@ impl<'sess> AttributeParser<'sess> { should_emit: ShouldEmit, tools: Option<&'sess RegisteredTools>, ) -> Vec { - let mut p = Self { features, tools, parse_only, sess, should_emit }; + let mut p = AttributeParser { features, tools, parse_filter, sess, should_emit }; p.parse_attribute_list( attrs, target_span, @@ -210,7 +236,7 @@ impl<'sess> AttributeParser<'sess> { parse_fn: fn(cx: &mut AcceptContext<'_, '_>, item: &I) -> T, template: &AttributeTemplate, ) -> T { - let mut parser = Self { features, tools: None, parse_only: None, sess, should_emit }; + let mut parser = Self { features, tools: None, parse_filter: None, sess, should_emit }; let mut emit_lint = |lint_id: LintId, span: MultiSpan, kind: EmitAttribute| { sess.psess.dyn_buffer_lint_sess(lint_id.lint, span, target_node_id, kind.0) }; @@ -253,7 +279,7 @@ impl<'sess> AttributeParser<'sess> { tools: &'sess RegisteredTools, should_emit: ShouldEmit, ) -> Self { - Self { features: Some(features), tools: Some(tools), parse_only: None, sess, should_emit } + Self { features: Some(features), tools: Some(tools), parse_filter: None, sess, should_emit } } pub(crate) fn sess(&self) -> &'sess Session { @@ -297,8 +323,8 @@ impl<'sess> AttributeParser<'sess> { for attr in attrs { // 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.path_matches(expected) { + if let Some(filter) = self.parse_filter { + if !filter(attr) { continue; } } diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 1a0a65d4f7c64..cc036fab83c9d 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -493,7 +493,7 @@ impl<'a> TraitDef<'a> { match item { Annotatable::Item(item) => { let is_packed = matches!( - AttributeParser::parse_limited(cx.sess, &item.attrs, &[sym::repr]), + AttributeParser::parse_limited_sym(cx.sess, &item.attrs, &[sym::repr]), Some(Attribute::Parsed(AttributeKind::Repr { reprs, .. })) if reprs.iter().any(|(x, _)| matches!(x, ReprPacked(..))) ); diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs index 404f2358ab863..8259758272e7e 100644 --- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -100,7 +100,7 @@ impl<'a> CollectProcMacros<'a> { attr: &'a ast::Attribute, ) { let Some(rustc_hir::Attribute::Parsed(AttributeKind::ProcMacroDerive { .. })) = - AttributeParser::parse_limited( + AttributeParser::parse_limited_sym( self.session, slice::from_ref(attr), &[sym::proc_macro_derive], diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index 25ba01b275964..78d8f89b57f9e 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -486,7 +486,7 @@ fn should_ignore_message(i: &ast::Item) -> Option { fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic { if let Some(Attribute::Parsed(AttributeKind::ShouldPanic { reason, .. })) = - AttributeParser::parse_limited(cx.sess, &i.attrs, &[sym::should_panic]) + AttributeParser::parse_limited_sym(cx.sess, &i.attrs, &[sym::should_panic]) { ShouldPanic::Yes(reason) } else { diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index af0c9ed7ee656..ff9d9f10dd4e1 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -391,7 +391,7 @@ fn get_test_name(i: &ast::Item) -> Option { } fn get_test_runner(sess: &Session, krate: &ast::Crate) -> Option { - match AttributeParser::parse_limited(sess, &krate.attrs, &[sym::test_runner]) { + match AttributeParser::parse_limited_sym(sess, &krate.attrs, &[sym::test_runner]) { Some(rustc_hir::Attribute::Parsed(AttributeKind::TestRunner(path))) => Some(path), _ => None, } diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 39d8afbba3c9d..af017bea67697 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -52,7 +52,7 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) - let mut features = Features::default(); if let Some(hir::Attribute::Parsed(AttributeKind::Feature(feature_idents, _))) = - AttributeParser::parse_limited(sess, krate_attrs, &[sym::feature]) + AttributeParser::parse_limited_sym(sess, krate_attrs, &[sym::feature]) { for feature_ident in feature_idents { // If the enabled feature has been removed, issue an error. diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 2f32a6b208b6c..292b00a2d30ae 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, LazyLock, OnceLock}; use std::{env, fs, iter}; -use rustc_ast::{self as ast, CRATE_NODE_ID}; +use rustc_ast as ast; use rustc_attr_parsing::{AttributeParser, ShouldEmit}; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CompiledModules, CrateInfo}; @@ -23,7 +23,7 @@ use rustc_fs_util::try_canonicalize; use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::{LOCAL_CRATE, StableCrateId, StableCrateIdMap}; use rustc_hir::definitions::Definitions; -use rustc_hir::{Attribute, Target, find_attr}; +use rustc_hir::{Attribute, find_attr}; use rustc_incremental::setup_dep_graph; use rustc_lint::{BufferedEarlyLint, EarlyCheckNode, LintStore, unerased_lint_store}; use rustc_metadata::EncodedMetadata; @@ -1378,13 +1378,11 @@ pub(crate) fn parse_crate_name( emit_errors: ShouldEmit, ) -> Option<(Symbol, Span)> { let rustc_hir::Attribute::Parsed(AttributeKind::CrateName { name, name_span, .. }) = - AttributeParser::parse_limited_should_emit( + AttributeParser::parse_limited_sym_should_emit( sess, attrs, &[sym::crate_name], DUMMY_SP, - rustc_ast::node_id::CRATE_NODE_ID, - Target::Crate, None, emit_errors, )? @@ -1428,13 +1426,11 @@ pub fn collect_crate_types( let mut base = session.opts.crate_types.clone(); if base.is_empty() { if let Some(Attribute::Parsed(AttributeKind::CrateType(crate_type))) = - AttributeParser::parse_limited_should_emit( + AttributeParser::parse_limited_sym_should_emit( session, attrs, &[sym::crate_type], crate_span, - CRATE_NODE_ID, - Target::Crate, None, ShouldEmit::EarlyFatal { also_emit_lints: false }, ) @@ -1485,13 +1481,11 @@ fn default_output_for_target(sess: &Session) -> CrateType { } fn get_recursion_limit(krate_attrs: &[ast::Attribute], sess: &Session) -> Limit { - let attr = AttributeParser::parse_limited_should_emit( + let attr = AttributeParser::parse_limited_sym_should_emit( sess, &krate_attrs, &[sym::recursion_limit], DUMMY_SP, - rustc_ast::node_id::CRATE_NODE_ID, - Target::Crate, None, // errors are fatal here, but lints aren't. // If things aren't fatal we continue, and will parse this again. diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index dd56b13cb2f5e..34f526e4a7bce 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -243,7 +243,7 @@ impl EarlyLintPass for UnsafeCode { ast::ItemKind::MacroDef(..) => { if let Some(hir::Attribute::Parsed(AttributeKind::AllowInternalUnsafe(span))) = - AttributeParser::parse_limited( + AttributeParser::parse_limited_sym( cx.builder.sess(), &it.attrs, &[sym::allow_internal_unsafe], diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs index f7f5708bd424e..5a1b56f004be0 100644 --- a/compiler/rustc_lint/src/nonstandard_style.rs +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -160,7 +160,7 @@ impl NonCamelCaseTypes { impl EarlyLintPass for NonCamelCaseTypes { fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) { let has_repr_c = matches!( - AttributeParser::parse_limited(cx.sess(), &it.attrs, &[sym::repr]), + AttributeParser::parse_limited_sym(cx.sess(), &it.attrs, &[sym::repr]), Some(Attribute::Parsed(AttributeKind::Repr { reprs, ..})) if reprs.iter().any(|(r, _)| r == &ReprAttr::ReprC) ); diff --git a/compiler/rustc_passes/src/debugger_visualizer.rs b/compiler/rustc_passes/src/debugger_visualizer.rs index 493abd74cbfb8..459e6da7d961b 100644 --- a/compiler/rustc_passes/src/debugger_visualizer.rs +++ b/compiler/rustc_passes/src/debugger_visualizer.rs @@ -16,7 +16,7 @@ use crate::diagnostics::DebugVisualizerUnreadable; impl DebuggerVisualizerCollector<'_> { fn check_for_debugger_visualizer(&mut self, attrs: &[ast::Attribute]) { if let Some(Attribute::Parsed(AttributeKind::DebuggerVisualizer(visualizers))) = - AttributeParser::parse_limited(&self.sess, attrs, &[sym::debugger_visualizer]) + AttributeParser::parse_limited_sym(&self.sess, attrs, &[sym::debugger_visualizer]) { for DebugVisualizer { span, visualizer_type, path } in visualizers { let file = match resolve_path(&self.sess, path.as_str(), span) { diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 970d7486d640c..4ce07ffe45ed7 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -1148,7 +1148,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { let mut import_all = None; let mut single_imports = ThinVec::new(); if let Some(Attribute::Parsed(AttributeKind::MacroUse { span, arguments })) = - AttributeParser::parse_limited(self.r.tcx.sess, &item.attrs, &[sym::macro_use]) + AttributeParser::parse_limited_sym(self.r.tcx.sess, &item.attrs, &[sym::macro_use]) { if self.parent_scope.module.expect_local().parent.is_some() { self.r.dcx().emit_err(diagnostics::ExternCrateLoadingMacroNotAtCrateRoot { diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 0b6b9899f48a6..96d926864fc87 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -4214,7 +4214,7 @@ impl OnUnknownData { ) -> Option { if r.features.diagnostic_on_unknown() && let Some(Attribute::Parsed(AttributeKind::OnUnknown { directive, .. })) = - AttributeParser::parse_limited( + AttributeParser::parse_limited_sym( r.tcx.sess, attrs, &[sym::diagnostic, sym::on_unknown], diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index f2c26caa58aab..9027b5a38c8b6 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -135,7 +135,7 @@ pub fn registered_tools_ast( let mut registered_tools = RegisteredTools::default(); if let Some(Attribute::Parsed(AttributeKind::RegisterTool(tools))) = - AttributeParser::parse_limited(sess, pre_configured_attrs, &[sym::register_tool]) + AttributeParser::parse_limited_sym(sess, pre_configured_attrs, &[sym::register_tool]) { for tool in tools { if let Some(old_tool) = registered_tools.replace(tool) { From c8238fadb1276eeb1a4c59ae6b4ea1163faab83e Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 17 Jun 2026 15:32:42 +0100 Subject: [PATCH 2/8] Stop mentioning `register_attr` feature from `custom_attribute` It is also removed. --- compiler/rustc_feature/src/removed.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index 6191269b24857..43de680bfa747 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -95,7 +95,7 @@ declare_features! ( (removed, crate_visibility_modifier, "1.63.0", Some(53120), Some("removed in favor of `pub(crate)`"), 97254), /// Allows using custom attributes (RFC 572). (removed, custom_attribute, "1.0.0", Some(29642), - Some("removed in favor of `#![register_tool]` and `#![register_attr]`"), 66070), + Some("removed in favor of `#![register_tool]`"), 66070), /// Allows the use of `#[derive(Anything)]` as sugar for `#[derive_Anything]`. (removed, custom_derive, "1.32.0", Some(29644), Some("subsumed by `#[proc_macro_derive]`")), From 178bddc6c9b7a869f57a7793c0afcdc4344bb55f Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 19 Jun 2026 14:56:12 +0100 Subject: [PATCH 3/8] Make `RegisterToolParser` implement `AttributeParser` directly --- .../src/attributes/crate_level.rs | 91 +++++++++++-------- compiler/rustc_attr_parsing/src/context.rs | 2 +- .../rustc_attr_parsing/src/diagnostics.rs | 12 ++- compiler/rustc_resolve/src/diagnostics/mod.rs | 10 -- compiler/rustc_resolve/src/macros.rs | 20 ++-- 5 files changed, 73 insertions(+), 62 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index 4e10a2060b812..82bb6fd1aeb3b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -1,3 +1,4 @@ +use rustc_data_structures::fx::FxIndexSet; use rustc_feature::AttributeStability; use rustc_hir::attrs::{CrateType, WindowsSubsystemKind}; use rustc_session::lint::builtin::UNKNOWN_CRATE_TYPES; @@ -5,7 +6,9 @@ use rustc_span::Symbol; use rustc_span::edit_distance::find_best_match_for_name_with_substrings; use super::prelude::*; -use crate::diagnostics::{UnknownCrateTypes, UnknownCrateTypesSuggestion}; +use crate::diagnostics::{ + ToolWasAlreadyRegistered, UnknownCrateTypes, UnknownCrateTypesSuggestion, +}; pub(crate) struct CrateNameParser; @@ -317,49 +320,65 @@ impl CombineAttributeParser for FeatureParser { } } -pub(crate) struct RegisterToolParser; +#[derive(Default)] +pub(crate) struct RegisterToolParser { + tools: FxIndexSet, +} -impl CombineAttributeParser for RegisterToolParser { - const PATH: &[Symbol] = &[sym::register_tool]; - type Item = Ident; - const CONVERT: ConvertFn = |tools, _span| AttributeKind::RegisterTool(tools); - const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Crate)]); - const TEMPLATE: AttributeTemplate = template!(List: &["tool1, tool2, ..."]); - const STABILITY: AttributeStability = unstable!(register_tool); +fn parse_register_tool( + tools: &mut FxIndexSet, + cx: &mut AcceptContext<'_, '_>, + args: &ArgParser, +) { + let Some(list) = cx.expect_list(args, cx.attr_span) else { + return; + }; + + if list.is_empty() { + let attr_span = cx.attr_span; + cx.adcx().warn_empty_attribute(attr_span); + } - fn extend( - cx: &mut AcceptContext<'_, '_>, - args: &ArgParser, - ) -> impl IntoIterator { - let Some(list) = cx.expect_list(args, cx.attr_span) else { - return Vec::new(); + for elem in list.mixed() { + let Some(elem) = elem.meta_item() else { + cx.adcx().expected_identifier(elem.span()); + continue; + }; + let Some(()) = cx.expect_no_args(elem.args()) else { + continue; }; - if list.is_empty() { - let attr_span = cx.attr_span; - cx.adcx().warn_empty_attribute(attr_span); - } + let path = elem.path(); + let Some(ident) = path.word() else { + cx.adcx().expected_identifier(path.span()); + continue; + }; - let mut res = Vec::new(); + if let Some(old_ident) = tools.replace(ident) { + cx.dcx().emit_err(ToolWasAlreadyRegistered { + span: ident.span, + tool: ident, + old_ident_span: old_ident.span, + }); + } + } +} - for elem in list.mixed() { - let Some(elem) = elem.meta_item() else { - cx.adcx().expected_identifier(elem.span()); - continue; - }; - let Some(()) = cx.expect_no_args(elem.args()) else { - continue; - }; +impl AttributeParser for RegisterToolParser { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::register_tool], + template!(List: &["tool1, tool2, ..."]), + unstable!(register_tool), + |this, cx, args| parse_register_tool(&mut this.tools, cx, args), + )]; - let path = elem.path(); - let Some(ident) = path.word() else { - cx.adcx().expected_identifier(path.span()); - continue; - }; + const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Crate)]); - res.push(ident); + fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option { + if self.tools.is_empty() { + None + } else { + Some(AttributeKind::RegisterTool(self.tools.into_iter().collect())) } - - res } } diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 81911c1b2f335..33ad500cf9b48 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -174,6 +174,7 @@ attribute_parsers!( OnUnknownParser, OnUnmatchedArgsParser, OpaqueParser, + RegisterToolParser, RustcAlignParser, RustcAlignStaticParser, RustcCguTestAttributeParser, @@ -188,7 +189,6 @@ attribute_parsers!( Combine, Combine, Combine, - Combine, Combine, Combine, Combine, diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index fa7a5bc4e5078..3707d669bf36b 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -1,7 +1,7 @@ use rustc_errors::{Applicability, DiagArgValue, E0232, E0264, MultiSpan}; use rustc_hir::AttrPath; use rustc_macros::{Diagnostic, Subdiagnostic}; -use rustc_span::{Span, Symbol}; +use rustc_span::{Ident, Span, Symbol}; #[derive(Diagnostic)] #[diag("`{$name}` attribute cannot be used at crate level")] @@ -828,3 +828,13 @@ pub(crate) struct UnknownExternLangItem { pub span: Span, pub lang_item: Symbol, } + +#[derive(Diagnostic)] +#[diag("tool `{$tool}` was already registered")] +pub(crate) struct ToolWasAlreadyRegistered { + #[primary_span] + pub(crate) span: Span, + pub(crate) tool: Ident, + #[label("already registered here")] + pub(crate) old_ident_span: Span, +} diff --git a/compiler/rustc_resolve/src/diagnostics/mod.rs b/compiler/rustc_resolve/src/diagnostics/mod.rs index 9de2a682dc1fd..cadfab22c8862 100644 --- a/compiler/rustc_resolve/src/diagnostics/mod.rs +++ b/compiler/rustc_resolve/src/diagnostics/mod.rs @@ -1230,16 +1230,6 @@ pub(crate) struct CannotFindBuiltinMacroWithName { pub(crate) ident: Ident, } -#[derive(Diagnostic)] -#[diag("tool `{$tool}` was already registered")] -pub(crate) struct ToolWasAlreadyRegistered { - #[primary_span] - pub(crate) span: Span, - pub(crate) tool: Ident, - #[label("already registered here")] - pub(crate) old_ident_span: Span, -} - #[derive(Subdiagnostic)] pub(crate) enum DefinedHere { #[label("similarly named {$candidate_descr} `{$candidate}` defined here")] diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 9027b5a38c8b6..9d1fbd642212a 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -128,25 +128,17 @@ pub(crate) fn registered_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools { } pub fn registered_tools_ast( - dcx: DiagCtxtHandle<'_>, + _dcx: DiagCtxtHandle<'_>, pre_configured_attrs: &[ast::Attribute], sess: &Session, ) -> RegisteredTools { - let mut registered_tools = RegisteredTools::default(); - - if let Some(Attribute::Parsed(AttributeKind::RegisterTool(tools))) = + let mut registered_tools = if let Some(Attribute::Parsed(AttributeKind::RegisterTool(tools))) = AttributeParser::parse_limited_sym(sess, pre_configured_attrs, &[sym::register_tool]) { - for tool in tools { - if let Some(old_tool) = registered_tools.replace(tool) { - dcx.emit_err(diagnostics::ToolWasAlreadyRegistered { - span: tool.span, - tool, - old_ident_span: old_tool.span, - }); - } - } - } + tools.into_iter().collect::() + } else { + Default::default() + }; // We implicitly add `rustfmt`, `clippy`, `diagnostic`, `miri` and `rust_analyzer` to known // tools, but it's not an error to register them explicitly. From 75999c498f46f1bd9cbf264d0297fdbb4e129bf9 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 17 Jun 2026 16:05:05 +0100 Subject: [PATCH 4/8] Split register_tool into register_attribute_tool and register_lint_tool This splits the functionality to two parts per RFC. The `#![register_tool]` attribute registers the tool in both ways. Semantics remain unchanged and will be adjusted in later commit. --- compiler/rustc_ast_lowering/src/lib.rs | 2 +- .../src/attributes/codegen_attrs.rs | 2 +- .../src/attributes/crate_level.rs | 54 ++++++++++----- compiler/rustc_attr_parsing/src/interface.rs | 18 +++-- compiler/rustc_driver_impl/src/lib.rs | 4 +- compiler/rustc_expand/src/base.rs | 9 ++- compiler/rustc_expand/src/expand.rs | 4 +- compiler/rustc_feature/src/builtin_attrs.rs | 2 + .../rustc_hir/src/attrs/data_structures.rs | 7 +- .../rustc_hir/src/attrs/encode_cross_crate.rs | 2 +- compiler/rustc_interface/src/passes.rs | 15 +++-- compiler/rustc_lint/src/context.rs | 8 +-- compiler/rustc_lint/src/early.rs | 4 +- compiler/rustc_lint/src/levels.rs | 19 +++--- compiler/rustc_middle/src/queries.rs | 12 +++- compiler/rustc_passes/src/check_attr.rs | 2 +- compiler/rustc_resolve/src/def_collector.rs | 2 +- .../rustc_resolve/src/diagnostics/impls.rs | 4 +- compiler/rustc_resolve/src/ident.rs | 8 +-- compiler/rustc_resolve/src/lib.rs | 24 ++++--- compiler/rustc_resolve/src/macros.rs | 67 +++++++++++++------ compiler/rustc_span/src/symbol.rs | 2 + 22 files changed, 174 insertions(+), 97 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 8a6a2aceedd2f..e9d3610cdde69 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -291,7 +291,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { attribute_parser: AttributeParser::new( tcx.sess, tcx.features(), - tcx.registered_tools(()), + tcx.registered_attr_tools(()), ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, ), delayed_lints: Vec::new(), diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 28016aec48a50..ffa067479773f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -280,7 +280,7 @@ impl AttributeParser for NakedParser { let span = self.span?; - let Some(tools) = cx.tools else { + let Some(tools) = cx.attr_tools else { unreachable!("tools required while parsing attributes"); }; diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index 82bb6fd1aeb3b..ba91fe2520d12 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -322,11 +322,12 @@ impl CombineAttributeParser for FeatureParser { #[derive(Default)] pub(crate) struct RegisterToolParser { - tools: FxIndexSet, + attr_tools: FxIndexSet, + lint_tools: FxIndexSet, } fn parse_register_tool( - tools: &mut FxIndexSet, + tools: &mut [&mut FxIndexSet], cx: &mut AcceptContext<'_, '_>, args: &ArgParser, ) { @@ -354,31 +355,52 @@ fn parse_register_tool( continue; }; - if let Some(old_ident) = tools.replace(ident) { - cx.dcx().emit_err(ToolWasAlreadyRegistered { - span: ident.span, - tool: ident, - old_ident_span: old_ident.span, - }); + for tools in tools.iter_mut() { + if let Some(old_ident) = tools.replace(ident) { + cx.dcx().emit_err(ToolWasAlreadyRegistered { + span: ident.span, + tool: ident, + old_ident_span: old_ident.span, + }); + } } } } impl AttributeParser for RegisterToolParser { - const ATTRIBUTES: AcceptMapping = &[( - &[sym::register_tool], - template!(List: &["tool1, tool2, ..."]), - unstable!(register_tool), - |this, cx, args| parse_register_tool(&mut this.tools, cx, args), - )]; + const ATTRIBUTES: AcceptMapping = &[ + ( + &[sym::register_tool], + template!(List: &["tool1, tool2, ..."]), + unstable!(register_tool), + |this, cx, args| { + parse_register_tool(&mut [&mut this.attr_tools, &mut this.lint_tools], cx, args) + }, + ), + ( + &[sym::register_attribute_tool], + template!(List: &["tool1, tool2, ..."]), + unstable!(register_tool), + |this, cx, args| parse_register_tool(&mut [&mut this.attr_tools], cx, args), + ), + ( + &[sym::register_lint_tool], + template!(List: &["tool1, tool2, ..."]), + unstable!(register_tool), + |this, cx, args| parse_register_tool(&mut [&mut this.lint_tools], cx, args), + ), + ]; const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Crate)]); fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option { - if self.tools.is_empty() { + if self.attr_tools.is_empty() && self.lint_tools.is_empty() { None } else { - Some(AttributeKind::RegisterTool(self.tools.into_iter().collect())) + Some(AttributeKind::RegisterTool { + attr_tools: self.attr_tools.into_iter().collect(), + lint_tools: self.lint_tools.into_iter().collect(), + }) } } } diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 11048a9daa262..cea549e310476 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -38,7 +38,7 @@ pub struct EmitAttribute( /// Context created once, for example as part of the ast lowering /// context, through which all attributes can be lowered. pub struct AttributeParser<'sess> { - pub(crate) tools: Option<&'sess RegisteredTools>, + pub(crate) attr_tools: Option<&'sess RegisteredTools>, pub(crate) features: Option<&'sess Features>, pub(crate) sess: &'sess Session, pub(crate) should_emit: ShouldEmit, @@ -155,9 +155,9 @@ impl<'sess> AttributeParser<'sess> { target_node_id: NodeId, features: Option<&'sess Features>, should_emit: ShouldEmit, - tools: Option<&'sess RegisteredTools>, + attr_tools: Option<&'sess RegisteredTools>, ) -> Vec { - let mut p = AttributeParser { features, tools, parse_filter, sess, should_emit }; + let mut p = AttributeParser { features, attr_tools, parse_filter, sess, should_emit }; p.parse_attribute_list( attrs, target_span, @@ -236,7 +236,7 @@ impl<'sess> AttributeParser<'sess> { parse_fn: fn(cx: &mut AcceptContext<'_, '_>, item: &I) -> T, template: &AttributeTemplate, ) -> T { - let mut parser = Self { features, tools: None, parse_filter: None, sess, should_emit }; + let mut parser = Self { features, attr_tools: None, parse_filter: None, sess, should_emit }; let mut emit_lint = |lint_id: LintId, span: MultiSpan, kind: EmitAttribute| { sess.psess.dyn_buffer_lint_sess(lint_id.lint, span, target_node_id, kind.0) }; @@ -276,10 +276,16 @@ impl<'sess> AttributeParser<'sess> { pub fn new( sess: &'sess Session, features: &'sess Features, - tools: &'sess RegisteredTools, + attr_tools: &'sess RegisteredTools, should_emit: ShouldEmit, ) -> Self { - Self { features: Some(features), tools: Some(tools), parse_filter: None, sess, should_emit } + Self { + features: Some(features), + attr_tools: Some(attr_tools), + parse_filter: None, + sess, + should_emit, + } } pub(crate) fn sess(&self) -> &'sess Session { diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 4411ecb4f128b..7b4c5626a79e1 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -712,13 +712,13 @@ fn print_crate_info( let crate_name = passes::get_crate_name(sess, attrs); let lint_store = crate::unerased_lint_store(sess); let features = rustc_expand::config::features(sess, attrs, crate_name); - let registered_tools = rustc_resolve::registered_tools_ast(sess.dcx(), attrs, sess); + let registered_lint_tools = rustc_resolve::registered_lint_tools_ast(sess, attrs); let builder = rustc_lint::LintLevelsBuilder::crate_root( sess, &features, true, lint_store, - ®istered_tools, + ®istered_lint_tools, attrs, ); for lint in lint_store.get_lints() { diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 5c42f94f51ef8..f15b005960c9f 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -1111,8 +1111,11 @@ pub trait ResolverExpand { cfg_span: Span, ); - /// Tools registered with `#![register_tool]` and used by tool attributes and lints. - fn registered_tools(&self) -> &RegisteredTools; + /// Tools registered with `#![register_tool]` or `#![register_attribute_tool]`. + fn registered_attr_tools(&self) -> &RegisteredTools; + + /// Tools registered with `#![register_tool]` or `#![register_lint_tool]`. + fn registered_lint_tools(&self) -> &RegisteredTools; /// Mark this invocation id as a glob delegation. fn register_glob_delegation(&mut self, invoc_id: LocalExpnId); @@ -1139,7 +1142,7 @@ pub trait LintStoreExpand { &self, sess: &Session, features: &Features, - registered_tools: &RegisteredTools, + registered_lint_tools: &RegisteredTools, node_id: NodeId, attrs: &[Attribute], items: &[Box], diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 4f55e0506e799..187dcea4f91a5 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1378,7 +1378,7 @@ impl InvocationCollectorNode for Box { lint_store.pre_expansion_lint( ecx.sess, ecx.ecfg.features, - ecx.resolver.registered_tools(), + ecx.resolver.registered_lint_tools(), ecx.current_expansion.lint_node_id, &node.attrs, &items, @@ -2231,7 +2231,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { self.cx.current_expansion.lint_node_id, Some(self.cx.ecfg.features), ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, - Some(self.cx.resolver.registered_tools()), + Some(self.cx.resolver.registered_attr_tools()), ); let current_span = if let Some(sp) = span { sp.to(attr.span) } else { attr.span }; diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index c7e9a939f2ba1..0e8eb44a4e774 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -185,6 +185,8 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ sym::ffi_pure, sym::ffi_const, + sym::register_attribute_tool, + sym::register_lint_tool, sym::register_tool, // `#[cfi_encoding = ""]` sym::cfi_encoding, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index bb0d7227bd171..5568e76257431 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1304,8 +1304,11 @@ pub enum AttributeKind { /// Represents `#[reexport_test_harness_main]` ReexportTestHarnessMain(Symbol), - /// Represents `#[register_tool]` - RegisterTool(ThinVec), + /// Represents `#[register_attribute_tool]`, `#[register_lint_tool]` and `#[register_tool]` + RegisterTool { + attr_tools: ThinVec, + lint_tools: ThinVec, + }, /// Represents [`#[repr]`](https://doc.rust-lang.org/stable/reference/type-layout.html#representations). Repr { diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 1e634513fdce2..eab53a533e6c5 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -97,7 +97,7 @@ impl AttributeKind { ProfilerRuntime => No, RecursionLimit { .. } => No, ReexportTestHarnessMain(..) => No, - RegisterTool(..) => No, + RegisterTool { .. } => No, Repr { .. } => No, RustcAbi { .. } => No, RustcAlign { .. } => No, diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 292b00a2d30ae..20e2fc833c88d 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -87,7 +87,7 @@ fn pre_expansion_lint<'a>( sess: &Session, features: &Features, lint_store: &LintStore, - registered_tools: &RegisteredTools, + registered_lint_tools: &RegisteredTools, check_node: EarlyCheckNode<'a>, node_name: Symbol, ) { @@ -98,7 +98,7 @@ fn pre_expansion_lint<'a>( features, true, lint_store, - registered_tools, + registered_lint_tools, None, check_node, ); @@ -114,14 +114,14 @@ impl LintStoreExpand for LintStoreExpandImpl<'_> { &self, sess: &Session, features: &Features, - registered_tools: &RegisteredTools, + registered_lint_tools: &RegisteredTools, node_id: ast::NodeId, attrs: &[ast::Attribute], items: &[Box], name: Symbol, ) { let check_node = EarlyCheckNode::LoadedMod(node_id, attrs, items); - pre_expansion_lint(sess, features, self.0, registered_tools, check_node, name); + pre_expansion_lint(sess, features, self.0, registered_lint_tools, check_node, name); } } @@ -144,7 +144,7 @@ fn configure_and_expand( sess, features, lint_store, - tcx.registered_tools(()), + tcx.registered_lint_tools(()), EarlyCheckNode::CrateRoot(&krate, pre_configured_attrs), crate_name, ); @@ -475,7 +475,7 @@ fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) { tcx.features(), false, lint_store, - tcx.registered_tools(()), + tcx.registered_lint_tools(()), Some(lint_buffer), EarlyCheckNode::CrateRoot(&*krate, &*krate.attrs), ) @@ -791,7 +791,8 @@ fn resolver_for_lowering_raw<'tcx>( &'tcx ty::ResolverGlobalCtxt, ) { let arenas = WorkerLocal::new(|_| Resolver::arenas()); - let _ = tcx.registered_tools(()); // Uses `crate_for_resolver`. + let _ = tcx.registered_attr_tools(()); // Uses `crate_for_resolver`. + let _ = tcx.registered_lint_tools(()); // Uses `crate_for_resolver`. let (krate, pre_configured_attrs) = tcx.crate_for_resolver(()).steal(); let mut resolver = Resolver::new( tcx, diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 62db3208d521b..e32f04308b3d6 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -346,13 +346,13 @@ impl LintStore { &self, lint_name: &str, tool_name: Option, - registered_tools: &RegisteredTools, + registered_lint_tools: &RegisteredTools, ) -> CheckLintNameResult<'_> { if let Some(tool_name) = tool_name { // FIXME: rustc and rustdoc are considered tools for lints, but not for attributes. if tool_name != sym::rustc && tool_name != sym::rustdoc - && !registered_tools.contains(&Ident::with_dummy_span(tool_name)) + && !registered_lint_tools.contains(&Ident::with_dummy_span(tool_name)) { return CheckLintNameResult::NoTool; } @@ -572,7 +572,7 @@ impl<'a> EarlyContext<'a> { features: &'a Features, lint_added_lints: bool, lint_store: &'a LintStore, - registered_tools: &'a RegisteredTools, + registered_lint_tools: &'a RegisteredTools, buffered: LintBuffer, ) -> EarlyContext<'a> { EarlyContext { @@ -581,7 +581,7 @@ impl<'a> EarlyContext<'a> { features, lint_added_lints, lint_store, - registered_tools, + registered_lint_tools, ), buffered, } diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 9c123f67abd5f..e7459eb86e4db 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -314,7 +314,7 @@ pub fn check_ast_node<'a>( features: &Features, pre_expansion: bool, lint_store: &LintStore, - registered_tools: &RegisteredTools, + registered_lint_tools: &RegisteredTools, lint_buffer: Option, check_node: EarlyCheckNode<'a>, ) { @@ -323,7 +323,7 @@ pub fn check_ast_node<'a>( features, !pre_expansion, lint_store, - registered_tools, + registered_lint_tools, lint_buffer.unwrap_or_default(), ); diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index ef23085417352..5671e86283f9b 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -171,7 +171,7 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe }, lint_added_lints: false, store, - registered_tools: tcx.registered_tools(()), + registered_lint_tools: tcx.registered_lint_tools(()), }; if owner == hir::CRATE_OWNER_ID { @@ -389,7 +389,7 @@ pub struct LintLevelsBuilder<'s, P> { provider: P, lint_added_lints: bool, store: &'s LintStore, - registered_tools: &'s RegisteredTools, + registered_lint_tools: &'s RegisteredTools, } pub(crate) struct BuilderPush { @@ -402,7 +402,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { features: &'s Features, lint_added_lints: bool, store: &'s LintStore, - registered_tools: &'s RegisteredTools, + registered_lint_tools: &'s RegisteredTools, ) -> Self { let mut builder = LintLevelsBuilder { sess, @@ -410,7 +410,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE }, lint_added_lints, store, - registered_tools, + registered_lint_tools, }; builder.process_command_line(); assert_eq!(builder.provider.sets.list.len(), 1); @@ -422,10 +422,10 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { features: &'s Features, lint_added_lints: bool, store: &'s LintStore, - registered_tools: &'s RegisteredTools, + registered_lint_tools: &'s RegisteredTools, crate_attrs: &[ast::Attribute], ) -> Self { - let mut builder = Self::new(sess, features, lint_added_lints, store, registered_tools); + let mut builder = Self::new(sess, features, lint_added_lints, store, registered_lint_tools); builder.add(crate_attrs, true); builder } @@ -503,7 +503,8 @@ where .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(lint_name_only, tool_name, self.registered_lint_tools) + { CheckLintNameResult::Renamed(ref replace) => { let name = lint_name.as_str(); let suggestion = RenamedLintSuggestion::WithoutSpan { replace }; @@ -768,7 +769,7 @@ where 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); + self.store.check_lint_name(&name, tool_name, self.registered_lint_tools); let (ids, name) = match lint_result { CheckLintNameResult::Ok(ids) => { @@ -837,7 +838,7 @@ where // 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) + self.store.check_lint_name(replace, None, self.registered_lint_tools) else { panic!("renamed lint does not exist: {replace}"); }; diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index b19a54ff52eb5..a878df27bd832 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -151,10 +151,16 @@ rustc_queries! { desc { "triggering a delayed bug for testing incremental" } } - /// Collects the list of all tools registered using `#![register_tool]`. - query registered_tools(_: ()) -> &'tcx ty::RegisteredTools { + /// Collects the list of all tools registered using `#![register_tool]` or `#![register_attribute_tool]`. + query registered_attr_tools(_: ()) -> &'tcx ty::RegisteredTools { arena_cache - desc { "compute registered tools for crate" } + desc { "compute registered attribute tools for crate" } + } + + /// Collects the list of all tools registered using `#![register_tool]` or `#![register_lint_tool]`. + query registered_lint_tools(_: ()) -> &'tcx ty::RegisteredTools { + arena_cache + desc { "compute registered lint tools for crate" } } query early_lint_checks(_: ()) { diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 6115811b18af6..c15ef02e251aa 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -300,7 +300,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::ProfilerRuntime => (), AttributeKind::RecursionLimit { .. } => (), AttributeKind::ReexportTestHarnessMain(..) => (), - AttributeKind::RegisterTool(..) => (), + AttributeKind::RegisterTool { .. } => (), // handled below this loop and elsewhere AttributeKind::Repr { .. } => (), AttributeKind::RustcAbi { .. } => (), diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 0b54b265fcee7..63bff7a3f4498 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -180,7 +180,7 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { let mut parser = AttributeParser::new( &self.r.tcx.sess, self.r.features, - self.r.tcx().registered_tools(()), + self.r.tcx().registered_attr_tools(()), ShouldEmit::Nothing, ); let attrs = parser.parse_attribute_list( diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 96d926864fc87..5ff31f5d90834 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -1526,10 +1526,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { })); } Scope::ExternPreludeFlags => {} - Scope::ToolPrelude => { + Scope::ToolAttributePrelude => { let res = Res::NonMacroAttr(NonMacroAttrKind::Tool); suggestions.extend( - this.registered_tools + this.registered_attr_tools .iter() .map(|ident| TypoSuggestion::new(ident.name, ident.span, res)), ); diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index d18180ea64afc..1c94779a6009a 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -159,7 +159,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Scope::ExternPreludeItems | Scope::ExternPreludeFlags => { use_prelude || module_and_extern_prelude || extern_prelude } - Scope::ToolPrelude => use_prelude, + Scope::ToolAttributePrelude => use_prelude, Scope::StdLibPrelude => use_prelude || ns == MacroNS, Scope::BuiltinTypes => true, }; @@ -225,8 +225,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Scope::BuiltinAttrs => break, // nowhere else to search Scope::ExternPreludeItems => Scope::ExternPreludeFlags, Scope::ExternPreludeFlags if module_and_extern_prelude || extern_prelude => break, - Scope::ExternPreludeFlags => Scope::ToolPrelude, - Scope::ToolPrelude => Scope::StdLibPrelude, + Scope::ExternPreludeFlags => Scope::ToolAttributePrelude, + Scope::ToolAttributePrelude => Scope::StdLibPrelude, Scope::StdLibPrelude => match ns { TypeNS => Scope::BuiltinTypes, ValueNS => break, // nowhere else to search @@ -738,7 +738,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { None => Err(Determinacy::Determined), } } - Scope::ToolPrelude => match self.registered_tool_decls.get(&ident) { + Scope::ToolAttributePrelude => match self.registered_attr_tool_decls.get(&ident) { Some(decl) => Ok(*decl), None => Err(Determinacy::Determined), }, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 111eae6a4134f..2dbf32dc20288 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -35,7 +35,7 @@ use late::{ ForwardGenericParamBanReason, HasGenericParams, PathSource, PatternSource, UnnecessaryQualification, }; -pub use macros::registered_tools_ast; +pub use macros::registered_lint_tools_ast; use macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use rustc_arena::{DroplessArena, TypedArena}; use rustc_ast::node_id::NodeMap; @@ -135,8 +135,8 @@ enum Scope<'ra> { ExternPreludeItems, /// Extern prelude names introduced by `--extern` flags. ExternPreludeFlags, - /// Tool modules introduced with `#![register_tool]`. - ToolPrelude, + /// Tool modules introduced with `#![register_tool]` or `#![register_attribute_tool]`. + ToolAttributePrelude, /// Standard library prelude introduced with an internal `#[prelude_import]` import. StdLibPrelude, /// Built-in types. @@ -1403,10 +1403,11 @@ pub struct Resolver<'ra, 'tcx> { dummy_decl: Decl<'ra>, builtin_type_decls: FxHashMap>, builtin_attr_decls: FxHashMap>, - registered_tool_decls: FxHashMap>, + registered_attr_tool_decls: FxHashMap>, macro_names: FxHashSet = default::fx_hash_set(), builtin_macros: FxHashMap = default::fx_hash_map(), - registered_tools: &'tcx RegisteredTools, + registered_attr_tools: &'tcx RegisteredTools, + registered_lint_tools: &'tcx RegisteredTools, macro_use_prelude: FxIndexMap>, /// Eagerly populated map of all local macro definitions. local_macro_map: FxHashMap> = default::fx_hash_map(), @@ -1786,7 +1787,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { invocation_parents.insert(LocalExpnId::ROOT, InvocationParent::ROOT); let extern_prelude = build_extern_prelude(tcx, attrs); - let registered_tools = tcx.registered_tools(()); + let registered_attr_tools = tcx.registered_attr_tools(()); + let registered_lint_tools = tcx.registered_lint_tools(()); let edition = tcx.sess.edition(); let mut resolver = Resolver { @@ -1824,7 +1826,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { (*builtin_attr, decl) }) .collect(), - registered_tool_decls: registered_tools + registered_attr_tool_decls: registered_attr_tools .iter() .map(|&ident| { let res = Res::ToolMod; @@ -1832,7 +1834,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { (IdentKey::new(ident), decl) }) .collect(), - registered_tools, + registered_attr_tools, + registered_lint_tools, macro_use_prelude: Default::default(), extern_macro_map: Default::default(), dummy_ext_bang: arenas.alloc_macro(SyntaxExtension::dummy_bang(edition)), @@ -2092,7 +2095,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } Scope::ExternPreludeItems | Scope::ExternPreludeFlags - | Scope::ToolPrelude + | Scope::ToolAttributePrelude | Scope::BuiltinTypes => {} _ => unreachable!(), } @@ -2780,7 +2783,8 @@ impl Finalize { } pub fn provide(providers: &mut Providers) { - providers.registered_tools = macros::registered_tools; + providers.registered_attr_tools = macros::registered_attr_tools; + providers.registered_lint_tools = macros::registered_lint_tools; } /// A wrapper around `&mut Resolver` that may be mutable or immutable, depending on a conditions. diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 9d1fbd642212a..54f9aa8e914ec 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use rustc_ast::{self as ast, Crate, DelegationSuffixes, NodeId}; use rustc_ast_pretty::pprust; use rustc_attr_parsing::AttributeParser; -use rustc_errors::{Applicability, DiagCtxtHandle, StashKey}; +use rustc_errors::{Applicability, StashKey}; use rustc_expand::base::{ Annotatable, DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension, SyntaxExtensionKind, @@ -122,29 +122,52 @@ fn fast_print_path(path: &ast::Path) -> Symbol { } } -pub(crate) fn registered_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools { +const PREDEFINED_TOOLS: &[Symbol] = + &[sym::clippy, sym::rustfmt, sym::diagnostic, sym::miri, sym::rust_analyzer]; + +pub(crate) fn registered_attr_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools { let (_, pre_configured_attrs) = &*tcx.crate_for_resolver(()).borrow(); - registered_tools_ast(tcx.dcx(), pre_configured_attrs, tcx.sess) + + let mut registered_tools = + if let Some(Attribute::Parsed(AttributeKind::RegisterTool { attr_tools, .. })) = + AttributeParser::parse_limited(tcx.sess, pre_configured_attrs, &|attr| { + attr.path_matches(&[sym::register_tool]) + || attr.path_matches(&[sym::register_attribute_tool]) + }) + { + attr_tools.into_iter().collect::() + } else { + Default::default() + }; + + // We implicitly add predefined tools, but it's not an error to register them explicitly. + registered_tools.extend(PREDEFINED_TOOLS.iter().cloned().map(Ident::with_dummy_span)); + registered_tools } -pub fn registered_tools_ast( - _dcx: DiagCtxtHandle<'_>, - pre_configured_attrs: &[ast::Attribute], +pub(crate) fn registered_lint_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools { + let (_, pre_configured_attrs) = &*tcx.crate_for_resolver(()).borrow(); + registered_lint_tools_ast(tcx.sess, pre_configured_attrs) +} + +pub fn registered_lint_tools_ast( sess: &Session, + pre_configured_attrs: &[ast::Attribute], ) -> RegisteredTools { - let mut registered_tools = if let Some(Attribute::Parsed(AttributeKind::RegisterTool(tools))) = - AttributeParser::parse_limited_sym(sess, pre_configured_attrs, &[sym::register_tool]) - { - tools.into_iter().collect::() - } else { - Default::default() - }; + let mut registered_tools = + if let Some(Attribute::Parsed(AttributeKind::RegisterTool { lint_tools, .. })) = + AttributeParser::parse_limited(sess, pre_configured_attrs, &|attr| { + attr.path_matches(&[sym::register_tool]) + || attr.path_matches(&[sym::register_lint_tool]) + }) + { + lint_tools.into_iter().collect::() + } else { + Default::default() + }; - // We implicitly add `rustfmt`, `clippy`, `diagnostic`, `miri` and `rust_analyzer` to known - // tools, but it's not an error to register them explicitly. - let predefined_tools = - [sym::clippy, sym::rustfmt, sym::diagnostic, sym::miri, sym::rust_analyzer]; - registered_tools.extend(predefined_tools.iter().cloned().map(Ident::with_dummy_span)); + // We implicitly add predefined tools, but it's not an error to register them explicitly. + registered_tools.extend(PREDEFINED_TOOLS.iter().cloned().map(Ident::with_dummy_span)); registered_tools } @@ -520,8 +543,12 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> { }); } - fn registered_tools(&self) -> &RegisteredTools { - self.registered_tools + fn registered_attr_tools(&self) -> &RegisteredTools { + self.registered_attr_tools + } + + fn registered_lint_tools(&self) -> &RegisteredTools { + self.registered_lint_tools } fn register_glob_delegation(&mut self, invoc_id: LocalExpnId) { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index c874c1af265f8..c613e1b8edf7d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1692,6 +1692,8 @@ symbols! { reg_ptr, reg_upper, register_attr, + register_attribute_tool, + register_lint_tool, register_tool, relaxed_adts, relaxed_struct_unsize, From 43444bf3b69264bef5267c3561fc954f9945b4e0 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 17 Jun 2026 16:45:50 +0100 Subject: [PATCH 5/8] Remove duplicate tool error and add reserved error Under the RFC, it's not an error to register tool multiple times, but it is an error to register "rustc" tool. --- .../src/attributes/crate_level.rs | 18 ++++++++---------- compiler/rustc_attr_parsing/src/diagnostics.rs | 6 ++---- tests/ui/tool-attributes/duplicate-tool.rs | 15 +++++++++++++++ tests/ui/tool-attributes/predefined-tool.rs | 11 +++++++++++ .../ui/tool-attributes/predefined-tool.stderr | 8 ++++++++ 5 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 tests/ui/tool-attributes/duplicate-tool.rs create mode 100644 tests/ui/tool-attributes/predefined-tool.rs create mode 100644 tests/ui/tool-attributes/predefined-tool.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index ba91fe2520d12..930f87e697c04 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -6,9 +6,7 @@ use rustc_span::Symbol; use rustc_span::edit_distance::find_best_match_for_name_with_substrings; use super::prelude::*; -use crate::diagnostics::{ - ToolWasAlreadyRegistered, UnknownCrateTypes, UnknownCrateTypesSuggestion, -}; +use crate::diagnostics::{ToolReserved, UnknownCrateTypes, UnknownCrateTypesSuggestion}; pub(crate) struct CrateNameParser; @@ -355,14 +353,14 @@ fn parse_register_tool( continue; }; + if ident.name == sym::rustc { + cx.should_emit + .emit_err(cx.dcx().create_err(ToolReserved { span: ident.span, tool: ident })); + continue; + } + for tools in tools.iter_mut() { - if let Some(old_ident) = tools.replace(ident) { - cx.dcx().emit_err(ToolWasAlreadyRegistered { - span: ident.span, - tool: ident, - old_ident_span: old_ident.span, - }); - } + tools.insert(ident); } } } diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index 3707d669bf36b..d5ea103dd1ae3 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -830,11 +830,9 @@ pub(crate) struct UnknownExternLangItem { } #[derive(Diagnostic)] -#[diag("tool `{$tool}` was already registered")] -pub(crate) struct ToolWasAlreadyRegistered { +#[diag("tool `{$tool}` is reserved and cannot be registered")] +pub(crate) struct ToolReserved { #[primary_span] pub(crate) span: Span, pub(crate) tool: Ident, - #[label("already registered here")] - pub(crate) old_ident_span: Span, } diff --git a/tests/ui/tool-attributes/duplicate-tool.rs b/tests/ui/tool-attributes/duplicate-tool.rs new file mode 100644 index 0000000000000..603292d1eb3cd --- /dev/null +++ b/tests/ui/tool-attributes/duplicate-tool.rs @@ -0,0 +1,15 @@ +//@ check-pass +#![feature(register_tool)] +// Register a tool multiple times is okay. +#![register_tool(foo)] +#![register_tool(foo)] +#![register_tool(bar)] +#![register_attribute_tool(bar)] +#![register_tool(baz)] +#![register_lint_tool(baz)] +#![register_attribute_tool(qux)] +#![register_attribute_tool(qux)] +#![register_lint_tool(quux)] +#![register_lint_tool(quux)] + +fn main() {} diff --git a/tests/ui/tool-attributes/predefined-tool.rs b/tests/ui/tool-attributes/predefined-tool.rs new file mode 100644 index 0000000000000..7181012d6827e --- /dev/null +++ b/tests/ui/tool-attributes/predefined-tool.rs @@ -0,0 +1,11 @@ +#![feature(register_tool)] +// Registering predefined tool is okay. +#![register_tool(clippy)] +#![register_attribute_tool(miri)] +#![register_lint_tool(rustfmt)] +#![register_tool(diagnostic)] +#![register_attribute_tool(rust_analyzer)] +// Registering "rustc" is an error. +#![register_tool(rustc)] //~ ERROR reserved + +fn main() {} diff --git a/tests/ui/tool-attributes/predefined-tool.stderr b/tests/ui/tool-attributes/predefined-tool.stderr new file mode 100644 index 0000000000000..d9b19f2177ab9 --- /dev/null +++ b/tests/ui/tool-attributes/predefined-tool.stderr @@ -0,0 +1,8 @@ +error: tool `rustc` is reserved and cannot be registered + --> $DIR/predefined-tool.rs:9:18 + | +LL | #![register_tool(rustc)] + | ^^^^^ + +error: aborting due to 1 previous error + From a22359b7dfba95dd4fe141afe55dc4b2f47647a1 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 17 Jun 2026 16:57:32 +0100 Subject: [PATCH 6/8] Add test for `register_{attribute,lint}_tool` with cfg --- tests/ui/tool-attributes/cfg-register.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/ui/tool-attributes/cfg-register.rs diff --git a/tests/ui/tool-attributes/cfg-register.rs b/tests/ui/tool-attributes/cfg-register.rs new file mode 100644 index 0000000000000..c835e6bd54740 --- /dev/null +++ b/tests/ui/tool-attributes/cfg-register.rs @@ -0,0 +1,12 @@ +//@ check-pass +// +//@revisions: tool split_attr_lint + +#![feature(register_tool)] +#![cfg_attr(tool, register_tool(foo, bar))] +#![cfg_attr(split_attr_lint, register_attribute_tool(foo))] +#![cfg_attr(split_attr_lint, register_lint_tool(bar))] + +#[foo::a] +#[allow(bar::b)] +fn main() {} From d0f008d427a47133d30e450c38f71d0b79e4ccf1 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 27 Jul 2026 20:05:56 +0100 Subject: [PATCH 7/8] Expand register_tool feature gate test to cover attr and lint tool --- .../feature-gate-register_tool.rs | 2 ++ .../feature-gate-register_tool.stderr | 22 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/ui/feature-gates/feature-gate-register_tool.rs b/tests/ui/feature-gates/feature-gate-register_tool.rs index 82f757a03af10..4034a95e8fb65 100644 --- a/tests/ui/feature-gates/feature-gate-register_tool.rs +++ b/tests/ui/feature-gates/feature-gate-register_tool.rs @@ -1,3 +1,5 @@ #![register_tool(tool)] //~ ERROR the `register_tool` attribute is an experimental feature +#![register_attribute_tool(attr_tool)] //~ ERROR the `register_attribute_tool` attribute is an experimental feature +#![register_lint_tool(lint_tool)] //~ ERROR the `register_lint_tool` attribute is an experimental feature fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-register_tool.stderr b/tests/ui/feature-gates/feature-gate-register_tool.stderr index fdb556926f6f7..678ae48a00143 100644 --- a/tests/ui/feature-gates/feature-gate-register_tool.stderr +++ b/tests/ui/feature-gates/feature-gate-register_tool.stderr @@ -8,6 +8,26 @@ LL | #![register_tool(tool)] = help: add `#![feature(register_tool)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 1 previous error +error[E0658]: the `register_attribute_tool` attribute is an experimental feature + --> $DIR/feature-gate-register_tool.rs:2:4 + | +LL | #![register_attribute_tool(attr_tool)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #66079 for more information + = help: add `#![feature(register_tool)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: the `register_lint_tool` attribute is an experimental feature + --> $DIR/feature-gate-register_tool.rs:3:4 + | +LL | #![register_lint_tool(lint_tool)] + | ^^^^^^^^^^^^^^^^^^ + | + = note: see issue #66079 for more information + = help: add `#![feature(register_tool)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0658`. From b0da12057789d097ae0350778af5054078ab54ad Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 27 Jul 2026 20:09:12 +0100 Subject: [PATCH 8/8] Add register_{attribute,lint}_tool test with crate-attr --- tests/ui/tool-attributes/crate-attr.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/ui/tool-attributes/crate-attr.rs b/tests/ui/tool-attributes/crate-attr.rs index c6d7974945f4a..b2fd0dbd2ad2c 100644 --- a/tests/ui/tool-attributes/crate-attr.rs +++ b/tests/ui/tool-attributes/crate-attr.rs @@ -1,5 +1,7 @@ //@ check-pass //@ compile-flags: -Z crate-attr=feature(register_tool) -Z crate-attr=register_tool(foo) +//@ compile-flags: -Z crate-attr=register_attribute_tool(bar) -Z crate-attr=register_lint_tool(baz) -#[allow(foo::bar)] +#[allow(foo::bar, baz::quuxx)] +#[bar::qux] fn main() {}