use std::cmp;
use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_errors::{Diagnostic, DiagnosticId, LintDiagnosticBuilder, MultiSpan};
use rustc_hir::HirId;
+use rustc_index::vec::IndexVec;
+use rustc_query_system::ich::StableHashingContext;
use rustc_session::lint::{
builtin::{self, FORBIDDEN_LINT_GROUPS},
- FutureIncompatibilityReason, Level, Lint, LintId,
+ FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId,
};
use rustc_session::Session;
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 {
Default,
/// Lint level was set by an attribute.
- Node {
- name: Symbol,
- span: Span,
- /// RFC 2383 reason
- reason: Option<Symbol>,
- },
+ Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */),
/// Lint level was set by a command-line flag.
/// The provided `Level` is the level specified on the command line.
pub fn name(&self) -> Symbol {
match *self {
LintLevelSource::Default => symbol::kw::Default,
- LintLevelSource::Node { name, .. } => name,
+ LintLevelSource::Node(name, _, _) => name,
LintLevelSource::CommandLine(name, _) => name,
}
}
pub fn span(&self) -> Span {
match *self {
LintLevelSource::Default => DUMMY_SP,
- LintLevelSource::Node { span, .. } => span,
+ LintLevelSource::Node(_, span, _) => span,
LintLevelSource::CommandLine(_, _) => DUMMY_SP,
}
}
/// A tuple of a lint level and its source.
pub type LevelAndSource = (Level, LintLevelSource);
-/// Return type for the `shallow_lint_levels_on` query.
-///
-/// This map represents the set of allowed lints and allowance levels given
-/// by the attributes for *a single HirId*.
-#[derive(Default, Debug, HashStable)]
-pub struct ShallowLintLevelMap {
+#[derive(Debug, HashStable)]
+pub struct LintLevelSets {
+ pub list: IndexVec<LintStackIndex, LintSet>,
+ pub lint_cap: Level,
+}
+
+rustc_index::newtype_index! {
+ #[derive(HashStable)]
+ pub struct LintStackIndex {
+ const COMMAND_LINE = 0,
+ }
+}
+
+#[derive(Debug, HashStable)]
+pub struct LintSet {
+ // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
+ // flag.
pub specs: FxHashMap<LintId, LevelAndSource>,
+
+ pub parent: LintStackIndex,
}
-/// From an initial level and source, verify the effect of special annotations:
-/// `warnings` lint level and lint caps.
-///
-/// The return of this function is suitable for diagnostics.
-pub fn reveal_actual_level(
- level: Option<Level>,
- src: &mut LintLevelSource,
- sess: &Session,
- lint: LintId,
- probe_for_lint_level: impl FnOnce(LintId) -> (Option<Level>, LintLevelSource),
-) -> Level {
- // If `level` is none then we actually assume the default level for this lint.
- let mut level = level.unwrap_or_else(|| lint.lint.default_level(sess.edition()));
-
- // If we're about to issue a warning, check at the last minute for any
- // directives against the warnings "lint". If, for example, there's an
- // `allow(warnings)` in scope then we want to respect that instead.
- //
- // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically
- // triggers in cases (like #80988) where you have `forbid(warnings)`,
- // and so if we turned that into an error, it'd defeat the purpose of the
- // future compatibility warning.
- if level == Level::Warn && lint != LintId::of(FORBIDDEN_LINT_GROUPS) {
- let (warnings_level, warnings_src) = probe_for_lint_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;
+impl LintLevelSets {
+ pub fn new() -> Self {
+ LintLevelSets { list: IndexVec::new(), lint_cap: Level::Forbid }
+ }
+
+ pub fn get_lint_level(
+ &self,
+ lint: &'static Lint,
+ idx: LintStackIndex,
+ aux: Option<&FxHashMap<LintId, LevelAndSource>>,
+ sess: &Session,
+ ) -> LevelAndSource {
+ let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
+
+ // 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()));
+
+ // If we're about to issue a warning, check at the last minute for any
+ // directives against the warnings "lint". If, for example, there's an
+ // `allow(warnings)` in scope then we want to respect that instead.
+ //
+ // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically
+ // triggers in cases (like #80988) where you have `forbid(warnings)`,
+ // 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);
+ if let Some(configured_warning_level) = warnings_level {
+ if configured_warning_level != Level::Warn {
+ level = configured_warning_level;
+ src = warnings_src;
+ }
}
}
- }
- // Ensure that we never exceed the `--cap-lints` argument unless the source is a --force-warn
- level = if let LintLevelSource::CommandLine(_, Level::ForceWarn(_)) = src {
- level
- } else {
- cmp::min(level, sess.opts.lint_cap.unwrap_or(Level::Forbid))
- };
+ // Ensure that we never exceed the `--cap-lints` argument
+ // unless the source is a --force-warn
+ level = if let LintLevelSource::CommandLine(_, Level::ForceWarn(_)) = src {
+ level
+ } else {
+ cmp::min(level, self.lint_cap)
+ };
+
+ if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
+ // Ensure that we never exceed driver level.
+ level = cmp::min(*driver_level, level);
+ }
- if let Some(driver_level) = sess.driver_lint_caps.get(&lint) {
- // Ensure that we never exceed driver level.
- level = cmp::min(*driver_level, level);
+ (level, src)
}
- level
-}
-
-impl ShallowLintLevelMap {
- /// Perform a deep probe in the HIR tree looking for the actual level for the lint.
- /// This lint level is not usable for diagnostics, it needs to be corrected by
- /// `reveal_actual_level` beforehand.
- fn probe_for_lint_level(
+ pub fn get_lint_id_level(
&self,
- tcx: TyCtxt<'_>,
id: LintId,
- start: HirId,
+ mut idx: LintStackIndex,
+ aux: Option<&FxHashMap<LintId, LevelAndSource>>,
) -> (Option<Level>, LintLevelSource) {
- if let Some(&(level, src)) = self.specs.get(&id) {
- return (Some(level), src);
+ if let Some(specs) = aux {
+ if let Some(&(level, src)) = specs.get(&id) {
+ return (Some(level), src);
+ }
}
-
- for parent in tcx.hir().parent_id_iter(start) {
- let specs = tcx.shallow_lint_levels_on(parent);
- if let Some(&(level, src)) = specs.specs.get(&id) {
+ 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;
}
- (None, LintLevelSource::Default)
}
+}
+
+#[derive(Debug)]
+pub struct LintLevelMap {
+ /// This is a collection of lint expectations as described in RFC 2383, that
+ /// can be fulfilled during this compilation session. This means that at least
+ /// one expected lint is currently registered in the lint store.
+ ///
+ /// The [`LintExpectationId`] is stored as a part of the [`Expect`](Level::Expect)
+ /// lint level.
+ pub lint_expectations: Vec<(LintExpectationId, LintExpectation)>,
+ pub sets: LintLevelSets,
+ pub id_to_set: FxHashMap<HirId, LintStackIndex>,
+}
- /// Fetch and return the user-visible lint level for the given lint at the given HirId.
- pub fn lint_level_id_at_node(
+impl LintLevelMap {
+ /// If the `id` was previously registered with `register_id` when building
+ /// this `LintLevelMap` this returns the corresponding lint level and source
+ /// of the lint level for the lint provided.
+ ///
+ /// If the `id` was not previously registered, returns `None`. If `None` is
+ /// returned then the parent of `id` should be acquired and this function
+ /// should be called again.
+ pub fn level_and_source(
&self,
- tcx: TyCtxt<'_>,
- lint: LintId,
+ lint: &'static Lint,
id: HirId,
- ) -> (Level, LintLevelSource) {
- let (level, mut src) = self.probe_for_lint_level(tcx, lint, id);
- let level = reveal_actual_level(level, &mut src, tcx.sess, lint, |lint| {
- self.probe_for_lint_level(tcx, lint, id)
- });
- debug!(?id, ?level, ?src);
- (level, src)
+ session: &Session,
+ ) -> Option<LevelAndSource> {
+ self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session))
}
}
-impl TyCtxt<'_> {
- /// Fetch and return the user-visible lint level for the given lint at the given HirId.
- pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> (Level, LintLevelSource) {
- self.shallow_lint_levels_on(id).lint_level_id_at_node(self, LintId::of(lint), id)
- }
+impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
+ #[inline]
+ fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
+ let LintLevelMap { ref sets, ref id_to_set, ref lint_expectations } = *self;
- /// Walks upwards from `id` to find a node which might change lint levels with attributes.
- /// It stops at `bound` and just returns it if reached.
- pub fn maybe_lint_level_root_bounded(self, mut id: HirId, bound: HirId) -> HirId {
- let hir = self.hir();
- while id != bound && self.shallow_lint_levels_on(id).specs.is_empty() {
- id = hir.get_parent_node(id)
- }
- id
+ id_to_set.hash_stable(hcx, hasher);
+ lint_expectations.hash_stable(hcx, hasher);
+
+ hcx.while_hashing_spans(true, |hcx| sets.hash_stable(hcx, hasher))
}
}
));
}
}
- LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => {
+ LintLevelSource::Node(lint_attr_name, src, reason) => {
if let Some(rationale) = reason {
err.note(rationale.as_str());
}
- err.span_note_once(span, "the lint level is defined here");
+ err.span_note_once(src, "the lint level is defined here");
if lint_attr_name.as_str() != name {
let level_str = level.as_str();
err.note_once(&format!(