diff --git a/compiler/rustc_attr_parsing/src/attributes/prototype.rs b/compiler/rustc_attr_parsing/src/attributes/prototype.rs index ac50fe33839d2..f1aa09ee53635 100644 --- a/compiler/rustc_attr_parsing/src/attributes/prototype.rs +++ b/compiler/rustc_attr_parsing/src/attributes/prototype.rs @@ -9,6 +9,7 @@ use super::{AttributeOrder, OnDuplicate}; use crate::attributes::SingleAttributeParser; use crate::context::{AcceptContext, Stage}; use crate::parser::ArgParser; +use crate::session_diagnostics; use crate::target_checking::AllowedTargets; use crate::target_checking::Policy::Allow; @@ -58,6 +59,8 @@ impl SingleAttributeParser for CustomMirParser { let dialect = parse_dialect(cx, dialect, &mut failed); let phase = parse_phase(cx, phase, &mut failed); + check_custom_mir(cx, dialect, phase, &mut failed); + if failed { return None; } @@ -138,3 +141,51 @@ fn parse_phase( Some((phase, span)) } + +fn check_custom_mir( + cx: &mut AcceptContext<'_, '_, S>, + dialect: Option<(MirDialect, Span)>, + phase: Option<(MirPhase, Span)>, + failed: &mut bool, +) { + let attr_span = cx.attr_span; + let Some((dialect, dialect_span)) = dialect else { + if let Some((_, phase_span)) = phase { + *failed = true; + cx.emit_err(session_diagnostics::CustomMirPhaseRequiresDialect { + attr_span, + phase_span, + }); + } + return; + }; + + match dialect { + MirDialect::Analysis => { + if let Some((MirPhase::Optimized, phase_span)) = phase { + *failed = true; + cx.emit_err(session_diagnostics::CustomMirIncompatibleDialectAndPhase { + dialect, + phase: MirPhase::Optimized, + attr_span, + dialect_span, + phase_span, + }); + } + } + + MirDialect::Built => { + if let Some((phase, phase_span)) = phase { + *failed = true; + cx.emit_err(session_diagnostics::CustomMirIncompatibleDialectAndPhase { + dialect, + phase, + attr_span, + dialect_span, + phase_span, + }); + } + } + MirDialect::Runtime => {} + } +} diff --git a/compiler/rustc_attr_parsing/src/late_validation.rs b/compiler/rustc_attr_parsing/src/late_validation.rs new file mode 100644 index 0000000000000..59e608a613aae --- /dev/null +++ b/compiler/rustc_attr_parsing/src/late_validation.rs @@ -0,0 +1,282 @@ +//! Late validation of attributes using context only available after HIR is built. +//! +//! Some attribute checks need information that isn't available during parsing (e.g. whether +//! the parent item is a trait impl, or the ABI of a function). This module provides: +//! +//! - **[`LateValidationContext`]**: A context struct built by the compiler pass that has access +//! to HIR/tcx. It holds only plain data (e.g. `Target`, `parent_is_trait_impl`) so this crate +//! stays free of `rustc_middle` and type-checking. +//! +//! - **Per-attribute validators**: Functions that take a reference to the context and return +//! an optional "validation result" (e.g. a span to lint). The pass in `rustc_passes` builds +//! the context, calls the validator, and emits the actual diagnostic/lint. +//! +//! This design keeps validation *logic* in the attribute crate while diagnostics stay in +//! the pass crate. It is part of the [check_attrs cleanup](https://github.com/rust-lang/rust/issues/153101). +//! +//! **Note:** Several of these checks ideally run with full *target* context during parsing, not in a +//! later pass. That needs plumbing from AST/HIR into the parser pipeline (see the issue linked +//! above). Until then, this module centralizes predicates that still must run after HIR is built. + +use rustc_hir::Target; +use rustc_hir::attrs::diagnostic::Directive; +use rustc_span::{Span, Symbol}; + +/// When the attribute is on an expression, describes that expression for validation. +#[derive(Clone, Debug)] +pub struct ExprContext { + pub node_span: Span, + pub is_loop: bool, + pub is_break: bool, +} + +/// Context passed to late validators. Built in `rustc_passes` from HIR/tcx. +/// +/// Extend this struct when moving checks that need more than `Target` (e.g. `fn_abi`, …). +#[derive(Clone, Debug)] +pub struct LateValidationContext { + /// The syntactic target the attribute was applied to. + pub target: Target, + /// Whether the parent item is a trait impl (e.g. `impl SomeTrait for T`). + /// Used e.g. for `#[deprecated]` on trait impl members. + pub parent_is_trait_impl: bool, + /// When `target` is `Expression`, this is set with the expression's span and kind. + pub expr_context: Option, + /// When `target` is `Impl { of_trait: true }`, whether the impl is `const`. + pub impl_is_const: Option, + /// When `target` is `Impl`, whether this is a trait impl (vs inherent). + pub impl_of_trait: Option, + /// When `target` is `ForeignMod`, whether the extern block has ABI = Rust (link invalid). + pub foreign_mod_abi_is_rust: Option, + /// When `target` is `MacroDef`, whether the macro is a decl macro (macro 2.0). + pub macro_export_is_decl_macro: Option, +} + +/// Result of late validation for `#[deprecated]`: emit a lint when present. +#[derive(Clone, Debug)] +pub struct DeprecatedValidation { + pub attr_span: Span, +} + +/// Validates `#[deprecated]` in contexts where it has no effect (e.g. on trait impl members). +/// +/// Returns `Some(...)` when the pass should emit `DeprecatedAnnotationHasNoEffect`. +/// `attr_span` is the span of the `#[deprecated]` attribute. +pub fn validate_deprecated( + ctx: &LateValidationContext, + attr_span: Span, +) -> Option { + let has_no_effect = + matches!(ctx.target, Target::AssocConst | Target::Method(..) | Target::AssocTy) + && ctx.parent_is_trait_impl; + + if has_no_effect { Some(DeprecatedValidation { attr_span }) } else { None } +} + +/// Result of late validation for `#[loop_match]`: emit error when not on a loop. +#[derive(Clone, Debug)] +pub struct LoopMatchValidation { + pub attr_span: Span, + pub node_span: Span, +} + +/// Validates `#[loop_match]`: must be applied to a loop expression. +pub fn validate_loop_match( + ctx: &LateValidationContext, + attr_span: Span, +) -> Option { + if !matches!(ctx.target, Target::Expression) { + return None; + } + let Some(expr) = &ctx.expr_context else { + return None; + }; + if expr.is_loop { + None + } else { + Some(LoopMatchValidation { attr_span, node_span: expr.node_span }) + } +} + +/// Result of late validation for `#[const_continue]`: emit error when not on a break. +#[derive(Clone, Debug)] +pub struct ConstContinueValidation { + pub attr_span: Span, + pub node_span: Span, +} + +/// Validates `#[const_continue]`: must be applied to a break expression. +pub fn validate_const_continue( + ctx: &LateValidationContext, + attr_span: Span, +) -> Option { + if !matches!(ctx.target, Target::Expression) { + return None; + } + let Some(expr) = &ctx.expr_context else { + return None; + }; + if expr.is_break { + None + } else { + Some(ConstContinueValidation { attr_span, node_span: expr.node_span }) + } +} + +// --- diagnostic::on_unimplemented (target-only) --- + +/// Result: emit lint when `#[diagnostic::on_unimplemented]` is not on a trait. +#[derive(Clone, Debug)] +pub struct OnUnimplementedValidation { + pub attr_span: Span, +} + +/// Validates that `#[diagnostic::on_unimplemented]` is only applied to trait definitions. +pub fn validate_diagnostic_on_unimplemented( + ctx: &LateValidationContext, + attr_span: Span, +) -> Option { + if matches!(ctx.target, Target::Trait) { + None + } else { + Some(OnUnimplementedValidation { attr_span }) + } +} + +/// Invokes `on_unknown` for each `{ident}` in `directive`'s format strings that is not a declared +/// type (or type-alias) generic parameter of the surrounding item. Lifetimes in the generic list +/// are ignored by the caller via `is_declared_type_param`. +/// +/// The pass supplies `is_declared_type_param` using HIR; parsing cannot do this without the item. +pub fn for_each_unknown_diagnostic_format_param( + directive: &Directive, + is_declared_type_param: impl Fn(Symbol) -> bool, + mut on_unknown: impl FnMut(Symbol, Span), +) { + directive.visit_params(&mut |argument_name, span| { + if !is_declared_type_param(argument_name) { + on_unknown(argument_name, span); + } + }); +} + +// --- diagnostic::on_move (target-only; format literals use [`for_each_unknown_diagnostic_format_param`]) --- + +/// Result: emit lint when `#[diagnostic::on_move]` is not on an ADT definition. +#[derive(Clone, Debug)] +pub struct OnMoveTargetValidation { + pub attr_span: Span, +} + +/// Validates that `#[diagnostic::on_move]` is only applied to enums, structs, or unions. +pub fn validate_diagnostic_on_move_target( + ctx: &LateValidationContext, + attr_span: Span, +) -> Option { + if matches!(ctx.target, Target::Enum | Target::Struct | Target::Union) { + None + } else { + Some(OnMoveTargetValidation { attr_span }) + } +} + +// --- diagnostic::on_const --- + +/// What went wrong with `#[diagnostic::on_const]`. +#[derive(Clone, Debug)] +pub enum OnConstValidation { + /// Not on a trait impl. + WrongTarget { item_span: Span }, + /// On a const trait impl (only non-const allowed). + ConstImpl { item_span: Span }, +} + +/// Validates `#[diagnostic::on_const]`: only on non-const trait impls. +pub fn validate_diagnostic_on_const( + ctx: &LateValidationContext, + _attr_span: Span, + item_span: Span, +) -> Option { + if ctx.target == (Target::Impl { of_trait: true }) { + match ctx.impl_is_const { + Some(true) => Some(OnConstValidation::ConstImpl { item_span }), + Some(false) => None, + None => None, // e.g. foreign item, skip + } + } else { + Some(OnConstValidation::WrongTarget { item_span }) + } +} + +// --- diagnostic::do_not_recommend --- + +/// Result: emit lint when `#[diagnostic::do_not_recommend]` is not on a trait impl. +#[derive(Clone, Debug)] +pub struct DoNotRecommendValidation { + pub attr_span: Span, +} + +/// Validates that `#[diagnostic::do_not_recommend]` is only on trait implementations. +pub fn validate_do_not_recommend( + ctx: &LateValidationContext, + attr_span: Span, +) -> Option { + let on_trait_impl = + matches!(ctx.target, Target::Impl { of_trait: true }) && ctx.impl_of_trait == Some(true); + if on_trait_impl { None } else { Some(DoNotRecommendValidation { attr_span }) } +} + +// --- macro_export --- + +/// Result: emit lint when `#[macro_export]` is on a decl macro (macro 2.0). +#[derive(Clone, Debug)] +pub struct MacroExportValidation { + pub attr_span: Span, +} + +/// Validates `#[macro_export]`: not allowed on decl macros. +pub fn validate_macro_export( + ctx: &LateValidationContext, + attr_span: Span, +) -> Option { + if ctx.target == Target::MacroDef && ctx.macro_export_is_decl_macro == Some(true) { + Some(MacroExportValidation { attr_span }) + } else { + None + } +} + +// --- link --- + +/// Result: emit lint when `#[link]` is used in the wrong place. +#[derive(Clone, Debug)] +pub struct LinkValidation { + pub attr_span: Span, + /// If wrong target, pass the item span for the diagnostic. + pub wrong_target_span: Option, +} + +/// Validates `#[link]`: only on foreign modules, and not on `extern "Rust"`. +pub fn validate_link( + ctx: &LateValidationContext, + attr_span: Span, + target_span: Span, +) -> Option { + let valid = + matches!(ctx.target, Target::ForeignMod) && ctx.foreign_mod_abi_is_rust != Some(true); + if valid { + None + } else { + Some(LinkValidation { + attr_span, + wrong_target_span: (ctx.target != Target::ForeignMod).then_some(target_span), + }) + } +} + +// --------------------------------------------------------------------------- +// Further moves (issue #153101) +// --------------------------------------------------------------------------- +// +// See the issue for remaining `check_attr` checks (naked, non_exhaustive, cross-attribute rules, +// argument-dependent targets, etc.). `check_proc_macro` cannot move (needs type information). diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index db09572cc56b3..5a4d6845ae813 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -99,6 +99,7 @@ mod interface; pub mod parser; mod early_parsed; +pub mod late_validation; mod safety; mod session_diagnostics; mod target_checking; diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 751b8f8646ab0..f82aeeb0341c5 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -7,6 +7,7 @@ use rustc_errors::{ }; use rustc_feature::AttributeTemplate; use rustc_hir::AttrPath; +use rustc_hir::attrs::{MirDialect, MirPhase}; use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_span::{Span, Symbol}; use rustc_target::spec::TargetTuple; @@ -467,6 +468,28 @@ pub(crate) struct InvalidTarget { pub only: &'static str, } +#[derive(Diagnostic)] +#[diag("`dialect` key required")] +pub(crate) struct CustomMirPhaseRequiresDialect { + #[primary_span] + pub attr_span: Span, + #[label("`phase` argument requires a `dialect` argument")] + pub phase_span: Span, +} + +#[derive(Diagnostic)] +#[diag("the {$dialect} dialect is not compatible with the {$phase} phase")] +pub(crate) struct CustomMirIncompatibleDialectAndPhase { + pub dialect: MirDialect, + pub phase: MirPhase, + #[primary_span] + pub attr_span: Span, + #[label("this dialect...")] + pub dialect_span: Span, + #[label("... is not compatible with this phase")] + pub phase_span: Span, +} + #[derive(Diagnostic)] #[diag("invalid alignment value: {$error_part}", code = E0589)] pub(crate) struct InvalidAlignmentValue { diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 7c66b3f844691..acfe6711f4cac 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -27,7 +27,8 @@ impl Directive { /// generic of the item. If not then `visit` must issue a diagnostic. /// /// We can't check this while parsing the attribute because `rustc_attr_parsing` doesn't have - /// access to the item an attribute is on. Instead we later call this function in `check_attr`. + /// access to the item an attribute is on. Instead the compiler calls this from + /// `late_validation::for_each_unknown_diagnostic_format_param` (built from HIR in `check_attr`). pub fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { if let Some(condition) = &self.condition { condition.visit_params(visit); diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index e495088c55649..715befdadfbf3 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -11,7 +11,7 @@ use std::slice; use rustc_abi::ExternAbi; use rustc_ast::{AttrStyle, MetaItemKind, ast}; -use rustc_attr_parsing::{AttributeParser, Late}; +use rustc_attr_parsing::{AttributeParser, Late, late_validation}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::thin_vec::ThinVec; use rustc_data_structures::unord::UnordMap; @@ -20,10 +20,9 @@ use rustc_feature::{ ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, }; -use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::{ AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr, - MirDialect, MirPhase, ReprAttr, SanitizerSet, + ReprAttr, SanitizerSet, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalModDefId; @@ -98,6 +97,16 @@ fn target_from_impl_item<'tcx>(tcx: TyCtxt<'tcx>, impl_item: &hir::ImplItem<'_>) } } +/// `true` when `name` is a non-lifetime generic parameter declared on `generics`. +/// +/// Used for `{ident}` placeholders in `#[diagnostic::on_unimplemented]` / `#[diagnostic::on_move]`. +fn generics_has_type_param_named(generics: &hir::Generics<'_>, name: Symbol) -> bool { + generics.params.iter().any(|p| { + !matches!(p.kind, GenericParamKind::Lifetime { .. }) + && matches!(p.name, ParamName::Plain(n) if n.name == name) + }) +} + #[derive(Clone, Copy)] enum ItemLike<'tcx> { Item(&'tcx Item<'tcx>), @@ -144,6 +153,69 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ) { let mut seen = FxHashMap::default(); let attrs = self.tcx.hir_attrs(hir_id); + + let late_ctx = late_validation::LateValidationContext { + target, + parent_is_trait_impl: self.tcx.opt_local_parent(hir_id.owner.def_id).is_some_and( + |parent| self.tcx.def_kind(parent) == (DefKind::Impl { of_trait: true }), + ), + expr_context: if matches!(target, Target::Expression) { + let expr = self.tcx.hir_expect_expr(hir_id); + Some(late_validation::ExprContext { + node_span: self.tcx.hir_span(hir_id), + is_loop: matches!(expr.kind, hir::ExprKind::Loop(..)), + is_break: matches!(expr.kind, hir::ExprKind::Break(..)), + }) + } else { + None + }, + impl_is_const: if target == (Target::Impl { of_trait: true }) { + item.and_then(|i| match i { + ItemLike::Item(it) => match it.kind { + ItemKind::Impl(impl_) => Some(impl_.constness == Constness::Const), + _ => None, + }, + ItemLike::ForeignItem => None, + }) + } else { + None + }, + impl_of_trait: if matches!(target, Target::Impl { .. }) { + item.map(|i| match i { + ItemLike::Item(it) => match it.kind { + ItemKind::Impl(impl_) => impl_.of_trait.is_some(), + _ => false, + }, + ItemLike::ForeignItem => false, + }) + } else { + None + }, + foreign_mod_abi_is_rust: if target == Target::ForeignMod { + item.and_then(|i| match i { + ItemLike::Item(it) => match it.kind { + ItemKind::ForeignMod { abi, .. } => Some(matches!(abi, ExternAbi::Rust)), + _ => None, + }, + ItemLike::ForeignItem => None, + }) + } else { + None + }, + macro_export_is_decl_macro: if target == Target::MacroDef { + let node = self.tcx.hir_node(hir_id); + if let Node::Item(it) = node + && let ItemKind::Macro(_, macro_def, _) = it.kind + { + Some(!macro_def.macro_rules) + } else { + None + } + } else { + None + }, + }; + for attr in attrs { let mut style = None; match attr { @@ -171,10 +243,10 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.check_inline(hir_id, *attr_span, kind, target) } Attribute::Parsed(AttributeKind::LoopMatch(attr_span)) => { - self.check_loop_match(hir_id, *attr_span, target) + self.check_loop_match(hir_id, *attr_span, target); } Attribute::Parsed(AttributeKind::ConstContinue(attr_span)) => { - self.check_const_continue(hir_id, *attr_span, target) + self.check_const_continue(hir_id, *attr_span, target); } Attribute::Parsed(AttributeKind::AllowInternalUnsafe(attr_span) | AttributeKind::AllowInternalUnstable(.., attr_span)) => { self.check_macro_only_attr(*attr_span, span, target, attrs) @@ -183,7 +255,14 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.check_rustc_allow_const_fn_unstable(hir_id, *first_span, span, target) } Attribute::Parsed(AttributeKind::Deprecated { span: attr_span, .. }) => { - self.check_deprecated(hir_id, *attr_span, target) + if let Some(v) = late_validation::validate_deprecated(&late_ctx, *attr_span) { + self.tcx.emit_node_span_lint( + UNUSED_ATTRIBUTES, + hir_id, + v.attr_span, + errors::DeprecatedAnnotationHasNoEffect { span: v.attr_span }, + ); + } } Attribute::Parsed(AttributeKind::TargetFeature{ attr_span, ..}) => { self.check_target_feature(hir_id, *attr_span, target, attrs) @@ -212,18 +291,55 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Parsed(AttributeKind::MayDangle(attr_span)) => { self.check_may_dangle(hir_id, *attr_span) } - &Attribute::Parsed(AttributeKind::CustomMir(dialect, phase, attr_span)) => { - self.check_custom_mir(dialect, phase, attr_span) + &Attribute::Parsed(AttributeKind::CustomMir(..)) => { + // Validation (dialect/phase compatibility) is done in the attribute parser. } + fn check_loop_match(&self, hir_id: HirId, attr_span: Span, target: Target) { + let node_span = self.tcx.hir_span(hir_id); + if !matches!(target, Target::Expression) { + return; // Handled in target checking during attr parse + } + if !matches!(self.tcx.hir_expect_expr(hir_id).kind, hir::ExprKind::Loop(..)) { + self.dcx().emit_err(errors::LoopMatchAttr { attr_span, node_span }); + } + } + + fn check_const_continue(&self, hir_id: HirId, attr_span: Span, target: Target) { + let node_span = self.tcx.hir_span(hir_id); + if !matches!(target, Target::Expression) { + return; // Handled in target checking during attr parse + } + if !matches!(self.tcx.hir_expect_expr(hir_id).kind, hir::ExprKind::Break(..)) { + self.dcx().emit_err(errors::ConstContinueAttr { attr_span, node_span }); + } + } &Attribute::Parsed(AttributeKind::Sanitize { on_set, off_set, rtsan: _, span: attr_span}) => { self.check_sanitize(attr_span, on_set | off_set, span, target); }, Attribute::Parsed(AttributeKind::Link(_, attr_span)) => { - self.check_link(hir_id, *attr_span, span, target) - }, + if let Some(v) = + late_validation::validate_link(&late_ctx, *attr_span, span) + { + self.tcx.emit_node_span_lint( + UNUSED_ATTRIBUTES, + hir_id, + v.attr_span, + errors::Link { span: v.wrong_target_span }, + ); + } + } Attribute::Parsed(AttributeKind::MacroExport { span, .. }) => { - self.check_macro_export(hir_id, *span, target) - }, + if let Some(v) = + late_validation::validate_macro_export(&late_ctx, *span) + { + self.tcx.emit_node_span_lint( + UNUSED_ATTRIBUTES, + hir_id, + v.attr_span, + errors::MacroExport::OnDeclMacro, + ); + } + } Attribute::Parsed(AttributeKind::RustcLegacyConstGenerics{attr_span, fn_indexes}) => { self.check_rustc_legacy_const_generics(item, *attr_span, fn_indexes) }, @@ -234,12 +350,115 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Parsed(AttributeKind::RustcMustImplementOneOf { attr_span, fn_names }) => { self.check_rustc_must_implement_one_of(*attr_span, fn_names, hir_id,target) }, - Attribute::Parsed(AttributeKind::DoNotRecommend{attr_span}) => {self.check_do_not_recommend(*attr_span, hir_id, target, item)}, - Attribute::Parsed(AttributeKind::OnUnimplemented{span, directive}) => {self.check_diagnostic_on_unimplemented(*span, hir_id, target,directive.as_deref())}, - Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)} + Attribute::Parsed(AttributeKind::DoNotRecommend { attr_span }) => { + if let Some(v) = + late_validation::validate_do_not_recommend(&late_ctx, *attr_span) + { + self.tcx.emit_node_span_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + v.attr_span, + errors::IncorrectDoNotRecommendLocation, + ); + } + } + Attribute::Parsed(AttributeKind::OnUnimplemented { span, directive }) => { + if let Some(v) = + late_validation::validate_diagnostic_on_unimplemented(&late_ctx, *span) + { + self.tcx.emit_node_span_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + v.attr_span, + DiagnosticOnUnimplementedOnlyForTraits, + ); + } + if let Some(directive) = directive.as_deref() { + if let Node::Item(Item { + kind: ItemKind::Trait(_, _, _, trait_name, generics, _, _), + .. + }) = self.tcx.hir_node(hir_id) + { + let is_rustc_attr = directive.is_rustc_attr; + late_validation::for_each_unknown_diagnostic_format_param( + directive, + |name| generics_has_type_param_named(generics, name), + |argument_name, sp| { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + hir_id, + sp, + errors::UnknownFormatParameterForOnUnimplementedAttr { + argument_name, + trait_name: *trait_name, + help: !is_rustc_attr, + }, + ); + }, + ); + } + } + } + Attribute::Parsed(AttributeKind::OnConst { span, .. }) => { + let item_span = self.tcx.hir_span(hir_id); + if let Some(v) = + late_validation::validate_diagnostic_on_const(&late_ctx, *span, item_span) + { + match v { + late_validation::OnConstValidation::WrongTarget { item_span } => { + self.tcx.emit_node_span_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + *span, + DiagnosticOnConstOnlyForTraitImpls { item_span }, + ); + } + late_validation::OnConstValidation::ConstImpl { item_span } => { + self.tcx.emit_node_span_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + *span, + DiagnosticOnConstOnlyForNonConstTraitImpls { item_span }, + ); + } + } + } + } Attribute::Parsed(AttributeKind::OnMove { span, directive }) => { - self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref()) - }, + if let Some(v) = + late_validation::validate_diagnostic_on_move_target(&late_ctx, *span) + { + self.tcx.emit_node_span_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + v.attr_span, + DiagnosticOnMoveOnlyForAdt, + ); + } + if let Some(directive) = directive.as_deref() { + if let Node::Item(Item { + kind: + ItemKind::Struct(_, generics, _) + | ItemKind::Enum(_, generics, _) + | ItemKind::Union(_, generics, _), + .. + }) = self.tcx.hir_node(hir_id) + { + late_validation::for_each_unknown_diagnostic_format_param( + directive, + |name| generics_has_type_param_named(generics, name), + |name, sp| { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + hir_id, + sp, + errors::OnMoveMalformedFormatLiterals { name }, + ); + }, + ); + } + } + } Attribute::Parsed( // tidy-alphabetical-start AttributeKind::RustcAllowIncoherentImpl(..) @@ -578,169 +797,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - /// Checks if `#[diagnostic::do_not_recommend]` is applied on a trait impl - fn check_do_not_recommend( - &self, - attr_span: Span, - hir_id: HirId, - target: Target, - item: Option>, - ) { - if !matches!(target, Target::Impl { .. }) - || matches!( - item, - Some(ItemLike::Item(hir::Item { kind: hir::ItemKind::Impl(_impl),.. })) - if _impl.of_trait.is_none() - ) - { - self.tcx.emit_node_span_lint( - MISPLACED_DIAGNOSTIC_ATTRIBUTES, - hir_id, - attr_span, - errors::IncorrectDoNotRecommendLocation, - ); - } - } - - /// Checks if `#[diagnostic::on_unimplemented]` is applied to a trait definition - fn check_diagnostic_on_unimplemented( - &self, - attr_span: Span, - hir_id: HirId, - target: Target, - directive: Option<&Directive>, - ) { - if !matches!(target, Target::Trait) { - self.tcx.emit_node_span_lint( - MISPLACED_DIAGNOSTIC_ATTRIBUTES, - hir_id, - attr_span, - DiagnosticOnUnimplementedOnlyForTraits, - ); - } - - if let Some(directive) = directive { - if let Node::Item(Item { - kind: ItemKind::Trait(_, _, _, trait_name, generics, _, _), - .. - }) = self.tcx.hir_node(hir_id) - { - directive.visit_params(&mut |argument_name, span| { - let has_generic = generics.params.iter().any(|p| { - if !matches!(p.kind, GenericParamKind::Lifetime { .. }) - && let ParamName::Plain(name) = p.name - && name.name == argument_name - { - true - } else { - false - } - }); - if !has_generic { - self.tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - hir_id, - span, - errors::UnknownFormatParameterForOnUnimplementedAttr { - argument_name, - trait_name: *trait_name, - help: !directive.is_rustc_attr, - }, - ) - } - }) - } - } - } - - /// Checks if `#[diagnostic::on_const]` is applied to a trait impl - fn check_diagnostic_on_const( - &self, - attr_span: Span, - hir_id: HirId, - target: Target, - item: Option>, - ) { - if target == (Target::Impl { of_trait: true }) { - match item.unwrap() { - ItemLike::Item(it) => match it.expect_impl().constness { - Constness::Const => { - let item_span = self.tcx.hir_span(hir_id); - self.tcx.emit_node_span_lint( - MISPLACED_DIAGNOSTIC_ATTRIBUTES, - hir_id, - attr_span, - DiagnosticOnConstOnlyForNonConstTraitImpls { item_span }, - ); - return; - } - Constness::NotConst => return, - }, - ItemLike::ForeignItem => {} - } - } - let item_span = self.tcx.hir_span(hir_id); - self.tcx.emit_node_span_lint( - MISPLACED_DIAGNOSTIC_ATTRIBUTES, - hir_id, - attr_span, - DiagnosticOnConstOnlyForTraitImpls { item_span }, - ); - - // We don't check the validity of generic args here...whose generics would that be, anyway? - // The traits' or the impls'? - } - - /// Checks if `#[diagnostic::on_move]` is applied to an ADT definition - fn check_diagnostic_on_move( - &self, - attr_span: Span, - hir_id: HirId, - target: Target, - directive: Option<&Directive>, - ) { - if !matches!(target, Target::Enum | Target::Struct | Target::Union) { - self.tcx.emit_node_span_lint( - MISPLACED_DIAGNOSTIC_ATTRIBUTES, - hir_id, - attr_span, - DiagnosticOnMoveOnlyForAdt, - ); - } - - if let Some(directive) = directive { - if let Node::Item(Item { - kind: - ItemKind::Struct(_, generics, _) - | ItemKind::Enum(_, generics, _) - | ItemKind::Union(_, generics, _), - .. - }) = self.tcx.hir_node(hir_id) - { - directive.visit_params(&mut |argument_name, span| { - let has_generic = generics.params.iter().any(|p| { - if !matches!(p.kind, GenericParamKind::Lifetime { .. }) - && let ParamName::Plain(name) = p.name - && name.name == argument_name - { - true - } else { - false - } - }); - if !has_generic { - self.tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - hir_id, - span, - errors::OnMoveMalformedFormatLiterals { name: argument_name }, - ) - } - }); - } - } - } - /// Checks if an `#[inline]` is applied to a function or a closure. fn check_inline(&self, hir_id: HirId, attr_span: Span, kind: &InlineAttr, target: Target) { match target { @@ -1276,24 +1332,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.dcx().emit_err(errors::InvalidMayDangle { attr_span }); } - /// Checks if `#[link]` is applied to an item other than a foreign module. - fn check_link(&self, hir_id: HirId, attr_span: Span, span: Span, target: Target) { - if target == Target::ForeignMod - && let hir::Node::Item(item) = self.tcx.hir_node(hir_id) - && let Item { kind: ItemKind::ForeignMod { abi, .. }, .. } = item - && !matches!(abi, ExternAbi::Rust) - { - return; - } - - self.tcx.emit_node_span_lint( - UNUSED_ATTRIBUTES, - hir_id, - attr_span, - errors::Link { span: (target != Target::ForeignMod).then_some(span) }, - ); - } - /// Checks if `#[rustc_legacy_const_generics]` is applied to a function and has a valid argument. fn check_rustc_legacy_const_generics( &self, @@ -1590,42 +1628,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - fn check_deprecated(&self, hir_id: HirId, attr_span: Span, target: Target) { - match target { - Target::AssocConst | Target::Method(..) | Target::AssocTy - if self.tcx.def_kind(self.tcx.local_parent(hir_id.owner.def_id)) - == DefKind::Impl { of_trait: true } => - { - self.tcx.emit_node_span_lint( - UNUSED_ATTRIBUTES, - hir_id, - attr_span, - errors::DeprecatedAnnotationHasNoEffect { span: attr_span }, - ); - } - _ => {} - } - } - - fn check_macro_export(&self, hir_id: HirId, attr_span: Span, target: Target) { - if target != Target::MacroDef { - return; - } - - // special case when `#[macro_export]` is applied to a macro 2.0 - let (_, macro_definition, _) = self.tcx.hir_node(hir_id).expect_item().expect_macro(); - let is_decl_macro = !macro_definition.macro_rules; - - if is_decl_macro { - self.tcx.emit_node_span_lint( - UNUSED_ATTRIBUTES, - hir_id, - attr_span, - errors::MacroExport::OnDeclMacro, - ); - } - } - fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute, style: Option) { // Warn on useless empty attributes. // FIXME(jdonszelmann): this lint should be moved to attribute parsing, see `AcceptContext::warn_empty_attribute` @@ -1889,72 +1891,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ); } } - - fn check_loop_match(&self, hir_id: HirId, attr_span: Span, target: Target) { - let node_span = self.tcx.hir_span(hir_id); - - if !matches!(target, Target::Expression) { - return; // Handled in target checking during attr parse - } - - if !matches!(self.tcx.hir_expect_expr(hir_id).kind, hir::ExprKind::Loop(..)) { - self.dcx().emit_err(errors::LoopMatchAttr { attr_span, node_span }); - }; - } - - fn check_const_continue(&self, hir_id: HirId, attr_span: Span, target: Target) { - let node_span = self.tcx.hir_span(hir_id); - - if !matches!(target, Target::Expression) { - return; // Handled in target checking during attr parse - } - - if !matches!(self.tcx.hir_expect_expr(hir_id).kind, hir::ExprKind::Break(..)) { - self.dcx().emit_err(errors::ConstContinueAttr { attr_span, node_span }); - }; - } - - fn check_custom_mir( - &self, - dialect: Option<(MirDialect, Span)>, - phase: Option<(MirPhase, Span)>, - attr_span: Span, - ) { - let Some((dialect, dialect_span)) = dialect else { - if let Some((_, phase_span)) = phase { - self.dcx() - .emit_err(errors::CustomMirPhaseRequiresDialect { attr_span, phase_span }); - } - return; - }; - - match dialect { - MirDialect::Analysis => { - if let Some((MirPhase::Optimized, phase_span)) = phase { - self.dcx().emit_err(errors::CustomMirIncompatibleDialectAndPhase { - dialect, - phase: MirPhase::Optimized, - attr_span, - dialect_span, - phase_span, - }); - } - } - - MirDialect::Built => { - if let Some((phase, phase_span)) = phase { - self.dcx().emit_err(errors::CustomMirIncompatibleDialectAndPhase { - dialect, - phase, - attr_span, - dialect_span, - phase_span, - }); - } - } - MirDialect::Runtime => {} - } - } } impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index cf0b9afbade68..6efbcd7bf85b6 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -7,7 +7,6 @@ use rustc_errors::{ MultiSpan, msg, }; use rustc_hir::Target; -use rustc_hir::attrs::{MirDialect, MirPhase}; use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_middle::ty::{MainDefinition, Ty}; use rustc_span::{DUMMY_SP, Ident, Span, Symbol}; @@ -1274,28 +1273,6 @@ pub(crate) struct ReprAlignShouldBeAlignStatic { pub item: &'static str, } -#[derive(Diagnostic)] -#[diag("`dialect` key required")] -pub(crate) struct CustomMirPhaseRequiresDialect { - #[primary_span] - pub attr_span: Span, - #[label("`phase` argument requires a `dialect` argument")] - pub phase_span: Span, -} - -#[derive(Diagnostic)] -#[diag("the {$dialect} dialect is not compatible with the {$phase} phase")] -pub(crate) struct CustomMirIncompatibleDialectAndPhase { - pub dialect: MirDialect, - pub phase: MirPhase, - #[primary_span] - pub attr_span: Span, - #[label("this dialect...")] - pub dialect_span: Span, - #[label("... is not compatible with this phase")] - pub phase_span: Span, -} - #[derive(Diagnostic)] #[diag("`eii_macro_for` is only valid on functions")] pub(crate) struct EiiImplNotFunction {