blob: 12c5748d5ecdf64ed860cbf359e2eb7a23083f4d [file]
use rustc_ast as ast;
use rustc_ast::{DUMMY_NODE_ID, NodeId};
use rustc_attr_parsing::AttributeParser;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_data_structures::unord::UnordSet;
use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, MultiSpan, msg};
use rustc_feature::{Features, GateIssue};
use rustc_hir::attrs::{LintAttribute, LintAttributeKind, LintInstance};
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{self as hir, HirId, Target, find_attr};
use rustc_index::IndexVec;
use rustc_middle::bug;
use rustc_middle::hir::nested_filter;
use rustc_middle::lint::{
LevelAndSource, LintExpectation, LintLevelSource, ShallowLintLevelMap, emit_lint_base,
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,
};
use rustc_session::lint::{
CheckLintNameResult, Level, Lint, LintExpectationId, LintId, TargetLint,
};
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
use tracing::{debug, instrument};
use crate::builtin::MISSING_DOCS;
use crate::context::LintStore;
use crate::errors::{
CheckNameUnknownTool, OverruledAttribute, OverruledAttributeSub, RequestedLevel,
UnsupportedGroup,
};
use crate::late::unerased_lint_store;
use crate::lints::{
DeprecatedLintNameFromCommandLine, OverruledAttributeLint, RemovedLintFromCommandLine,
RenamedLintFromCommandLine, RenamedLintSuggestion, UnknownLintFromCommandLine,
UnknownLintSuggestion,
};
const ALLOW_LISTED_ATTRS: &[Symbol] = &[
sym::allow,
sym::deny,
sym::expect,
sym::forbid,
sym::warn,
sym::automatically_derived,
sym::doc,
];
/// 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), 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);
let parsed_crate_attrs = AttributeParser::parse_limited_all_filtered(
sess,
crate_attrs,
ALLOW_LISTED_ATTRS,
Target::Crate,
DUMMY_SP,
DUMMY_NODE_ID,
Some(features),
rustc_attr_parsing::ShouldEmit::Nothing,
registered_tools,
);
builder.add(&parsed_crate_attrs, 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],
node_id: NodeId,
target_span: Span,
) -> BuilderPush {
let prev = self.provider.cur;
self.provider.cur =
self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev });
if !attrs.is_empty() {
let attrs = AttributeParser::parse_limited_all_filtered(
self.sess,
attrs,
ALLOW_LISTED_ATTRS,
Target::Fn,
target_span,
node_id,
Some(self.features),
rustc_attr_parsing::ShouldEmit::Nothing,
self.registered_tools,
);
self.add(&attrs, None);
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(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(name, _) => {
OverruledAttributeSub::CommandLineSource { id: name }
}
};
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 simple_add(
&mut self,
level: Level,
lint: &LintInstance,
reason: Option<Symbol>,
expect_lint_id: Option<LintExpectationId>,
) {
// If this function returns none, it means the attribute parser has already emitted appropriate errors
let src =
LintLevelSource::Node { name: lint.original_lint_name(), span: lint.span(), reason };
let id = match self.store.get_lint_by_name(lint.full_lint().as_str()) {
Some(TargetLint::Id(id)) => id,
None | Some(_) => bug!(
"guaranteed to find id due to previous parsing, happened while parsing {:?}",
lint,
),
};
if self.check_gated_lint(*id, lint.span(), false) {
self.insert_spec(*id, LevelAndSource { level, lint_id: expect_lint_id, src });
}
}
fn add(&mut self, attrs: &[hir::Attribute], source_hir_id: Option<HirId>) {
if find_attr!(attrs, AutomaticallyDerived(..)) {
self.insert(
LintId::of(SINGLE_USE_LIFETIMES),
LevelAndSource {
level: Level::Allow,
lint_id: None,
src: LintLevelSource::Default,
},
);
}
// `#[doc(hidden)]` disables missing_docs check.
if find_attr!(attrs, Doc(d) if d.hidden.is_some()) {
self.insert(
LintId::of(MISSING_DOCS),
LevelAndSource {
level: Level::Allow,
lint_id: None,
src: LintLevelSource::Default,
},
);
}
let Some(attrs) = find_attr!(attrs, LintAttributes(sub_attrs) => sub_attrs.into_iter())
else {
return;
};
for (attr_index, LintAttribute { reason, lint_instances, attr_id, kind, .. }) in
attrs.enumerate()
{
let attr_id = attr_id.attr_id;
let level = match kind {
LintAttributeKind::Allow => Level::Allow,
LintAttributeKind::Deny => Level::Deny,
LintAttributeKind::Forbid => Level::Forbid,
LintAttributeKind::Warn => Level::Warn,
LintAttributeKind::Expect => {
for lint in lint_instances {
let lint_index = lint.lint_index().try_into().unwrap();
let attr_index = attr_index.try_into().unwrap();
let expectation_id = match source_hir_id {
None => LintExpectationId::Unstable { attr_id, lint_index },
Some(hir_id) => LintExpectationId::Stable {
hir_id,
attr_id,
lint_index,
attr_index,
},
};
self.simple_add(Level::Expect, lint, *reason, Some(expectation_id));
let is_unfulfilled_lint_expectations =
lint.lint_name().as_str() == UNFULFILLED_LINT_EXPECTATIONS.name_lower();
self.provider.push_expectation(
expectation_id,
LintExpectation::new(
*reason,
lint.span(),
is_unfulfilled_lint_expectations,
lint.tool_name(),
),
);
}
continue;
}
};
for lint in lint_instances {
self.simple_add(level, lint, *reason, None);
}
}
}
/// 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;
};
struct UnknownLint<'a> {
sess: &'a Session,
lint_id: LintId,
feature: Symbol,
lint_from_cli: bool,
}
impl<'a, 'b> Diagnostic<'a, ()> for UnknownLint<'b> {
fn into_diag(
self,
dcx: DiagCtxtHandle<'a>,
level: rustc_errors::Level,
) -> Diag<'a, ()> {
let Self { sess, lint_id, feature, lint_from_cli } = self;
let mut lint = Diag::new(dcx, level, msg!("unknown lint: `{$name}`"))
.with_arg("name", lint_id.lint.name_lower())
.with_note(msg!("the `{$name}` lint is unstable"));
rustc_session::parse::add_feature_diagnostics_for_issue(
&mut lint,
sess,
feature,
GateIssue::Language,
lint_from_cli,
None,
);
lint
}
}
if self.lint_added_lints {
let lint = builtin::UNKNOWN_LINTS;
let level = self.lint_level(builtin::UNKNOWN_LINTS);
emit_lint_base(
self.sess,
lint,
level,
Some(span.into()),
UnknownLint { sess: &self.sess, lint_id, feature, lint_from_cli },
);
}
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.
#[track_caller]
pub(crate) fn opt_span_lint(
&self,
lint: &'static Lint,
span: Option<MultiSpan>,
decorator: impl for<'a> Diagnostic<'a, ()>,
) {
let level = self.lint_level(lint);
emit_lint_base(self.sess, lint, level, span, decorator)
}
#[track_caller]
pub fn emit_span_lint(
&self,
lint: &'static Lint,
span: MultiSpan,
decorator: impl for<'a> Diagnostic<'a, ()>,
) {
let level = self.lint_level(lint);
emit_lint_base(self.sess, lint, level, Some(span), decorator);
}
#[track_caller]
pub fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> Diagnostic<'a, ()>) {
let level = self.lint_level(lint);
emit_lint_base(self.sess, lint, level, None, decorator);
}
}
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),
}
}