Skip to main content

rustc_attr_parsing/
target_checking.rs

1use std::borrow::Cow;
2
3use rustc_ast::AttrStyle;
4use rustc_errors::{DiagArgValue, Diagnostic, MultiSpan, StashKey};
5use rustc_feature::Features;
6use rustc_hir::attrs::AttributeKind;
7use rustc_hir::{AttrItem, Attribute, MethodKind, Target};
8use rustc_span::{BytePos, FileName, RemapPathScopeComponents, Span, Symbol, sym};
9
10use crate::context::AcceptContext;
11use crate::errors::{
12    InvalidAttrAtCrateLevel, InvalidTargetLint, ItemFollowingInnerAttr,
13    UnsupportedAttributesInWhere,
14};
15use crate::session_diagnostics::InvalidTarget;
16use crate::target_checking::Policy::Allow;
17use crate::{AttributeParser, ShouldEmit};
18
19#[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)]
20pub(crate) enum AllowedTargets {
21    AllowList(&'static [Policy]),
22    AllowListWarnRest(&'static [Policy]),
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        }
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        }
64        .iter()
65        .filter_map(|target| match target {
66            Policy::Allow(target) => Some(*target),
67            Policy::AllowSilent(_) => None, // Not listed in possible targets
68            Policy::Warn(_) => None,
69            Policy::Error(_) => None,
70        })
71        .collect()
72    }
73}
74
75/// This policy determines what diagnostics should be emitted based on the `Target` of the attribute.
76#[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_fields_are_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)]
77pub(crate) enum Policy {
78    /// A target that is allowed.
79    Allow(Target),
80    /// A target that is allowed and not listed in the possible targets.
81    /// This is useful if the target is checked elsewhere.
82    AllowSilent(Target),
83    /// Emits a FCW on this target.
84    /// This is useful if the target was previously allowed but should not be.
85    Warn(Target),
86    /// Emits an error on this target.
87    Error(Target),
88}
89
90impl<'sess> AttributeParser<'sess> {
91    pub(crate) fn check_target(
92        allowed_targets: &AllowedTargets,
93        cx: &mut AcceptContext<'_, 'sess>,
94    ) {
95        if #[allow(non_exhaustive_omitted_patterns)] match cx.should_emit {
    ShouldEmit::Nothing => true,
    _ => false,
}matches!(cx.should_emit, ShouldEmit::Nothing) {
96            return;
97        }
98
99        // For crate-level attributes we emit a specific set of lints to warn
100        // people about accidentally not using them on the crate.
101        if let &AllowedTargets::AllowList(&[Allow(Target::Crate)]) = allowed_targets {
102            Self::check_crate_level(cx);
103            return;
104        }
105
106        if #[allow(non_exhaustive_omitted_patterns)] match cx.attr_path.segments.as_ref()
    {
    [sym::repr] => true,
    _ => false,
}matches!(cx.attr_path.segments.as_ref(), [sym::repr]) && cx.target == Target::Crate {
107            // The allowed targets of `repr` depend on its arguments. They can't be checked using
108            // the `AttributeParser` code.
109            let span = cx.attr_span;
110            let item =
111                cx.cx.first_line_of_next_item(span).map(|span| ItemFollowingInnerAttr { span });
112
113            let pound_to_opening_bracket = cx.attr_span.until(cx.inner_span);
114
115            cx.dcx()
116                .create_err(InvalidAttrAtCrateLevel {
117                    span,
118                    pound_to_opening_bracket,
119                    name: sym::repr,
120                    item,
121                })
122                .emit();
123        }
124
125        match allowed_targets.is_allowed(cx.target) {
126            AllowedResult::Allowed => {}
127            AllowedResult::Warn => {
128                let allowed_targets = allowed_targets.allowed_targets();
129                let (applied, only) =
130                    allowed_targets_applied(allowed_targets, cx.target, cx.features);
131                let name = cx.attr_path.clone();
132
133                let lint = if name.segments[0] == sym::deprecated
134                    && ![
135                        Target::Closure,
136                        Target::Expression,
137                        Target::Statement,
138                        Target::Arm,
139                        Target::MacroCall,
140                    ]
141                    .contains(&cx.target)
142                {
143                    rustc_session::lint::builtin::USELESS_DEPRECATED
144                } else {
145                    rustc_session::lint::builtin::UNUSED_ATTRIBUTES
146                };
147
148                let attr_span = cx.attr_span;
149                let target = cx.target;
150                cx.emit_lint_with_sess(
151                    lint,
152                    move |dcx, level, _| {
153                        InvalidTargetLint {
154                            name: name.to_string(),
155                            target: target.plural_name(),
156                            only: if only { "only " } else { "" },
157                            applied: DiagArgValue::StrListSepByAnd(
158                                applied.iter().map(|i| Cow::Owned(i.to_string())).collect(),
159                            ),
160                            attr_span,
161                        }
162                        .into_diag(dcx, level)
163                    },
164                    attr_span,
165                );
166            }
167            AllowedResult::Error => {
168                let allowed_targets = allowed_targets.allowed_targets();
169                let (applied, only) =
170                    allowed_targets_applied(allowed_targets, cx.target, cx.features);
171                let name = cx.attr_path.clone();
172                cx.dcx().emit_err(InvalidTarget {
173                    span: cx.attr_span.clone(),
174                    name,
175                    target: cx.target.plural_name(),
176                    only: if only { "only " } else { "" },
177                    applied: DiagArgValue::StrListSepByAnd(
178                        applied.into_iter().map(Cow::Owned).collect(),
179                    ),
180                });
181            }
182        }
183    }
184
185    pub(crate) fn check_crate_level(cx: &mut AcceptContext<'_, 'sess>) {
186        if cx.target == Target::Crate {
187            return;
188        }
189
190        let name = cx.attr_path.to_string();
191        let is_used_as_inner = cx.attr_style == AttrStyle::Inner;
192        let target_span = cx.target_span;
193        let attr_span = cx.attr_span;
194
195        let (show_crate_root_help, crate_root_path) = is_used_as_inner
196            .then(|| cx.cx.sess.local_crate_source_file())
197            .flatten()
198            .filter(|src| {
199                !#[allow(non_exhaustive_omitted_patterns)] match cx.cx.sess.source_map().span_to_filename(attr_span)
    {
    FileName::Real(ref name) if name == src => true,
    _ => false,
}matches!(
200                    cx.cx.sess.source_map().span_to_filename(attr_span),
201                    FileName::Real(ref name) if name == src
202                )
203            })
204            .map(|src| {
205                (true, src.path(RemapPathScopeComponents::DIAGNOSTICS).display().to_string())
206            })
207            .unwrap_or_default();
208
209        let target = cx.target;
210        cx.emit_lint(
211            rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
212            crate::errors::InvalidAttrStyle {
213                name,
214                is_used_as_inner,
215                target_span: (!is_used_as_inner).then_some(target_span),
216                target: target.name(),
217                crate_root_path,
218                show_crate_root_help,
219            },
220            attr_span,
221        );
222    }
223
224    // FIXME: Fix "Cannot determine resolution" error and remove built-in macros
225    // from this check.
226    pub(crate) fn check_invalid_crate_level_attr_item(&self, attr: &AttrItem, inner_span: Span) {
227        // Check for builtin attributes at the crate level
228        // which were unsuccessfully resolved due to cannot determine
229        // resolution for the attribute macro error.
230        const ATTRS_TO_CHECK: &[Symbol] =
231            &[sym::derive, sym::test, sym::test_case, sym::global_allocator, sym::bench];
232
233        // FIXME(jdonszelmann): all attrs should be combined here cleaning this up some day.
234        if let Some(name) = ATTRS_TO_CHECK.iter().find(|attr_to_check| #[allow(non_exhaustive_omitted_patterns)] match attr.path.segments.as_ref() {
    [segment] if segment == *attr_to_check => true,
    _ => false,
}matches!(attr.path.segments.as_ref(), [segment] if segment == *attr_to_check)) {
235            let span = attr.span;
236            let name = *name;
237
238            let item = self.first_line_of_next_item(span).map(|span| ItemFollowingInnerAttr { span });
239
240            let err = self.dcx().create_err(InvalidAttrAtCrateLevel {
241                span,
242                pound_to_opening_bracket: span.until(inner_span),
243                name,
244                item,
245            });
246
247            self.dcx().try_steal_replace_and_emit_err(
248                attr.path.span,
249                StashKey::UndeterminedMacroResolution,
250                err,
251            );
252        }
253    }
254
255    fn first_line_of_next_item(&self, span: Span) -> Option<Span> {
256        // We can't exactly call `tcx.hir_free_items()` here because it's too early and querying
257        // this would create a circular dependency. Instead, we resort to getting the original
258        // source code that follows `span` and find the next item from here.
259
260        self.sess()
261            .source_map()
262            .span_to_source(span, |content, _, span_end| {
263                let mut source = &content[span_end..];
264                let initial_source_len = source.len();
265                let span = try {
266                    loop {
267                        let first = source.chars().next()?;
268
269                        if first.is_whitespace() {
270                            let split_idx = source.find(|c: char| !c.is_whitespace())?;
271                            source = &source[split_idx..];
272                        } else if source.starts_with("//") {
273                            let line_idx = source.find('\n')?;
274                            source = &source[line_idx + '\n'.len_utf8()..];
275                        } else if source.starts_with("/*") {
276                            // FIXME: support nested comments.
277                            let close_idx = source.find("*/")?;
278                            source = &source[close_idx + "*/".len()..];
279                        } else if first == '#' {
280                            // FIXME: properly find the end of the attributes in order to accurately
281                            // skip them. This version just consumes the source code until the next
282                            // `]`.
283                            let close_idx = source.find(']')?;
284                            source = &source[close_idx + ']'.len_utf8()..];
285                        } else {
286                            let lo = span_end + initial_source_len - source.len();
287                            let last_line = source.split('\n').next().map(|s| s.trim_end())?;
288
289                            let hi = lo + last_line.len();
290                            let lo = BytePos(lo as u32);
291                            let hi = BytePos(hi as u32);
292                            let next_item_span = Span::new(lo, hi, span.ctxt(), None);
293
294                            break next_item_span;
295                        }
296                    }
297                };
298
299                Ok(span)
300            })
301            .ok()
302            .flatten()
303    }
304
305    pub(crate) fn check_invalid_where_predicate_attrs<'attr>(
306        &self,
307        attrs: impl IntoIterator<Item = &'attr Attribute>,
308    ) {
309        // FIXME(where_clause_attrs): Currently, as the following check shows,
310        // only `#[cfg]` and `#[cfg_attr]` are allowed, but it should be removed
311        // if we allow more attributes (e.g., tool attributes and `allow/deny/warn`)
312        // in where clauses. After that, this function would become useless.
313        let spans = attrs
314            .into_iter()
315            .filter_map(|attr| {
316                match attr {
317                    Attribute::Parsed(AttributeKind::DocComment { span, .. }) => Some(*span),
318                    // FIXME: We shouldn't need to special-case `doc`!
319                    Attribute::Parsed(AttributeKind::Doc(attr)) => Some(attr.first_span),
320                    // Checked during attribute parsing target checking
321                    Attribute::Parsed(_) => None,
322                    Attribute::Unparsed(attr) => Some(attr.span),
323                }
324            })
325            .collect::<Vec<_>>();
326        if !spans.is_empty() {
327            self.dcx()
328                .emit_err(UnsupportedAttributesInWhere { span: MultiSpan::from_spans(spans) });
329        }
330    }
331}
332
333/// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to.
334/// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string
335pub(crate) fn allowed_targets_applied(
336    mut allowed_targets: Vec<Target>,
337    target: Target,
338    features: Option<&Features>,
339) -> (Vec<String>, bool) {
340    // Remove unstable targets from `allowed_targets` if their features are not enabled
341    if let Some(features) = features {
342        if !features.fn_delegation() {
343            allowed_targets.retain(|t| !#[allow(non_exhaustive_omitted_patterns)] match t {
    Target::Delegation { .. } => true,
    _ => false,
}matches!(t, Target::Delegation { .. }));
344        }
345        if !features.stmt_expr_attributes() {
346            allowed_targets.retain(|t| !#[allow(non_exhaustive_omitted_patterns)] match t {
    Target::Expression | Target::Statement => true,
    _ => false,
}matches!(t, Target::Expression | Target::Statement));
347        }
348        if !features.extern_types() {
349            allowed_targets.retain(|t| !#[allow(non_exhaustive_omitted_patterns)] match t {
    Target::ForeignTy => true,
    _ => false,
}matches!(t, Target::ForeignTy));
350        }
351    }
352
353    // We define groups of "similar" targets.
354    // If at least two of the targets are allowed, and the `target` is not in the group,
355    // we collapse the entire group to a single entry to simplify the target list
356    const FUNCTION_LIKE: &[Target] = &[
357        Target::Fn,
358        Target::Closure,
359        Target::ForeignFn,
360        Target::Method(MethodKind::Inherent),
361        Target::Method(MethodKind::Trait { body: false }),
362        Target::Method(MethodKind::Trait { body: true }),
363        Target::Method(MethodKind::TraitImpl),
364    ];
365    const METHOD_LIKE: &[Target] = &[
366        Target::Method(MethodKind::Inherent),
367        Target::Method(MethodKind::Trait { body: false }),
368        Target::Method(MethodKind::Trait { body: true }),
369        Target::Method(MethodKind::TraitImpl),
370    ];
371    const IMPL_LIKE: &[Target] =
372        &[Target::Impl { of_trait: false }, Target::Impl { of_trait: true }];
373    const ADT_LIKE: &[Target] = &[Target::Struct, Target::Enum, Target::Union];
374
375    let mut added_fake_targets = Vec::new();
376    filter_targets(
377        &mut allowed_targets,
378        FUNCTION_LIKE,
379        "functions",
380        target,
381        &mut added_fake_targets,
382    );
383    filter_targets(&mut allowed_targets, METHOD_LIKE, "methods", target, &mut added_fake_targets);
384    filter_targets(&mut allowed_targets, IMPL_LIKE, "impl blocks", target, &mut added_fake_targets);
385    filter_targets(&mut allowed_targets, ADT_LIKE, "data types", target, &mut added_fake_targets);
386
387    let mut target_strings: Vec<_> = added_fake_targets
388        .iter()
389        .copied()
390        .chain(allowed_targets.iter().map(|t| t.plural_name()))
391        .map(|i| i.to_string())
392        .collect();
393
394    // ensure a consistent order
395    target_strings.sort();
396
397    // If there is now only 1 target left, show that as the only possible target
398    let only_target = target_strings.len() == 1;
399
400    (target_strings, only_target)
401}
402
403fn filter_targets(
404    allowed_targets: &mut Vec<Target>,
405    target_group: &'static [Target],
406    target_group_name: &'static str,
407    target: Target,
408    added_fake_targets: &mut Vec<&'static str>,
409) {
410    if target_group.contains(&target) {
411        return;
412    }
413    if allowed_targets.iter().filter(|at| target_group.contains(at)).count() < 2 {
414        return;
415    }
416    allowed_targets.retain(|t| !target_group.contains(t));
417    added_fake_targets.push(target_group_name);
418}
419
420/// This is the list of all targets to which a attribute can be applied
421/// This is used for:
422/// - `rustc_dummy`, which can be applied to all targets
423/// - Attributes that are not parted to the new target system yet can use this list as a placeholder
424pub(crate) const ALL_TARGETS: &'static [Policy] = {
425    use Policy::Allow;
426    &[
427        Allow(Target::ExternCrate),
428        Allow(Target::Use),
429        Allow(Target::Static),
430        Allow(Target::Const),
431        Allow(Target::Fn),
432        Allow(Target::Closure),
433        Allow(Target::Mod),
434        Allow(Target::ForeignMod),
435        Allow(Target::GlobalAsm),
436        Allow(Target::TyAlias),
437        Allow(Target::Enum),
438        Allow(Target::Variant),
439        Allow(Target::Struct),
440        Allow(Target::Field),
441        Allow(Target::Union),
442        Allow(Target::Trait),
443        Allow(Target::TraitAlias),
444        Allow(Target::Impl { of_trait: false }),
445        Allow(Target::Impl { of_trait: true }),
446        Allow(Target::Expression),
447        Allow(Target::Statement),
448        Allow(Target::Arm),
449        Allow(Target::AssocConst),
450        Allow(Target::Method(MethodKind::Inherent)),
451        Allow(Target::Method(MethodKind::Trait { body: false })),
452        Allow(Target::Method(MethodKind::Trait { body: true })),
453        Allow(Target::Method(MethodKind::TraitImpl)),
454        Allow(Target::AssocTy),
455        Allow(Target::ForeignFn),
456        Allow(Target::ForeignStatic),
457        Allow(Target::ForeignTy),
458        Allow(Target::MacroDef),
459        Allow(Target::Param),
460        Allow(Target::PatField),
461        Allow(Target::ExprField),
462        Allow(Target::WherePredicate),
463        Allow(Target::MacroCall),
464        Allow(Target::Crate),
465        Allow(Target::Delegation { mac: false }),
466        Allow(Target::Delegation { mac: true }),
467        Allow(Target::GenericParam {
468            kind: rustc_hir::target::GenericParamKind::Const,
469            has_default: false,
470        }),
471        Allow(Target::GenericParam {
472            kind: rustc_hir::target::GenericParamKind::Const,
473            has_default: true,
474        }),
475        Allow(Target::GenericParam {
476            kind: rustc_hir::target::GenericParamKind::Lifetime,
477            has_default: false,
478        }),
479        Allow(Target::GenericParam {
480            kind: rustc_hir::target::GenericParamKind::Lifetime,
481            has_default: true,
482        }),
483        Allow(Target::GenericParam {
484            kind: rustc_hir::target::GenericParamKind::Type,
485            has_default: false,
486        }),
487        Allow(Target::GenericParam {
488            kind: rustc_hir::target::GenericParamKind::Type,
489            has_default: true,
490        }),
491    ]
492};