rustc_lint/
levels.rs

1use rustc_ast::attr::AttributeExt;
2use rustc_ast_pretty::pprust;
3use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
4use rustc_errors::{Diag, LintDiagnostic, MultiSpan};
5use rustc_feature::{Features, GateIssue};
6use rustc_hir::intravisit::{self, Visitor};
7use rustc_hir::{CRATE_HIR_ID, HirId};
8use rustc_index::IndexVec;
9use rustc_middle::bug;
10use rustc_middle::hir::nested_filter;
11use rustc_middle::lint::{
12    LevelAndSource, LintExpectation, LintLevelSource, ShallowLintLevelMap, lint_level,
13    reveal_actual_level,
14};
15use rustc_middle::query::Providers;
16use rustc_middle::ty::{RegisteredTools, TyCtxt};
17use rustc_session::Session;
18use rustc_session::lint::builtin::{
19    self, FORBIDDEN_LINT_GROUPS, RENAMED_AND_REMOVED_LINTS, SINGLE_USE_LIFETIMES,
20    UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES,
21};
22use rustc_session::lint::{Level, Lint, LintExpectationId, LintId};
23use rustc_span::{DUMMY_SP, Span, Symbol, sym};
24use tracing::{debug, instrument};
25use {rustc_ast as ast, rustc_hir as hir};
26
27use crate::builtin::MISSING_DOCS;
28use crate::context::{CheckLintNameResult, LintStore};
29use crate::errors::{
30    CheckNameUnknownTool, MalformedAttribute, MalformedAttributeSub, OverruledAttribute,
31    OverruledAttributeSub, RequestedLevel, UnknownToolInScopedLint, UnsupportedGroup,
32};
33use crate::fluent_generated as fluent;
34use crate::late::unerased_lint_store;
35use crate::lints::{
36    DeprecatedLintName, DeprecatedLintNameFromCommandLine, IgnoredUnlessCrateSpecified,
37    OverruledAttributeLint, RemovedLint, RemovedLintFromCommandLine, RenamedLint,
38    RenamedLintFromCommandLine, RenamedLintSuggestion, UnknownLint, UnknownLintFromCommandLine,
39    UnknownLintSuggestion,
40};
41
42/// Collection of lint levels for the whole crate.
43/// This is used by AST-based lints, which do not
44/// wait until we have built HIR to be emitted.
45#[derive(Debug)]
46struct LintLevelSets {
47    /// Linked list of specifications.
48    list: IndexVec<LintStackIndex, LintSet>,
49}
50
51rustc_index::newtype_index! {
52    struct LintStackIndex {
53        const COMMAND_LINE = 0;
54    }
55}
56
57/// Specifications found at this position in the stack. This map only represents the lints
58/// found for one set of attributes (like `shallow_lint_levels_on` does).
59///
60/// We store the level specifications as a linked list.
61/// Each `LintSet` represents a set of attributes on the same AST node.
62/// The `parent` forms a linked list that matches the AST tree.
63/// This way, walking the linked list is equivalent to walking the AST bottom-up
64/// to find the specifications for a given lint.
65#[derive(Debug)]
66struct LintSet {
67    // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
68    // flag.
69    specs: FxIndexMap<LintId, LevelAndSource>,
70    parent: LintStackIndex,
71}
72
73impl LintLevelSets {
74    fn new() -> Self {
75        LintLevelSets { list: IndexVec::new() }
76    }
77
78    fn get_lint_level(
79        &self,
80        lint: &'static Lint,
81        idx: LintStackIndex,
82        aux: Option<&FxIndexMap<LintId, LevelAndSource>>,
83        sess: &Session,
84    ) -> LevelAndSource {
85        let lint = LintId::of(lint);
86        let (level, mut src) = self.raw_lint_id_level(lint, idx, aux);
87        let level = reveal_actual_level(level, &mut src, sess, lint, |id| {
88            self.raw_lint_id_level(id, idx, aux)
89        });
90        (level, src)
91    }
92
93    fn raw_lint_id_level(
94        &self,
95        id: LintId,
96        mut idx: LintStackIndex,
97        aux: Option<&FxIndexMap<LintId, LevelAndSource>>,
98    ) -> (Option<Level>, LintLevelSource) {
99        if let Some(specs) = aux
100            && let Some(&(level, src)) = specs.get(&id)
101        {
102            return (Some(level), src);
103        }
104
105        loop {
106            let LintSet { ref specs, parent } = self.list[idx];
107            if let Some(&(level, src)) = specs.get(&id) {
108                return (Some(level), src);
109            }
110            if idx == COMMAND_LINE {
111                return (None, LintLevelSource::Default);
112            }
113            idx = parent;
114        }
115    }
116}
117
118fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet<LintId> {
119    let store = unerased_lint_store(&tcx.sess);
120
121    let map = tcx.shallow_lint_levels_on(rustc_hir::CRATE_OWNER_ID);
122
123    let dont_need_to_run: FxIndexSet<LintId> = store
124        .get_lints()
125        .into_iter()
126        .filter(|lint| {
127            // Lints that show up in future-compat reports must always be run.
128            let has_future_breakage =
129                lint.future_incompatible.is_some_and(|fut| fut.reason.has_future_breakage());
130            !has_future_breakage && !lint.eval_always
131        })
132        .filter_map(|lint| {
133            let lint_level = map.lint_level_id_at_node(tcx, LintId::of(lint), CRATE_HIR_ID);
134            if matches!(lint_level, (Level::Allow, ..))
135                || (matches!(lint_level, (.., LintLevelSource::Default)))
136                    && lint.default_level(tcx.sess.edition()) == Level::Allow
137            {
138                Some(LintId::of(lint))
139            } else {
140                None
141            }
142        })
143        .collect();
144
145    let mut visitor = LintLevelMaximum { tcx, dont_need_to_run };
146    visitor.process_opts();
147    tcx.hir_walk_attributes(&mut visitor);
148
149    visitor.dont_need_to_run
150}
151
152#[instrument(level = "trace", skip(tcx), ret)]
153fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLevelMap {
154    let store = unerased_lint_store(tcx.sess);
155    let attrs = tcx.hir_attr_map(owner);
156
157    let mut levels = LintLevelsBuilder {
158        sess: tcx.sess,
159        features: tcx.features(),
160        provider: LintLevelQueryMap {
161            tcx,
162            cur: owner.into(),
163            specs: ShallowLintLevelMap::default(),
164            empty: FxIndexMap::default(),
165            attrs,
166        },
167        lint_added_lints: false,
168        store,
169        registered_tools: tcx.registered_tools(()),
170    };
171
172    if owner == hir::CRATE_OWNER_ID {
173        levels.add_command_line();
174    }
175
176    match attrs.map.range(..) {
177        // There is only something to do if there are attributes at all.
178        [] => {}
179        // Most of the time, there is only one attribute. Avoid fetching HIR in that case.
180        &[(local_id, _)] => levels.add_id(HirId { owner, local_id }),
181        // Otherwise, we need to visit the attributes in source code order, so we fetch HIR and do
182        // a standard visit.
183        // FIXME(#102522) Just iterate on attrs once that iteration order matches HIR's.
184        _ => match tcx.hir_owner_node(owner) {
185            hir::OwnerNode::Item(item) => levels.visit_item(item),
186            hir::OwnerNode::ForeignItem(item) => levels.visit_foreign_item(item),
187            hir::OwnerNode::TraitItem(item) => levels.visit_trait_item(item),
188            hir::OwnerNode::ImplItem(item) => levels.visit_impl_item(item),
189            hir::OwnerNode::Crate(mod_) => {
190                levels.add_id(hir::CRATE_HIR_ID);
191                levels.visit_mod(mod_, mod_.spans.inner_span, hir::CRATE_HIR_ID)
192            }
193            hir::OwnerNode::Synthetic => unreachable!(),
194        },
195    }
196
197    let specs = levels.provider.specs;
198
199    #[cfg(debug_assertions)]
200    for (_, v) in specs.specs.iter() {
201        debug_assert!(!v.is_empty());
202    }
203
204    specs
205}
206
207pub struct TopDown {
208    sets: LintLevelSets,
209    cur: LintStackIndex,
210}
211
212pub trait LintLevelsProvider {
213    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource>;
214    fn insert(&mut self, id: LintId, lvl: LevelAndSource);
215    fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource;
216    fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation);
217}
218
219impl LintLevelsProvider for TopDown {
220    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> {
221        &self.sets.list[self.cur].specs
222    }
223
224    fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
225        self.sets.list[self.cur].specs.insert(id, lvl);
226    }
227
228    fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource {
229        self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), sess)
230    }
231
232    fn push_expectation(&mut self, _: LintExpectationId, _: LintExpectation) {}
233}
234
235struct LintLevelQueryMap<'tcx> {
236    tcx: TyCtxt<'tcx>,
237    cur: HirId,
238    specs: ShallowLintLevelMap,
239    /// Empty hash map to simplify code.
240    empty: FxIndexMap<LintId, LevelAndSource>,
241    attrs: &'tcx hir::AttributeMap<'tcx>,
242}
243
244impl LintLevelsProvider for LintLevelQueryMap<'_> {
245    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> {
246        self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty)
247    }
248    fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
249        self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl);
250    }
251    fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource {
252        self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur)
253    }
254    fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) {
255        self.specs.expectations.push((id, expectation))
256    }
257}
258
259impl<'tcx> LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
260    fn add_id(&mut self, hir_id: HirId) {
261        self.provider.cur = hir_id;
262        self.add(
263            self.provider.attrs.get(hir_id.local_id),
264            hir_id == hir::CRATE_HIR_ID,
265            Some(hir_id),
266        );
267    }
268}
269
270impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
271    type NestedFilter = nested_filter::OnlyBodies;
272
273    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
274        self.provider.tcx
275    }
276
277    fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
278        self.add_id(param.hir_id);
279        intravisit::walk_param(self, param);
280    }
281
282    fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
283        self.add_id(it.hir_id());
284        intravisit::walk_item(self, it);
285    }
286
287    fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
288        self.add_id(it.hir_id());
289        intravisit::walk_foreign_item(self, it);
290    }
291
292    fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) {
293        self.add_id(s.hir_id);
294        intravisit::walk_stmt(self, s);
295    }
296
297    fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
298        self.add_id(e.hir_id);
299        intravisit::walk_expr(self, e);
300    }
301
302    fn visit_pat_field(&mut self, f: &'tcx hir::PatField<'tcx>) -> Self::Result {
303        self.add_id(f.hir_id);
304        intravisit::walk_pat_field(self, f);
305    }
306
307    fn visit_expr_field(&mut self, f: &'tcx hir::ExprField<'tcx>) {
308        self.add_id(f.hir_id);
309        intravisit::walk_expr_field(self, f);
310    }
311
312    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
313        self.add_id(s.hir_id);
314        intravisit::walk_field_def(self, s);
315    }
316
317    fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) {
318        self.add_id(v.hir_id);
319        intravisit::walk_variant(self, v);
320    }
321
322    fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) {
323        self.add_id(l.hir_id);
324        intravisit::walk_local(self, l);
325    }
326
327    fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
328        self.add_id(a.hir_id);
329        intravisit::walk_arm(self, a);
330    }
331
332    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
333        self.add_id(trait_item.hir_id());
334        intravisit::walk_trait_item(self, trait_item);
335    }
336
337    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
338        self.add_id(impl_item.hir_id());
339        intravisit::walk_impl_item(self, impl_item);
340    }
341}
342
343/// Visitor with the only function of visiting every item-like in a crate and
344/// computing the highest level that every lint gets put to.
345///
346/// E.g., if a crate has a global #![allow(lint)] attribute, but a single item
347/// uses #[warn(lint)], this visitor will set that lint level as `Warn`
348struct LintLevelMaximum<'tcx> {
349    tcx: TyCtxt<'tcx>,
350    /// The actual list of detected lints.
351    dont_need_to_run: FxIndexSet<LintId>,
352}
353
354impl<'tcx> LintLevelMaximum<'tcx> {
355    fn process_opts(&mut self) {
356        let store = unerased_lint_store(self.tcx.sess);
357        for (lint_group, level) in &self.tcx.sess.opts.lint_opts {
358            if *level != Level::Allow {
359                let Ok(lints) = store.find_lints(lint_group) else {
360                    return;
361                };
362                for lint in lints {
363                    self.dont_need_to_run.swap_remove(&lint);
364                }
365            }
366        }
367    }
368}
369
370impl<'tcx> Visitor<'tcx> for LintLevelMaximum<'tcx> {
371    type NestedFilter = nested_filter::All;
372
373    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
374        self.tcx
375    }
376
377    /// FIXME(blyxyas): In a future revision, we should also graph #![allow]s,
378    /// but that is handled with more care
379    fn visit_attribute(&mut self, attribute: &'tcx hir::Attribute) {
380        if matches!(
381            Level::from_attr(attribute),
382            Some(
383                Level::Warn
384                    | Level::Deny
385                    | Level::Forbid
386                    | Level::Expect(..)
387                    | Level::ForceWarn(..),
388            )
389        ) {
390            let store = unerased_lint_store(self.tcx.sess);
391            // Lint attributes are always a metalist inside a
392            // metalist (even with just one lint).
393            let Some(meta_item_list) = attribute.meta_item_list() else { return };
394
395            for meta_list in meta_item_list {
396                // Convert Path to String
397                let Some(meta_item) = meta_list.meta_item() else { return };
398                let ident: &str = &meta_item
399                    .path
400                    .segments
401                    .iter()
402                    .map(|segment| segment.ident.as_str())
403                    .collect::<Vec<&str>>()
404                    .join("::");
405                let Ok(lints) = store.find_lints(
406                    // Lint attributes can only have literals
407                    ident,
408                ) else {
409                    return;
410                };
411                for lint in lints {
412                    self.dont_need_to_run.swap_remove(&lint);
413                }
414            }
415        }
416    }
417}
418
419pub struct LintLevelsBuilder<'s, P> {
420    sess: &'s Session,
421    features: &'s Features,
422    provider: P,
423    lint_added_lints: bool,
424    store: &'s LintStore,
425    registered_tools: &'s RegisteredTools,
426}
427
428pub(crate) struct BuilderPush {
429    prev: LintStackIndex,
430}
431
432impl<'s> LintLevelsBuilder<'s, TopDown> {
433    pub(crate) fn new(
434        sess: &'s Session,
435        features: &'s Features,
436        lint_added_lints: bool,
437        store: &'s LintStore,
438        registered_tools: &'s RegisteredTools,
439    ) -> Self {
440        let mut builder = LintLevelsBuilder {
441            sess,
442            features,
443            provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE },
444            lint_added_lints,
445            store,
446            registered_tools,
447        };
448        builder.process_command_line();
449        assert_eq!(builder.provider.sets.list.len(), 1);
450        builder
451    }
452
453    fn process_command_line(&mut self) {
454        self.provider.cur = self
455            .provider
456            .sets
457            .list
458            .push(LintSet { specs: FxIndexMap::default(), parent: COMMAND_LINE });
459        self.add_command_line();
460    }
461
462    /// Pushes a list of AST lint attributes onto this context.
463    ///
464    /// This function will return a `BuilderPush` object which should be passed
465    /// to `pop` when this scope for the attributes provided is exited.
466    ///
467    /// This function will perform a number of tasks:
468    ///
469    /// * It'll validate all lint-related attributes in `attrs`
470    /// * It'll mark all lint-related attributes as used
471    /// * Lint levels will be updated based on the attributes provided
472    /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
473    ///   `#[allow]`
474    ///
475    /// Don't forget to call `pop`!
476    pub(crate) fn push(
477        &mut self,
478        attrs: &[ast::Attribute],
479        is_crate_node: bool,
480        source_hir_id: Option<HirId>,
481    ) -> BuilderPush {
482        let prev = self.provider.cur;
483        self.provider.cur =
484            self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev });
485
486        self.add(attrs, is_crate_node, source_hir_id);
487
488        if self.provider.current_specs().is_empty() {
489            self.provider.sets.list.pop();
490            self.provider.cur = prev;
491        }
492
493        BuilderPush { prev }
494    }
495
496    /// Called after `push` when the scope of a set of attributes are exited.
497    pub(crate) fn pop(&mut self, push: BuilderPush) {
498        self.provider.cur = push.prev;
499        std::mem::forget(push);
500    }
501}
502
503#[cfg(debug_assertions)]
504impl Drop for BuilderPush {
505    fn drop(&mut self) {
506        panic!("Found a `push` without a `pop`.");
507    }
508}
509
510impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
511    pub(crate) fn sess(&self) -> &Session {
512        self.sess
513    }
514
515    pub(crate) fn features(&self) -> &Features {
516        self.features
517    }
518
519    fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> {
520        self.provider.current_specs()
521    }
522
523    fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
524        self.provider.insert(id, lvl)
525    }
526
527    fn add_command_line(&mut self) {
528        for &(ref lint_name, level) in &self.sess.opts.lint_opts {
529            // Checks the validity of lint names derived from the command line.
530            let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name);
531            if lint_name_only == crate::WARNINGS.name_lower()
532                && matches!(level, Level::ForceWarn(_))
533            {
534                self.sess
535                    .dcx()
536                    .emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() });
537            }
538            match self.store.check_lint_name(lint_name_only, tool_name, self.registered_tools) {
539                CheckLintNameResult::Renamed(ref replace) => {
540                    let name = lint_name.as_str();
541                    let suggestion = RenamedLintSuggestion::WithoutSpan { replace };
542                    let requested_level = RequestedLevel { level, lint_name };
543                    let lint = RenamedLintFromCommandLine { name, suggestion, requested_level };
544                    self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint);
545                }
546                CheckLintNameResult::Removed(ref reason) => {
547                    let name = lint_name.as_str();
548                    let requested_level = RequestedLevel { level, lint_name };
549                    let lint = RemovedLintFromCommandLine { name, reason, requested_level };
550                    self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint);
551                }
552                CheckLintNameResult::NoLint(suggestion) => {
553                    let name = lint_name.clone();
554                    let suggestion = suggestion.map(|(replace, from_rustc)| {
555                        UnknownLintSuggestion::WithoutSpan { replace, from_rustc }
556                    });
557                    let requested_level = RequestedLevel { level, lint_name };
558                    let lint = UnknownLintFromCommandLine { name, suggestion, requested_level };
559                    self.emit_lint(UNKNOWN_LINTS, lint);
560                }
561                CheckLintNameResult::Tool(_, Some(ref replace)) => {
562                    let name = lint_name.clone();
563                    let requested_level = RequestedLevel { level, lint_name };
564                    let lint = DeprecatedLintNameFromCommandLine { name, replace, requested_level };
565                    self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint);
566                }
567                CheckLintNameResult::NoTool => {
568                    self.sess.dcx().emit_err(CheckNameUnknownTool {
569                        tool_name: tool_name.unwrap(),
570                        sub: RequestedLevel { level, lint_name },
571                    });
572                }
573                _ => {}
574            };
575
576            let orig_level = level;
577            let lint_flag_val = Symbol::intern(lint_name);
578
579            let Ok(ids) = self.store.find_lints(lint_name) else {
580                // errors already handled above
581                continue;
582            };
583            for id in ids {
584                // ForceWarn and Forbid cannot be overridden
585                if let Some((Level::ForceWarn(_) | Level::Forbid, _)) =
586                    self.current_specs().get(&id)
587                {
588                    continue;
589                }
590
591                if self.check_gated_lint(id, DUMMY_SP, true) {
592                    let src = LintLevelSource::CommandLine(lint_flag_val, orig_level);
593                    self.insert(id, (level, src));
594                }
595            }
596        }
597    }
598
599    /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful
600    /// (e.g. if a forbid was already inserted on the same scope), then emits a
601    /// diagnostic with no change to `specs`.
602    fn insert_spec(&mut self, id: LintId, (level, src): LevelAndSource) {
603        let (old_level, old_src) = self.provider.get_lint_level(id.lint, self.sess);
604
605        // Setting to a non-forbid level is an error if the lint previously had
606        // a forbid level. Note that this is not necessarily true even with a
607        // `#[forbid(..)]` attribute present, as that is overridden by `--cap-lints`.
608        //
609        // This means that this only errors if we're truly lowering the lint
610        // level from forbid.
611        if self.lint_added_lints && level == Level::Deny && old_level == Level::Forbid {
612            // Having a deny inside a forbid is fine and is ignored, so we skip this check.
613            return;
614        } else if self.lint_added_lints && level != Level::Forbid && old_level == Level::Forbid {
615            // Backwards compatibility check:
616            //
617            // We used to not consider `forbid(lint_group)`
618            // as preventing `allow(lint)` for some lint `lint` in
619            // `lint_group`. For now, issue a future-compatibility
620            // warning for this case.
621            let id_name = id.lint.name_lower();
622            let fcw_warning = match old_src {
623                LintLevelSource::Default => false,
624                LintLevelSource::Node { name, .. } => self.store.is_lint_group(name),
625                LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
626            };
627            debug!(
628                "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
629                fcw_warning,
630                self.current_specs(),
631                old_src,
632                id_name
633            );
634            let sub = match old_src {
635                LintLevelSource::Default => {
636                    OverruledAttributeSub::DefaultSource { id: id.to_string() }
637                }
638                LintLevelSource::Node { span, reason, .. } => {
639                    OverruledAttributeSub::NodeSource { span, reason }
640                }
641                LintLevelSource::CommandLine(_, _) => OverruledAttributeSub::CommandLineSource,
642            };
643            if !fcw_warning {
644                self.sess.dcx().emit_err(OverruledAttribute {
645                    span: src.span(),
646                    overruled: src.span(),
647                    lint_level: level.as_str(),
648                    lint_source: src.name(),
649                    sub,
650                });
651            } else {
652                self.emit_span_lint(
653                    FORBIDDEN_LINT_GROUPS,
654                    src.span().into(),
655                    OverruledAttributeLint {
656                        overruled: src.span(),
657                        lint_level: level.as_str(),
658                        lint_source: src.name(),
659                        sub,
660                    },
661                );
662            }
663
664            // Retain the forbid lint level, unless we are
665            // issuing a FCW. In the FCW case, we want to
666            // respect the new setting.
667            if !fcw_warning {
668                return;
669            }
670        }
671
672        // The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself.
673        // Handling expectations of this lint would add additional complexity with little to no
674        // benefit. The expect level for this lint will therefore be ignored.
675        if let Level::Expect(_) = level
676            && id == LintId::of(UNFULFILLED_LINT_EXPECTATIONS)
677        {
678            return;
679        }
680
681        match (old_level, level) {
682            // If the new level is an expectation store it in `ForceWarn`
683            (Level::ForceWarn(_), Level::Expect(expectation_id)) => {
684                self.insert(id, (Level::ForceWarn(Some(expectation_id)), old_src))
685            }
686            // Keep `ForceWarn` level but drop the expectation
687            (Level::ForceWarn(_), _) => self.insert(id, (Level::ForceWarn(None), old_src)),
688            // Set the lint level as normal
689            _ => self.insert(id, (level, src)),
690        };
691    }
692
693    fn add(
694        &mut self,
695        attrs: &[impl AttributeExt],
696        is_crate_node: bool,
697        source_hir_id: Option<HirId>,
698    ) {
699        let sess = self.sess;
700        for (attr_index, attr) in attrs.iter().enumerate() {
701            if attr.has_name(sym::automatically_derived) {
702                self.insert(
703                    LintId::of(SINGLE_USE_LIFETIMES),
704                    (Level::Allow, LintLevelSource::Default),
705                );
706                continue;
707            }
708
709            // `#[doc(hidden)]` disables missing_docs check.
710            if attr.has_name(sym::doc)
711                && attr
712                    .meta_item_list()
713                    .is_some_and(|l| ast::attr::list_contains_name(&l, sym::hidden))
714            {
715                self.insert(LintId::of(MISSING_DOCS), (Level::Allow, LintLevelSource::Default));
716                continue;
717            }
718
719            let level = match Level::from_attr(attr) {
720                None => continue,
721                // This is the only lint level with a `LintExpectationId` that can be created from
722                // an attribute.
723                Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {
724                    let LintExpectationId::Unstable { lint_index: None, attr_id: _ } = unstable_id
725                    else {
726                        bug!("stable id Level::from_attr")
727                    };
728
729                    let stable_id = LintExpectationId::Stable {
730                        hir_id,
731                        attr_index: attr_index.try_into().unwrap(),
732                        lint_index: None,
733                    };
734
735                    Level::Expect(stable_id)
736                }
737                Some(lvl) => lvl,
738            };
739
740            let Some(mut metas) = attr.meta_item_list() else { continue };
741
742            // Check whether `metas` is empty, and get its last element.
743            let Some(tail_li) = metas.last() else {
744                // This emits the unused_attributes lint for `#[level()]`
745                continue;
746            };
747
748            // Before processing the lint names, look for a reason (RFC 2383)
749            // at the end.
750            let mut reason = None;
751            if let Some(item) = tail_li.meta_item() {
752                match item.kind {
753                    ast::MetaItemKind::Word => {} // actual lint names handled later
754                    ast::MetaItemKind::NameValue(ref name_value) => {
755                        if item.path == sym::reason {
756                            if let ast::LitKind::Str(rationale, _) = name_value.kind {
757                                reason = Some(rationale);
758                            } else {
759                                sess.dcx().emit_err(MalformedAttribute {
760                                    span: name_value.span,
761                                    sub: MalformedAttributeSub::ReasonMustBeStringLiteral(
762                                        name_value.span,
763                                    ),
764                                });
765                            }
766                            // found reason, reslice meta list to exclude it
767                            metas.pop().unwrap();
768                        } else {
769                            sess.dcx().emit_err(MalformedAttribute {
770                                span: item.span,
771                                sub: MalformedAttributeSub::BadAttributeArgument(item.span),
772                            });
773                        }
774                    }
775                    ast::MetaItemKind::List(_) => {
776                        sess.dcx().emit_err(MalformedAttribute {
777                            span: item.span,
778                            sub: MalformedAttributeSub::BadAttributeArgument(item.span),
779                        });
780                    }
781                }
782            }
783
784            for (lint_index, li) in metas.iter_mut().enumerate() {
785                let level = match level {
786                    Level::Expect(mut id) => {
787                        id.set_lint_index(Some(lint_index as u16));
788                        Level::Expect(id)
789                    }
790                    level => level,
791                };
792
793                let sp = li.span();
794                let meta_item = match li {
795                    ast::MetaItemInner::MetaItem(meta_item) if meta_item.is_word() => meta_item,
796                    _ => {
797                        let sub = if let Some(item) = li.meta_item()
798                            && let ast::MetaItemKind::NameValue(_) = item.kind
799                            && item.path == sym::reason
800                        {
801                            MalformedAttributeSub::ReasonMustComeLast(sp)
802                        } else {
803                            MalformedAttributeSub::BadAttributeArgument(sp)
804                        };
805
806                        sess.dcx().emit_err(MalformedAttribute { span: sp, sub });
807                        continue;
808                    }
809                };
810                let tool_ident = if meta_item.path.segments.len() > 1 {
811                    Some(meta_item.path.segments.remove(0).ident)
812                } else {
813                    None
814                };
815                let tool_name = tool_ident.map(|ident| ident.name);
816                let name = pprust::path_to_string(&meta_item.path);
817                let lint_result =
818                    self.store.check_lint_name(&name, tool_name, self.registered_tools);
819
820                let (ids, name) = match lint_result {
821                    CheckLintNameResult::Ok(ids) => {
822                        let name =
823                            meta_item.path.segments.last().expect("empty lint name").ident.name;
824                        (ids, name)
825                    }
826
827                    CheckLintNameResult::Tool(ids, new_lint_name) => {
828                        let name = match new_lint_name {
829                            None => {
830                                let complete_name =
831                                    &format!("{}::{}", tool_ident.unwrap().name, name);
832                                Symbol::intern(complete_name)
833                            }
834                            Some(new_lint_name) => {
835                                self.emit_span_lint(
836                                    builtin::RENAMED_AND_REMOVED_LINTS,
837                                    sp.into(),
838                                    DeprecatedLintName {
839                                        name,
840                                        suggestion: sp,
841                                        replace: &new_lint_name,
842                                    },
843                                );
844                                Symbol::intern(&new_lint_name)
845                            }
846                        };
847                        (ids, name)
848                    }
849
850                    CheckLintNameResult::MissingTool => {
851                        // If `MissingTool` is returned, then either the lint does not
852                        // exist in the tool or the code was not compiled with the tool and
853                        // therefore the lint was never added to the `LintStore`. To detect
854                        // this is the responsibility of the lint tool.
855                        continue;
856                    }
857
858                    CheckLintNameResult::NoTool => {
859                        sess.dcx().emit_err(UnknownToolInScopedLint {
860                            span: tool_ident.map(|ident| ident.span),
861                            tool_name: tool_name.unwrap(),
862                            lint_name: pprust::path_to_string(&meta_item.path),
863                            is_nightly_build: sess.is_nightly_build(),
864                        });
865                        continue;
866                    }
867
868                    CheckLintNameResult::Renamed(ref replace) => {
869                        if self.lint_added_lints {
870                            let suggestion =
871                                RenamedLintSuggestion::WithSpan { suggestion: sp, replace };
872                            let name =
873                                tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
874                            let lint = RenamedLint { name: name.as_str(), suggestion };
875                            self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint);
876                        }
877
878                        // If this lint was renamed, apply the new lint instead of ignoring the
879                        // attribute. Ignore any errors or warnings that happen because the new
880                        // name is inaccurate.
881                        // NOTE: `new_name` already includes the tool name, so we don't
882                        // have to add it again.
883                        let CheckLintNameResult::Ok(ids) =
884                            self.store.check_lint_name(replace, None, self.registered_tools)
885                        else {
886                            panic!("renamed lint does not exist: {replace}");
887                        };
888
889                        (ids, Symbol::intern(&replace))
890                    }
891
892                    CheckLintNameResult::Removed(ref reason) => {
893                        if self.lint_added_lints {
894                            let name =
895                                tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
896                            let lint = RemovedLint { name: name.as_str(), reason };
897                            self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint);
898                        }
899                        continue;
900                    }
901
902                    CheckLintNameResult::NoLint(suggestion) => {
903                        if self.lint_added_lints {
904                            let name =
905                                tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
906                            let suggestion = suggestion.map(|(replace, from_rustc)| {
907                                UnknownLintSuggestion::WithSpan {
908                                    suggestion: sp,
909                                    replace,
910                                    from_rustc,
911                                }
912                            });
913                            let lint = UnknownLint { name, suggestion };
914                            self.emit_span_lint(UNKNOWN_LINTS, sp.into(), lint);
915                        }
916                        continue;
917                    }
918                };
919
920                let src = LintLevelSource::Node { name, span: sp, reason };
921                for &id in ids {
922                    if self.check_gated_lint(id, sp, false) {
923                        self.insert_spec(id, (level, src));
924                    }
925                }
926
927                // This checks for instances where the user writes
928                // `#[expect(unfulfilled_lint_expectations)]` in that case we want to avoid
929                // overriding the lint level but instead add an expectation that can't be
930                // fulfilled. The lint message will include an explanation, that the
931                // `unfulfilled_lint_expectations` lint can't be expected.
932                if let Level::Expect(expect_id) = level {
933                    // The `unfulfilled_lint_expectations` lint is not part of any lint
934                    // groups. Therefore. we only need to check the slice if it contains a
935                    // single lint.
936                    let is_unfulfilled_lint_expectations = match ids {
937                        [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS),
938                        _ => false,
939                    };
940                    self.provider.push_expectation(
941                        expect_id,
942                        LintExpectation::new(
943                            reason,
944                            sp,
945                            is_unfulfilled_lint_expectations,
946                            tool_name,
947                        ),
948                    );
949                }
950            }
951        }
952
953        if self.lint_added_lints && !is_crate_node {
954            for (id, &(level, ref src)) in self.current_specs().iter() {
955                if !id.lint.crate_level_only {
956                    continue;
957                }
958
959                let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src
960                else {
961                    continue;
962                };
963
964                self.emit_span_lint(
965                    UNUSED_ATTRIBUTES,
966                    lint_attr_span.into(),
967                    IgnoredUnlessCrateSpecified { level: level.as_str(), name: lint_attr_name },
968                );
969                // don't set a separate error for every lint in the group
970                break;
971            }
972        }
973    }
974
975    /// Checks if the lint is gated on a feature that is not enabled.
976    ///
977    /// Returns `true` if the lint's feature is enabled.
978    #[track_caller]
979    fn check_gated_lint(&self, lint_id: LintId, span: Span, lint_from_cli: bool) -> bool {
980        let feature = if let Some(feature) = lint_id.lint.feature_gate
981            && !self.features.enabled(feature)
982        {
983            // Lint is behind a feature that is not enabled; eventually return false.
984            feature
985        } else {
986            // Lint is ungated or its feature is enabled; exit early.
987            return true;
988        };
989
990        if self.lint_added_lints {
991            let lint = builtin::UNKNOWN_LINTS;
992            let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
993            // FIXME: make this translatable
994            #[allow(rustc::diagnostic_outside_of_impl)]
995            lint_level(self.sess, lint, level, src, Some(span.into()), |lint| {
996                lint.primary_message(fluent::lint_unknown_gated_lint);
997                lint.arg("name", lint_id.lint.name_lower());
998                lint.note(fluent::lint_note);
999                rustc_session::parse::add_feature_diagnostics_for_issue(
1000                    lint,
1001                    &self.sess,
1002                    feature,
1003                    GateIssue::Language,
1004                    lint_from_cli,
1005                    None,
1006                );
1007            });
1008        }
1009
1010        false
1011    }
1012
1013    /// Find the lint level for a lint.
1014    pub fn lint_level(&self, lint: &'static Lint) -> LevelAndSource {
1015        self.provider.get_lint_level(lint, self.sess)
1016    }
1017
1018    /// Used to emit a lint-related diagnostic based on the current state of
1019    /// this lint context.
1020    ///
1021    /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature
1022    #[rustc_lint_diagnostics]
1023    #[track_caller]
1024    pub(crate) fn opt_span_lint(
1025        &self,
1026        lint: &'static Lint,
1027        span: Option<MultiSpan>,
1028        decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
1029    ) {
1030        let (level, src) = self.lint_level(lint);
1031        lint_level(self.sess, lint, level, src, span, decorate)
1032    }
1033
1034    #[track_caller]
1035    pub fn emit_span_lint(
1036        &self,
1037        lint: &'static Lint,
1038        span: MultiSpan,
1039        decorate: impl for<'a> LintDiagnostic<'a, ()>,
1040    ) {
1041        let (level, src) = self.lint_level(lint);
1042        lint_level(self.sess, lint, level, src, Some(span), |lint| {
1043            decorate.decorate_lint(lint);
1044        });
1045    }
1046
1047    #[track_caller]
1048    pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> LintDiagnostic<'a, ()>) {
1049        let (level, src) = self.lint_level(lint);
1050        lint_level(self.sess, lint, level, src, None, |lint| {
1051            decorate.decorate_lint(lint);
1052        });
1053    }
1054}
1055
1056pub(crate) fn provide(providers: &mut Providers) {
1057    *providers = Providers { shallow_lint_levels_on, lints_that_dont_need_to_run, ..*providers };
1058}
1059
1060pub(crate) fn parse_lint_and_tool_name(lint_name: &str) -> (Option<Symbol>, &str) {
1061    match lint_name.split_once("::") {
1062        Some((tool_name, lint_name)) => {
1063            let tool_name = Symbol::intern(tool_name);
1064
1065            (Some(tool_name), lint_name)
1066        }
1067        None => (None, lint_name),
1068    }
1069}