Skip to main content

rustc_attr_parsing/attributes/
lint.rs

1use rustc_ast::LitKind;
2use rustc_hir::HashIgnoredAttrId;
3use rustc_hir::attrs::{LintAttribute, LintAttributeKind, LintInstance};
4use rustc_hir::lints::AttributeLintKind;
5use rustc_hir::target::GenericParamKind;
6use rustc_session::DynLintStore;
7use rustc_session::lint::builtin::{RENAMED_AND_REMOVED_LINTS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES};
8use rustc_session::lint::{CheckLintNameResult, LintId};
9
10use super::prelude::*;
11use crate::attributes::AcceptFn;
12use crate::session_diagnostics::UnknownToolInScopedLint;
13
14pub(crate) trait Lint {
15    const KIND: LintAttributeKind;
16    const ATTR_SYMBOL: Symbol = Self::KIND.symbol();
17}
18
19pub(crate) struct Allow;
20
21impl Lint for Allow {
22    const KIND: LintAttributeKind = LintAttributeKind::Allow;
23}
24pub(crate) struct Deny;
25
26impl Lint for Deny {
27    const KIND: LintAttributeKind = LintAttributeKind::Deny;
28}
29pub(crate) struct Expect;
30
31impl Lint for Expect {
32    const KIND: LintAttributeKind = LintAttributeKind::Expect;
33}
34pub(crate) struct Forbid;
35
36impl Lint for Forbid {
37    const KIND: LintAttributeKind = LintAttributeKind::Forbid;
38}
39pub(crate) struct Warn;
40
41impl Lint for Warn {
42    const KIND: LintAttributeKind = LintAttributeKind::Warn;
43}
44
45#[derive(#[automatically_derived]
impl ::core::default::Default for LintParser {
    #[inline]
    fn default() -> LintParser {
        LintParser { lint_attrs: ::core::default::Default::default() }
    }
}Default)]
46pub(crate) struct LintParser {
47    lint_attrs: ThinVec<LintAttribute>,
48}
49
50trait Mapping<S: Stage> {
51    const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn<LintParser, S>);
52}
53impl<S: Stage, T: Lint> Mapping<S> for T {
54    const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn<LintParser, S>) = (
55        &[T::ATTR_SYMBOL],
56        ::rustc_feature::AttributeTemplate {
    word: false,
    list: Some(&["lint1", "lint1, lint2, ...",
                    r#"lint1, lint2, lint3, reason = "...""#]),
    one_of: &[],
    name_value_str: None,
    docs: Some("https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"),
}template!(
57            List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#],
58            "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"
59        ),
60        |this, cx, args| {
61            if let Some(lint_attr) = validate_lint_attr::<T, S>(cx, args) {
62                this.lint_attrs.push(lint_attr);
63            }
64        },
65    );
66}
67
68impl<S: Stage> AttributeParser<S> for LintParser {
69    const ATTRIBUTES: AcceptMapping<Self, S> =
70        &[Allow::MAPPING, Deny::MAPPING, Expect::MAPPING, Forbid::MAPPING, Warn::MAPPING];
71
72    const ALLOWED_TARGETS: AllowedTargets = {
73        use super::prelude::{Allow, Warn};
74        AllowedTargets::AllowList(&[
75            Allow(Target::ExternCrate),
76            Allow(Target::Use),
77            Allow(Target::Static),
78            Allow(Target::Const),
79            Allow(Target::Fn),
80            Allow(Target::Closure),
81            Allow(Target::Mod),
82            Allow(Target::ForeignMod),
83            Allow(Target::GlobalAsm),
84            Allow(Target::TyAlias),
85            Allow(Target::Enum),
86            Allow(Target::Variant),
87            Allow(Target::Struct),
88            Allow(Target::Field),
89            Allow(Target::Union),
90            Allow(Target::Trait),
91            Allow(Target::TraitAlias),
92            Allow(Target::Impl { of_trait: false }),
93            Allow(Target::Impl { of_trait: true }),
94            Allow(Target::Expression),
95            Allow(Target::Statement),
96            Allow(Target::Arm),
97            Allow(Target::AssocConst),
98            Allow(Target::Method(MethodKind::Inherent)),
99            Allow(Target::Method(MethodKind::Trait { body: false })),
100            Allow(Target::Method(MethodKind::Trait { body: true })),
101            Allow(Target::Method(MethodKind::TraitImpl)),
102            Allow(Target::AssocTy),
103            Allow(Target::ForeignFn),
104            Allow(Target::ForeignStatic),
105            Allow(Target::ForeignTy),
106            Allow(Target::MacroDef),
107            Allow(Target::Param),
108            Allow(Target::PatField),
109            Allow(Target::ExprField),
110            Allow(Target::Crate),
111            Allow(Target::Delegation { mac: false }),
112            Allow(Target::Delegation { mac: true }),
113            Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: false }),
114            Allow(Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: false }),
115            Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: false }),
116            Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: true }),
117            Allow(Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: true }),
118            Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: true }),
119            Warn(Target::MacroCall),
120        ])
121    };
122
123    fn finalize(mut self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
124        if !self.lint_attrs.is_empty() {
125            // Sort to ensure correct order operations later
126            self.lint_attrs.sort_by(|a, b| a.attr_span.cmp(&b.attr_span));
127            Some(AttributeKind::LintAttributes(self.lint_attrs))
128        } else {
129            None
130        }
131    }
132}
133
134#[inline(always)]
135fn validate_lint_attr<T: Lint, S: Stage>(
136    cx: &mut AcceptContext<'_, '_, S>,
137    args: &ArgParser,
138) -> Option<LintAttribute> {
139    let Some(lint_store) = cx.sess.lint_store.as_ref().map(|store| store.to_owned()) else {
140        {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("lint_store required while parsing attributes")));
};unreachable!("lint_store required while parsing attributes");
141    };
142    let lint_store = lint_store.as_ref();
143    let Some(list) = args.list() else {
144        let span = cx.inner_span;
145        cx.adcx().expected_list(span, args);
146        return None;
147    };
148    let mut list = list.mixed().peekable();
149
150    let mut skip_unused_check = false;
151    let mut errored = false;
152    let mut reason = None;
153    let mut lint_instances = ThinVec::new();
154    let mut lint_index = 0;
155    let targeting_crate = #[allow(non_exhaustive_omitted_patterns)] match cx.target {
    Target::Crate => true,
    _ => false,
}matches!(cx.target, Target::Crate);
156    while let Some(item) = list.next() {
157        let Some(meta_item) = item.meta_item() else {
158            cx.adcx().expected_identifier(item.span());
159            errored = true;
160            continue;
161        };
162
163        match meta_item.args() {
164            ArgParser::NameValue(nv_parser) if meta_item.path().word_is(sym::reason) => {
165                //FIXME replace this with duplicate check?
166                if list.peek().is_some() {
167                    cx.adcx().expected_nv_as_last_argument(meta_item.span(), sym::reason);
168                    errored = true;
169                    continue;
170                }
171
172                let val_lit = nv_parser.value_as_lit();
173                let LitKind::Str(reason_sym, _) = val_lit.kind else {
174                    cx.adcx().expected_string_literal(nv_parser.value_span, Some(val_lit));
175                    errored = true;
176                    continue;
177                };
178                reason = Some(reason_sym);
179            }
180            ArgParser::NameValue(_) => {
181                cx.adcx().expected_specific_argument(meta_item.span(), &[sym::reason]);
182                errored = true;
183            }
184            ArgParser::List(list) => {
185                cx.adcx().expected_no_args(list.span);
186                errored = true;
187            }
188            ArgParser::NoArgs => {
189                skip_unused_check = true;
190                let mut segments = meta_item.path().segments();
191
192                let Some(tool_or_name) = segments.next() else {
193                    {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("first segment should always exist")));
};unreachable!("first segment should always exist");
194                };
195
196                let rest = segments.collect::<Vec<_>>();
197                let (tool_name, tool_span, name): (Option<Symbol>, Option<Span>, _) =
198                    if rest.is_empty() {
199                        let name = tool_or_name.name;
200                        (None, None, name.to_string())
201                    } else {
202                        let tool = tool_or_name;
203                        let name = rest
204                            .into_iter()
205                            .map(|ident| ident.to_string())
206                            .collect::<Vec<_>>()
207                            .join("::");
208                        (Some(tool.name), Some(tool.span), name)
209                    };
210
211                let meta_item_span = meta_item.span();
212                let original_name = Symbol::intern(&name);
213                let mut full_name = tool_name
214                    .map(|tool| Symbol::intern(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{1}::{0}", original_name, tool))
    })format!("{tool}::{}", original_name)))
