Rollup merge of #154432 - GuillaumeGomez:try-rm-AttributeLint, r=JonathanBrouwer

Set up API to make it possible to pass closures instead of `AttributeLint`

Part of https://github.com/rust-lang/rust/issues/153099.

This PR sets up the base implementations needed to remove `AttributeLintKind` entirely and migrate two variants as examples.

r? @JonathanBrouwer
This commit is contained in:
Jonathan Brouwer
2026-04-18 11:41:30 +02:00
committed by GitHub
17 changed files with 200 additions and 103 deletions
+1
View File
@@ -4004,6 +4004,7 @@ dependencies = [
"rustc_ast_pretty",
"rustc_data_structures",
"rustc_error_messages",
"rustc_errors",
"rustc_hashes",
"rustc_hir_id",
"rustc_index",
+19 -9
View File
@@ -41,7 +41,7 @@
use rustc_ast::node_id::NodeMap;
use rustc_ast::visit::Visitor;
use rustc_ast::{self as ast, *};
use rustc_attr_parsing::{AttributeParser, Late, OmitDoc};
use rustc_attr_parsing::{AttributeParser, EmitAttribute, Late, OmitDoc};
use rustc_data_structures::fingerprint::Fingerprint;
use rustc_data_structures::fx::FxIndexSet;
use rustc_data_structures::sorted_map::SortedMap;
@@ -52,7 +52,7 @@
use rustc_hir::def::{DefKind, LifetimeRes, Namespace, PartialRes, PerNS, Res};
use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId};
use rustc_hir::definitions::PerParentDisambiguatorState;
use rustc_hir::lints::{AttributeLint, DelayedLint};
use rustc_hir::lints::{AttributeLint, DelayedLint, DynAttribute};
use rustc_hir::{
self as hir, AngleBrackets, ConstArg, GenericArg, HirId, ItemLocalMap, LifetimeSource,
LifetimeSyntax, ParamName, Target, TraitCandidate, find_attr,
@@ -1174,13 +1174,23 @@ fn lower_attrs_vec(
target,
OmitDoc::Lower,
|s| l.lower(s),
|lint_id, span, kind| {
self.delayed_lints.push(DelayedLint::AttributeParsing(AttributeLint {
lint_id,
id: target_hir_id,
span,
kind,
}));
|lint_id, span, kind| match kind {
EmitAttribute::Static(attr_kind) => {
self.delayed_lints.push(DelayedLint::AttributeParsing(AttributeLint {
lint_id,
id: target_hir_id,
span,
kind: attr_kind,
}));
}
EmitAttribute::Dynamic(callback) => {
self.delayed_lints.push(DelayedLint::Dynamic(DynAttribute {
lint_id,
id: target_hir_id,
span,
callback,
}));
}
},
)
}
@@ -1,5 +1,5 @@
use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
use rustc_errors::msg;
use rustc_errors::{Diagnostic, msg};
use rustc_feature::template;
use rustc_hir::Target;
use rustc_hir::attrs::{
@@ -171,12 +171,15 @@ fn parse_single_test_doc_attr_item<S: Stage>(
if let Some(used_span) = self.attribute.no_crate_inject {
let unused_span = path.span();
cx.emit_lint(
cx.emit_dyn_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::UnusedDuplicate {
this: unused_span,
other: used_span,
warning: true,
move |dcx, level| {
rustc_errors::lints::UnusedDuplicate {
this: unused_span,
other: used_span,
warning: true,
}
.into_diag(dcx, level)
},
unused_span,
);
+45 -15
View File
@@ -7,7 +7,8 @@
use private::Sealed;
use rustc_ast::{AttrStyle, MetaItemLit, NodeId};
use rustc_errors::{Diag, Diagnostic, Level, MultiSpan};
use rustc_data_structures::sync::{DynSend, DynSync};
use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, Level, MultiSpan};
use rustc_feature::{AttrSuggestionStyle, AttributeTemplate};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::lints::AttributeLintKind;
@@ -17,7 +18,6 @@
use rustc_session::lint::{Lint, LintId};
use rustc_span::{ErrorGuaranteed, Span, Symbol};
use crate::AttributeParser;
// Glob imports to avoid big, bitrotty import lists
use crate::attributes::allow_unstable::*;
use crate::attributes::autodiff::*;
@@ -66,6 +66,7 @@
ParsedDescription,
};
use crate::target_checking::AllowedTargets;
use crate::{AttributeParser, EmitAttribute};
type GroupType<S> = LazyLock<GroupTypeInner<S>>;
pub(super) struct GroupTypeInner<S: Stage> {
@@ -461,11 +462,34 @@ pub(crate) fn emit_err(&self, diag: impl for<'x> Diagnostic<'x>) -> ErrorGuarant
/// Emit a lint. This method is somewhat special, since lints emitted during attribute parsing
/// must be delayed until after HIR is built. This method will take care of the details of
/// that.
pub(crate) fn emit_lint<M: Into<MultiSpan>>(
pub(crate) fn emit_lint(
&mut self,
lint: &'static Lint,
kind: AttributeLintKind,
span: M,
span: impl Into<MultiSpan>,
) {
self.emit_lint_inner(lint, EmitAttribute::Static(kind), span);
}
/// Emit a lint. This method is somewhat special, since lints emitted during attribute parsing
/// must be delayed until after HIR is built. This method will take care of the details of
/// that.
pub(crate) fn emit_dyn_lint<
F: for<'a> Fn(DiagCtxtHandle<'a>, Level) -> Diag<'a, ()> + DynSend + DynSync + 'static,
>(
&mut self,
lint: &'static Lint,
callback: F,
span: impl Into<MultiSpan>,
) {
self.emit_lint_inner(lint, EmitAttribute::Dynamic(Box::new(callback)), span);
}
fn emit_lint_inner(
&mut self,
lint: &'static Lint,
kind: EmitAttribute,
span: impl Into<MultiSpan>,
) {
if !matches!(
self.stage.should_emit(),
@@ -477,12 +501,15 @@ pub(crate) fn emit_lint<M: Into<MultiSpan>>(
}
pub(crate) fn warn_unused_duplicate(&mut self, used_span: Span, unused_span: Span) {
self.emit_lint(
self.emit_dyn_lint(
rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
AttributeLintKind::UnusedDuplicate {
this: unused_span,
other: used_span,
warning: false,
move |dcx, level| {
rustc_errors::lints::UnusedDuplicate {
this: unused_span,
other: used_span,
warning: false,
}
.into_diag(dcx, level)
},
unused_span,
)
@@ -493,12 +520,15 @@ pub(crate) fn warn_unused_duplicate_future_error(
used_span: Span,
unused_span: Span,
) {
self.emit_lint(
self.emit_dyn_lint(
rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
AttributeLintKind::UnusedDuplicate {
this: unused_span,
other: used_span,
warning: true,
move |dcx, level| {
rustc_errors::lints::UnusedDuplicate {
this: unused_span,
other: used_span,
warning: true,
}
.into_diag(dcx, level)
},
unused_span,
)
@@ -569,7 +599,7 @@ pub struct SharedContext<'p, 'sess, S: Stage> {
/// The second argument of the closure is a [`NodeId`] if `S` is `Early` and a [`HirId`] if `S`
/// is `Late` and is the ID of the syntactical component this attribute was applied to.
pub(crate) emit_lint: &'p mut dyn FnMut(LintId, MultiSpan, AttributeLintKind),
pub(crate) emit_lint: &'p mut dyn FnMut(LintId, MultiSpan, EmitAttribute),
}
/// Context given to every attribute parser during finalization.
@@ -57,3 +57,12 @@ pub(crate) struct MustBeNameOfAssociatedFunction {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag("unsafe attribute used without unsafe")]
pub(crate) struct UnsafeAttrOutsideUnsafeLint {
#[label("usage of unsafe attribute")]
pub span: Span,
#[subdiagnostic]
pub suggestion: Option<crate::session_diagnostics::UnsafeAttrOutsideUnsafeSuggestion>,
}
+28 -6
View File
@@ -3,7 +3,8 @@
use rustc_ast as ast;
use rustc_ast::token::DocFragmentKind;
use rustc_ast::{AttrItemKind, AttrStyle, NodeId, Safety};
use rustc_errors::{DiagCtxtHandle, MultiSpan};
use rustc_data_structures::sync::{DynSend, DynSync};
use rustc_errors::{Diag, DiagCtxtHandle, Level, MultiSpan};
use rustc_feature::{AttributeTemplate, Features};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::lints::AttributeLintKind;
@@ -19,6 +20,15 @@
use crate::session_diagnostics::ParsedDescription;
use crate::{Early, Late, OmitDoc, ShouldEmit};
pub enum EmitAttribute {
Static(AttributeLintKind),
Dynamic(
Box<
dyn for<'a> Fn(DiagCtxtHandle<'a>, Level) -> Diag<'a, ()> + DynSend + DynSync + 'static,
>,
),
}
/// 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> {
@@ -119,7 +129,14 @@ pub fn parse_limited_all(
target,
OmitDoc::Skip,
std::convert::identity,
|lint_id, span, kind| sess.psess.buffer_lint(lint_id.lint, span, target_node_id, kind),
|lint_id, span, kind| match kind {
EmitAttribute::Static(kind) => {
sess.psess.buffer_lint(lint_id.lint, span, target_node_id, kind)
}
EmitAttribute::Dynamic(callback) => {
sess.psess.dyn_buffer_lint(lint_id.lint, span, target_node_id, callback)
}
},
)
}
@@ -199,8 +216,13 @@ pub fn parse_single_args<T, I>(
sess,
stage: Early { emit_errors },
};
let mut emit_lint = |lint_id: LintId, span: MultiSpan, kind: AttributeLintKind| {
sess.psess.buffer_lint(lint_id.lint, span, target_node_id, kind)
let mut emit_lint = |lint_id: LintId, span: MultiSpan, kind: EmitAttribute| match kind {
EmitAttribute::Static(kind) => {
sess.psess.buffer_lint(lint_id.lint, span, target_node_id, kind)
}
EmitAttribute::Dynamic(callback) => {
sess.psess.dyn_buffer_lint(lint_id.lint, span, target_node_id, callback)
}
};
if let Some(safety) = attr_safety {
parser.check_attribute_safety(
@@ -209,7 +231,7 @@ pub fn parse_single_args<T, I>(
safety,
expected_safety,
&mut emit_lint,
)
);
}
let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext {
shared: SharedContext {
@@ -266,7 +288,7 @@ pub fn parse_attribute_list(
target: Target,
omit_doc: OmitDoc,
lower_span: impl Copy + Fn(Span) -> Span,
mut emit_lint: impl FnMut(LintId, MultiSpan, AttributeLintKind),
mut emit_lint: impl FnMut(LintId, MultiSpan, EmitAttribute),
) -> Vec<Attribute> {
let mut attributes = Vec::new();
// We store the attributes we intend to discard at the end of this function in order to
+1 -1
View File
@@ -113,5 +113,5 @@
pub use attributes::cfg_select::*;
pub use attributes::util::{is_builtin_attr, parse_version};
pub use context::{Early, Late, OmitDoc, ShouldEmit};
pub use interface::AttributeParser;
pub use interface::{AttributeParser, EmitAttribute};
pub use session_diagnostics::ParsedDescription;
+14 -9
View File
@@ -1,14 +1,13 @@
use rustc_ast::Safety;
use rustc_errors::MultiSpan;
use rustc_errors::{Diagnostic, MultiSpan};
use rustc_hir::AttrPath;
use rustc_hir::lints::AttributeLintKind;
use rustc_session::lint::LintId;
use rustc_session::lint::builtin::UNSAFE_ATTR_OUTSIDE_UNSAFE;
use rustc_span::Span;
use crate::attributes::AttributeSafety;
use crate::context::Stage;
use crate::{AttributeParser, ShouldEmit};
use crate::{AttributeParser, EmitAttribute, ShouldEmit, errors};
impl<'sess, S: Stage> AttributeParser<'sess, S> {
pub fn check_attribute_safety(
@@ -17,7 +16,7 @@ pub fn check_attribute_safety(
attr_span: Span,
attr_safety: Safety,
expected_safety: AttributeSafety,
emit_lint: &mut impl FnMut(LintId, MultiSpan, AttributeLintKind),
emit_lint: &mut impl FnMut(LintId, MultiSpan, EmitAttribute),
) {
if matches!(self.stage.should_emit(), ShouldEmit::Nothing) {
return;
@@ -80,11 +79,17 @@ pub fn check_attribute_safety(
emit_lint(
LintId::of(UNSAFE_ATTR_OUTSIDE_UNSAFE),
path_span.into(),
AttributeLintKind::UnsafeAttrOutsideUnsafe {
attribute_name_span: path_span,
sugg_spans: not_from_proc_macro
.then(|| (diag_span.shrink_to_lo(), diag_span.shrink_to_hi())),
},
EmitAttribute::Dynamic(Box::new(move |dcx, level| {
errors::UnsafeAttrOutsideUnsafeLint {
span: path_span,
suggestion: not_from_proc_macro
.then(|| (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()))
.map(|(left, right)| {
crate::session_diagnostics::UnsafeAttrOutsideUnsafeSuggestion { left, right }
}),
}
.into_diag(dcx, level)
})),
)
}
}
+23
View File
@@ -7,6 +7,7 @@
use std::path::PathBuf;
use std::thread::panicking;
use rustc_data_structures::sync::{DynSend, DynSync};
use rustc_error_messages::{DiagArgMap, DiagArgName, DiagArgValue, IntoDiagArg};
use rustc_lint_defs::{Applicability, LintExpectationId};
use rustc_macros::{Decodable, Encodable};
@@ -118,6 +119,28 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> {
}
}
impl<'a> Diagnostic<'a, ()>
for Box<
dyn for<'b> FnOnce(DiagCtxtHandle<'b>, Level) -> Diag<'b, ()> + DynSync + DynSend + 'static,
>
{
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
self(dcx, level)
}
}
pub struct DiagCallback<'a>(
pub &'a Box<
dyn for<'b> Fn(DiagCtxtHandle<'b>, Level) -> Diag<'b, ()> + DynSend + DynSync + 'static,
>,
);
impl<'a, 'b> Diagnostic<'a, ()> for DiagCallback<'b> {
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
(self.0)(dcx, level)
}
}
/// Type used to emit diagnostic through a closure instead of implementing the `Diagnostic` trait.
pub struct DiagDecorator<F: FnOnce(&mut Diag<'_, ()>)>(pub F);
+3 -2
View File
@@ -36,8 +36,8 @@
pub use codes::*;
pub use decorate_diag::{BufferedEarlyLint, DecorateDiagCompat, LintBuffer};
pub use diagnostic::{
BugAbort, Diag, DiagDecorator, DiagInner, DiagLocation, DiagStyledString, Diagnostic,
EmissionGuarantee, FatalAbort, StringPart, Subdiag, Subdiagnostic,
BugAbort, Diag, DiagCallback, DiagDecorator, DiagInner, DiagLocation, DiagStyledString,
Diagnostic, EmissionGuarantee, FatalAbort, StringPart, Subdiag, Subdiagnostic,
};
pub use diagnostic_impls::{
DiagSymbolList, ElidedLifetimeInPathSubdiag, ExpectedLifetimeParameter,
@@ -77,6 +77,7 @@
pub mod emitter;
pub mod formatting;
pub mod json;
pub mod lints;
mod lock;
pub mod markdown;
pub mod timings;
+15
View File
@@ -0,0 +1,15 @@
use rustc_macros::Diagnostic;
use rustc_span::Span;
#[derive(Diagnostic)]
#[diag("unused attribute")]
pub struct UnusedDuplicate {
#[suggestion("remove this attribute", code = "", applicability = "machine-applicable")]
pub this: Span,
#[note("attribute also specified here")]
pub other: Span,
#[warning(
"this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!"
)]
pub warning: bool,
}
+1
View File
@@ -13,6 +13,7 @@ rustc_ast = { path = "../rustc_ast" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_error_messages = { path = "../rustc_error_messages" }
rustc_errors = { path = "../rustc_errors" }
rustc_hashes = { path = "../rustc_hashes" }
rustc_hir_id = { path = "../rustc_hir_id" }
rustc_index = { path = "../rustc_index" }
+25 -3
View File
@@ -1,4 +1,6 @@
use rustc_data_structures::sync::{DynSend, DynSync};
use rustc_error_messages::MultiSpan;
use rustc_errors::{Diag, DiagCtxtHandle, Level};
use rustc_lint_defs::LintId;
pub use rustc_lint_defs::{AttributeLintKind, FormatWarning};
@@ -14,13 +16,33 @@
/// AST lowering to be emitted once HIR is built.
#[derive(Debug)]
pub enum DelayedLint {
AttributeParsing(AttributeLint<HirId>),
AttributeParsing(AttributeLint),
Dynamic(DynAttribute),
}
#[derive(Debug)]
pub struct AttributeLint<Id> {
pub struct AttributeLint {
pub lint_id: LintId,
pub id: Id,
pub id: HirId,
pub span: MultiSpan,
pub kind: AttributeLintKind,
}
pub struct DynAttribute {
pub lint_id: LintId,
pub id: HirId,
pub span: MultiSpan,
pub callback: Box<
dyn for<'a> Fn(DiagCtxtHandle<'a>, Level) -> Diag<'a, ()> + DynSend + DynSync + 'static,
>,
}
impl std::fmt::Debug for DynAttribute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DynAttribute")
.field("lint_id", &self.lint_id)
.field("id", &self.id)
.field("span", &self.span)
.finish()
}
}
+7
View File
@@ -13,6 +13,7 @@
use rustc_data_structures::steal::Steal;
use rustc_data_structures::sync::{AppendOnlyIndexVec, FreezeLock, WorkerLocal, par_fns};
use rustc_data_structures::thousands;
use rustc_errors::DiagCallback;
use rustc_errors::timings::TimingSection;
use rustc_expand::base::{ExtCtxt, LintStoreExpand};
use rustc_feature::Features;
@@ -1044,6 +1045,12 @@ pub fn emit_delayed_lints(tcx: TyCtxt<'_>) {
},
);
}
DelayedLint::Dynamic(attribute_lint) => tcx.emit_node_span_lint(
attribute_lint.lint_id.lint,
attribute_lint.id,
attribute_lint.span.clone(),
DiagCallback(&attribute_lint.callback),
),
}
}
}
@@ -35,9 +35,6 @@ pub struct DecorateAttrLint<'a, 'sess, 'tcx> {
impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> {
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
match self.diagnostic {
&AttributeLintKind::UnusedDuplicate { this, other, warning } => {
lints::UnusedDuplicate { this, other, warning }.into_diag(dcx, level)
}
AttributeLintKind::IllFormedAttributeInput { suggestions, docs, help } => {
lints::IllFormedAttributeInput {
num_suggestions: suggestions.len(),
@@ -82,15 +79,6 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
target,
}
.into_diag(dcx, level),
&AttributeLintKind::UnsafeAttrOutsideUnsafe { attribute_name_span, sugg_spans } => {
lints::UnsafeAttrOutsideUnsafeLint {
span: attribute_name_span,
suggestion: sugg_spans.map(|(left, right)| {
lints::UnsafeAttrOutsideUnsafeSuggestion { left, right }
}),
}
.into_diag(dcx, level)
}
&AttributeLintKind::UnexpectedCfgName(name, value) => {
check_cfg::unexpected_cfg_name(self.sess, self.tcx, name, value)
.into_diag(dcx, level)
-31
View File
@@ -3365,19 +3365,6 @@ pub(crate) struct InvalidAttrStyle {
pub target: &'static str,
}
#[derive(Diagnostic)]
#[diag("unused attribute")]
pub(crate) struct UnusedDuplicate {
#[suggestion("remove this attribute", code = "", applicability = "machine-applicable")]
pub this: Span,
#[note("attribute also specified here")]
pub other: Span,
#[warning(
"this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!"
)]
pub warning: bool,
}
#[derive(Diagnostic)]
#[diag("malformed `doc` attribute input")]
#[warning(
@@ -3399,24 +3386,6 @@ pub(crate) struct UnusedDuplicate {
)]
pub(crate) struct ExpectedNameValue;
#[derive(Diagnostic)]
#[diag("unsafe attribute used without unsafe")]
pub(crate) struct UnsafeAttrOutsideUnsafeLint {
#[label("usage of unsafe attribute")]
pub span: Span,
#[subdiagnostic]
pub suggestion: Option<UnsafeAttrOutsideUnsafeSuggestion>,
}
#[derive(Subdiagnostic)]
#[multipart_suggestion("wrap the attribute in `unsafe(...)`", applicability = "machine-applicable")]
pub(crate) struct UnsafeAttrOutsideUnsafeSuggestion {
#[suggestion_part(code = "unsafe(")]
pub left: Span,
#[suggestion_part(code = ")")]
pub right: Span,
}
#[derive(Diagnostic)]
#[diag("doc alias is duplicated")]
pub(crate) struct DocAliasDuplicated {
-9
View File
@@ -654,11 +654,6 @@ pub enum DeprecatedSinceKind {
#[derive(Debug)]
pub enum AttributeLintKind {
UnusedDuplicate {
this: Span,
other: Span,
warning: bool,
},
IllFormedAttributeInput {
suggestions: Vec<String>,
docs: Option<&'static str>,
@@ -682,10 +677,6 @@ pub enum AttributeLintKind {
target: &'static str,
target_span: Span,
},
UnsafeAttrOutsideUnsafe {
attribute_name_span: Span,
sugg_spans: Option<(Span, Span)>,
},
UnexpectedCfgName((Symbol, Span), Option<(Symbol, Span)>),
UnexpectedCfgValue((Symbol, Span), Option<(Symbol, Span)>),
DuplicateDocAlias {