Skip to content

Commit b5ad742

Browse files
committed
refactor: shrink check_attr by moving attribute checks to parsers
Move check_custom_mir validation into rustc_attr_parsing (CustomMirParser). Introduce late_validation with LateValidationContext and validators for: - #[deprecated], #[loop_match], #[const_continue] - diagnostic::on_unimplemented, diagnostic::on_const, diagnostic::do_not_recommend - #[link], #[macro_export] Remove duplicated check_* logic from check_attr pass. Fix crate-root ICE (opt_local_parent), CustomMir types, and directive/impl target handling. Part of #153101. Made-with: Cursor
1 parent a0e206b commit b5ad742

File tree

6 files changed

+480
-233
lines changed

6 files changed

+480
-233
lines changed

compiler/rustc_attr_parsing/src/attributes/prototype.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ use super::{AttributeOrder, OnDuplicate};
99
use crate::attributes::SingleAttributeParser;
1010
use crate::context::{AcceptContext, Stage};
1111
use crate::parser::ArgParser;
12+
use crate::session_diagnostics::{
13+
CustomMirIncompatibleDialectAndPhase, CustomMirPhaseRequiresDialect,
14+
};
1215
use crate::target_checking::AllowedTargets;
1316
use crate::target_checking::Policy::Allow;
1417

@@ -62,7 +65,41 @@ impl<S: Stage> SingleAttributeParser<S> for CustomMirParser {
6265
return None;
6366
}
6467

