Skip to content

Commit 8d6add0

Browse files
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.
1 parent b2600e0 commit 8d6add0

File tree

8 files changed

+293
-11
lines changed

8 files changed

+293
-11
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3480,6 +3480,7 @@ dependencies = [
34803480
name = "rustc_attr_parsing"
34813481
version = "0.0.0"
34823482
dependencies = [
3483+
"itertools",
34833484
"rustc_abi",
34843485
"rustc_ast",
34853486
"rustc_ast_pretty",

compiler/rustc_attr_parsing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2024"
55

66
[dependencies]
77
# tidy-alphabetical-start
8+
itertools = "0.12"
89
rustc_abi = { path = "../rustc_abi" }
910
rustc_ast = { path = "../rustc_ast" }
1011
rustc_ast_pretty = { path = "../rustc_ast_pretty" }

compiler/rustc_attr_parsing/messages.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ attr_parsing_empty_attribute =
1010
unused attribute
1111
.suggestion = remove this attribute
1212
13+
attr_parsing_invalid_target = `#[{$name}]` attribute cannot be used on {$target}
14+
.help = `#[{$name}]` can {$only}be applied to {$applied}
15+
attr_parsing_invalid_target_lint = `#[{$name}]` attribute cannot be used on {$target}
16+
.warn = {-attr_parsing_previously_accepted}
17+
.help = `#[{$name}]` can {$only}be applied to {$applied}
18+
1319
attr_parsing_empty_confusables =
1420
expected at least one confusable name
1521
attr_parsing_expected_one_cfg_pattern =

compiler/rustc_attr_parsing/src/attributes/mod.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use rustc_hir::attrs::AttributeKind;
2121
use rustc_span::{Span, Symbol};
2222
use thin_vec::ThinVec;
2323

24-
use crate::context::{AcceptContext, FinalizeContext, Stage};
24+
use crate::context::{AcceptContext, AllowedTargets, FinalizeContext, Stage};
2525
use crate::parser::ArgParser;
2626
use crate::session_diagnostics::UnusedMultiple;
2727

@@ -80,6 +80,8 @@ pub(crate) trait AttributeParser<S: Stage>: Default + 'static {
8080
/// If an attribute has this symbol, the `accept` function will be called on it.
8181
const ATTRIBUTES: AcceptMapping<Self, S>;
8282

83+
const ALLOWED_TARGETS: AllowedTargets;
84+
8385
/// The parser has gotten a chance to accept the attributes on an item,
8486
/// here it can produce an attribute.
8587
///
@@ -116,6 +118,8 @@ pub(crate) trait SingleAttributeParser<S: Stage>: 'static {
116118
/// and this specified whether to, for example, warn or error on the other one.
117119
const ON_DUPLICATE: OnDuplicate<S>;
118120

121+
const ALLOWED_TARGETS: AllowedTargets;
122+
119123
/// The template this attribute parser should implement. Used for diagnostics.
120124
const TEMPLATE: AttributeTemplate;
121125

@@ -163,6 +167,7 @@ impl<T: SingleAttributeParser<S>, S: Stage> AttributeParser<S> for Single<T, S>
163167
}
164168
},
165169
)];
170+
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
166171

