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