65-
Some(AttributeKind::CustomMir(dialect, phase, cx.attr_span))
68+
// Validate dialect/phase compatibility (moved from check_attr.rs).
69+
let Some((dialect, dialect_span)) = dialect else {
70+
if let Some((_, phase_span)) = phase {
71+
cx.emit_err(CustomMirPhaseRequiresDialect { attr_span: cx.attr_span, phase_span });
72+
}
73+
return None;
74+
};
75+
76+
match dialect {
77+
MirDialect::Analysis => {
78+
if let Some((MirPhase::Optimized, phase_span)) = phase {
79+
cx.emit_err(CustomMirIncompatibleDialectAndPhase {
80+
dialect,
81+
phase: MirPhase::Optimized,
82+
attr_span: cx.attr_span,
83+
dialect_span,
84+
phase_span,
85+
});
86+
}
87+
}
88+
MirDialect::Built => {
89+
if let Some((phase_val, phase_span)) = phase {
90+
cx.emit_err(CustomMirIncompatibleDialectAndPhase {
91+
dialect,
92+
phase: phase_val,
93+
attr_span: cx.attr_span,
94+
dialect_span,
95+
phase_span,
96+
});
97+
}
98+
}
99+
MirDialect::Runtime => {}
100+
}
101+
102+
Some(AttributeKind::CustomMir(Some((dialect, dialect_span)), phase, cx.attr_span))
66103
}
67104
}
68105

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
//! Late validation of attributes using context only available after HIR is built.
2+
//!
3+
//! Some attribute checks need information that isn't available during parsing (e.g. whether
4+
//! the parent item is a trait impl, or the ABI of a function). This module provides:
5+
//!
6+
//! - **[`LateValidationContext`]**: A context struct built by the compiler pass that has access
7+
//! to HIR/tcx. It holds only plain data (e.g. `Target`, `parent_is_trait_impl`) so this crate
8+
//! stays free of `rustc_middle` and type-checking.
9+
//!
10+
//! - **Per-attribute validators**: Functions that take a reference to the context and return
11+
//! an optional "validation result" (e.g. a span to lint). The pass in `rustc_passes` builds
12+
//! the context, calls the validator, and emits the actual diagnostic/lint.
13+
//!
14+
//! This design keeps validation *logic* in the attribute crate while diagnostics stay in
15+
//! the pass crate. It is part of the [check_attrs cleanup](https://github.com/rust-lang/rust/issues/153101).
16+
17+
use rustc_hir::Target;
18+
use rustc_span::Span;
19+
20+
/// When the attribute is on an expression, describes that expression for validation.
21+
#[derive(Clone, Debug)]
22+
pub struct ExprContext {
23+
pub node_span: Span,
24+
pub is_loop: bool,
25+
pub is_break: bool,
26+
}
27+
28+
/// Context passed to late validators. Built in `rustc_passes` from HIR/tcx.
29+
///
30+
/// Extend this struct when moving checks that need more than `Target` (e.g. `fn_abi`, …).
31+
#[derive(Clone, Debug)]
32+
pub struct LateValidationContext {
33+
/// The syntactic target the attribute was applied to.
34+
pub target: Target,
35+
/// Whether the parent item is a trait impl (e.g. `impl SomeTrait for T`).
36+
/// Used e.g. for `#[deprecated]` on trait impl members.
37+
pub parent_is_trait_impl: bool,
38+
/// When `target` is `Expression`, this is set with the expression's span and kind.
39+
pub expr_context: Option<ExprContext>,
40+
/// When `target` is `Impl { of_trait: true }`, whether the impl is `const`.
41+
pub impl_is_const: Option<bool>,
42+
/// When `target` is `Impl`, whether this is a trait impl (vs inherent).
43+
pub impl_of_trait: Option<bool>,
44+
/// When `target` is `ForeignMod`, whether the extern block has ABI = Rust (link invalid).
45+
pub foreign_mod_abi_is_rust: Option<bool>,
46+
/// When `target` is `MacroDef`, whether the macro is a decl macro (macro 2.0).
47+
pub macro_export_is_decl_macro: Option<bool>,
48+
}
49+
50+
/// Result of late validation for `#[deprecated]`: emit a lint when present.
51+
#[derive(Clone, Debug)]
52+
pub struct DeprecatedValidation {
53+
pub attr_span: Span,
54+
}
55+
56+
/// Validates `#[deprecated]` in contexts where it has no effect (e.g. on trait impl members).
57+
///
58+
/// Returns `Some(...)` when the pass should emit `DeprecatedAnnotationHasNoEffect`.
59+
/// `attr_span` is the span of the `#[deprecated]` attribute.
60+
pub fn validate_deprecated(
61+
ctx: &LateValidationContext,
62+
attr_span: Span,
63+
) -> Option<DeprecatedValidation> {
64+
let has_no_effect =
65+
matches!(ctx.target, Target::AssocConst | Target::Method(..) | Target::AssocTy)
66+
&& ctx.parent_is_trait_impl;
67+
68+
if has_no_effect { Some(DeprecatedValidation { attr_span }) } else { None }
69+
}
70+
71+
/// Result of late validation for `#[loop_match]`: emit error when not on a loop.
72+
#[derive(Clone, Debug)]
73+
pub struct LoopMatchValidation {
74+
pub attr_span: Span,
75+
pub node_span: Span,
76+
}
77+
78+
/// Validates `#[loop_match]`: must be applied to a loop expression.
79+
pub fn validate_loop_match(
80+
ctx: &LateValidationContext,
81+
attr_span: Span,
82+
) -> Option<LoopMatchValidation> {
83+
if !matches!(ctx.target, Target::Expression) {
84+
return None;
85+
}
86+
let Some(expr) = &ctx.expr_context else {
87+
return None;
88+
};
89+
if expr.is_loop {
90+
None
91+
} else {
92+
Some(LoopMatchValidation { attr_span, node_span: expr.node_span })
93+
}
94+
}
95+
96+
/// Result of late validation for `#[const_continue]`: emit error when not on a break.
97+
#[derive(Clone, Debug)]
98+
pub struct ConstContinueValidation {
99+
pub attr_span: Span,
100+
pub node_span: Span,
101+
}
102+
103+
/// Validates `#[const_continue]`: must be applied to a break expression.
104+
pub fn validate_const_continue(
105+
ctx: &LateValidationContext,
106+
attr_span: Span,
107+
) -> Option<ConstContinueValidation> {
108+
if !matches!(ctx.target, Target::Expression) {
109+
return None;
110+
}
111+
let Some(expr) = &ctx.expr_context else {
112+
return None;
113+
};
114+
if expr.is_break {
115+
None
116+
} else {
117+
Some(ConstContinueValidation { attr_span, node_span: expr.node_span })
118+
}
119+
}
120+
121+
// --- diagnostic::on_unimplemented (target-only) ---
122+
123+
/// Result: emit lint when `#[diagnostic::on_unimplemented]` is not on a trait.
124+
#[derive(Clone, Debug)]
125+
pub struct OnUnimplementedValidation {
126+
pub attr_span: Span,
127+
}
128+
129+
/// Validates that `#[diagnostic::on_unimplemented]` is only applied to trait definitions.
130+
pub fn validate_diagnostic_on_unimplemented(
131+
ctx: &LateValidationContext,
132+
attr_span: Span,
133+
) -> Option<OnUnimplementedValidation> {
134+
if matches!(ctx.target, Target::Trait) {
135+
None
136+
} else {
137+
Some(OnUnimplementedValidation { attr_span })
138+
}
139+
}
140+
141+
// --- diagnostic::on_const ---
142+
143+
/// What went wrong with `#[diagnostic::on_const]`.
144+
#[derive(Clone, Debug)]
145+
pub enum OnConstValidation {
146+
/// Not on a trait impl.
147+
WrongTarget { item_span: Span },
148+
/// On a const trait impl (only non-const allowed).
149+
ConstImpl { item_span: Span },
150+
}
151+
152+
/// Validates `#[diagnostic::on_const]`: only on non-const trait impls.
153+
pub fn validate_diagnostic_on_const(
154+
ctx: &LateValidationContext,
155+
_attr_span: Span,
156+
item_span: Span,
157+
) -> Option<OnConstValidation> {
158+
if ctx.target == (Target::Impl { of_trait: true }) {
159+
match ctx.impl_is_const {
160+
Some(true) => Some(OnConstValidation::ConstImpl { item_span }),
161+
Some(false) => None,
162+
None => None, // e.g. foreign item, skip
163+
}
164+
} else {
165+
Some(OnConstValidation::WrongTarget { item_span })
166+
}
167+
}
168+
169+
// --- diagnostic::do_not_recommend ---
170+
171+
/// Result: emit lint when `#[diagnostic::do_not_recommend]` is not on a trait impl.
172+
#[derive(Clone, Debug)]
173+
pub struct DoNotRecommendValidation {
174+
pub attr_span: Span,
175+
}
176+
177+
/// Validates that `#[diagnostic::do_not_recommend]` is only on trait implementations.
178+
pub fn validate_do_not_recommend(
179+
ctx: &LateValidationContext,
180+
attr_span: Span,
181+
) -> Option<DoNotRecommendValidation> {
182+
let on_trait_impl =
183+
matches!(ctx.target, Target::Impl { of_trait: true }) && ctx.impl_of_trait == Some(true);
184+
if on_trait_impl { None } else { Some(DoNotRecommendValidation { attr_span }) }
185+
}
186+
187+
// --- macro_export ---
188+
189+
/// Result: emit lint when `#[macro_export]` is on a decl macro (macro 2.0).
190+
#[derive(Clone, Debug)]
191+
pub struct MacroExportValidation {
192+
pub attr_span: Span,
193+
}
194+
195+
/// Validates `#[macro_export]`: not allowed on decl macros.
196+
pub fn validate_macro_export(
197+
ctx: &LateValidationContext,
198+
attr_span: Span,
199+
) -> Option<MacroExportValidation> {
200+
if ctx.target == Target::MacroDef && ctx.macro_export_is_decl_macro == Some(true) {
201+
Some(MacroExportValidation { attr_span })
202+
} else {
203+
None
204+
}
205+
}
206+
207+
// --- link ---
208+
209+
/// Result: emit lint when `#[link]` is used in the wrong place.
210+
#[derive(Clone, Debug)]
211+
pub struct LinkValidation {
212+
pub attr_span: Span,
213+
/// If wrong target, pass the item span for the diagnostic.
214+
pub wrong_target_span: Option<Span>,
215+
}
216+
217+
/// Validates `#[link]`: only on foreign modules, and not on `extern "Rust"`.
218+
pub fn validate_link(
219+
ctx: &LateValidationContext,
220+
attr_span: Span,
221+
target_span: Span,
222+
) -> Option<LinkValidation> {
223+
let valid =
224+
matches!(ctx.target, Target::ForeignMod) && ctx.foreign_mod_abi_is_rust != Some(true);
225+
if valid {
226+
None
227+
} else {
228+
Some(LinkValidation {
229+
attr_span,
230+
wrong_target_span: (ctx.target != Target::ForeignMod).then_some(target_span),
231+
})
232+
}
233+
}
234+
235+
// ---------------------------------------------------------------------------
236+
// Next steps (issue #153101)
237+
// ---------------------------------------------------------------------------
238+
//
239+
// Checks that could be moved next with small context extensions:
240+
//
241+
// - check_naked: add `fn_abi: Option<ExternAbi>` (or `naked_requires_feature: bool`); validator
242+
// emits when target is Fn/Method and ABI is rustic without feature.
243+
// - check_non_exhaustive: add `struct_has_default_field_values: bool` (or similar); validator
244+
// when on struct with default values.
245+
//
246+
// Checks that need cross-attribute or argument-dependent infra (see issue):
247+
//
248+
// - check_ffi_pure, check_track_caller, check_macro_only_attr, check_rustc_pub_transparent, etc.:
249+
// need "requires other attrs" / "conflicts with" (2).
250+
// - check_sanitize, check_inline (partial), check_doc_attrs: need argument-dependent allowed
251+
// targets (3).
252+
//
253+
// check_proc_macro: cannot move (requires type information).

