|
| 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). |
0 commit comments