167172
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
168173
Some(self.1?.0)
@@ -247,6 +252,7 @@ pub(crate) enum AttributeOrder {
247252
pub(crate) trait NoArgsAttributeParser<S: Stage>: 'static {
248253
const PATH: &[Symbol];
249254
const ON_DUPLICATE: OnDuplicate<S>;
255+
const ALLOWED_TARGETS: AllowedTargets;
250256

251257
/// Create the [`AttributeKind`] given attribute's [`Span`].
252258
const CREATE: fn(Span) -> AttributeKind;
@@ -264,6 +270,7 @@ impl<T: NoArgsAttributeParser<S>, S: Stage> SingleAttributeParser<S> for Without
264270
const PATH: &[Symbol] = T::PATH;
265271
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
266272
const ON_DUPLICATE: OnDuplicate<S> = T::ON_DUPLICATE;
273+
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
267274
const TEMPLATE: AttributeTemplate = template!(Word);
268275

269276
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
@@ -293,6 +300,8 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
293300
/// where `x` is a vec of these individual reprs.
294301
const CONVERT: ConvertFn<Self::Item>;
295302

303+
const ALLOWED_TARGETS: AllowedTargets;
304+
296305
/// The template this attribute parser should implement. Used for diagnostics.
297306
const TEMPLATE: AttributeTemplate;
298307

@@ -324,15 +333,13 @@ impl<T: CombineAttributeParser<S>, S: Stage> Default for Combine<T, S> {
324333
}
325334

326335
impl<T: CombineAttributeParser<S>, S: Stage> AttributeParser<S> for Combine<T, S> {
327-
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
328-
T::PATH,
329-
<T as CombineAttributeParser<S>>::TEMPLATE,
330-
|group: &mut Combine<T, S>, cx, args| {
336+
const ATTRIBUTES: AcceptMapping<Self, S> =
337+
&[(T::PATH, T::TEMPLATE, |group: &mut Combine<T, S>, cx, args| {
331338
// Keep track of the span of the first attribute, for diagnostics
332339
group.first_span.get_or_insert(cx.attr_span);
333340
group.items.extend(T::extend(cx, args))
334-
},
335-
)];
341+
})];
342+
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
336343

337344
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
338345
if let Some(first_span) = self.first_span {

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 234 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
33
use std::ops::{Deref, DerefMut};
44
use std::sync::LazyLock;
55

6+
use itertools::Itertools;
67
use private::Sealed;
78
use rustc_ast::{self as ast, LitKind, MetaItemLit, NodeId};
89
use rustc_errors::{DiagCtxtHandle, Diagnostic};
@@ -61,8 +62,11 @@ use crate::attributes::traits::{
6162
};
6263
use crate::attributes::transparency::TransparencyParser;
6364
use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
65+
use crate::context::MaybeWarn::{Allow, Error, Warn};
6466
use crate::parser::{ArgParser, MetaItemParser, PathParser};
65-
use crate::session_diagnostics::{AttributeParseError, AttributeParseErrorReason, UnknownMetaItem};
67+
use crate::session_diagnostics::{
68+
AttributeParseError, AttributeParseErrorReason, InvalidTarget, UnknownMetaItem,
69+
};
6670

6771
type GroupType<S> = LazyLock<GroupTypeInner<S>>;
6872

@@ -74,6 +78,7 @@ struct GroupTypeInner<S: Stage> {
7478
struct GroupTypeInnerAccept<S: Stage> {
7579
template: AttributeTemplate,
7680
accept_fn: AcceptFn<S>,
81+
allowed_targets: AllowedTargets,
7782
}
7883

7984
type AcceptFn<S> =
@@ -121,7 +126,8 @@ macro_rules! attribute_parsers {
121126
STATE_OBJECT.with_borrow_mut(|s| {
122127
accept_fn(s, cx, args)
123128
})
124-
})
129+
}),
130+
allowed_targets: <$names as crate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS,
125131
});
126132
}
127133

@@ -641,6 +647,64 @@ impl ShouldEmit {
641647
}
642648
}
643649