215                    .unwrap_or(original_name);
216
217                if let Some(ids) = check_lint(
218                    cx,
219                    lint_store,
220                    original_name,
221                    &mut full_name,
222                    tool_name,
223                    tool_span,
224                    meta_item_span,
225                ) {
226                    if !targeting_crate && ids.iter().any(|lint_id| lint_id.lint.crate_level_only) {
227                        cx.emit_lint(
228                            UNUSED_ATTRIBUTES,
229                            AttributeLintKind::IgnoredUnlessCrateSpecified {
230                                level: T::ATTR_SYMBOL,
231                                name: original_name,
232                            },
233                            meta_item_span,
234                        );
235                    }
236                    lint_instances.extend(ids.into_iter().map(|id| {
237                        LintInstance::new(full_name, id.to_string(), meta_item_span, lint_index)
238                    }));
239                }
240                lint_index += 1;
241            }
242        }
243    }
244    if !skip_unused_check && !errored && lint_instances.is_empty() {
245        let span = cx.attr_span;
246        cx.adcx().warn_empty_attribute(span);
247    }
248
249    (!errored).then_some(LintAttribute {
250        reason,
251        lint_instances,
252        attr_span: cx.attr_span,
253        attr_style: cx.attr_style,
254        attr_id: HashIgnoredAttrId { attr_id: cx.attr_id },
255        kind: T::KIND,
256    })
257}
258
259fn check_lint<'a, S: Stage>(
260    cx: &mut AcceptContext<'_, '_, S>,
261    lint_store: &'a dyn DynLintStore,
262    original_name: Symbol,
263    full_name: &mut Symbol,
264    tool_name: Option<Symbol>,
265    tool_span: Option<Span>,
266    span: Span,
267) -> Option<&'a [LintId]> {
268    let Some(tools) = cx.tools else {
269        {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("tools required while parsing attributes")));
};unreachable!("tools required while parsing attributes");
270    };
271    if tools.is_empty() {
272        {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("tools should never be empty")));
}unreachable!("tools should never be empty")
273    }
274
275    match lint_store.check_lint_name(original_name.as_str(), tool_name, tools) {
276        CheckLintNameResult::Ok(ids) => Some(ids),
277        CheckLintNameResult::Tool(ids, new_lint_name) => {
278            let _name = match new_lint_name {
279                None => original_name,
280                Some(new_lint_name) => {
281                    let new_lint_name = Symbol::intern(&new_lint_name);
282                    cx.emit_lint(
283                        RENAMED_AND_REMOVED_LINTS,
284                        AttributeLintKind::DeprecatedLintName {
285                            name: *full_name,
286                            suggestion: span,
287                            replace: new_lint_name,
288                        },
289                        span,
290                    );
291                    new_lint_name
292                }
293            };
294            Some(ids)
295        }
296
297        CheckLintNameResult::MissingTool => {
298            // If `MissingTool` is returned, then either the lint does not
299            // exist in the tool or the code was not compiled with the tool and
300            // therefore the lint was never added to the `LintStore`. To detect
301            // this is the responsibility of the lint tool.
302            None
303        }
304
305        CheckLintNameResult::NoTool => {
306            cx.emit_err(UnknownToolInScopedLint {
307                span: tool_span,
308                tool_name: tool_name.unwrap(),
309                full_lint_name: *full_name,
310                is_nightly_build: cx.sess.is_nightly_build(),
311            });
312            None
313        }
314
315        CheckLintNameResult::Renamed(replace) => {
316            cx.emit_lint(
317                RENAMED_AND_REMOVED_LINTS,
318                AttributeLintKind::RenamedLint { name: *full_name, replace, suggestion: span },
319                span,
320            );
321
322            // Since it was renamed, and we have emitted the warning
323            // we replace the "full_name", to ensure we don't get notes with:
324            // `#[allow(NEW_NAME)]` implied by `#[allow(OLD_NAME)]`
325            // Other lints still have access to the original name as the user wrote it,
326            // through `original_name`
327            *full_name = replace;
328
329            // If this lint was renamed, apply the new lint instead of ignoring the
330            // attribute. Ignore any errors or warnings that happen because the new
331            // name is inaccurate.
332            // NOTE: `new_name` already includes the tool name, so we don't
333            // have to add it again.
334            match lint_store.check_lint_name(replace.as_str(), None, tools) {
335                CheckLintNameResult::Ok(ids) => Some(ids),
336                _ => {
    ::core::panicking::panic_fmt(format_args!("renamed lint does not exist: {0}",
            replace));
}panic!("renamed lint does not exist: {replace}"),
337            }
338        }
339
340        CheckLintNameResult::RenamedToolLint(new_name) => {
341            cx.emit_lint(
342                RENAMED_AND_REMOVED_LINTS,
343                AttributeLintKind::RenamedLint {
344                    name: *full_name,
345                    replace: new_name,
346                    suggestion: span,
347                },
348                span,
349            );
350            None
351        }
352
353        CheckLintNameResult::Removed(reason) => {
354            cx.emit_lint(
355                RENAMED_AND_REMOVED_LINTS,
356                AttributeLintKind::RemovedLint { name: *full_name, reason },
357                span,
358            );
359            None
360        }
361
362        CheckLintNameResult::NoLint(suggestion) => {
363            cx.emit_lint(
364                UNKNOWN_LINTS,
365                AttributeLintKind::UnknownLint { name: *full_name, suggestion, span },
366                span,
367            );
368            None
369        }
370    }
371}