summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_lint/src/levels.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--compiler/rustc_lint/src/levels.rs957
1 files changed, 605 insertions, 352 deletions
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
index 1e16ac51e..db0a3419e 100644
--- a/compiler/rustc_lint/src/levels.rs
+++ b/compiler/rustc_lint/src/levels.rs
@@ -3,13 +3,15 @@ use crate::late::unerased_lint_store;
use rustc_ast as ast;
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{Applicability, Diagnostic, LintDiagnosticBuilder, MultiSpan};
+use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage, MultiSpan};
use rustc_hir as hir;
-use rustc_hir::{intravisit, HirId};
+use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::HirId;
+use rustc_index::vec::IndexVec;
use rustc_middle::hir::nested_filter;
use rustc_middle::lint::{
- struct_lint_level, LevelAndSource, LintExpectation, LintLevelMap, LintLevelSets,
- LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE,
+ reveal_actual_level, struct_lint_level, LevelAndSource, LintExpectation, LintLevelSource,
+ ShallowLintLevelMap,
};
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{RegisteredTools, TyCtxt};
@@ -27,47 +29,408 @@ use crate::errors::{
UnknownToolInScopedLint,
};
-fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap {
- 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();
+/// 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 {
+ ENCODABLE = custom, // we don't need encoding
+ 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: FxHashMap<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<&FxHashMap<LintId, LevelAndSource>>,
+ sess: &Session,
+ ) -> LevelAndSource {
+ let lint = LintId::of(lint);
+ let (level, mut src) = self.raw_lint_id_level(lint, idx, aux);
+ let level = reveal_actual_level(level, &mut src, sess, lint, |id| {
+ self.raw_lint_id_level(id, idx, aux)
+ });
+ (level, src)
+ }
- builder.levels.id_to_set.reserve(krate.owners.len() + 1);
+ fn raw_lint_id_level(
+ &self,
+ id: LintId,
+ mut idx: LintStackIndex,
+ aux: Option<&FxHashMap<LintId, LevelAndSource>>,
+ ) -> (Option<Level>, LintLevelSource) {
+ if let Some(specs) = aux {
+ if let Some(&(level, src)) = specs.get(&id) {
+ return (Some(level), src);
+ }
+ }
+ loop {
+ let LintSet { ref specs, parent } = self.list[idx];
+ if let Some(&(level, src)) = specs.get(&id) {
+ return (Some(level), src);
+ }
+ if idx == COMMAND_LINE {
+ return (None, LintLevelSource::Default);
+ }
+ idx = parent;
+ }
+ }
+}
- let push =
- builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true, Some(hir::CRATE_HIR_ID));
+fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExpectation)> {
+ let store = unerased_lint_store(tcx);
- builder.levels.register_id(hir::CRATE_HIR_ID);
+ let mut builder = LintLevelsBuilder {
+ sess: tcx.sess,
+ provider: QueryMapExpectationsWrapper {
+ tcx,
+ cur: hir::CRATE_HIR_ID,
+ specs: ShallowLintLevelMap::default(),
+ expectations: Vec::new(),
+ unstable_to_stable_ids: FxHashMap::default(),
+ empty: 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);
- builder.levels.pop(push);
- builder.levels.update_unstable_expectation_ids();
- builder.levels.build_map()
+ tcx.sess.diagnostic().update_unstable_expectation_id(&builder.provider.unstable_to_stable_ids);
+
+ builder.provider.expectations
}
-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<LintExpectationId, LintExpectationId>,
+#[instrument(level = "trace", skip(tcx), ret)]
+fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLevelMap {
+ let store = unerased_lint_store(tcx);
+ let attrs = tcx.hir_attrs(owner);
+
+ let mut levels = LintLevelsBuilder {
+ sess: tcx.sess,
+ provider: LintLevelQueryMap {
+ tcx,
+ cur: owner.into(),
+ specs: ShallowLintLevelMap::default(),
+ empty: FxHashMap::default(),
+ attrs,
+ },
+ warn_about_weird_lints: false,
+ store,
+ registered_tools: &tcx.resolutions(()).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: *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().expect_owner(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)
+ }
+ },
+ }
+
+ 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,
- id_to_set: FxHashMap<HirId, LintStackIndex>,
cur: LintStackIndex,
+}
+
+pub trait LintLevelsProvider {
+ fn current_specs(&self) -> &FxHashMap<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) -> &FxHashMap<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)
+ }
+}
+
+struct LintLevelQueryMap<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ cur: HirId,
+ specs: ShallowLintLevelMap,
+ /// Empty hash map to simplify code.
+ empty: FxHashMap<LintId, LevelAndSource>,
+ attrs: &'tcx hir::AttributeMap<'tcx>,
+}
+
+impl LintLevelsProvider for LintLevelQueryMap<'_> {
+ fn current_specs(&self) -> &FxHashMap<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)
+ }
+}
+
+struct QueryMapExpectationsWrapper<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ cur: HirId,
+ specs: ShallowLintLevelMap,
+ expectations: Vec<(LintExpectationId, LintExpectation)>,
+ unstable_to_stable_ids: FxHashMap<LintExpectationId, LintExpectationId>,
+ /// Empty hash map to simplify code.
+ empty: FxHashMap<LintId, LevelAndSource>,
+}
+
+impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> {
+ fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+ self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty)
+ }
+ fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
+ let specs = self.specs.specs.get_mut_or_insert_default(self.cur.local_id);
+ specs.clear();
+ specs.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) {
+ 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<'_, 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 nested_visit_map(&mut self) -> Self::Map {
+ self.provider.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);
+ }
+}
+
+impl<'tcx> LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> {
+ fn add_id(&mut self, hir_id: HirId) {
+ self.provider.cur = hir_id;
+ self.add(self.provider.tcx.hir().attrs(hir_id), hir_id == hir::CRATE_HIR_ID, Some(hir_id));
+ }
+}
+
+impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> {
+ type NestedFilter = nested_filter::All;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.provider.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,
}
-pub struct BuilderPush {
+pub(crate) struct BuilderPush {
prev: LintStackIndex,
- pub changed: bool,
}
-impl<'s> LintLevelsBuilder<'s> {
- pub fn new(
+impl<'s> LintLevelsBuilder<'s, TopDown> {
+ pub(crate) fn new(
sess: &'s Session,
warn_about_weird_lints: bool,
store: &'s LintStore,
@@ -75,20 +438,74 @@ 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<HirId>,
+ ) -> 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 }
+ }
+
+ /// 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
}
@@ -98,24 +515,20 @@ impl<'s> LintLevelsBuilder<'s> {
}
fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
- &self.sets.list[self.cur].specs
+ self.provider.current_specs()
}
- fn current_specs_mut(&mut self) -> &mut FxHashMap<LintId, LevelAndSource> {
- &mut self.sets.list[self.cur].specs
+ fn insert(&mut self, id: LintId, lvl: LevelAndSource) {
+ self.provider.insert(id, lvl)
}
- 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
};
@@ -129,7 +542,7 @@ impl<'s> LintLevelsBuilder<'s> {
if self.check_gated_lint(id, DUMMY_SP) {
let src = LintLevelSource::CommandLine(lint_flag_val, orig_level);
- self.current_specs_mut().insert(id, (level, src));
+ self.insert(id, (level, src));
}
}
}
@@ -138,9 +551,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`.
@@ -158,7 +573,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!(
@@ -178,8 +593,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());
}
@@ -199,11 +614,8 @@ impl<'s> LintLevelsBuilder<'s> {
LintLevelSource::Default => {
OverruledAttributeSub::DefaultSource { id: id.to_string() }
}
- LintLevelSource::Node(_, forbid_source_span, reason) => {
- OverruledAttributeSub::NodeSource {
- span: forbid_source_span,
- reason,
- }
+ LintLevelSource::Node { span, reason, .. } => {
+ OverruledAttributeSub::NodeSource { span, reason }
}
LintLevelSource::CommandLine(_, _) => {
OverruledAttributeSub::CommandLineSource
@@ -214,14 +626,14 @@ impl<'s> LintLevelsBuilder<'s> {
self.struct_lint(
FORBIDDEN_LINT_GROUPS,
Some(src.span().into()),
- |diag_builder| {
- let mut diag_builder = diag_builder.build(&format!(
- "{}({}) incompatible with previous forbid",
- level.as_str(),
- src.name(),
- ));
- decorate_diag(&mut diag_builder);
- diag_builder.emit();
+ format!(
+ "{}({}) incompatible with previous forbid",
+ level.as_str(),
+ src.name(),
+ ),
+ |lint| {
+ decorate_diag(lint);
+ lint
},
);
}
@@ -244,45 +656,21 @@ impl<'s> LintLevelsBuilder<'s> {
match (old_level, level) {
// If the new level is an expectation store it in `ForceWarn`
- (Level::ForceWarn(_), Level::Expect(expectation_id)) => self
- .current_specs_mut()
- .insert(id, (Level::ForceWarn(Some(expectation_id)), old_src)),
- // Keep `ForceWarn` level but drop the expectation
- (Level::ForceWarn(_), _) => {
- self.current_specs_mut().insert(id, (Level::ForceWarn(None), old_src))
+ (Level::ForceWarn(_), Level::Expect(expectation_id)) => {
+ self.insert(id, (Level::ForceWarn(Some(expectation_id)), old_src))
}
+ // Keep `ForceWarn` level but drop the expectation
+ (Level::ForceWarn(_), _) => self.insert(id, (Level::ForceWarn(None), old_src)),
// Set the lint level as normal
- _ => self.current_specs_mut().insert(id, (level, src)),
+ _ => self.insert(id, (level, src)),
};
}
- /// 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.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<HirId>) {
let sess = self.sess;
for (attr_index, attr) in attrs.iter().enumerate() {
if attr.has_name(sym::automatically_derived) {
- self.current_specs_mut().insert(
+ self.insert(
LintId::of(SINGLE_USE_LIFETIMES),
(Level::Allow, LintLevelSource::Default),
);
@@ -293,7 +681,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)
}
@@ -408,7 +806,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,
@@ -416,13 +814,19 @@ 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,
- );
+ };
for &id in *ids {
if self.check_gated_lint(id, attr.span) {
self.insert_spec(id, (level, src));
@@ -435,67 +839,60 @@ 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,
- );
+ };
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),
- ));
+ );
}
}
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,
lvl,
src,
Some(sp.into()),
+ format!(
+ "lint name `{}` is deprecated \
+ and may not have an effect in the future.",
+ name
+ ),
|lint| {
- let msg = format!(
- "lint name `{}` is deprecated \
- and may not have an effect in the future.",
- name
- );
- lint.build(&msg)
- .span_suggestion(
- sp,
- "change it to",
- new_lint_name,
- Applicability::MachineApplicable,
- )
- .emit();
+ lint.span_suggestion(
+ sp,
+ "change it to",
+ new_lint_name,
+ Applicability::MachineApplicable,
+ )
},
);
- let src = LintLevelSource::Node(
- Symbol::intern(&new_lint_name),
- sp,
+ let src = LintLevelSource::Node {
+ name: Symbol::intern(&new_lint_name),
+ span: sp,
reason,
- );
+ };
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, _)) => {
@@ -521,57 +918,54 @@ 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,
renamed_lint_level,
src,
Some(sp.into()),
+ msg,
|lint| {
- let mut err = lint.build(msg);
if let Some(new_name) = &renamed {
- err.span_suggestion(
+ lint.span_suggestion(
sp,
"use the new name",
new_name,
Applicability::MachineApplicable,
);
}
- err.emit();
+ lint
},
);
}
CheckLintNameResult::NoLint(suggestion) => {
let lint = builtin::UNKNOWN_LINTS;
- let (level, src) = self.sets.get_lint_level(
- lint,
- self.cur,
- Some(self.current_specs()),
+ let (level, src) = self.provider.get_lint_level(lint, self.sess);
+ let name = if let Some(tool_ident) = tool_ident {
+ format!("{}::{}", tool_ident.name, name)
+ } else {
+ name.to_string()
+ };
+ struct_lint_level(
self.sess,
+ lint,
+ level,
+ src,
+ Some(sp.into()),
+ format!("unknown lint: `{}`", name),
+ |lint| {
+ if let Some(suggestion) = suggestion {
+ lint.span_suggestion(
+ sp,
+ "did you mean",
+ suggestion,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ lint
+ },
);
- 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)
- } else {
- name.to_string()
- };
- let mut db = lint.build(format!("unknown lint: `{}`", name));
- if let Some(suggestion) = suggestion {
- db.span_suggestion(
- sp,
- "did you mean",
- suggestion,
- Applicability::MachineApplicable,
- );
- }
- db.emit();
- });
}
}
// If this lint was renamed, apply the new lint instead of ignoring the attribute.
@@ -583,17 +977,21 @@ 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,
+ };
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);
@@ -608,232 +1006,87 @@ 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,
lint_level,
lint_src,
Some(lint_attr_span.into()),
- |lint| {
- let mut db = lint.build(&format!(
- "{}({}) is ignored unless specified at crate level",
- level.as_str(),
- lint_attr_name
- ));
- db.emit();
- },
+ format!(
+ "{}({}) is ignored unless specified at crate level",
+ level.as_str(),
+ lint_attr_name
+ ),
+ |lint| lint,
);
// don't set a separate error for every lint in the group
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.
///
/// Returns `true` if the lint's feature is enabled.
+ // FIXME only emit this once for each attribute, instead of repeating it 4 times for
+ // pre-expansion lints, post-expansion lints, `shallow_lint_levels_on` and `lint_expectations`.
fn check_gated_lint(&self, lint_id: LintId, span: Span) -> bool {
if let Some(feature) = lint_id.lint.feature_gate {
if !self.sess.features_untracked().enabled(feature) {
let lint = builtin::UNKNOWN_LINTS;
let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
- struct_lint_level(self.sess, lint, level, src, Some(span.into()), |lint_db| {
- let mut db =
- lint_db.build(&format!("unknown lint: `{}`", lint_id.lint.name_lower()));
- db.note(&format!("the `{}` lint is unstable", lint_id.lint.name_lower(),));
- add_feature_diagnostics(&mut db, &self.sess.parse_sess, feature);
- db.emit();
- });
+ struct_lint_level(
+ self.sess,
+ lint,
+ level,
+ src,
+ Some(span.into()),
+ format!("unknown lint: `{}`", lint_id.lint.name_lower()),
+ |lint| {
+ lint.note(
+ &format!("the `{}` lint is unstable", lint_id.lint.name_lower(),),
+ );
+ add_feature_diagnostics(lint, &self.sess.parse_sess, feature);
+ lint
+ },
+ );
return false;
}
}
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
/// this lint context.
- pub fn struct_lint(
+ ///
+ /// Return value of the `decorate` closure is ignored, see [`struct_lint_level`] for a detailed explanation.
+ ///
+ /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature
+ pub(crate) fn struct_lint(
&self,
lint: &'static Lint,
span: Option<MultiSpan>,
- decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+ msg: impl Into<DiagnosticMessage>,
+ decorate: impl for<'a, 'b> FnOnce(
+ &'b mut DiagnosticBuilder<'a, ()>,
+ ) -> &'b mut DiagnosticBuilder<'a, ()>,
) {
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<F>(&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);
- });
+ struct_lint_level(self.sess, lint, level, src, span, msg, decorate)
}
}
-pub fn provide(providers: &mut Providers) {
- providers.lint_levels = lint_levels;
+pub(crate) fn provide(providers: &mut Providers) {
+ *providers = Providers { shallow_lint_levels_on, lint_expectations, ..*providers };
}