rustc_attr_parsing/
target_checking.rs

1use std::borrow::Cow;
2
3use rustc_ast::AttrStyle;
4use rustc_errors::DiagArgValue;
5use rustc_feature::{AttributeType, Features};
6use rustc_hir::lints::{AttributeLint, AttributeLintKind};
7use rustc_hir::{AttrPath, MethodKind, Target};
8use rustc_span::Span;
9
10use crate::AttributeParser;
11use crate::context::{AcceptContext, Stage};
12use crate::session_diagnostics::InvalidTarget;
13
14#[derive(Debug)]
15pub(crate) enum AllowedTargets {
16    AllowList(&'static [Policy]),
17    AllowListWarnRest(&'static [Policy]),
18}
19
20pub(crate) enum AllowedResult {
21    Allowed,
22    Warn,
23    Error,
24}
25
26impl AllowedTargets {
27    pub(crate) fn is_allowed(&self, target: Target) -> AllowedResult {
28        match self {
29            AllowedTargets::AllowList(list) => {
30                if list.contains(&Policy::Allow(target)) {
31                    AllowedResult::Allowed
32                } else if list.contains(&Policy::Warn(target)) {
33                    AllowedResult::Warn
34                } else {
35                    AllowedResult::Error
36                }
37            }
38            AllowedTargets::AllowListWarnRest(list) => {
39                if list.contains(&Policy::Allow(target)) {
40                    AllowedResult::Allowed
41                } else if list.contains(&Policy::Error(target)) {
42                    AllowedResult::Error
43                } else {
44                    AllowedResult::Warn
45                }
46            }
47        }
48    }
49
50    pub(crate) fn allowed_targets(&self) -> Vec<Target> {
51        match self {
52            AllowedTargets::AllowList(list) => list,
53            AllowedTargets::AllowListWarnRest(list) => list,
54        }
55        .iter()
56        .filter_map(|target| match target {
57            Policy::Allow(target) => Some(*target),
58            Policy::Warn(_) => None,
59            Policy::Error(_) => None,
60        })
61        .collect()
62    }
63}
64
65#[derive(Debug, Eq, PartialEq)]
66pub(crate) enum Policy {
67    Allow(Target),
68    Warn(Target),
69    Error(Target),
70}
71
72impl<'sess, S: Stage> AttributeParser<'sess, S> {
73    pub(crate) fn check_target(
74        &self,
75        attr_name: AttrPath,
76        attr_span: Span,
77        allowed_targets: &AllowedTargets,
78        target: Target,
79        target_id: S::Id,
80        mut emit_lint: impl FnMut(AttributeLint<S::Id>),
81    ) {
82        match allowed_targets.is_allowed(target) {
83            AllowedResult::Allowed => {}
84            AllowedResult::Warn => {
85                let allowed_targets = allowed_targets.allowed_targets();
86                let (applied, only) =
87                    allowed_targets_applied(allowed_targets, target, self.features);
88                emit_lint(AttributeLint {
89                    id: target_id,
90                    span: attr_span,
91                    kind: AttributeLintKind::InvalidTarget {
92                        name: attr_name,
93                        target,
94                        only: if only { "only " } else { "" },
95                        applied,
96                    },
97                });
98            }
99            AllowedResult::Error => {
100                let allowed_targets = allowed_targets.allowed_targets();
101                let (applied, only) =
102                    allowed_targets_applied(allowed_targets, target, self.features);
103                self.dcx().emit_err(InvalidTarget {
104                    span: attr_span,
105                    name: attr_name,
106                    target: target.plural_name(),
107                    only: if only { "only " } else { "" },
108                    applied: DiagArgValue::StrListSepByAnd(
109                        applied.into_iter().map(Cow::Owned).collect(),
110                    ),
111                });
112            }
113        }
114    }
115
116    pub(crate) fn check_type(
117        attribute_type: AttributeType,
118        target: Target,
119        cx: &mut AcceptContext<'_, 'sess, S>,
120    ) {
121        let is_crate_root = S::id_is_crate_root(cx.target_id);
122
123        if is_crate_root {
124            return;
125        }
126
127        if attribute_type != AttributeType::CrateLevel {
128            return;
129        }
130
131        let lint = AttributeLintKind::InvalidStyle {
132            name: cx.attr_path.clone(),
133            is_used_as_inner: cx.attr_style == AttrStyle::Inner,
134            target,
135            target_span: cx.target_span,
136        };
137        let attr_span = cx.attr_span;
138
139        cx.emit_lint(lint, attr_span);
140    }
141}
142
143/// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to.
144/// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string
145pub(crate) fn allowed_targets_applied(
146    mut allowed_targets: Vec<Target>,
147    target: Target,
148    features: Option<&Features>,
149) -> (Vec<String>, bool) {
150    // Remove unstable targets from `allowed_targets` if their features are not enabled
151    if let Some(features) = features {
152        if !features.fn_delegation() {
153            allowed_targets.retain(|t| !matches!(t, Target::Delegation { .. }));
154        }
155        if !features.stmt_expr_attributes() {
156            allowed_targets.retain(|t| !matches!(t, Target::Expression | Target::Statement));
157        }
158        if !features.extern_types() {
159            allowed_targets.retain(|t| !matches!(t, Target::ForeignTy));
160        }
161    }
162
163    // We define groups of "similar" targets.
164    // If at least two of the targets are allowed, and the `target` is not in the group,
165    // we collapse the entire group to a single entry to simplify the target list
166    const FUNCTION_LIKE: &[Target] = &[
167        Target::Fn,
168        Target::Closure,
169        Target::ForeignFn,
170        Target::Method(MethodKind::Inherent),
171        Target::Method(MethodKind::Trait { body: false }),
172        Target::Method(MethodKind::Trait { body: true }),
173        Target::Method(MethodKind::TraitImpl),
174    ];
175    const METHOD_LIKE: &[Target] = &[
176        Target::Method(MethodKind::Inherent),
177        Target::Method(MethodKind::Trait { body: false }),
178        Target::Method(MethodKind::Trait { body: true }),
179        Target::Method(MethodKind::TraitImpl),
180    ];
181    const IMPL_LIKE: &[Target] =
182        &[Target::Impl { of_trait: false }, Target::Impl { of_trait: true }];
183    const ADT_LIKE: &[Target] = &[Target::Struct, Target::Enum];
184
185    let mut added_fake_targets = Vec::new();
186    filter_targets(
187        &mut allowed_targets,
188        FUNCTION_LIKE,
189        "functions",
190        target,
191        &mut added_fake_targets,
192    );
193    filter_targets(&mut allowed_targets, METHOD_LIKE, "methods", target, &mut added_fake_targets);
194    filter_targets(&mut allowed_targets, IMPL_LIKE, "impl blocks", target, &mut added_fake_targets);
195    filter_targets(&mut allowed_targets, ADT_LIKE, "data types", target, &mut added_fake_targets);
196
197    // If there is now only 1 target left, show that as the only possible target
198    (
199        added_fake_targets
200            .iter()
201            .copied()
202            .chain(allowed_targets.iter().map(|t| t.plural_name()))
203            .map(|i| i.to_string())
204            .collect(),
205        allowed_targets.len() + added_fake_targets.len() == 1,
206    )
207}
208
209fn filter_targets(
210    allowed_targets: &mut Vec<Target>,
211    target_group: &'static [Target],
212    target_group_name: &'static str,
213    target: Target,
214    added_fake_targets: &mut Vec<&'static str>,
215) {
216    if target_group.contains(&target) {
217        return;
218    }
219    if allowed_targets.iter().filter(|at| target_group.contains(at)).count() < 2 {
220        return;
221    }
222    allowed_targets.retain(|t| !target_group.contains(t));
223    added_fake_targets.push(target_group_name);
224}
225
226/// This is the list of all targets to which a attribute can be applied
227/// This is used for:
228/// - `rustc_dummy`, which can be applied to all targets
229/// - Attributes that are not parted to the new target system yet can use this list as a placeholder
230pub(crate) const ALL_TARGETS: &'static [Policy] = {
231    use Policy::Allow;
232    &[
233        Allow(Target::ExternCrate),
234        Allow(Target::Use),
235        Allow(Target::Static),
236        Allow(Target::Const),
237        Allow(Target::Fn),
238        Allow(Target::Closure),
239        Allow(Target::Mod),
240        Allow(Target::ForeignMod),
241        Allow(Target::GlobalAsm),
242        Allow(Target::TyAlias),
243        Allow(Target::Enum),
244        Allow(Target::Variant),
245        Allow(Target::Struct),
246        Allow(Target::Field),
247        Allow(Target::Union),
248        Allow(Target::Trait),
249        Allow(Target::TraitAlias),
250        Allow(Target::Impl { of_trait: false }),
251        Allow(Target::Impl { of_trait: true }),
252        Allow(Target::Expression),
253        Allow(Target::Statement),
254        Allow(Target::Arm),
255        Allow(Target::AssocConst),
256        Allow(Target::Method(MethodKind::Inherent)),
257        Allow(Target::Method(MethodKind::Trait { body: false })),
258        Allow(Target::Method(MethodKind::Trait { body: true })),
259        Allow(Target::Method(MethodKind::TraitImpl)),
260        Allow(Target::AssocTy),
261        Allow(Target::ForeignFn),
262        Allow(Target::ForeignStatic),
263        Allow(Target::ForeignTy),
264        Allow(Target::MacroDef),
265        Allow(Target::Param),
266        Allow(Target::PatField),
267        Allow(Target::ExprField),
268        Allow(Target::WherePredicate),
269        Allow(Target::MacroCall),
270        Allow(Target::Crate),
271        Allow(Target::Delegation { mac: false }),
272        Allow(Target::Delegation { mac: true }),
273    ]
274};