| use std::borrow::Cow; |
| |
| use rustc_ast::AttrStyle; |
| use rustc_errors::DiagArgValue; |
| use rustc_feature::{AttributeType, Features}; |
| use rustc_hir::lints::AttributeLintKind; |
| use rustc_hir::{MethodKind, Target}; |
| |
| use crate::AttributeParser; |
| use crate::context::{AcceptContext, 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<Target> { |
| 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<'sess, S: Stage> AttributeParser<'sess, S> { |
| pub(crate) fn check_target( |
| allowed_targets: &AllowedTargets, |
| target: Target, |
| cx: &mut AcceptContext<'_, 'sess, S>, |
| ) { |
| 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, cx.features); |
| let name = cx.attr_path.clone(); |
| let attr_span = cx.attr_span; |
| cx.emit_lint( |
| AttributeLintKind::InvalidTarget { |
| name, |
| target, |
| only: if only { "only " } else { "" }, |
| applied, |
| }, |
| attr_span, |
| ); |
| } |
| AllowedResult::Error => { |
| let allowed_targets = allowed_targets.allowed_targets(); |
| let (applied, only) = allowed_targets_applied(allowed_targets, target, cx.features); |
| let name = cx.attr_path.clone(); |
| cx.dcx().emit_err(InvalidTarget { |
| span: cx.attr_span.clone(), |
| name, |
| target: target.plural_name(), |
| only: if only { "only " } else { "" }, |
| applied: DiagArgValue::StrListSepByAnd( |
| applied.into_iter().map(Cow::Owned).collect(), |
| ), |
| }); |
| } |
| } |
| } |
| |
| pub(crate) fn check_type( |
| attribute_type: AttributeType, |
| target: Target, |
| cx: &mut AcceptContext<'_, 'sess, S>, |
| ) { |
| let is_crate_root = S::id_is_crate_root(cx.target_id); |
| |
| if is_crate_root { |
| return; |
| } |
| |
| if attribute_type != AttributeType::CrateLevel { |
| return; |
| } |
| |
| let lint = AttributeLintKind::InvalidStyle { |
| name: cx.attr_path.clone(), |
| is_used_as_inner: cx.attr_style == AttrStyle::Inner, |
| target, |
| target_span: cx.target_span, |
| }; |
| let attr_span = cx.attr_span; |
| |
| cx.emit_lint(lint, attr_span); |
| } |
| } |
| |
| /// 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: Target, |
| features: Option<&Features>, |
| ) -> (Vec<String>, 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>, |
| 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 }), |
| ] |
| }; |