|  | use rustc_ast::attr::AttributeExt; | 
|  | use rustc_ast_pretty::pprust; | 
|  | use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; | 
|  | use rustc_data_structures::unord::UnordSet; | 
|  | use rustc_errors::{Diag, LintDiagnostic, MultiSpan}; | 
|  | use rustc_feature::{Features, GateIssue}; | 
|  | use rustc_hir::HirId; | 
|  | use rustc_hir::intravisit::{self, Visitor}; | 
|  | use rustc_index::IndexVec; | 
|  | use rustc_middle::bug; | 
|  | use rustc_middle::hir::nested_filter; | 
|  | use rustc_middle::lint::{ | 
|  | LevelAndSource, LintExpectation, LintLevelSource, ShallowLintLevelMap, lint_level, | 
|  | reveal_actual_level, | 
|  | }; | 
|  | use rustc_middle::query::Providers; | 
|  | use rustc_middle::ty::{RegisteredTools, TyCtxt}; | 
|  | use rustc_session::Session; | 
|  | use rustc_session::lint::builtin::{ | 
|  | self, FORBIDDEN_LINT_GROUPS, RENAMED_AND_REMOVED_LINTS, SINGLE_USE_LIFETIMES, | 
|  | UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES, | 
|  | }; | 
|  | use rustc_session::lint::{Level, Lint, LintExpectationId, LintId}; | 
|  | use rustc_span::{DUMMY_SP, Span, Symbol, sym}; | 
|  | use tracing::{debug, instrument}; | 
|  | use {rustc_ast as ast, rustc_hir as hir}; | 
|  |  | 
|  | use crate::builtin::MISSING_DOCS; | 
|  | use crate::context::{CheckLintNameResult, LintStore}; | 
|  | use crate::errors::{ | 
|  | CheckNameUnknownTool, MalformedAttribute, MalformedAttributeSub, OverruledAttribute, | 
|  | OverruledAttributeSub, RequestedLevel, UnknownToolInScopedLint, UnsupportedGroup, | 
|  | }; | 
|  | use crate::fluent_generated as fluent; | 
|  | use crate::late::unerased_lint_store; | 
|  | use crate::lints::{ | 
|  | DeprecatedLintName, DeprecatedLintNameFromCommandLine, IgnoredUnlessCrateSpecified, | 
|  | OverruledAttributeLint, RemovedLint, RemovedLintFromCommandLine, RenamedLint, | 
|  | RenamedLintFromCommandLine, RenamedLintSuggestion, UnknownLint, UnknownLintFromCommandLine, | 
|  | UnknownLintSuggestion, | 
|  | }; | 
|  |  | 
|  | /// Collection of lint levels for the whole crate. | 
|  | /// This is used by AST-based lints, which do not | 
|  | /// wait until we have built HIR to be emitted. | 
|  | #[derive(Debug)] | 
|  | struct LintLevelSets { | 
|  | /// Linked list of specifications. | 
|  | list: IndexVec<LintStackIndex, LintSet>, | 
|  | } | 
|  |  | 
|  | rustc_index::newtype_index! { | 
|  | struct LintStackIndex { | 
|  | const COMMAND_LINE = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Specifications found at this position in the stack. This map only represents the lints | 
|  | /// found for one set of attributes (like `shallow_lint_levels_on` does). | 
|  | /// | 
|  | /// We store the level specifications as a linked list. | 
|  | /// Each `LintSet` represents a set of attributes on the same AST node. | 
|  | /// The `parent` forms a linked list that matches the AST tree. | 
|  | /// This way, walking the linked list is equivalent to walking the AST bottom-up | 
|  | /// to find the specifications for a given lint. | 
|  | #[derive(Debug)] | 
|  | struct LintSet { | 
|  | // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which | 
|  | // flag. | 
|  | specs: FxIndexMap<LintId, LevelAndSource>, | 
|  | parent: LintStackIndex, | 
|  | } | 
|  |  | 
|  | impl LintLevelSets { | 
|  | fn new() -> Self { | 
|  | LintLevelSets { list: IndexVec::new() } | 
|  | } | 
|  |  | 
|  | fn get_lint_level( | 
|  | &self, | 
|  | lint: &'static Lint, | 
|  | idx: LintStackIndex, | 
|  | aux: Option<&FxIndexMap<LintId, LevelAndSource>>, | 
|  | sess: &Session, | 
|  | ) -> LevelAndSource { | 
|  | let lint = LintId::of(lint); | 
|  | let (level, mut src) = self.raw_lint_id_level(lint, idx, aux); | 
|  | let (level, lint_id) = reveal_actual_level(level, &mut src, sess, lint, |id| { | 
|  | self.raw_lint_id_level(id, idx, aux) | 
|  | }); | 
|  | LevelAndSource { level, lint_id, src } | 
|  | } | 
|  |  | 
|  | fn raw_lint_id_level( | 
|  | &self, | 
|  | id: LintId, | 
|  | mut idx: LintStackIndex, | 
|  | aux: Option<&FxIndexMap<LintId, LevelAndSource>>, | 
|  | ) -> (Option<(Level, Option<LintExpectationId>)>, LintLevelSource) { | 
|  | if let Some(specs) = aux | 
|  | && let Some(&LevelAndSource { level, lint_id, src }) = specs.get(&id) | 
|  | { | 
|  | return (Some((level, lint_id)), src); | 
|  | } | 
|  |  | 
|  | loop { | 
|  | let LintSet { ref specs, parent } = self.list[idx]; | 
|  | if let Some(&LevelAndSource { level, lint_id, src }) = specs.get(&id) { | 
|  | return (Some((level, lint_id)), src); | 
|  | } | 
|  | if idx == COMMAND_LINE { | 
|  | return (None, LintLevelSource::Default); | 
|  | } | 
|  | idx = parent; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> UnordSet<LintId> { | 
|  | let store = unerased_lint_store(&tcx.sess); | 
|  | let root_map = tcx.shallow_lint_levels_on(hir::CRATE_OWNER_ID); | 
|  |  | 
|  | let mut dont_need_to_run: FxHashSet<LintId> = store | 
|  | .get_lints() | 
|  | .into_iter() | 
|  | .filter(|lint| { | 
|  | // Lints that show up in future-compat reports must always be run. | 
|  | let has_future_breakage = | 
|  | lint.future_incompatible.is_some_and(|fut| fut.report_in_deps); | 
|  | !has_future_breakage && !lint.eval_always | 
|  | }) | 
|  | .filter(|lint| { | 
|  | let lint_level = | 
|  | root_map.lint_level_id_at_node(tcx, LintId::of(lint), hir::CRATE_HIR_ID); | 
|  | // Only include lints that are allowed at crate root or by default. | 
|  | matches!(lint_level.level, Level::Allow) | 
|  | || (matches!(lint_level.src, LintLevelSource::Default) | 
|  | && lint.default_level(tcx.sess.edition()) == Level::Allow) | 
|  | }) | 
|  | .map(|lint| LintId::of(*lint)) | 
|  | .collect(); | 
|  |  | 
|  | for owner in tcx.hir_crate_items(()).owners() { | 
|  | let map = tcx.shallow_lint_levels_on(owner); | 
|  |  | 
|  | // All lints that appear with a non-allow level must be run. | 
|  | for (_, specs) in map.specs.iter() { | 
|  | for (lint, level_and_source) in specs.iter() { | 
|  | if !matches!(level_and_source.level, Level::Allow) { | 
|  | dont_need_to_run.remove(lint); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | dont_need_to_run.into() | 
|  | } | 
|  |  | 
|  | #[instrument(level = "trace", skip(tcx), ret)] | 
|  | fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLevelMap { | 
|  | let store = unerased_lint_store(tcx.sess); | 
|  | let attrs = tcx.hir_attr_map(owner); | 
|  |  | 
|  | let mut levels = LintLevelsBuilder { | 
|  | sess: tcx.sess, | 
|  | features: tcx.features(), | 
|  | provider: LintLevelQueryMap { | 
|  | tcx, | 
|  | cur: owner.into(), | 
|  | specs: ShallowLintLevelMap::default(), | 
|  | empty: FxIndexMap::default(), | 
|  | attrs, | 
|  | }, | 
|  | lint_added_lints: false, | 
|  | store, | 
|  | registered_tools: tcx.registered_tools(()), | 
|  | }; | 
|  |  | 
|  | if owner == hir::CRATE_OWNER_ID { | 
|  | levels.add_command_line(); | 
|  | } | 
|  |  | 
|  | match attrs.map.range(..) { | 
|  | // There is only something to do if there are attributes at all. | 
|  | [] => {} | 
|  | // Most of the time, there is only one attribute. Avoid fetching HIR in that case. | 
|  | &[(local_id, _)] => levels.add_id(HirId { owner, local_id }), | 
|  | // Otherwise, we need to visit the attributes in source code order, so we fetch HIR and do | 
|  | // a standard visit. | 
|  | // FIXME(#102522) Just iterate on attrs once that iteration order matches HIR's. | 
|  | _ => match tcx.hir_owner_node(owner) { | 
|  | hir::OwnerNode::Item(item) => levels.visit_item(item), | 
|  | hir::OwnerNode::ForeignItem(item) => levels.visit_foreign_item(item), | 
|  | hir::OwnerNode::TraitItem(item) => levels.visit_trait_item(item), | 
|  | hir::OwnerNode::ImplItem(item) => levels.visit_impl_item(item), | 
|  | hir::OwnerNode::Crate(mod_) => { | 
|  | levels.add_id(hir::CRATE_HIR_ID); | 
|  | levels.visit_mod(mod_, mod_.spans.inner_span, hir::CRATE_HIR_ID) | 
|  | } | 
|  | hir::OwnerNode::Synthetic => unreachable!(), | 
|  | }, | 
|  | } | 
|  |  | 
|  | let specs = levels.provider.specs; | 
|  |  | 
|  | #[cfg(debug_assertions)] | 
|  | for (_, v) in specs.specs.iter() { | 
|  | debug_assert!(!v.is_empty()); | 
|  | } | 
|  |  | 
|  | specs | 
|  | } | 
|  |  | 
|  | pub struct TopDown { | 
|  | sets: LintLevelSets, | 
|  | cur: LintStackIndex, | 
|  | } | 
|  |  | 
|  | pub trait LintLevelsProvider { | 
|  | fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource>; | 
|  | fn insert(&mut self, id: LintId, lvl: LevelAndSource); | 
|  | fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource; | 
|  | fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation); | 
|  | } | 
|  |  | 
|  | impl LintLevelsProvider for TopDown { | 
|  | fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> { | 
|  | &self.sets.list[self.cur].specs | 
|  | } | 
|  |  | 
|  | fn insert(&mut self, id: LintId, lvl: LevelAndSource) { | 
|  | self.sets.list[self.cur].specs.insert(id, lvl); | 
|  | } | 
|  |  | 
|  | fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource { | 
|  | self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), sess) | 
|  | } | 
|  |  | 
|  | fn push_expectation(&mut self, _: LintExpectationId, _: LintExpectation) {} | 
|  | } | 
|  |  | 
|  | struct LintLevelQueryMap<'tcx> { | 
|  | tcx: TyCtxt<'tcx>, | 
|  | cur: HirId, | 
|  | specs: ShallowLintLevelMap, | 
|  | /// Empty hash map to simplify code. | 
|  | empty: FxIndexMap<LintId, LevelAndSource>, | 
|  | attrs: &'tcx hir::AttributeMap<'tcx>, | 
|  | } | 
|  |  | 
|  | impl LintLevelsProvider for LintLevelQueryMap<'_> { | 
|  | fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> { | 
|  | self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty) | 
|  | } | 
|  | fn insert(&mut self, id: LintId, lvl: LevelAndSource) { | 
|  | self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl); | 
|  | } | 
|  | fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource { | 
|  | self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur) | 
|  | } | 
|  | fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) { | 
|  | self.specs.expectations.push((id, expectation)) | 
|  | } | 
|  | } | 
|  |  | 
|  | impl<'tcx> LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> { | 
|  | fn add_id(&mut self, hir_id: HirId) { | 
|  | self.provider.cur = hir_id; | 
|  | self.add( | 
|  | self.provider.attrs.get(hir_id.local_id), | 
|  | hir_id == hir::CRATE_HIR_ID, | 
|  | Some(hir_id), | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> { | 
|  | type NestedFilter = nested_filter::OnlyBodies; | 
|  |  | 
|  | fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { | 
|  | self.provider.tcx | 
|  | } | 
|  |  | 
|  | fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { | 
|  | self.add_id(param.hir_id); | 
|  | intravisit::walk_param(self, param); | 
|  | } | 
|  |  | 
|  | fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) { | 
|  | self.add_id(it.hir_id()); | 
|  | intravisit::walk_item(self, it); | 
|  | } | 
|  |  | 
|  | fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) { | 
|  | self.add_id(it.hir_id()); | 
|  | intravisit::walk_foreign_item(self, it); | 
|  | } | 
|  |  | 
|  | fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) { | 
|  | self.add_id(s.hir_id); | 
|  | intravisit::walk_stmt(self, s); | 
|  | } | 
|  |  | 
|  | fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { | 
|  | self.add_id(e.hir_id); | 
|  | intravisit::walk_expr(self, e); | 
|  | } | 
|  |  | 
|  | fn visit_pat_field(&mut self, f: &'tcx hir::PatField<'tcx>) -> Self::Result { | 
|  | self.add_id(f.hir_id); | 
|  | intravisit::walk_pat_field(self, f); | 
|  | } | 
|  |  | 
|  | fn visit_expr_field(&mut self, f: &'tcx hir::ExprField<'tcx>) { | 
|  | self.add_id(f.hir_id); | 
|  | intravisit::walk_expr_field(self, f); | 
|  | } | 
|  |  | 
|  | fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) { | 
|  | self.add_id(s.hir_id); | 
|  | intravisit::walk_field_def(self, s); | 
|  | } | 
|  |  | 
|  | fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) { | 
|  | self.add_id(v.hir_id); | 
|  | intravisit::walk_variant(self, v); | 
|  | } | 
|  |  | 
|  | fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) { | 
|  | self.add_id(l.hir_id); | 
|  | intravisit::walk_local(self, l); | 
|  | } | 
|  |  | 
|  | fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) { | 
|  | self.add_id(a.hir_id); | 
|  | intravisit::walk_arm(self, a); | 
|  | } | 
|  |  | 
|  | fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { | 
|  | self.add_id(trait_item.hir_id()); | 
|  | intravisit::walk_trait_item(self, trait_item); | 
|  | } | 
|  |  | 
|  | fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { | 
|  | self.add_id(impl_item.hir_id()); | 
|  | intravisit::walk_impl_item(self, impl_item); | 
|  | } | 
|  | } | 
|  |  | 
|  | pub struct LintLevelsBuilder<'s, P> { | 
|  | sess: &'s Session, | 
|  | features: &'s Features, | 
|  | provider: P, | 
|  | lint_added_lints: bool, | 
|  | store: &'s LintStore, | 
|  | registered_tools: &'s RegisteredTools, | 
|  | } | 
|  |  | 
|  | pub(crate) struct BuilderPush { | 
|  | prev: LintStackIndex, | 
|  | } | 
|  |  | 
|  | impl<'s> LintLevelsBuilder<'s, TopDown> { | 
|  | pub(crate) fn new( | 
|  | sess: &'s Session, | 
|  | features: &'s Features, | 
|  | lint_added_lints: bool, | 
|  | store: &'s LintStore, | 
|  | registered_tools: &'s RegisteredTools, | 
|  | ) -> Self { | 
|  | let mut builder = LintLevelsBuilder { | 
|  | sess, | 
|  | features, | 
|  | provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE }, | 
|  | lint_added_lints, | 
|  | store, | 
|  | registered_tools, | 
|  | }; | 
|  | builder.process_command_line(); | 
|  | assert_eq!(builder.provider.sets.list.len(), 1); | 
|  | builder | 
|  | } | 
|  |  | 
|  | pub fn crate_root( | 
|  | sess: &'s Session, | 
|  | features: &'s Features, | 
|  | lint_added_lints: bool, | 
|  | store: &'s LintStore, | 
|  | registered_tools: &'s RegisteredTools, | 
|  | crate_attrs: &[ast::Attribute], | 
|  | ) -> Self { | 
|  | let mut builder = Self::new(sess, features, lint_added_lints, store, registered_tools); | 
|  | builder.add(crate_attrs, true, None); | 
|  | builder | 
|  | } | 
|  |  | 
|  | fn process_command_line(&mut self) { | 
|  | self.provider.cur = self | 
|  | .provider | 
|  | .sets | 
|  | .list | 
|  | .push(LintSet { specs: FxIndexMap::default(), parent: COMMAND_LINE }); | 
|  | self.add_command_line(); | 
|  | } | 
|  |  | 
|  | /// Pushes a list of AST lint attributes onto this context. | 
|  | /// | 
|  | /// This function will return a `BuilderPush` object which should be passed | 
|  | /// to `pop` when this scope for the attributes provided is exited. | 
|  | /// | 
|  | /// This function will perform a number of tasks: | 
|  | /// | 
|  | /// * It'll validate all lint-related attributes in `attrs` | 
|  | /// * It'll mark all lint-related attributes as used | 
|  | /// * Lint levels will be updated based on the attributes provided | 
|  | /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to | 
|  | ///   `#[allow]` | 
|  | /// | 
|  | /// Don't forget to call `pop`! | 
|  | pub(crate) fn push( | 
|  | &mut self, | 
|  | attrs: &[ast::Attribute], | 
|  | is_crate_node: bool, | 
|  | source_hir_id: Option<HirId>, | 
|  | ) -> BuilderPush { | 
|  | let prev = self.provider.cur; | 
|  | self.provider.cur = | 
|  | self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev }); | 
|  |  | 
|  | self.add(attrs, is_crate_node, source_hir_id); | 
|  |  | 
|  | if self.provider.current_specs().is_empty() { | 
|  | self.provider.sets.list.pop(); | 
|  | self.provider.cur = prev; | 
|  | } | 
|  |  | 
|  | BuilderPush { prev } | 
|  | } | 
|  |  | 
|  | /// Called after `push` when the scope of a set of attributes are exited. | 
|  | pub(crate) fn pop(&mut self, push: BuilderPush) { | 
|  | self.provider.cur = push.prev; | 
|  | std::mem::forget(push); | 
|  | } | 
|  | } | 
|  |  | 
|  | #[cfg(debug_assertions)] | 
|  | impl Drop for BuilderPush { | 
|  | fn drop(&mut self) { | 
|  | panic!("Found a `push` without a `pop`."); | 
|  | } | 
|  | } | 
|  |  | 
|  | impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { | 
|  | pub(crate) fn sess(&self) -> &Session { | 
|  | self.sess | 
|  | } | 
|  |  | 
|  | pub(crate) fn features(&self) -> &Features { | 
|  | self.features | 
|  | } | 
|  |  | 
|  | fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> { | 
|  | self.provider.current_specs() | 
|  | } | 
|  |  | 
|  | fn insert(&mut self, id: LintId, lvl: LevelAndSource) { | 
|  | self.provider.insert(id, lvl) | 
|  | } | 
|  |  | 
|  | fn add_command_line(&mut self) { | 
|  | for &(ref lint_name, level) in &self.sess.opts.lint_opts { | 
|  | // Checks the validity of lint names derived from the command line. | 
|  | let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name); | 
|  | if lint_name_only == crate::WARNINGS.name_lower() && matches!(level, Level::ForceWarn) { | 
|  | self.sess | 
|  | .dcx() | 
|  | .emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() }); | 
|  | } | 
|  | match self.store.check_lint_name(lint_name_only, tool_name, self.registered_tools) { | 
|  | CheckLintNameResult::Renamed(ref replace) => { | 
|  | let name = lint_name.as_str(); | 
|  | let suggestion = RenamedLintSuggestion::WithoutSpan { replace }; | 
|  | let requested_level = RequestedLevel { level, lint_name }; | 
|  | let lint = | 
|  | RenamedLintFromCommandLine { name, replace, suggestion, requested_level }; | 
|  | self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); | 
|  | } | 
|  | CheckLintNameResult::Removed(ref reason) => { | 
|  | let name = lint_name.as_str(); | 
|  | let requested_level = RequestedLevel { level, lint_name }; | 
|  | let lint = RemovedLintFromCommandLine { name, reason, requested_level }; | 
|  | self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); | 
|  | } | 
|  | CheckLintNameResult::NoLint(suggestion) => { | 
|  | let name = lint_name.clone(); | 
|  | let suggestion = suggestion.map(|(replace, from_rustc)| { | 
|  | UnknownLintSuggestion::WithoutSpan { replace, from_rustc } | 
|  | }); | 
|  | let requested_level = RequestedLevel { level, lint_name }; | 
|  | let lint = UnknownLintFromCommandLine { name, suggestion, requested_level }; | 
|  | self.emit_lint(UNKNOWN_LINTS, lint); | 
|  | } | 
|  | CheckLintNameResult::Tool(_, Some(ref replace)) => { | 
|  | let name = lint_name.clone(); | 
|  | let requested_level = RequestedLevel { level, lint_name }; | 
|  | let lint = DeprecatedLintNameFromCommandLine { name, replace, requested_level }; | 
|  | self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); | 
|  | } | 
|  | CheckLintNameResult::NoTool => { | 
|  | self.sess.dcx().emit_err(CheckNameUnknownTool { | 
|  | tool_name: tool_name.unwrap(), | 
|  | sub: RequestedLevel { level, lint_name }, | 
|  | }); | 
|  | } | 
|  | _ => {} | 
|  | }; | 
|  |  | 
|  | let lint_flag_val = Symbol::intern(lint_name); | 
|  |  | 
|  | let Some(ids) = self.store.find_lints(lint_name) else { | 
|  | // errors already handled above | 
|  | continue; | 
|  | }; | 
|  | for &id in ids { | 
|  | // ForceWarn and Forbid cannot be overridden | 
|  | if let Some(LevelAndSource { level: Level::ForceWarn | Level::Forbid, .. }) = | 
|  | self.current_specs().get(&id) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if self.check_gated_lint(id, DUMMY_SP, true) { | 
|  | let src = LintLevelSource::CommandLine(lint_flag_val, level); | 
|  | self.insert(id, LevelAndSource { level, lint_id: None, src }); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful | 
|  | /// (e.g. if a forbid was already inserted on the same scope), then emits a | 
|  | /// diagnostic with no change to `specs`. | 
|  | fn insert_spec(&mut self, id: LintId, LevelAndSource { level, lint_id, src }: LevelAndSource) { | 
|  | let LevelAndSource { level: old_level, src: old_src, .. } = | 
|  | self.provider.get_lint_level(id.lint, self.sess); | 
|  |  | 
|  | // Setting to a non-forbid level is an error if the lint previously had | 
|  | // a forbid level. Note that this is not necessarily true even with a | 
|  | // `#[forbid(..)]` attribute present, as that is overridden by `--cap-lints`. | 
|  | // | 
|  | // This means that this only errors if we're truly lowering the lint | 
|  | // level from forbid. | 
|  | if self.lint_added_lints && level == Level::Deny && old_level == Level::Forbid { | 
|  | // Having a deny inside a forbid is fine and is ignored, so we skip this check. | 
|  | return; | 
|  | } else if self.lint_added_lints && level != Level::Forbid && old_level == Level::Forbid { | 
|  | // Backwards compatibility check: | 
|  | // | 
|  | // We used to not consider `forbid(lint_group)` | 
|  | // as preventing `allow(lint)` for some lint `lint` in | 
|  | // `lint_group`. For now, issue a future-compatibility | 
|  | // warning for this case. | 
|  | let id_name = id.lint.name_lower(); | 
|  | let fcw_warning = match old_src { | 
|  | LintLevelSource::Default => false, | 
|  | LintLevelSource::Node { name, .. } => self.store.is_lint_group(name), | 
|  | LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol), | 
|  | }; | 
|  | debug!( | 
|  | "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}", | 
|  | fcw_warning, | 
|  | self.current_specs(), | 
|  | old_src, | 
|  | id_name | 
|  | ); | 
|  | let sub = match old_src { | 
|  | LintLevelSource::Default => { | 
|  | OverruledAttributeSub::DefaultSource { id: id.to_string() } | 
|  | } | 
|  | LintLevelSource::Node { span, reason, .. } => { | 
|  | OverruledAttributeSub::NodeSource { span, reason } | 
|  | } | 
|  | LintLevelSource::CommandLine(_, _) => OverruledAttributeSub::CommandLineSource, | 
|  | }; | 
|  | if !fcw_warning { | 
|  | self.sess.dcx().emit_err(OverruledAttribute { | 
|  | span: src.span(), | 
|  | overruled: src.span(), | 
|  | lint_level: level.as_str(), | 
|  | lint_source: src.name(), | 
|  | sub, | 
|  | }); | 
|  | } else { | 
|  | self.emit_span_lint( | 
|  | FORBIDDEN_LINT_GROUPS, | 
|  | src.span().into(), | 
|  | OverruledAttributeLint { | 
|  | overruled: src.span(), | 
|  | lint_level: level.as_str(), | 
|  | lint_source: src.name(), | 
|  | sub, | 
|  | }, | 
|  | ); | 
|  | } | 
|  |  | 
|  | // Retain the forbid lint level, unless we are | 
|  | // issuing a FCW. In the FCW case, we want to | 
|  | // respect the new setting. | 
|  | if !fcw_warning { | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself. | 
|  | // Handling expectations of this lint would add additional complexity with little to no | 
|  | // benefit. The expect level for this lint will therefore be ignored. | 
|  | if let Level::Expect = level | 
|  | && id == LintId::of(UNFULFILLED_LINT_EXPECTATIONS) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | match (old_level, level) { | 
|  | // If the new level is an expectation store it in `ForceWarn` | 
|  | (Level::ForceWarn, Level::Expect) => { | 
|  | self.insert(id, LevelAndSource { level: Level::ForceWarn, lint_id, src: old_src }) | 
|  | } | 
|  | // Keep `ForceWarn` level but drop the expectation | 
|  | (Level::ForceWarn, _) => self.insert( | 
|  | id, | 
|  | LevelAndSource { level: Level::ForceWarn, lint_id: None, src: old_src }, | 
|  | ), | 
|  | // Set the lint level as normal | 
|  | _ => self.insert(id, LevelAndSource { level, lint_id, src }), | 
|  | }; | 
|  | } | 
|  |  | 
|  | fn add( | 
|  | &mut self, | 
|  | attrs: &[impl AttributeExt], | 
|  | is_crate_node: bool, | 
|  | source_hir_id: Option<HirId>, | 
|  | ) { | 
|  | let sess = self.sess; | 
|  | for (attr_index, attr) in attrs.iter().enumerate() { | 
|  | if attr.is_automatically_derived_attr() { | 
|  | self.insert( | 
|  | LintId::of(SINGLE_USE_LIFETIMES), | 
|  | LevelAndSource { | 
|  | level: Level::Allow, | 
|  | lint_id: None, | 
|  | src: LintLevelSource::Default, | 
|  | }, | 
|  | ); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // `#[doc(hidden)]` disables missing_docs check. | 
|  | if attr.has_name(sym::doc) | 
|  | && attr | 
|  | .meta_item_list() | 
|  | .is_some_and(|l| ast::attr::list_contains_name(&l, sym::hidden)) | 
|  | { | 
|  | self.insert( | 
|  | LintId::of(MISSING_DOCS), | 
|  | LevelAndSource { | 
|  | level: Level::Allow, | 
|  | lint_id: None, | 
|  | src: LintLevelSource::Default, | 
|  | }, | 
|  | ); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | let (level, lint_id) = match Level::from_attr(attr) { | 
|  | None => continue, | 
|  | // This is the only lint level with a `LintExpectationId` that can be created from | 
|  | // an attribute. | 
|  | Some((Level::Expect, Some(unstable_id))) if let Some(hir_id) = source_hir_id => { | 
|  | let LintExpectationId::Unstable { lint_index: None, attr_id: _ } = unstable_id | 
|  | else { | 
|  | bug!("stable id Level::from_attr") | 
|  | }; | 
|  |  | 
|  | let stable_id = LintExpectationId::Stable { | 
|  | hir_id, | 
|  | attr_index: attr_index.try_into().unwrap(), | 
|  | lint_index: None, | 
|  | }; | 
|  |  | 
|  | (Level::Expect, Some(stable_id)) | 
|  | } | 
|  | Some((lvl, id)) => (lvl, id), | 
|  | }; | 
|  |  | 
|  | let Some(mut metas) = attr.meta_item_list() else { continue }; | 
|  |  | 
|  | // Check whether `metas` is empty, and get its last element. | 
|  | let Some(tail_li) = metas.last() else { | 
|  | // This emits the unused_attributes lint for `#[level()]` | 
|  | continue; | 
|  | }; | 
|  |  | 
|  | // Before processing the lint names, look for a reason (RFC 2383) | 
|  | // at the end. | 
|  | let mut reason = None; | 
|  | if let Some(item) = tail_li.meta_item() { | 
|  | match item.kind { | 
|  | ast::MetaItemKind::Word => {} // actual lint names handled later | 
|  | ast::MetaItemKind::NameValue(ref name_value) => { | 
|  | if item.path == sym::reason { | 
|  | if let ast::LitKind::Str(rationale, _) = name_value.kind { | 
|  | reason = Some(rationale); | 
|  | } else { | 
|  | sess.dcx().emit_err(MalformedAttribute { | 
|  | span: name_value.span, | 
|  | sub: MalformedAttributeSub::ReasonMustBeStringLiteral( | 
|  | name_value.span, | 
|  | ), | 
|  | }); | 
|  | } | 
|  | // found reason, reslice meta list to exclude it | 
|  | metas.pop().unwrap(); | 
|  | } else { | 
|  | sess.dcx().emit_err(MalformedAttribute { | 
|  | span: item.span, | 
|  | sub: MalformedAttributeSub::BadAttributeArgument(item.span), | 
|  | }); | 
|  | } | 
|  | } | 
|  | ast::MetaItemKind::List(_) => { | 
|  | sess.dcx().emit_err(MalformedAttribute { | 
|  | span: item.span, | 
|  | sub: MalformedAttributeSub::BadAttributeArgument(item.span), | 
|  | }); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | for (lint_index, li) in metas.iter_mut().enumerate() { | 
|  | let mut lint_id = lint_id; | 
|  | if let Some(id) = &mut lint_id { | 
|  | id.set_lint_index(Some(lint_index as u16)); | 
|  | } | 
|  |  | 
|  | let sp = li.span(); | 
|  | let meta_item = match li { | 
|  | ast::MetaItemInner::MetaItem(meta_item) if meta_item.is_word() => meta_item, | 
|  | _ => { | 
|  | let sub = if let Some(item) = li.meta_item() | 
|  | && let ast::MetaItemKind::NameValue(_) = item.kind | 
|  | && item.path == sym::reason | 
|  | { | 
|  | MalformedAttributeSub::ReasonMustComeLast(sp) | 
|  | } else { | 
|  | MalformedAttributeSub::BadAttributeArgument(sp) | 
|  | }; | 
|  |  | 
|  | sess.dcx().emit_err(MalformedAttribute { span: sp, sub }); | 
|  | continue; | 
|  | } | 
|  | }; | 
|  | let tool_ident = if meta_item.path.segments.len() > 1 { | 
|  | Some(meta_item.path.segments.remove(0).ident) | 
|  | } else { | 
|  | None | 
|  | }; | 
|  | let tool_name = tool_ident.map(|ident| ident.name); | 
|  | let name = pprust::path_to_string(&meta_item.path); | 
|  | let lint_result = | 
|  | self.store.check_lint_name(&name, tool_name, self.registered_tools); | 
|  |  | 
|  | let (ids, name) = match lint_result { | 
|  | CheckLintNameResult::Ok(ids) => { | 
|  | let name = | 
|  | meta_item.path.segments.last().expect("empty lint name").ident.name; | 
|  | (ids, name) | 
|  | } | 
|  |  | 
|  | CheckLintNameResult::Tool(ids, new_lint_name) => { | 
|  | let name = match new_lint_name { | 
|  | None => { | 
|  | let complete_name = | 
|  | &format!("{}::{}", tool_ident.unwrap().name, name); | 
|  | Symbol::intern(complete_name) | 
|  | } | 
|  | Some(new_lint_name) => { | 
|  | self.emit_span_lint( | 
|  | builtin::RENAMED_AND_REMOVED_LINTS, | 
|  | sp.into(), | 
|  | DeprecatedLintName { | 
|  | name, | 
|  | suggestion: sp, | 
|  | replace: &new_lint_name, | 
|  | }, | 
|  | ); | 
|  | Symbol::intern(&new_lint_name) | 
|  | } | 
|  | }; | 
|  | (ids, name) | 
|  | } | 
|  |  | 
|  | CheckLintNameResult::MissingTool => { | 
|  | // If `MissingTool` is returned, then either the lint does not | 
|  | // exist in the tool or the code was not compiled with the tool and | 
|  | // therefore the lint was never added to the `LintStore`. To detect | 
|  | // this is the responsibility of the lint tool. | 
|  | continue; | 
|  | } | 
|  |  | 
|  | CheckLintNameResult::NoTool => { | 
|  | sess.dcx().emit_err(UnknownToolInScopedLint { | 
|  | span: tool_ident.map(|ident| ident.span), | 
|  | tool_name: tool_name.unwrap(), | 
|  | lint_name: pprust::path_to_string(&meta_item.path), | 
|  | is_nightly_build: sess.is_nightly_build(), | 
|  | }); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | CheckLintNameResult::Renamed(ref replace) => { | 
|  | if self.lint_added_lints { | 
|  | let suggestion = | 
|  | RenamedLintSuggestion::WithSpan { suggestion: sp, replace }; | 
|  | let name = | 
|  | tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); | 
|  | let lint = RenamedLint { name: name.as_str(), replace, suggestion }; | 
|  | self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint); | 
|  | } | 
|  |  | 
|  | // If this lint was renamed, apply the new lint instead of ignoring the | 
|  | // attribute. Ignore any errors or warnings that happen because the new | 
|  | // name is inaccurate. | 
|  | // NOTE: `new_name` already includes the tool name, so we don't | 
|  | // have to add it again. | 
|  | let CheckLintNameResult::Ok(ids) = | 
|  | self.store.check_lint_name(replace, None, self.registered_tools) | 
|  | else { | 
|  | panic!("renamed lint does not exist: {replace}"); | 
|  | }; | 
|  |  | 
|  | (ids, Symbol::intern(&replace)) | 
|  | } | 
|  |  | 
|  | CheckLintNameResult::Removed(ref reason) => { | 
|  | if self.lint_added_lints { | 
|  | let name = | 
|  | tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); | 
|  | let lint = RemovedLint { name: name.as_str(), reason }; | 
|  | self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint); | 
|  | } | 
|  | continue; | 
|  | } | 
|  |  | 
|  | CheckLintNameResult::NoLint(suggestion) => { | 
|  | if self.lint_added_lints { | 
|  | let name = | 
|  | tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); | 
|  | let suggestion = suggestion.map(|(replace, from_rustc)| { | 
|  | UnknownLintSuggestion::WithSpan { | 
|  | suggestion: sp, | 
|  | replace, | 
|  | from_rustc, | 
|  | } | 
|  | }); | 
|  | let lint = UnknownLint { name, suggestion }; | 
|  | self.emit_span_lint(UNKNOWN_LINTS, sp.into(), lint); | 
|  | } | 
|  | continue; | 
|  | } | 
|  | }; | 
|  |  | 
|  | let src = LintLevelSource::Node { name, span: sp, reason }; | 
|  | for &id in ids { | 
|  | if self.check_gated_lint(id, sp, false) { | 
|  | self.insert_spec(id, LevelAndSource { level, lint_id, src }); | 
|  | } | 
|  | } | 
|  |  | 
|  | // This checks for instances where the user writes | 
|  | // `#[expect(unfulfilled_lint_expectations)]` in that case we want to avoid | 
|  | // overriding the lint level but instead add an expectation that can't be | 
|  | // fulfilled. The lint message will include an explanation, that the | 
|  | // `unfulfilled_lint_expectations` lint can't be expected. | 
|  | if let (Level::Expect, Some(expect_id)) = (level, lint_id) { | 
|  | // The `unfulfilled_lint_expectations` lint is not part of any lint | 
|  | // groups. Therefore. we only need to check the slice if it contains a | 
|  | // single lint. | 
|  | let is_unfulfilled_lint_expectations = match ids { | 
|  | [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS), | 
|  | _ => false, | 
|  | }; | 
|  | self.provider.push_expectation( | 
|  | expect_id, | 
|  | LintExpectation::new( | 
|  | reason, | 
|  | sp, | 
|  | is_unfulfilled_lint_expectations, | 
|  | tool_name, | 
|  | ), | 
|  | ); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if self.lint_added_lints && !is_crate_node { | 
|  | for (id, &LevelAndSource { level, ref src, .. }) in self.current_specs().iter() { | 
|  | if !id.lint.crate_level_only { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src | 
|  | else { | 
|  | continue; | 
|  | }; | 
|  |  | 
|  | self.emit_span_lint( | 
|  | UNUSED_ATTRIBUTES, | 
|  | lint_attr_span.into(), | 
|  | IgnoredUnlessCrateSpecified { level: level.as_str(), name: lint_attr_name }, | 
|  | ); | 
|  | // don't set a separate error for every lint in the group | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Checks if the lint is gated on a feature that is not enabled. | 
|  | /// | 
|  | /// Returns `true` if the lint's feature is enabled. | 
|  | #[track_caller] | 
|  | fn check_gated_lint(&self, lint_id: LintId, span: Span, lint_from_cli: bool) -> bool { | 
|  | let feature = if let Some(feature) = lint_id.lint.feature_gate | 
|  | && !self.features.enabled(feature) | 
|  | && !span.allows_unstable(feature) | 
|  | { | 
|  | // Lint is behind a feature that is not enabled; eventually return false. | 
|  | feature | 
|  | } else { | 
|  | // Lint is ungated or its feature is enabled; exit early. | 
|  | return true; | 
|  | }; | 
|  |  | 
|  | if self.lint_added_lints { | 
|  | let lint = builtin::UNKNOWN_LINTS; | 
|  | let level = self.lint_level(builtin::UNKNOWN_LINTS); | 
|  | // FIXME: make this translatable | 
|  | #[allow(rustc::diagnostic_outside_of_impl)] | 
|  | lint_level(self.sess, lint, level, Some(span.into()), |lint| { | 
|  | lint.primary_message(fluent::lint_unknown_gated_lint); | 
|  | lint.arg("name", lint_id.lint.name_lower()); | 
|  | lint.note(fluent::lint_note); | 
|  | rustc_session::parse::add_feature_diagnostics_for_issue( | 
|  | lint, | 
|  | &self.sess, | 
|  | feature, | 
|  | GateIssue::Language, | 
|  | lint_from_cli, | 
|  | None, | 
|  | ); | 
|  | }); | 
|  | } | 
|  |  | 
|  | false | 
|  | } | 
|  |  | 
|  | /// Find the lint level for a lint. | 
|  | pub fn lint_level(&self, lint: &'static Lint) -> LevelAndSource { | 
|  | self.provider.get_lint_level(lint, self.sess) | 
|  | } | 
|  |  | 
|  | /// Used to emit a lint-related diagnostic based on the current state of | 
|  | /// this lint context. | 
|  | /// | 
|  | /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature | 
|  | #[rustc_lint_diagnostics] | 
|  | #[track_caller] | 
|  | pub(crate) fn opt_span_lint( | 
|  | &self, | 
|  | lint: &'static Lint, | 
|  | span: Option<MultiSpan>, | 
|  | decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), | 
|  | ) { | 
|  | let level = self.lint_level(lint); | 
|  | lint_level(self.sess, lint, level, span, decorate) | 
|  | } | 
|  |  | 
|  | #[track_caller] | 
|  | pub fn emit_span_lint( | 
|  | &self, | 
|  | lint: &'static Lint, | 
|  | span: MultiSpan, | 
|  | decorate: impl for<'a> LintDiagnostic<'a, ()>, | 
|  | ) { | 
|  | let level = self.lint_level(lint); | 
|  | lint_level(self.sess, lint, level, Some(span), |lint| { | 
|  | decorate.decorate_lint(lint); | 
|  | }); | 
|  | } | 
|  |  | 
|  | #[track_caller] | 
|  | pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> LintDiagnostic<'a, ()>) { | 
|  | let level = self.lint_level(lint); | 
|  | lint_level(self.sess, lint, level, None, |lint| { | 
|  | decorate.decorate_lint(lint); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | pub(crate) fn provide(providers: &mut Providers) { | 
|  | *providers = Providers { shallow_lint_levels_on, lints_that_dont_need_to_run, ..*providers }; | 
|  | } | 
|  |  | 
|  | pub(crate) fn parse_lint_and_tool_name(lint_name: &str) -> (Option<Symbol>, &str) { | 
|  | match lint_name.split_once("::") { | 
|  | Some((tool_name, lint_name)) => { | 
|  | let tool_name = Symbol::intern(tool_name); | 
|  |  | 
|  | (Some(tool_name), lint_name) | 
|  | } | 
|  | None => (None, lint_name), | 
|  | } | 
|  | } |