diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 17e6c9e9575fd..be1a0eb6483f6 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -311,9 +311,10 @@ impl Diagnostic { // The lint index inside the attribute is manually transferred here. let lint_index = expectation_id.get_lint_index(); expectation_id.set_lint_index(None); - let mut stable_id = *unstable_to_stable + let mut stable_id = unstable_to_stable .get(&expectation_id) - .expect("each unstable `LintExpectationId` must have a matching stable id"); + .expect("each unstable `LintExpectationId` must have a matching stable id") + .normalize(); stable_id.set_lint_index(lint_index); *expectation_id = stable_id; diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 395bf5aad01b6..54109b75e3415 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -1145,7 +1145,7 @@ impl HandlerInner { if let Some(expectation_id) = diagnostic.level.get_expectation_id() { self.suppressed_expected_diag = true; - self.fulfilled_expectations.insert(expectation_id); + self.fulfilled_expectations.insert(expectation_id.normalize()); } if matches!(diagnostic.level, Warning(_)) diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index b95fc341db656..03f04b1488552 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -574,7 +574,7 @@ pub struct LateContext<'tcx> { /// Context for lint checking of the AST, after expansion, before lowering to HIR. pub struct EarlyContext<'a> { - pub builder: LintLevelsBuilder<'a>, + pub builder: LintLevelsBuilder<'a, crate::levels::TopDown>, pub buffered: LintBuffer, } diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 580a4566869f0..31c757e8452b9 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -60,6 +60,7 @@ impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> { F: FnOnce(&mut Self), { let is_crate_node = id == ast::CRATE_NODE_ID; + debug!(?id); let push = self.context.builder.push(attrs, is_crate_node, None); self.check_id(id); diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs index 699e81543188f..b9a3d52ca9b19 100644 --- a/compiler/rustc_lint/src/expect.rs +++ b/compiler/rustc_lint/src/expect.rs @@ -16,8 +16,10 @@ fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option) { return; } + let lint_expectations = tcx.lint_expectations(()); let fulfilled_expectations = tcx.sess.diagnostic().steal_fulfilled_expectation_ids(); - let lint_expectations = &tcx.lint_levels(()).lint_expectations; + + tracing::debug!(?lint_expectations, ?fulfilled_expectations); for (id, expectation) in lint_expectations { // This check will always be true, since `lint_expectations` only diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 1cabb58bbebfd..f8329187c1f9b 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -8,7 +8,7 @@ use rustc_hir as hir; use rustc_hir::{intravisit, HirId}; use rustc_middle::hir::nested_filter; use rustc_middle::lint::{ - struct_lint_level, LevelAndSource, LintExpectation, LintLevelMap, LintLevelSets, + struct_lint_level, LevelAndSource, LintExpectation, LintLevelQueryMap, LintLevelSets, LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE, }; use rustc_middle::ty::query::Providers; @@ -23,35 +23,202 @@ use rustc_span::symbol::{sym, Symbol}; use rustc_span::{Span, DUMMY_SP}; use tracing::debug; -fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap { +fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExpectation)> { let store = unerased_lint_store(tcx); - let levels = - LintLevelsBuilder::new(tcx.sess, false, &store, &tcx.resolutions(()).registered_tools); - let mut builder = LintLevelMapBuilder { levels, tcx }; - let krate = tcx.hir().krate(); - builder.levels.id_to_set.reserve(krate.owners.len() + 1); + let mut builder = LintLevelsBuilder { + sess: tcx.sess, + provider: QueryMapExpectationsWrapper { + map: LintLevelQueryMap { tcx, cur: hir::CRATE_HIR_ID, specs: FxHashMap::default() }, + expectations: Vec::new(), + unstable_to_stable_ids: FxHashMap::default(), + }, + warn_about_weird_lints: false, + store, + registered_tools: &tcx.resolutions(()).registered_tools, + }; + + builder.add_command_line(); + builder.add_id(hir::CRATE_HIR_ID); + tcx.hir().walk_toplevel_module(&mut builder); - let push = - builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true, Some(hir::CRATE_HIR_ID)); + tcx.sess.diagnostic().update_unstable_expectation_id(&builder.provider.unstable_to_stable_ids); - builder.levels.register_id(hir::CRATE_HIR_ID); - tcx.hir().walk_toplevel_module(&mut builder); - builder.levels.pop(push); + builder.provider.expectations +} + +fn lint_levels_on(tcx: TyCtxt<'_>, hir_id: HirId) -> FxHashMap { + let store = unerased_lint_store(tcx); + + let mut levels = LintLevelsBuilder { + sess: tcx.sess, + provider: LintLevelQueryMap { tcx, cur: hir_id, specs: FxHashMap::default() }, + warn_about_weird_lints: false, + store, + registered_tools: &tcx.resolutions(()).registered_tools, + }; + + let is_crate = hir::CRATE_HIR_ID == hir_id; + if is_crate { + levels.add_command_line(); + } + debug!(?hir_id); + levels.add(tcx.hir().attrs(hir_id), is_crate, Some(hir_id)); - builder.levels.update_unstable_expectation_ids(); - builder.levels.build_map() + levels.provider.specs } -pub struct LintLevelsBuilder<'s> { - sess: &'s Session, - lint_expectations: Vec<(LintExpectationId, LintExpectation)>, - /// Each expectation has a stable and an unstable identifier. This map - /// is used to map from unstable to stable [`LintExpectationId`]s. - expectation_id_map: FxHashMap, +pub struct TopDown { sets: LintLevelSets, - id_to_set: FxHashMap, cur: LintStackIndex, +} + +pub struct QueryMapExpectationsWrapper<'tcx> { + map: LintLevelQueryMap<'tcx>, + expectations: Vec<(LintExpectationId, LintExpectation)>, + unstable_to_stable_ids: FxHashMap, +} + +pub trait LintLevelsProvider { + fn current_specs(&self) -> &FxHashMap; + fn current_specs_mut(&mut self) -> &mut FxHashMap; + 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) -> &FxHashMap { + &self.sets.list[self.cur].specs + } + + fn current_specs_mut(&mut self) -> &mut FxHashMap { + &mut self.sets.list[self.cur].specs + } + + fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource { + self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), sess) + } +} + +impl LintLevelsProvider for LintLevelQueryMap<'_> { + fn current_specs(&self) -> &FxHashMap { + &self.specs + } + fn current_specs_mut(&mut self) -> &mut FxHashMap { + &mut self.specs + } + fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource { + self.lint_level(lint) + } +} + +impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> { + fn current_specs(&self) -> &FxHashMap { + &self.map.specs + } + fn current_specs_mut(&mut self) -> &mut FxHashMap { + self.map.specs.clear(); + &mut self.map.specs + } + fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource { + self.map.lint_level(lint) + } + fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) { + let LintExpectationId::Stable { attr_id: Some(attr_id), hir_id, attr_index, .. } = id else { bug!("unstable expectation id should already be mapped") }; + let key = LintExpectationId::Unstable { attr_id, lint_index: None }; + + if !self.unstable_to_stable_ids.contains_key(&key) { + self.unstable_to_stable_ids.insert( + key, + LintExpectationId::Stable { hir_id, attr_index, lint_index: None, attr_id: None }, + ); + } + + self.expectations.push((id.normalize(), expectation)); + } +} + +impl<'tcx> LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> { + fn add_id(&mut self, hir_id: HirId) { + self.add( + self.provider.map.tcx.hir().attrs(hir_id), + hir_id == hir::CRATE_HIR_ID, + Some(hir_id), + ); + } +} + +impl<'tcx> intravisit::Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> { + type NestedFilter = nested_filter::All; + + fn nested_visit_map(&mut self) -> Self::Map { + self.provider.map.tcx.hir() + } + + 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, e: &'tcx hir::Stmt<'tcx>) { + // We will call `add_id` when we walk + // the `StmtKind`. The outer statement itself doesn't + // define the lint levels. + intravisit::walk_stmt(self, e); + } + + fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { + self.add_id(e.hir_id); + intravisit::walk_expr(self, e); + } + + 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.id); + intravisit::walk_variant(self, v); + } + + fn visit_local(&mut self, l: &'tcx hir::Local<'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, + provider: P, warn_about_weird_lints: bool, store: &'s LintStore, registered_tools: &'s RegisteredTools, @@ -62,7 +229,7 @@ pub struct BuilderPush { pub changed: bool, } -impl<'s> LintLevelsBuilder<'s> { +impl<'s> LintLevelsBuilder<'s, TopDown> { pub fn new( sess: &'s Session, warn_about_weird_lints: bool, @@ -71,20 +238,66 @@ impl<'s> LintLevelsBuilder<'s> { ) -> Self { let mut builder = LintLevelsBuilder { sess, - lint_expectations: Default::default(), - expectation_id_map: Default::default(), - sets: LintLevelSets::new(), - cur: COMMAND_LINE, - id_to_set: Default::default(), + provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE }, warn_about_weird_lints, store, registered_tools, }; - builder.process_command_line(sess, store); - assert_eq!(builder.sets.list.len(), 1); + builder.process_command_line(); + assert_eq!(builder.provider.sets.list.len(), 1); builder } + fn process_command_line(&mut self) { + self.provider.cur = self + .provider + .sets + .list + .push(LintSet { specs: FxHashMap::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, + ) -> BuilderPush { + let prev = self.provider.cur; + self.provider.cur = + self.provider.sets.list.push(LintSet { specs: FxHashMap::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, changed: prev != self.provider.cur } + } + + /// Called after `push` when the scope of a set of attributes are exited. + pub fn pop(&mut self, push: BuilderPush) { + self.provider.cur = push.prev; + } +} + +impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { pub(crate) fn sess(&self) -> &Session { self.sess } @@ -94,24 +307,20 @@ impl<'s> LintLevelsBuilder<'s> { } fn current_specs(&self) -> &FxHashMap { - &self.sets.list[self.cur].specs + self.provider.current_specs() } fn current_specs_mut(&mut self) -> &mut FxHashMap { - &mut self.sets.list[self.cur].specs + self.provider.current_specs_mut() } - fn process_command_line(&mut self, sess: &Session, store: &LintStore) { - self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid); - - self.cur = - self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE }); - for &(ref lint_name, level) in &sess.opts.lint_opts { - store.check_lint_name_cmdline(sess, &lint_name, level, self.registered_tools); + fn add_command_line(&mut self) { + for &(ref lint_name, level) in &self.sess.opts.lint_opts { + self.store.check_lint_name_cmdline(self.sess, &lint_name, level, self.registered_tools); let orig_level = level; let lint_flag_val = Symbol::intern(lint_name); - let Ok(ids) = store.find_lints(&lint_name) else { + let Ok(ids) = self.store.find_lints(&lint_name) else { // errors handled in check_lint_name_cmdline above continue }; @@ -134,9 +343,11 @@ impl<'s> LintLevelsBuilder<'s> { /// 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, (level, src): LevelAndSource) { - let (old_level, old_src) = - self.sets.get_lint_level(id.lint, self.cur, Some(self.current_specs()), &self.sess); + fn insert_spec(&mut self, id: LintId, (mut level, src): LevelAndSource) { + let (old_level, old_src) = self.provider.get_lint_level(id.lint, &self.sess); + if let Level::Expect(id) = &mut level && let LintExpectationId::Stable { .. } = id { + *id = id.normalize(); + } // 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`. @@ -154,7 +365,7 @@ impl<'s> LintLevelsBuilder<'s> { let id_name = id.lint.name_lower(); let fcw_warning = match old_src { LintLevelSource::Default => false, - LintLevelSource::Node(symbol, _, _) => self.store.is_lint_group(symbol), + LintLevelSource::Node { name, .. } => self.store.is_lint_group(name), LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol), }; debug!( @@ -174,8 +385,8 @@ impl<'s> LintLevelsBuilder<'s> { id.to_string() )); } - LintLevelSource::Node(_, forbid_source_span, reason) => { - diag.span_label(forbid_source_span, "`forbid` level set here"); + LintLevelSource::Node { span, reason, .. } => { + diag.span_label(span, "`forbid` level set here"); if let Some(rationale) = reason { diag.note(rationale.as_str()); } @@ -242,29 +453,7 @@ impl<'s> LintLevelsBuilder<'s> { }; } - /// 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, - ) -> BuilderPush { - let prev = self.cur; - self.cur = self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: prev }); - + fn add(&mut self, attrs: &[ast::Attribute], is_crate_node: bool, source_hir_id: Option) { let sess = self.sess; let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input"); for (attr_index, attr) in attrs.iter().enumerate() { @@ -280,7 +469,17 @@ impl<'s> LintLevelsBuilder<'s> { None => continue, // This is the only lint level with a `LintExpectationId` that can be created from an attribute Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => { - let stable_id = self.create_stable_id(unstable_id, hir_id, attr_index); + let LintExpectationId::Unstable { attr_id, lint_index } = 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, + // we pass the previous unstable attr_id such that we can trace the ast id when building a map + // to go from unstable to stable id. + attr_id: Some(attr_id), + }; Level::Expect(stable_id) } @@ -387,7 +586,7 @@ impl<'s> LintLevelsBuilder<'s> { [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS), _ => false, }; - self.lint_expectations.push(( + self.provider.push_expectation( expect_id, LintExpectation::new( reason, @@ -395,13 +594,20 @@ impl<'s> LintLevelsBuilder<'s> { is_unfulfilled_lint_expectations, tool_name, ), - )); + ); } - let src = LintLevelSource::Node( - meta_item.path.segments.last().expect("empty lint name").ident.name, - sp, + let src = LintLevelSource::Node { + name: meta_item + .path + .segments + .last() + .expect("empty lint name") + .ident + .name, + span: sp, reason, - ); + tool: tool_name, + }; for &id in *ids { if self.check_gated_lint(id, attr.span) { self.insert_spec(id, (level, src)); @@ -414,29 +620,25 @@ impl<'s> LintLevelsBuilder<'s> { Ok(ids) => { let complete_name = &format!("{}::{}", tool_ident.unwrap().name, name); - let src = LintLevelSource::Node( - Symbol::intern(complete_name), - sp, + let src = LintLevelSource::Node { + name: Symbol::intern(complete_name), + span: sp, reason, - ); + tool: tool_name, + }; for id in ids { self.insert_spec(*id, (level, src)); } if let Level::Expect(expect_id) = level { - self.lint_expectations.push(( + self.provider.push_expectation( expect_id, LintExpectation::new(reason, sp, false, tool_name), - )); + ); } } Err((Some(ids), ref new_lint_name)) => { let lint = builtin::RENAMED_AND_REMOVED_LINTS; - let (lvl, src) = self.sets.get_lint_level( - lint, - self.cur, - Some(self.current_specs()), - &sess, - ); + let (lvl, src) = self.provider.get_lint_level(lint, &sess); struct_lint_level( self.sess, lint, @@ -460,19 +662,20 @@ impl<'s> LintLevelsBuilder<'s> { }, ); - let src = LintLevelSource::Node( - Symbol::intern(&new_lint_name), - sp, + let src = LintLevelSource::Node { + name: Symbol::intern(&new_lint_name), + span: sp, reason, - ); + tool: tool_name, + }; for id in ids { self.insert_spec(*id, (level, src)); } if let Level::Expect(expect_id) = level { - self.lint_expectations.push(( + self.provider.push_expectation( expect_id, LintExpectation::new(reason, sp, false, tool_name), - )); + ); } } Err((None, _)) => { @@ -508,12 +711,7 @@ impl<'s> LintLevelsBuilder<'s> { CheckLintNameResult::Warning(msg, renamed) => { let lint = builtin::RENAMED_AND_REMOVED_LINTS; - let (renamed_lint_level, src) = self.sets.get_lint_level( - lint, - self.cur, - Some(self.current_specs()), - &sess, - ); + let (renamed_lint_level, src) = self.provider.get_lint_level(lint, &sess); struct_lint_level( self.sess, lint, @@ -536,12 +734,7 @@ impl<'s> LintLevelsBuilder<'s> { } CheckLintNameResult::NoLint(suggestion) => { let lint = builtin::UNKNOWN_LINTS; - let (level, src) = self.sets.get_lint_level( - lint, - self.cur, - Some(self.current_specs()), - self.sess, - ); + let (level, src) = self.provider.get_lint_level(lint, self.sess); struct_lint_level(self.sess, lint, level, src, Some(sp.into()), |lint| { let name = if let Some(tool_ident) = tool_ident { format!("{}::{}", tool_ident.name, name) @@ -570,17 +763,22 @@ impl<'s> LintLevelsBuilder<'s> { if let CheckLintNameResult::Ok(ids) = self.store.check_lint_name(&new_name, None, self.registered_tools) { - let src = LintLevelSource::Node(Symbol::intern(&new_name), sp, reason); + let src = LintLevelSource::Node { + name: Symbol::intern(&new_name), + span: sp, + reason, + tool: tool_name, + }; for &id in ids { if self.check_gated_lint(id, attr.span) { self.insert_spec(id, (level, src)); } } if let Level::Expect(expect_id) = level { - self.lint_expectations.push(( + self.provider.push_expectation( expect_id, LintExpectation::new(reason, sp, false, tool_name), - )); + ); } } else { panic!("renamed lint does not exist: {}", new_name); @@ -595,13 +793,12 @@ impl<'s> LintLevelsBuilder<'s> { continue; } - let LintLevelSource::Node(lint_attr_name, lint_attr_span, _) = *src else { + let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src else { continue }; let lint = builtin::UNUSED_ATTRIBUTES; - let (lint_level, lint_src) = - self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), self.sess); + let (lint_level, lint_src) = self.provider.get_lint_level(lint, &self.sess); struct_lint_level( self.sess, lint, @@ -621,27 +818,6 @@ impl<'s> LintLevelsBuilder<'s> { break; } } - - if self.current_specs().is_empty() { - self.sets.list.pop(); - self.cur = prev; - } - - BuilderPush { prev, changed: prev != self.cur } - } - - fn create_stable_id( - &mut self, - unstable_id: LintExpectationId, - hir_id: HirId, - attr_index: usize, - ) -> LintExpectationId { - let stable_id = - LintExpectationId::Stable { hir_id, attr_index: attr_index as u16, lint_index: None }; - - self.expectation_id_map.insert(unstable_id, stable_id); - - stable_id } /// Checks if the lint is gated on a feature that is not enabled. @@ -665,14 +841,9 @@ impl<'s> LintLevelsBuilder<'s> { true } - /// Called after `push` when the scope of a set of attributes are exited. - pub fn pop(&mut self, push: BuilderPush) { - self.cur = push.prev; - } - /// Find the lint level for a lint. - pub fn lint_level(&self, lint: &'static Lint) -> (Level, LintLevelSource) { - self.sets.get_lint_level(lint, self.cur, None, self.sess) + 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 @@ -686,141 +857,8 @@ impl<'s> LintLevelsBuilder<'s> { let (level, src) = self.lint_level(lint); struct_lint_level(self.sess, lint, level, src, span, decorate) } - - /// Registers the ID provided with the current set of lints stored in - /// this context. - pub fn register_id(&mut self, id: HirId) { - self.id_to_set.insert(id, self.cur); - } - - fn update_unstable_expectation_ids(&self) { - self.sess.diagnostic().update_unstable_expectation_id(&self.expectation_id_map); - } - - pub fn build_map(self) -> LintLevelMap { - LintLevelMap { - sets: self.sets, - id_to_set: self.id_to_set, - lint_expectations: self.lint_expectations, - } - } -} - -struct LintLevelMapBuilder<'tcx> { - levels: LintLevelsBuilder<'tcx>, - tcx: TyCtxt<'tcx>, -} - -impl LintLevelMapBuilder<'_> { - fn with_lint_attrs(&mut self, id: hir::HirId, f: F) - where - F: FnOnce(&mut Self), - { - let is_crate_hir = id == hir::CRATE_HIR_ID; - let attrs = self.tcx.hir().attrs(id); - let push = self.levels.push(attrs, is_crate_hir, Some(id)); - - if push.changed { - self.levels.register_id(id); - } - f(self); - self.levels.pop(push); - } -} - -impl<'tcx> intravisit::Visitor<'tcx> for LintLevelMapBuilder<'tcx> { - type NestedFilter = nested_filter::All; - - fn nested_visit_map(&mut self) -> Self::Map { - self.tcx.hir() - } - - fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { - self.with_lint_attrs(param.hir_id, |builder| { - intravisit::walk_param(builder, param); - }); - } - - fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) { - self.with_lint_attrs(it.hir_id(), |builder| { - intravisit::walk_item(builder, it); - }); - } - - fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) { - self.with_lint_attrs(it.hir_id(), |builder| { - intravisit::walk_foreign_item(builder, it); - }) - } - - fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) { - // We will call `with_lint_attrs` when we walk - // the `StmtKind`. The outer statement itself doesn't - // define the lint levels. - intravisit::walk_stmt(self, e); - } - - fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { - self.with_lint_attrs(e.hir_id, |builder| { - intravisit::walk_expr(builder, e); - }) - } - - fn visit_expr_field(&mut self, field: &'tcx hir::ExprField<'tcx>) { - self.with_lint_attrs(field.hir_id, |builder| { - intravisit::walk_expr_field(builder, field); - }) - } - - fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) { - self.with_lint_attrs(s.hir_id, |builder| { - intravisit::walk_field_def(builder, s); - }) - } - - fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) { - self.with_lint_attrs(v.id, |builder| { - intravisit::walk_variant(builder, v); - }) - } - - fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) { - self.with_lint_attrs(l.hir_id, |builder| { - intravisit::walk_local(builder, l); - }) - } - - fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) { - self.with_lint_attrs(a.hir_id, |builder| { - intravisit::walk_arm(builder, a); - }) - } - - fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { - self.with_lint_attrs(trait_item.hir_id(), |builder| { - intravisit::walk_trait_item(builder, trait_item); - }); - } - - fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { - self.with_lint_attrs(impl_item.hir_id(), |builder| { - intravisit::walk_impl_item(builder, impl_item); - }); - } - - fn visit_pat_field(&mut self, field: &'tcx hir::PatField<'tcx>) { - self.with_lint_attrs(field.hir_id, |builder| { - intravisit::walk_pat_field(builder, field); - }) - } - - fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) { - self.with_lint_attrs(p.hir_id, |builder| { - intravisit::walk_generic_param(builder, p); - }); - } } pub fn provide(providers: &mut Providers) { - providers.lint_levels = lint_levels; + *providers = Providers { lint_levels_on, lint_expectations, ..*providers }; } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 6acbe97a7a118..c062e1fa8a19e 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -89,7 +89,7 @@ pub enum LintExpectationId { /// stable and can be cached. The additional index ensures that nodes with /// several expectations can correctly match diagnostics to the individual /// expectation. - Stable { hir_id: HirId, attr_index: u16, lint_index: Option }, + Stable { hir_id: HirId, attr_index: u16, lint_index: Option, attr_id: Option }, } impl LintExpectationId { @@ -113,13 +113,31 @@ impl LintExpectationId { *lint_index = new_lint_index } + + /// Prepares the id for hashing. Removes references to the ast. + /// Should only be called when the id is stable. + pub fn normalize(self) -> Self { + match self { + Self::Stable { hir_id, attr_index, lint_index, .. } => { + Self::Stable { hir_id, attr_index, lint_index, attr_id: None } + } + Self::Unstable { .. } => { + unreachable!("`normalize` called when `ExpectationId` is unstable") + } + } + } } impl HashStable for LintExpectationId { #[inline] fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) { match self { - LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => { + LintExpectationId::Stable { + hir_id, + attr_index, + lint_index: Some(lint_index), + attr_id: _, + } => { hir_id.hash_stable(hcx, hasher); attr_index.hash_stable(hcx, hasher); lint_index.hash_stable(hcx, hasher); @@ -139,9 +157,12 @@ impl ToStableHashKey for LintExpectation #[inline] fn to_stable_hash_key(&self, _: &HCX) -> Self::KeyType { match self { - LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => { - (*hir_id, *attr_index, *lint_index) - } + LintExpectationId::Stable { + hir_id, + attr_index, + lint_index: Some(lint_index), + attr_id: _, + } => (*hir_id, *attr_index, *lint_index), _ => { unreachable!("HashStable should only be called for a filled `LintExpectationId`") } diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index 2f45222de4728..c659e677fa0e1 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -15,6 +15,8 @@ use rustc_span::hygiene::MacroKind; use rustc_span::source_map::{DesugaringKind, ExpnKind}; use rustc_span::{symbol, Span, Symbol, DUMMY_SP}; +use crate::ty::TyCtxt; + /// How a lint level was set. #[derive(Clone, Copy, PartialEq, Eq, HashStable, Debug)] pub enum LintLevelSource { @@ -23,7 +25,14 @@ pub enum LintLevelSource { Default, /// Lint level was set by an attribute. - Node(Symbol, Span, Option /* RFC 2383 reason */), + Node { + name: Symbol, + span: Span, + /// RFC 2383 reason + reason: Option, + /// The lint tool. (e.g. rustdoc, clippy) + tool: Option, + }, /// Lint level was set by a command-line flag. /// The provided `Level` is the level specified on the command line. @@ -35,7 +44,7 @@ impl LintLevelSource { pub fn name(&self) -> Symbol { match *self { LintLevelSource::Default => symbol::kw::Default, - LintLevelSource::Node(name, _, _) => name, + LintLevelSource::Node { name, .. } => name, LintLevelSource::CommandLine(name, _) => name, } } @@ -43,7 +52,7 @@ impl LintLevelSource { pub fn span(&self) -> Span { match *self { LintLevelSource::Default => DUMMY_SP, - LintLevelSource::Node(_, span, _) => span, + LintLevelSource::Node { span, .. } => span, LintLevelSource::CommandLine(_, _) => DUMMY_SP, } } @@ -55,7 +64,6 @@ pub type LevelAndSource = (Level, LintLevelSource); #[derive(Debug, HashStable)] pub struct LintLevelSets { pub list: IndexVec, - pub lint_cap: Level, } rustc_index::newtype_index! { @@ -76,18 +84,16 @@ pub struct LintSet { impl LintLevelSets { pub fn new() -> Self { - LintLevelSets { list: IndexVec::new(), lint_cap: Level::Forbid } + LintLevelSets { list: IndexVec::new() } } - pub fn get_lint_level( - &self, - lint: &'static Lint, - idx: LintStackIndex, - aux: Option<&FxHashMap>, + pub fn actual_level( + level: Option, + src: &mut LintLevelSource, sess: &Session, - ) -> LevelAndSource { - let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux); - + lint: &'static Lint, + get_lint_id_level: impl FnOnce(LintId) -> (Option, LintLevelSource), + ) -> Level { // If `level` is none then we actually assume the default level for this // lint. let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition())); @@ -101,12 +107,11 @@ impl LintLevelSets { // and so if we turned that into an error, it'd defeat the purpose of the // future compatibility warning. if level == Level::Warn && LintId::of(lint) != LintId::of(FORBIDDEN_LINT_GROUPS) { - let (warnings_level, warnings_src) = - self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux); + let (warnings_level, warnings_src) = get_lint_id_level(LintId::of(builtin::WARNINGS)); if let Some(configured_warning_level) = warnings_level { if configured_warning_level != Level::Warn { level = configured_warning_level; - src = warnings_src; + *src = warnings_src; } } } @@ -116,7 +121,7 @@ impl LintLevelSets { level = if let LintLevelSource::CommandLine(_, Level::ForceWarn(_)) = src { level } else { - cmp::min(level, self.lint_cap) + cmp::min(level, sess.opts.lint_cap.unwrap_or(Level::Forbid)) }; if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) { @@ -124,6 +129,22 @@ impl LintLevelSets { level = cmp::min(*driver_level, level); } + level + } + + pub fn get_lint_level( + &self, + lint: &'static Lint, + idx: LintStackIndex, + aux: Option<&FxHashMap>, + sess: &Session, + ) -> LevelAndSource { + let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux); + + let level = Self::actual_level(level, &mut src, sess, lint, |id| { + self.get_lint_id_level(id, idx, aux) + }); + (level, src) } @@ -193,6 +214,58 @@ impl<'a> HashStable> for LintLevelMap { hcx.while_hashing_spans(true, |hcx| sets.hash_stable(hcx, hasher)) } } +pub struct LintLevelQueryMap<'tcx> { + pub tcx: TyCtxt<'tcx>, + pub cur: HirId, + pub specs: FxHashMap, +} + +impl<'tcx> LintLevelQueryMap<'tcx> { + pub fn lint_id_level(&self, id: LintId) -> (Option, LintLevelSource) { + Self::get_lint_id_level(id, self.cur, self.tcx, &self.specs) + } + + pub fn lint_level(&self, lint: &'static Lint) -> LevelAndSource { + Self::get_lint_level(LintId::of(lint), self.cur, self.tcx, &self.specs) + } + + pub fn get_lint_id_level( + id: LintId, + cur: HirId, + tcx: TyCtxt<'tcx>, + specs: &FxHashMap, + ) -> (Option, LintLevelSource) { + if let Some(&(level, src)) = specs.get(&id) { + return (Some(level), src); + } + let mut cur = cur; + + loop { + let parent = tcx.hir().get_parent_node(cur); + if cur == parent { + return (None, LintLevelSource::Default); + } + let specs = tcx.lint_levels_on(parent); + if let Some(&(level, src)) = specs.get(&id) { + return (Some(level), src); + } + cur = parent + } + } + + pub fn get_lint_level( + id: LintId, + cur: HirId, + tcx: TyCtxt<'tcx>, + specs: &FxHashMap, + ) -> (Level, LintLevelSource) { + let (level, mut src) = Self::get_lint_id_level(id, cur, tcx, specs); + let level = LintLevelSets::actual_level(level, &mut src, tcx.sess, id.lint, |id| { + Self::get_lint_id_level(id, cur, tcx, specs) + }); + (level, src) + } +} /// This struct represents a lint expectation and holds all required information /// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after @@ -261,11 +334,11 @@ pub fn explain_lint_level_source( )); } } - LintLevelSource::Node(lint_attr_name, src, reason) => { + LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => { if let Some(rationale) = reason { err.note(rationale.as_str()); } - err.span_note_once(src, "the lint level is defined here"); + err.span_note_once(span, "the lint level is defined here"); if lint_attr_name.as_str() != name { let level_str = level.as_str(); err.note_once(&format!( diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index cfc75f673c8f5..e24d5e09e0a96 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -266,10 +266,14 @@ rustc_queries! { separate_provide_extern } - query lint_levels(_: ()) -> LintLevelMap { + query lint_levels_on(key: HirId) -> FxHashMap { storage(ArenaCacheSelector<'tcx>) - eval_always - desc { "computing the lint levels for items in this crate" } + desc { |tcx| "looking up lint levels for `{}`", key } + } + + query lint_expectations(_: ()) -> Vec<(LintExpectationId, LintExpectation)> { + storage(ArenaCacheSelector<'tcx>) + desc { "computing `#[expect]`ed lints in this crate" } } query parent_module_from_def_id(key: LocalDefId) -> LocalDefId { diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 0a0f45ce1a0d2..76add26432c14 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -52,7 +52,7 @@ use rustc_query_system::ich::StableHashingContext; use rustc_serialize::opaque::{FileEncodeResult, FileEncoder}; use rustc_session::config::{CrateType, OutputFilenames}; use rustc_session::cstore::CrateStoreDyn; -use rustc_session::lint::{Level, Lint}; +use rustc_session::lint::{Level, Lint, LintId}; use rustc_session::Limit; use rustc_session::Session; use rustc_span::def_id::{DefPathHash, StableCrateId}; @@ -2833,19 +2833,16 @@ impl<'tcx> TyCtxt<'tcx> { pub fn lint_level_at_node( self, lint: &'static Lint, - mut id: hir::HirId, + id: hir::HirId, ) -> (Level, LintLevelSource) { - let sets = self.lint_levels(()); - loop { - if let Some(pair) = sets.level_and_source(lint, id, self.sess) { - return pair; - } - let next = self.hir().get_parent_node(id); - if next == id { - bug!("lint traversal reached the root of the crate"); - } - id = next; - } + let level_and_src = crate::lint::LintLevelQueryMap::get_lint_level( + LintId::of(lint), + id, + self, + self.lint_levels_on(id), + ); + debug!(?id, ?level_and_src); + level_and_src } /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`, diff --git a/compiler/rustc_middle/src/ty/query.rs b/compiler/rustc_middle/src/ty/query.rs index 2452bcf6a61b8..0741cde9959f5 100644 --- a/compiler/rustc_middle/src/ty/query.rs +++ b/compiler/rustc_middle/src/ty/query.rs @@ -1,6 +1,6 @@ use crate::dep_graph; use crate::infer::canonical::{self, Canonical}; -use crate::lint::LintLevelMap; +use crate::lint::{LevelAndSource, LintExpectation}; use crate::metadata::ModChild; use crate::middle::codegen_fn_attrs::CodegenFnAttrs; use crate::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo}; @@ -44,12 +44,14 @@ use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet, LocalDefId}; +use rustc_hir::hir_id::HirId; use rustc_hir::lang_items::{LangItem, LanguageItems}; use rustc_hir::{Crate, ItemLocalId, TraitCandidate}; use rustc_index::{bit_set::FiniteBitSet, vec::IndexVec}; use rustc_session::config::{EntryFnType, OptLevel, OutputFilenames, SymbolManglingVersion}; use rustc_session::cstore::{CrateDepKind, CrateSource}; use rustc_session::cstore::{ExternCrate, ForeignModule, LinkagePreference, NativeLib}; +use rustc_session::lint::{LintExpectationId, LintId}; use rustc_session::utils::NativeLibKind; use rustc_session::Limits; use rustc_span::symbol::Symbol; diff --git a/compiler/rustc_query_impl/src/keys.rs b/compiler/rustc_query_impl/src/keys.rs index 49175e97f4171..ea2d10cd14bf7 100644 --- a/compiler/rustc_query_impl/src/keys.rs +++ b/compiler/rustc_query_impl/src/keys.rs @@ -1,6 +1,7 @@ //! Defines the set of legal keys that can be used in queries. use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE}; +use rustc_hir::hir_id::HirId; use rustc_middle::infer::canonical::Canonical; use rustc_middle::mir; use rustc_middle::traits; @@ -543,3 +544,19 @@ impl<'tcx> Key for (Ty<'tcx>, ty::ValTree<'tcx>) { DUMMY_SP } } + +impl Key for HirId { + #[inline(always)] + fn query_crate_is_local(&self) -> bool { + true + } + + fn default_span(&self, tcx: TyCtxt<'_>) -> Span { + self.owner.default_span(tcx) + } + + #[inline(always)] + fn key_as_def_id(&self) -> Option { + Some(self.owner.to_def_id()) + } +} diff --git a/src/test/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr b/src/test/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr index 06befcbb5117e..5942fa8aeb4f0 100644 --- a/src/test/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr +++ b/src/test/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr @@ -1,11 +1,3 @@ -warning: denote infinite loops with `loop { ... }` - --> $DIR/force_warn_expected_lints_fulfilled.rs:10:5 - | -LL | while true { - | ^^^^^^^^^^ help: use `loop` - | - = note: requested on the command line with `--force-warn while-true` - warning: unused variable: `x` --> $DIR/force_warn_expected_lints_fulfilled.rs:20:9 | @@ -36,5 +28,13 @@ LL | let mut what_does_the_fox_say = "*ding* *deng* *dung*"; | = note: requested on the command line with `--force-warn unused-mut` +warning: denote infinite loops with `loop { ... }` + --> $DIR/force_warn_expected_lints_fulfilled.rs:10:5 + | +LL | while true { + | ^^^^^^^^^^ help: use `loop` + | + = note: requested on the command line with `--force-warn while-true` + warning: 5 warnings emitted