Skip to content

Commit 15b9255

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 daa08cd commit 15b9255

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

@@ -644,6 +650,64 @@ impl ShouldEmit {
644650
}
645651
}
646652

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

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

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

0 commit comments

Comments
 (0)