Skip to content

Commit 8227abe

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 c8a4d97 commit 8227abe

File tree

8 files changed

+245
-11
lines changed

8 files changed

+245
-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: 186 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,67 @@ impl ShouldEmit {
641647
}
642648
}
643649

650+
#[derive(Debug)]
651+
pub(crate) enum AllowedTargets {
652+
AllowAll,
653+
AllowList(&'static [MaybeWarn]),
654+
AllowListWarnRest(&'static [MaybeWarn]),
655+
}
656+
657+
pub(crate) enum AllowedResult {
658+
Allowed,
659+
Warn,
660+
Error,
661+
}
662+
663+
impl AllowedTargets {
664+
pub(crate) fn is_allowed(&self, target: Target) -> AllowedResult {
665+
match self {
666+
AllowedTargets::AllowAll => AllowedResult::Allowed,
667+
AllowedTargets::AllowList(list) => {
668+
if list.contains(&Allow(target)) {
669+
AllowedResult::Allowed
670+
} else if list.contains(&Warn(target)) {
671+
AllowedResult::Warn
672+
} else {
673+
AllowedResult::Error
674+
}
675+
}
676+
AllowedTargets::AllowListWarnRest(list) => {
677+
if list.contains(&Allow(target)) {
678+
AllowedResult::Allowed
679+
} else if list.contains(&Error(target)) {
680+
AllowedResult::Error
681+
} else {
682+
AllowedResult::Warn
683+
}
684+
}
685+
}
686+
}
687+
688+
pub(crate) fn allowed_targets(&self) -> Vec<Target> {
689+
match self {
690+
AllowedTargets::AllowAll => unreachable!(),
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+
644711
/// Context created once, for example as part of the ast lowering
645712
/// context, through which all attributes can be lowered.
646713
pub struct AttributeParser<'sess, S: Stage = Late> {
@@ -848,7 +915,52 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
848915
attr_path: path.get_attribute_path(),
849916
};
850917

851-
(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+
emit_lint(AttributeLint {
927+
id: target_id,
928+
span: attr.span,
929+
kind: AttributeLintKind::InvalidTarget {
930+
name: parts[0],
931+
target: target.plural_name(),
932+
only: if allowed_targets.len() == 1 {
933+
"only "
934+
} else {
935+
""
936+
},
937+
applied: allowed_targets_applied(
938+
allowed_targets,
939+
target,
940+
),
941+
},
942+
});
943+
}
944+
AllowedResult::Error => {
945+
let allowed_targets =
946+
accept.allowed_targets.allowed_targets();
947+
self.dcx().emit_err(InvalidTarget {
948+
span: attr.span,
949+
name: parts[0],
950+
target: target.plural_name(),
951+
only: if allowed_targets.len() == 1 {
952+
"only "
953+
} else {
954+
""
955+
},
956+
applied: allowed_targets_applied(
957+
allowed_targets,
958+
target,
959+
),
960+
});
961+
}
962+
}
963+
}
852964
}
853965
} else {
854966
// If we're here, we must be compiling a tool attribute... Or someone
@@ -936,6 +1048,77 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
9361048
}
9371049
}
9381050

