rustc_attr_parsing/
target_checking.rs

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