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