650+
#[derive(Debug)]
651+
pub(crate) enum AllowedTargets {
652+
AllowList(&'static [MaybeWarn]),
653+
AllowListWarnRest(&'static [MaybeWarn]),
654+
}
655+
656+
pub(crate) enum AllowedResult {
657+
Allowed,
658+
Warn,
659+
Error,
660+
}
661+
662+
impl AllowedTargets {
663+
pub(crate) fn is_allowed(&self, target: Target) -> AllowedResult {
664+
match self {
665+
AllowedTargets::AllowList(list) => {
666+
if list.contains(&Allow(target)) {
667+
AllowedResult::Allowed
668+
} else if list.contains(&Warn(target)) {
669+
AllowedResult::Warn
670+
} else {
671+
AllowedResult::Error
672+
}
673+
}
674+
AllowedTargets::AllowListWarnRest(list) => {
675+
if list.contains(&Allow(target)) {
676+
AllowedResult::Allowed
677+
} else if list.contains(&Error(target)) {
678+
AllowedResult::Error
679+
} else {
680+
AllowedResult::Warn
681+
}
682+
}
683+
}
684+
}
685+
686+
pub(crate) fn allowed_targets(&self) -> Vec<Target> {
687+
match self {
688+
AllowedTargets::AllowList(list) => list,
689+
AllowedTargets::AllowListWarnRest(list) => list,
690+
}
691+
.iter()
692+
.filter_map(|target| match target {
693+
Allow(target) => Some(*target),
694+
Warn(_) => None,
695+
Error(_) => None,
696+
})
697+
.collect()
698+
}
699+
}
700+
701+
#[derive(Debug, Eq, PartialEq)]
702+
pub(crate) enum MaybeWarn {
703+
Allow(Target),
704+
Warn(Target),
705+
Error(Target),
706+
}
707+
644708
/// Context created once, for example as part of the ast lowering
645709
/// context, through which all attributes can be lowered.
646710
pub struct AttributeParser<'sess, S: Stage = Late> {
@@ -848,7 +912,48 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
848912
attr_path: path.get_attribute_path(),
849913
};
850914

851-
(accept.accept_fn)(&mut cx, args)
915+
(accept.accept_fn)(&mut cx, args);
916+
917+
if self.stage.should_emit().should_emit() {
918+
match accept.allowed_targets.is_allowed(target) {
919+
AllowedResult::Allowed => {}
920+
AllowedResult::Warn => {
921+
let allowed_targets =
922+
accept.allowed_targets.allowed_targets();
923+
let (applied, only) = allowed_targets_applied(
924+
allowed_targets,
925+
target,
926+
self.features,
927+
);
928+
emit_lint(AttributeLint {
929+
id: target_id,
930+
span: attr.span,
931+
kind: AttributeLintKind::InvalidTarget {
932+
name: parts[0],
933+
target: target.plural_name(),
934+
only: if only { "only " } else { "" },
935+
applied,
936+
},
937+
});
938+
}
939+
AllowedResult::Error => {
940+
let allowed_targets =
941+
accept.allowed_targets.allowed_targets();
942+
let (applied, only) = allowed_targets_applied(
943+
allowed_targets,
944+
target,
945+
self.features,
946+
);
947+
self.dcx().emit_err(InvalidTarget {
948+
span: attr.span,
949+
name: parts[0],
950+
target: target.plural_name(),
951+
only: if only { "only " } else { "" },
952+
applied,
953+
});
954+
}
955+
}
956+
}
852957
}
853958
} else {
854959
// If we're here, we must be compiling a tool attribute... Or someone
@@ -936,6 +1041,132 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
9361041
}
9371042
}
9381043

