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

8 files changed

Lines changed: 304 additions & 13 deletions

File tree

‎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-
usecrate::context::{AcceptContext,FinalizeContext,Stage};
24+
usecrate::context::{AcceptContext,AllowedTargets,FinalizeContext,Stage};
2525
usecrate::parser::ArgParser;
2626
usecrate::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
constATTRIBUTES:AcceptMapping<Self,S>;
8282

83+
constALLOWED_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
constON_DUPLICATE:OnDuplicate<S>;
118120

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

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

167172
fnfinalize(self,_cx:&FinalizeContext<'_,'_,S>) -> Option<AttributeKind>{
168173
Some(self.1?.0)
@@ -247,6 +252,7 @@ pub(crate) enum AttributeOrder {
247252
pub(crate)traitNoArgsAttributeParser<S:Stage>:'static{
248253
constPATH:&[Symbol];
249254
constON_DUPLICATE:OnDuplicate<S>;
255+
constALLOWED_TARGETS:AllowedTargets;
250256

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

269276
fnconvert(cx:&mutAcceptContext<'_,'_,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
constCONVERT:ConvertFn<Self::Item>;
295302

303+
constALLOWED_TARGETS:AllowedTargets;
304+
296305
/// The template this attribute parser should implement. Used for diagnostics.
297306
constTEMPLATE: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>forCombine<T,S>{
327-
constATTRIBUTES:AcceptMapping<Self,S> = &[(
328-
T::PATH,
329-
<TasCombineAttributeParser<S>>::TEMPLATE,
330-
|group:&mutCombine<T,S>, cx, args| {
336+
constATTRIBUTES:AcceptMapping<Self,S> =
337+
&[(T::PATH,T::TEMPLATE, |group:&mutCombine<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+
constALLOWED_TARGETS:AllowedTargets = T::ALLOWED_TARGETS;
336343

337344
fnfinalize(self,_cx:&FinalizeContext<'_,'_,S>) -> Option<AttributeKind>{
338345
ifletSome(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::{selfas ast,LitKind,MetaItemLit,NodeId};
89
use rustc_errors::{DiagCtxtHandle,Diagnostic};
@@ -63,8 +64,11 @@ use crate::attributes::traits::{
6364
};
6465
usecrate::attributes::transparency::TransparencyParser;
6566
usecrate::attributes::{AttributeParseras _,Combine,Single,WithoutArgs};
67+
usecrate::context::MaybeWarn::{Allow,Error,Warn};
6668
usecrate::parser::{ArgParser,MetaItemParser,PathParser};
67-
usecrate::session_diagnostics::{AttributeParseError,AttributeParseErrorReason,UnknownMetaItem};
69+
usecrate::session_diagnostics::{
70+
AttributeParseError,AttributeParseErrorReason,InvalidTarget,UnknownMetaItem,
71+
};
6872

6973
typeGroupType<S> = LazyLock<GroupTypeInner<S>>;
7074

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

8186
typeAcceptFn<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 ascrate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS,
127133
});
128134
}
129135

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

654+
#[derive(Debug)]
655+
pub(crate)enumAllowedTargets{
656+
AllowList(&'static[MaybeWarn]),
657+
AllowListWarnRest(&'static[MaybeWarn]),
658+
}
659+
660+
pub(crate)enumAllowedResult{
661+
Allowed,
662+
Warn,
663+
Error,
664+
}
665+
666+
implAllowedTargets{
667+
pub(crate)fnis_allowed(&self,target:Target) -> AllowedResult{
668+
matchself{
669+
AllowedTargets::AllowList(list) => {
670+
if list.contains(&Allow(target)){
671+
AllowedResult::Allowed
672+
}elseif 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+
}elseif list.contains(&Error(target)){
682+
AllowedResult::Error
683+
}else{
684+
AllowedResult::Warn
685+
}
686+
}
687+
}
688+
}
689+
690+
pub(crate)fnallowed_targets(&self) -> Vec<Target>{
691+
matchself{
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)enumMaybeWarn{
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
pubstructAttributeParser<'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+
ifself.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)fnallowed_targets_applied(
1051+
mutallowed_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+
ifletSome(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+
constFUNCTION_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+
constMETHOD_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+
constIMPL_LIKE:&[Target] =
1084+
&[Target::Impl{of_trait:false},Target::Impl{of_trait:true}];
1085+
constADT_LIKE:&[Target] = &[Target::Struct,Target::Enum];
1086+
1087+
letmut 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+
fnfilter_targets(
1111+
allowed_targets:&mutVec<Target>,
1112+
target_group:&'static[Target],
1113+
target_group_name:&'staticstr,
1114+
target:Target,
1115+
added_fake_targets:&mutVec<&'staticstr>,
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)constALL_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)