use std::borrow::Cow; use rustc_errors::DiagArgValue; use rustc_feature::Features; use rustc_hir::lints::{AttributeLint, AttributeLintKind}; use rustc_hir::{AttrPath, MethodKind, Target}; use rustc_span::Span; use crate::AttributeParser; use crate::context::Stage; use crate::session_diagnostics::InvalidTarget; #[derive(Debug)] pub(crate) enum AllowedTargets { AllowList(&'static [Policy]), AllowListWarnRest(&'static [Policy]), } pub(crate) enum AllowedResult { Allowed, Warn, Error, } impl AllowedTargets { pub(crate) fn is_allowed(&self, target: Target) -> AllowedResult { match self { AllowedTargets::AllowList(list) => { if list.contains(&Policy::Allow(target)) { AllowedResult::Allowed } else if list.contains(&Policy::Warn(target)) { AllowedResult::Warn } else { AllowedResult::Error } } AllowedTargets::AllowListWarnRest(list) => { if list.contains(&Policy::Allow(target)) { AllowedResult::Allowed } else if list.contains(&Policy::Error(target)) { AllowedResult::Error } else { AllowedResult::Warn } } } } pub(crate) fn allowed_targets(&self) -> Vec { match self { AllowedTargets::AllowList(list) => list, AllowedTargets::AllowListWarnRest(list) => list, } .iter() .filter_map(|target| match target { Policy::Allow(target) => Some(*target), Policy::Warn(_) => None, Policy::Error(_) => None, }) .collect() } } #[derive(Debug, Eq, PartialEq)] pub(crate) enum Policy { Allow(Target), Warn(Target), Error(Target), } impl AttributeParser<'_, S> { pub(crate) fn check_target( &self, attr_name: AttrPath, attr_span: Span, allowed_targets: &AllowedTargets, target: Target, target_id: S::Id, mut emit_lint: impl FnMut(AttributeLint), ) { match allowed_targets.is_allowed(target) { AllowedResult::Allowed => {} AllowedResult::Warn => { let allowed_targets = allowed_targets.allowed_targets(); let (applied, only) = allowed_targets_applied(allowed_targets, target, self.features); emit_lint(AttributeLint { id: target_id, span: attr_span, kind: AttributeLintKind::InvalidTarget { name: attr_name, target, only: if only { "only " } else { "" }, applied, }, }); } AllowedResult::Error => { let allowed_targets = allowed_targets.allowed_targets(); let (applied, only) = allowed_targets_applied(allowed_targets, target, self.features); self.dcx().emit_err(InvalidTarget { span: attr_span, name: attr_name, target: target.plural_name(), only: if only { "only " } else { "" }, applied: DiagArgValue::StrListSepByAnd( applied.into_iter().map(Cow::Owned).collect(), ), }); } } } } /// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to. /// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string pub(crate) fn allowed_targets_applied( mut allowed_targets: Vec, target: Target, features: Option<&Features>, ) -> (Vec, bool) { // Remove unstable targets from `allowed_targets` if their features are not enabled if let Some(features) = features { if !features.fn_delegation() { allowed_targets.retain(|t| !matches!(t, Target::Delegation { .. })); } if !features.stmt_expr_attributes() { allowed_targets.retain(|t| !matches!(t, Target::Expression | Target::Statement)); } if !features.extern_types() { allowed_targets.retain(|t| !matches!(t, Target::ForeignTy)); } } // We define groups of "similar" targets. // If at least two of the targets are allowed, and the `target` is not in the group, // we collapse the entire group to a single entry to simplify the target list const FUNCTION_LIKE: &[Target] = &[ Target::Fn, Target::Closure, Target::ForeignFn, Target::Method(MethodKind::Inherent), Target::Method(MethodKind::Trait { body: false }), Target::Method(MethodKind::Trait { body: true }), Target::Method(MethodKind::TraitImpl), ]; const METHOD_LIKE: &[Target] = &[ Target::Method(MethodKind::Inherent), Target::Method(MethodKind::Trait { body: false }), Target::Method(MethodKind::Trait { body: true }), Target::Method(MethodKind::TraitImpl), ]; const IMPL_LIKE: &[Target] = &[Target::Impl { of_trait: false }, Target::Impl { of_trait: true }]; const ADT_LIKE: &[Target] = &[Target::Struct, Target::Enum]; let mut added_fake_targets = Vec::new(); filter_targets( &mut allowed_targets, FUNCTION_LIKE, "functions", target, &mut added_fake_targets, ); filter_targets(&mut allowed_targets, METHOD_LIKE, "methods", target, &mut added_fake_targets); filter_targets(&mut allowed_targets, IMPL_LIKE, "impl blocks", target, &mut added_fake_targets); filter_targets(&mut allowed_targets, ADT_LIKE, "data types", target, &mut added_fake_targets); // If there is now only 1 target left, show that as the only possible target ( added_fake_targets .iter() .copied() .chain(allowed_targets.iter().map(|t| t.plural_name())) .map(|i| i.to_string()) .collect(), allowed_targets.len() + added_fake_targets.len() == 1, ) } fn filter_targets( allowed_targets: &mut Vec, target_group: &'static [Target], target_group_name: &'static str, target: Target, added_fake_targets: &mut Vec<&'static str>, ) { if target_group.contains(&target) { return; } if allowed_targets.iter().filter(|at| target_group.contains(at)).count() < 2 { return; } allowed_targets.retain(|t| !target_group.contains(t)); added_fake_targets.push(target_group_name); } /// This is the list of all targets to which a attribute can be applied /// This is used for: /// - `rustc_dummy`, which can be applied to all targets /// - Attributes that are not parted to the new target system yet can use this list as a placeholder pub(crate) const ALL_TARGETS: &'static [Policy] = { use Policy::Allow; &[ Allow(Target::ExternCrate), Allow(Target::Use), Allow(Target::Static), Allow(Target::Const), Allow(Target::Fn), Allow(Target::Closure), Allow(Target::Mod), Allow(Target::ForeignMod), Allow(Target::GlobalAsm), Allow(Target::TyAlias), Allow(Target::Enum), Allow(Target::Variant), Allow(Target::Struct), Allow(Target::Field), Allow(Target::Union), Allow(Target::Trait), Allow(Target::TraitAlias), Allow(Target::Impl { of_trait: false }), Allow(Target::Impl { of_trait: true }), Allow(Target::Expression), Allow(Target::Statement), Allow(Target::Arm), Allow(Target::AssocConst), Allow(Target::Method(MethodKind::Inherent)), Allow(Target::Method(MethodKind::Trait { body: false })), Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), Allow(Target::AssocTy), Allow(Target::ForeignFn), Allow(Target::ForeignStatic), Allow(Target::ForeignTy), Allow(Target::MacroDef), Allow(Target::Param), Allow(Target::PatField), Allow(Target::ExprField), Allow(Target::WherePredicate), Allow(Target::MacroCall), Allow(Target::Crate), Allow(Target::Delegation { mac: false }), Allow(Target::Delegation { mac: true }), ] };