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