1044+
/// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to.
1045+
/// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string
1046+
pub(crate) fn allowed_targets_applied(
1047+
mut allowed_targets: Vec<Target>,
1048+
target: Target,
1049+
features: Option<&Features>,
1050+
) -> (String, bool) {
1051+
// Remove unstable targets from `allowed_targets` if their features are not enabled
1052+
if let Some(features) = features {
1053+
if !features.fn_delegation() {
1054+
allowed_targets.retain(|t| !matches!(t, Target::Delegation { .. }));
1055+
}
1056+
if !features.stmt_expr_attributes() {
1057+
allowed_targets.retain(|t| !matches!(t, Target::Expression | Target::Statement));
1058+
}
1059+
}
1060+
1061+
// We define groups of "similar" targets.
1062+
// If at least two of the targets are allowed, and the `target` is not in the group,
1063+
// we collapse the entire group to a single entry to simplify the target list
1064+
const FUNCTION_LIKE: &[Target] = &[
1065+
Target::Fn,
1066+
Target::Closure,
1067+
Target::ForeignFn,
1068+
Target::Method(MethodKind::Inherent),
1069+
Target::Method(MethodKind::Trait { body: false }),
1070+
Target::Method(MethodKind::Trait { body: true }),
1071+
Target::Method(MethodKind::TraitImpl),
1072+
];
1073+
const METHOD_LIKE: &[Target] = &[
1074+
Target::Method(MethodKind::Inherent),
1075+
Target::Method(MethodKind::Trait { body: false }),
1076+
Target::Method(MethodKind::Trait { body: true }),
1077+
Target::Method(MethodKind::TraitImpl),
1078+
];
1079+
const IMPL_LIKE: &[Target] =
1080+
&[Target::Impl { of_trait: false }, Target::Impl { of_trait: true }];
1081+
const ADT_LIKE: &[Target] = &[Target::Struct, Target::Enum];
1082+
1083+
let mut added_fake_targets = Vec::new();
1084+
filter_targets(
1085+
&mut allowed_targets,
1086+
FUNCTION_LIKE,
1087+
"functions",
1088+
target,
1089+
&mut added_fake_targets,
1090+
);
1091+
filter_targets(&mut allowed_targets, METHOD_LIKE, "methods", target, &mut added_fake_targets);
1092+
filter_targets(&mut allowed_targets, IMPL_LIKE, "impl blocks", target, &mut added_fake_targets);
1093+
filter_targets(&mut allowed_targets, ADT_LIKE, "data types", target, &mut added_fake_targets);
1094+
1095+
// If there is now only 1 target left, show that as the only possible target
1096+
(
1097+
added_fake_targets
1098+
.iter()
1099+
.copied()
1100+
.chain(allowed_targets.iter().map(|t| t.plural_name()))
1101+
.join(", "),
1102+
allowed_targets.len() + added_fake_targets.len() == 1,
1103+
)
1104+
}
1105+
1106+
fn filter_targets(
1107+
allowed_targets: &mut Vec<Target>,
1108+
target_group: &'static [Target],
1109+
target_group_name: &'static str,
1110+
target: Target,
1111+
added_fake_targets: &mut Vec<&'static str>,
1112+
) {
1113+
if target_group.contains(&target) {
1114+
return;
1115+
}
1116+
if allowed_targets.iter().filter(|at| target_group.contains(at)).count() < 2 {
1117+
return;
1118+
}
1119+
allowed_targets.retain(|t| !target_group.contains(t));
1120+
added_fake_targets.push(target_group_name);
1121+
}
1122+
1123+
/// This is the list of all targets to which a attribute can be applied
1124+
/// This is used for:
1125+
/// - `rustc_dummy`, which can be applied to all targets
1126+
/// - Attributes that are not parted to the new target system yet can use this list as a placeholder
1127+
pub(crate) const ALL_TARGETS: &'static [MaybeWarn] = &[
1128+
Allow(Target::ExternCrate),
1129+
Allow(Target::Use),
1130+
Allow(Target::Static),
1131+
Allow(Target::Const),
1132+
Allow(Target::Fn),
1133+
Allow(Target::Closure),
1134+
Allow(Target::Mod),
1135+
Allow(Target::ForeignMod),
1136+
Allow(Target::GlobalAsm),
1137+
Allow(Target::TyAlias),
1138+
Allow(Target::Enum),
1139+
Allow(Target::Variant),
1140+
Allow(Target::Struct),
1141+
Allow(Target::Field),
1142+
Allow(Target::Union),
1143+
Allow(Target::Trait),
1144+
Allow(Target::TraitAlias),
1145+
Allow(Target::Impl { of_trait: false }),
1146+
Allow(Target::Impl { of_trait: true }),
1147+
Allow(Target::Expression),
1148+
Allow(Target::Statement),
1149+
Allow(Target::Arm),
1150+
Allow(Target::AssocConst),
1151+
Allow(Target::Method(MethodKind::Inherent)),
1152+
Allow(Target::Method(MethodKind::Trait { body: false })),
1153+
Allow(Target::Method(MethodKind::Trait { body: true })),
1154+
Allow(Target::Method(MethodKind::TraitImpl)),
1155+
Allow(Target::AssocTy),
1156+
Allow(Target::ForeignFn),
1157+
Allow(Target::ForeignStatic),
1158+
Allow(Target::ForeignTy),
1159+
Allow(Target::MacroDef),
1160+
Allow(Target::Param),
1161+
Allow(Target::PatField),
1162+
Allow(Target::ExprField),
1163+
Allow(Target::WherePredicate),
1164+
Allow(Target::MacroCall),
1165+
Allow(Target::Crate),
1166+
Allow(Target::Delegation { mac: false }),
1167+
Allow(Target::Delegation { mac: true }),
1168+
];
1169+
9391170
/// Parse a single integer.
9401171
///
9411172
/// Used by attributes that take a single integer as argument, such as

0 commit comments

Comments
 (0)