1051+
/// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to.
1052+
/// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string
1053+
pub(crate) fn allowed_targets_applied(mut allowed_targets: Vec<Target>, target: Target) -> String {
1054+
// We define groups of "similar" targets.
1055+
// If at least two of the targets are allowed, and the `target` is not in the group,
1056+
// we collapse the entire group to a single entry to simplify the target list
1057+
const FUNCTION_LIKE: &[Target] = &[
1058+
Target::Fn,
1059+
Target::Closure,
1060+
Target::ForeignFn,
1061+
Target::Method(MethodKind::Inherent),
1062+
Target::Method(MethodKind::Trait { body: false }),
1063+
Target::Method(MethodKind::Trait { body: true }),
1064+
Target::Method(MethodKind::TraitImpl),
1065+
];
1066+
const METHOD_LIKE: &[Target] = &[
1067+
Target::Method(MethodKind::Inherent),
1068+
Target::Method(MethodKind::Trait { body: false }),
1069+
Target::Method(MethodKind::Trait { body: true }),
1070+
Target::Method(MethodKind::TraitImpl),
1071+
];
1072+
const IMPL_LIKE: &[Target] =
1073+
&[Target::Impl { of_trait: false }, Target::Impl { of_trait: true }];
1074+
const ADT_LIKE: &[Target] = &[Target::Struct, Target::Enum];
1075+
1076+
let mut added_fake_targets = Vec::new();
1077+
filter_targets(
1078+
&mut allowed_targets,
1079+
FUNCTION_LIKE,
1080+
"functions",
1081+
target,
1082+
&mut added_fake_targets,
1083+
);
1084+
filter_targets(&mut allowed_targets, METHOD_LIKE, "methods", target, &mut added_fake_targets);
1085+
filter_targets(&mut allowed_targets, IMPL_LIKE, "impl blocks", target, &mut added_fake_targets);
1086+
filter_targets(&mut allowed_targets, ADT_LIKE, "data types", target, &mut added_fake_targets);
1087+
1088+
// If there is now only 1 target left, show that as the only possible target
1089+
if allowed_targets.len() + added_fake_targets.len() == 1 {
1090+
return allowed_targets
1091+
.get(0)
1092+
.map(|t| t.plural_name())
1093+
.or(added_fake_targets.get(0).copied())
1094+
.unwrap()
1095+
.to_string();
1096+
}
1097+
1098+
added_fake_targets
1099+
.iter()
1100+
.copied()
1101+
.chain(allowed_targets.iter().map(|t| t.plural_name()))
1102+
.join(", ")
1103+
}
1104+
1105+
fn filter_targets(
1106+
allowed_targets: &mut Vec<Target>,
1107+
target_group: &'static [Target],
1108+
target_group_name: &'static str,
1109+
target: Target,
1110+
added_fake_targets: &mut Vec<&'static str>,
1111+
) {
1112+
if target_group.contains(&target) {
1113+
return;
1114+
}
1115+
if allowed_targets.iter().filter(|at| target_group.contains(at)).count() < 2 {
1116+
return;
1117+
}
1118+
allowed_targets.retain(|t| !target_group.contains(t));
1119+
added_fake_targets.push(target_group_name);
1120+
}
1121+
9391122
/// Parse a single integer.
9401123
///
9411124
/// Used by attributes that take a single integer as argument, such as

compiler/rustc_attr_parsing/src/lints.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,17 @@ pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<HirId>, lint_emi
3434
*first_span,
3535
session_diagnostics::EmptyAttributeList { attr_span: *first_span },
3636
),
37+
&AttributeLintKind::InvalidTarget { name, target, ref applied, only } => lint_emitter
38+
.emit_node_span_lint(
39+
rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
40+
*id,
41+
*span,
42+
session_diagnostics::InvalidTargetLint {
43+
name,
44+
target,
45+
applied: applied.clone(),
46+
only,
47+
},
48+
),
3749
}
3850
}

compiler/rustc_attr_parsing/src/session_diagnostics.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,29 @@ pub(crate) struct EmptyAttributeList {
480480
pub attr_span: Span,
481481
}
482482

483+
#[derive(LintDiagnostic)]
484+
#[diag(attr_parsing_invalid_target_lint)]
485+
#[warning]
486+
#[help]
487+
pub(crate) struct InvalidTargetLint {
488+
pub name: Symbol,
489+
pub target: &'static str,
490+
pub applied: String,
491+
pub only: &'static str,
492+
}
493+
494+
#[derive(Diagnostic)]
495+
#[help]
496+
#[diag(attr_parsing_invalid_target)]
497+
pub(crate) struct InvalidTarget {
498+
#[primary_span]
499+
pub span: Span,
500+
pub name: Symbol,
501+
pub target: &'static str,
502+
pub applied: String,
503+
pub only: &'static str,
504+
}
505+
483506
#[derive(Diagnostic)]
484507
#[diag(attr_parsing_invalid_alignment_value, code = E0589)]
485508
pub(crate) struct InvalidAlignmentValue {

0 commit comments

Comments
 (0)