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