compiler/rustc_attr_parsing/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ mod interface;
9999
pub mod parser;
100100

101101
mod early_parsed;
102+
pub mod late_validation;
102103
mod safety;
103104
mod session_diagnostics;
104105
mod target_checking;

compiler/rustc_attr_parsing/src/session_diagnostics.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rustc_errors::{
77
};
88
use rustc_feature::AttributeTemplate;
99
use rustc_hir::AttrPath;
10+
use rustc_hir::attrs::{MirDialect, MirPhase};
1011
use rustc_macros::{Diagnostic, Subdiagnostic};
1112
use rustc_span::{Span, Symbol};
1213
use rustc_target::spec::TargetTuple;
@@ -474,6 +475,28 @@ pub(crate) struct InvalidTarget {
474475
pub only: &'static str,
475476
}
476477

478+
#[derive(Diagnostic)]
479+
#[diag("`dialect` key required")]
480+
pub(crate) struct CustomMirPhaseRequiresDialect {
481+
#[primary_span]
482+
pub attr_span: Span,
483+
#[label("`phase` argument requires a `dialect` argument")]
484+
pub phase_span: Span,
485+
}
486+
487+
#[derive(Diagnostic)]
488+
#[diag("the {$dialect} dialect is not compatible with the {$phase} phase")]
489+
pub(crate) struct CustomMirIncompatibleDialectAndPhase {
490+
pub dialect: MirDialect,
491+
pub phase: MirPhase,
492+
#[primary_span]
493+
pub attr_span: Span,
494+
#[label("this dialect...")]
495+
pub dialect_span: Span,
496+
#[label("... is not compatible with this phase")]
497+
pub phase_span: Span,
498+
}
499+
477500
#[derive(Diagnostic)]
478501
#[diag("invalid alignment value: {$error_part}", code = E0589)]
479502
pub(crate) struct InvalidAlignmentValue {

0 commit comments

Comments
 (0)