Skip to content

Commit 744d39e

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 e7ef23e commit 744d39e

File tree

8 files changed

+304
-13
lines changed

8 files changed

+304
-13
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};
@@ -63,8 +64,11 @@ use crate::attributes::traits::{
6364
};
6465
use crate::attributes::transparency::TransparencyParser;
6566
use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
67+
use crate::context::MaybeWarn::{Allow, Error, Warn};
6668
use crate::parser::{ArgParser, MetaItemParser, PathParser};
67-
use crate::session_diagnostics::{AttributeParseError, AttributeParseErrorReason, UnknownMetaItem};
69+
use crate::session_diagnostics::{
70+
AttributeParseError, AttributeParseErrorReason, InvalidTarget, UnknownMetaItem,
71+
};
6872

6973
type GroupType<S> = LazyLock<GroupTypeInner<S>>;
7074

@@ -76,6 +80,7 @@ struct GroupTypeInner<S: Stage> {
7680
struct GroupTypeInnerAccept<S: Stage> {
7781
template: AttributeTemplate,
7882
accept_fn: AcceptFn<S>,
83+
allowed_targets: AllowedTargets,
7984
}
8085

8186
type AcceptFn<S> =
@@ -123,7 +128,8 @@ macro_rules! attribute_parsers {
123128
STATE_OBJECT.with_borrow_mut(|s| {
124129
accept_fn(s, cx, args)
125130
})
126-
})
131+
}),
132+
allowed_targets: <$names as crate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS,
127133
});
128134
}
129135

@@ -645,6 +651,64 @@ impl ShouldEmit {
645651
}
646652
}
647653

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

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

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

0 commit comments

Comments
 (0)