Allow attribute parsers to specify a list of allowed targets
Every acceptor gets an `ALLOWED_TARGETS` specification which can specify per target whether it is allowed, warned, or errored.
This commit is contained in:
parent
e7ef23e90e
commit
744d39ebe6
8 changed files with 304 additions and 13 deletions
|
|
@ -3480,6 +3480,7 @@ dependencies = [
|
|||
name = "rustc_attr_parsing"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"rustc_abi",
|
||||
"rustc_ast",
|
||||
"rustc_ast_pretty",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
# tidy-alphabetical-start
|
||||
itertools = "0.12"
|
||||
rustc_abi = { path = "../rustc_abi" }
|
||||
rustc_ast = { path = "../rustc_ast" }
|
||||
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ attr_parsing_empty_attribute =
|
|||
unused attribute
|
||||
.suggestion = remove this attribute
|
||||
|
||||
attr_parsing_invalid_target = `#[{$name}]` attribute cannot be used on {$target}
|
||||
.help = `#[{$name}]` can {$only}be applied to {$applied}
|
||||
attr_parsing_invalid_target_lint = `#[{$name}]` attribute cannot be used on {$target}
|
||||
.warn = {-attr_parsing_previously_accepted}
|
||||
.help = `#[{$name}]` can {$only}be applied to {$applied}
|
||||
|
||||
attr_parsing_empty_confusables =
|
||||
expected at least one confusable name
|
||||
attr_parsing_expected_one_cfg_pattern =
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use rustc_hir::attrs::AttributeKind;
|
|||
use rustc_span::{Span, Symbol};
|
||||
use thin_vec::ThinVec;
|
||||
|
||||
use crate::context::{AcceptContext, FinalizeContext, Stage};
|
||||
use crate::context::{AcceptContext, AllowedTargets, FinalizeContext, Stage};
|
||||
use crate::parser::ArgParser;
|
||||
use crate::session_diagnostics::UnusedMultiple;
|
||||
|
||||
|
|
@ -80,6 +80,8 @@ pub(crate) trait AttributeParser<S: Stage>: Default + 'static {
|
|||
/// If an attribute has this symbol, the `accept` function will be called on it.
|
||||
const ATTRIBUTES: AcceptMapping<Self, S>;
|
||||
|
||||
const ALLOWED_TARGETS: AllowedTargets;
|
||||
|
||||
/// The parser has gotten a chance to accept the attributes on an item,
|
||||
/// here it can produce an attribute.
|
||||
///
|
||||
|
|
@ -116,6 +118,8 @@ pub(crate) trait SingleAttributeParser<S: Stage>: 'static {
|
|||
/// and this specified whether to, for example, warn or error on the other one.
|
||||
const ON_DUPLICATE: OnDuplicate<S>;
|
||||
|
||||
const ALLOWED_TARGETS: AllowedTargets;
|
||||
|
||||
/// The template this attribute parser should implement. Used for diagnostics.
|
||||
const TEMPLATE: AttributeTemplate;
|
||||
|
||||
|
|
@ -163,6 +167,7 @@ impl<T: SingleAttributeParser<S>, S: Stage> AttributeParser<S> for Single<T, S>
|
|||
}
|
||||
},
|
||||
)];
|
||||
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
|
||||
|
||||
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
|
||||
Some(self.1?.0)
|
||||
|
|
@ -247,6 +252,7 @@ pub(crate) enum AttributeOrder {
|
|||
pub(crate) trait NoArgsAttributeParser<S: Stage>: 'static {
|
||||
const PATH: &[Symbol];
|
||||
const ON_DUPLICATE: OnDuplicate<S>;
|
||||
const ALLOWED_TARGETS: AllowedTargets;
|
||||
|
||||
/// Create the [`AttributeKind`] given attribute's [`Span`].
|
||||
const CREATE: fn(Span) -> AttributeKind;
|
||||
|
|
@ -264,6 +270,7 @@ impl<T: NoArgsAttributeParser<S>, S: Stage> SingleAttributeParser<S> for Without
|
|||
const PATH: &[Symbol] = T::PATH;
|
||||
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
|
||||
const ON_DUPLICATE: OnDuplicate<S> = T::ON_DUPLICATE;
|
||||
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
|
||||
const TEMPLATE: AttributeTemplate = template!(Word);
|
||||
|
||||
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
|
||||
|
|
@ -293,6 +300,8 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
|
|||
/// where `x` is a vec of these individual reprs.
|
||||
const CONVERT: ConvertFn<Self::Item>;
|
||||
|
||||
const ALLOWED_TARGETS: AllowedTargets;
|
||||
|
||||
/// The template this attribute parser should implement. Used for diagnostics.
|
||||
const TEMPLATE: AttributeTemplate;
|
||||
|
||||
|
|
@ -324,15 +333,13 @@ impl<T: CombineAttributeParser<S>, S: Stage> Default for Combine<T, S> {
|
|||
}
|
||||
|
||||
impl<T: CombineAttributeParser<S>, S: Stage> AttributeParser<S> for Combine<T, S> {
|
||||
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
|
||||
T::PATH,
|
||||
<T as CombineAttributeParser<S>>::TEMPLATE,
|
||||
|group: &mut Combine<T, S>, cx, args| {
|
||||
const ATTRIBUTES: AcceptMapping<Self, S> =
|
||||
&[(T::PATH, T::TEMPLATE, |group: &mut Combine<T, S>, cx, args| {
|
||||
// Keep track of the span of the first attribute, for diagnostics
|
||||
group.first_span.get_or_insert(cx.attr_span);
|
||||
group.items.extend(T::extend(cx, args))
|
||||
},
|
||||
)];
|
||||
})];
|
||||
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
|
||||
|
||||
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
|
||||
if let Some(first_span) = self.first_span {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::collections::BTreeMap;
|
|||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use itertools::Itertools;
|
||||
use private::Sealed;
|
||||
use rustc_ast::{self as ast, LitKind, MetaItemLit, NodeId};
|
||||
use rustc_errors::{DiagCtxtHandle, Diagnostic};
|
||||
|
|
@ -63,8 +64,11 @@ use crate::attributes::traits::{
|
|||
};
|
||||
use crate::attributes::transparency::TransparencyParser;
|
||||
use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
|
||||
use crate::context::MaybeWarn::{Allow, Error, Warn};
|
||||
use crate::parser::{ArgParser, MetaItemParser, PathParser};
|
||||
use crate::session_diagnostics::{AttributeParseError, AttributeParseErrorReason, UnknownMetaItem};
|
||||
use crate::session_diagnostics::{
|
||||
AttributeParseError, AttributeParseErrorReason, InvalidTarget, UnknownMetaItem,
|
||||
};
|
||||
|
||||
type GroupType<S> = LazyLock<GroupTypeInner<S>>;
|
||||
|
||||
|
|
@ -76,6 +80,7 @@ struct GroupTypeInner<S: Stage> {
|
|||
struct GroupTypeInnerAccept<S: Stage> {
|
||||
template: AttributeTemplate,
|
||||
accept_fn: AcceptFn<S>,
|
||||
allowed_targets: AllowedTargets,
|
||||
}
|
||||
|
||||
type AcceptFn<S> =
|
||||
|
|
@ -123,7 +128,8 @@ macro_rules! attribute_parsers {
|
|||
STATE_OBJECT.with_borrow_mut(|s| {
|
||||
accept_fn(s, cx, args)
|
||||
})
|
||||
})
|
||||
}),
|
||||
allowed_targets: <$names as crate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -645,6 +651,64 @@ impl ShouldEmit {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum AllowedTargets {
|
||||
AllowList(&'static [MaybeWarn]),
|
||||
AllowListWarnRest(&'static [MaybeWarn]),
|
||||
}
|
||||
|
||||
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(&Allow(target)) {
|
||||
AllowedResult::Allowed
|
||||
} else if list.contains(&Warn(target)) {
|
||||
AllowedResult::Warn
|
||||
} else {
|
||||
AllowedResult::Error
|
||||
}
|
||||
}
|
||||
AllowedTargets::AllowListWarnRest(list) => {
|
||||
if list.contains(&Allow(target)) {
|
||||
AllowedResult::Allowed
|
||||
} else if list.contains(&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 {
|
||||
Allow(target) => Some(*target),
|
||||
Warn(_) => None,
|
||||
Error(_) => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub(crate) enum MaybeWarn {
|
||||
Allow(Target),
|
||||
Warn(Target),
|
||||
Error(Target),
|
||||
}
|
||||
|
||||
/// Context created once, for example as part of the ast lowering
|
||||
/// context, through which all attributes can be lowered.
|
||||
pub struct AttributeParser<'sess, S: Stage = Late> {
|
||||
|
|
@ -852,7 +916,48 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
|
|||
attr_path: path.get_attribute_path(),
|
||||
};
|
||||
|
||||
(accept.accept_fn)(&mut cx, args)
|
||||
(accept.accept_fn)(&mut cx, args);
|
||||
|
||||
if self.stage.should_emit().should_emit() {
|
||||
match accept.allowed_targets.is_allowed(target) {
|
||||
AllowedResult::Allowed => {}
|
||||
AllowedResult::Warn => {
|
||||
let allowed_targets =
|
||||
accept.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: parts[0],
|
||||
target,
|
||||
only: if only { "only " } else { "" },
|
||||
applied,
|
||||
},
|
||||
});
|
||||
}
|
||||
AllowedResult::Error => {
|
||||
let allowed_targets =
|
||||
accept.allowed_targets.allowed_targets();
|
||||
let (applied, only) = allowed_targets_applied(
|
||||
allowed_targets,
|
||||
target,
|
||||
self.features,
|
||||
);
|
||||
self.dcx().emit_err(InvalidTarget {
|
||||
span: attr.span,
|
||||
name: parts[0],
|
||||
target: target.plural_name(),
|
||||
only: if only { "only " } else { "" },
|
||||
applied,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we're here, we must be compiling a tool attribute... Or someone
|
||||
|
|
@ -940,6 +1045,132 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
) -> (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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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()))
|
||||
.join(", "),
|
||||
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 [MaybeWarn] = &[
|
||||
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 }),
|
||||
];
|
||||
|
||||
/// Parse a single integer.
|
||||
///
|
||||
/// Used by attributes that take a single integer as argument, such as
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use rustc_errors::{DiagArgValue, LintEmitter};
|
||||
use rustc_hir::HirId;
|
||||
use rustc_hir::lints::{AttributeLint, AttributeLintKind};
|
||||
use rustc_hir::{HirId, Target};
|
||||
use rustc_span::sym;
|
||||
|
||||
use crate::session_diagnostics;
|
||||
|
||||
|
|
@ -34,5 +35,25 @@ pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<HirId>, lint_emi
|
|||
*first_span,
|
||||
session_diagnostics::EmptyAttributeList { attr_span: *first_span },
|
||||
),
|
||||
&AttributeLintKind::InvalidTarget { name, target, ref applied, only } => lint_emitter
|
||||
.emit_node_span_lint(
|
||||
// This check is here because `deprecated` had its own lint group and removing this would be a breaking change
|
||||
if name == sym::deprecated
|
||||
&& ![Target::Closure, Target::Expression, Target::Statement, Target::Arm]
|
||||
.contains(&target)
|
||||
{
|
||||
rustc_session::lint::builtin::USELESS_DEPRECATED
|
||||
} else {
|
||||
rustc_session::lint::builtin::UNUSED_ATTRIBUTES
|
||||
},
|
||||
*id,
|
||||
*span,
|
||||
session_diagnostics::InvalidTargetLint {
|
||||
name,
|
||||
target: target.plural_name(),
|
||||
applied: applied.clone(),
|
||||
only,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -480,6 +480,29 @@ pub(crate) struct EmptyAttributeList {
|
|||
pub attr_span: Span,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(attr_parsing_invalid_target_lint)]
|
||||
#[warning]
|
||||
#[help]
|
||||
pub(crate) struct InvalidTargetLint {
|
||||
pub name: Symbol,
|
||||
pub target: &'static str,
|
||||
pub applied: String,
|
||||
pub only: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[help]
|
||||
#[diag(attr_parsing_invalid_target)]
|
||||
pub(crate) struct InvalidTarget {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub name: Symbol,
|
||||
pub target: &'static str,
|
||||
pub applied: String,
|
||||
pub only: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(attr_parsing_invalid_alignment_value, code = E0589)]
|
||||
pub(crate) struct InvalidAlignmentValue {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use rustc_data_structures::fingerprint::Fingerprint;
|
||||
use rustc_macros::HashStable_Generic;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
use crate::HirId;
|
||||
use crate::{HirId, Target};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DelayedLints {
|
||||
|
|
@ -34,4 +34,5 @@ pub enum AttributeLintKind {
|
|||
UnusedDuplicate { this: Span, other: Span, warning: bool },
|
||||
IllFormedAttributeInput { suggestions: Vec<String> },
|
||||
EmptyAttribute { first_span: Span },
|
||||
InvalidTarget { name: Symbol, target: Target, applied: String, only: &'static str },
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue