Auto merge of #125116 - blyxyas:ignore-allowed-lints-final, r=cjgillot

(Big performance change) Do not run lints that cannot emit

Before this change, adding a lint was a difficult matter because it always had some overhead involved. This was because all lints would run, no matter their default level, or if the user had `#![allow]`ed them. This PR changes that. This change would improve both the Rust lint infrastructure and Clippy, but Clippy will see the most benefit, as it has about 900 registered lints (and growing!)

So yeah, with this little patch we filter all lints pre-linting, and remove any lint that is either:
- Manually `#![allow]`ed in the whole crate,
- Allowed in the command line, or
- Not manually enabled with `#[warn]` or similar, and its default level is `Allow`

As some lints **need** to run, this PR also adds **loadbearing lints**. On a lint declaration, you can use the ``@eval_always` = true` marker to label it as loadbearing. A loadbearing lint will never be filtered (it will always run)

Fixes #106983
This commit is contained in:
bors
2024-10-26 16:37:43 +00:00
45 changed files with 291 additions and 56 deletions

View File

@@ -1,9 +1,9 @@
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_errors::{Diag, LintDiagnostic, MultiSpan};
use rustc_feature::{Features, GateIssue};
use rustc_hir::HirId;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{CRATE_HIR_ID, HirId};
use rustc_index::IndexVec;
use rustc_middle::bug;
use rustc_middle::hir::nested_filter;
@@ -115,6 +115,38 @@ impl LintLevelSets {
}
}
fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet<LintId> {
let store = unerased_lint_store(&tcx.sess);
let map = tcx.shallow_lint_levels_on(rustc_hir::CRATE_OWNER_ID);
let dont_need_to_run: FxIndexSet<LintId> = store
.get_lints()
.into_iter()
.filter_map(|lint| {
if !lint.eval_always {
let lint_level = map.lint_level_id_at_node(tcx, LintId::of(lint), CRATE_HIR_ID);
if matches!(lint_level, (Level::Allow, ..))
|| (matches!(lint_level, (.., LintLevelSource::Default)))
&& lint.default_level(tcx.sess.edition()) == Level::Allow
{
Some(LintId::of(lint))
} else {
None
}
} else {
None
}
})
.collect();
let mut visitor = LintLevelMaximum { tcx, dont_need_to_run };
visitor.process_opts();
tcx.hir().walk_attributes(&mut visitor);
visitor.dont_need_to_run
}
#[instrument(level = "trace", skip(tcx), ret)]
fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLevelMap {
let store = unerased_lint_store(tcx.sess);
@@ -301,6 +333,83 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
}
}
/// Visitor with the only function of visiting every item-like in a crate and
/// computing the highest level that every lint gets put to.
///
/// E.g., if a crate has a global #![allow(lint)] attribute, but a single item
/// uses #[warn(lint)], this visitor will set that lint level as `Warn`
struct LintLevelMaximum<'tcx> {
tcx: TyCtxt<'tcx>,
/// The actual list of detected lints.
dont_need_to_run: FxIndexSet<LintId>,
}
impl<'tcx> LintLevelMaximum<'tcx> {
fn process_opts(&mut self) {
let store = unerased_lint_store(self.tcx.sess);
for (lint_group, level) in &self.tcx.sess.opts.lint_opts {
if *level != Level::Allow {
let Ok(lints) = store.find_lints(lint_group) else {
return;
};
for lint in lints {
self.dont_need_to_run.swap_remove(&lint);
}
}
}
}
}
impl<'tcx> Visitor<'tcx> for LintLevelMaximum<'tcx> {
type NestedFilter = nested_filter::All;
fn nested_visit_map(&mut self) -> Self::Map {
self.tcx.hir()
}
/// FIXME(blyxyas): In a future revision, we should also graph #![allow]s,
/// but that is handled with more care
fn visit_attribute(&mut self, attribute: &'tcx ast::Attribute) {
if matches!(
Level::from_attr(attribute),
Some(
Level::Warn
| Level::Deny
| Level::Forbid
| Level::Expect(..)
| Level::ForceWarn(..),
)
) {
let store = unerased_lint_store(self.tcx.sess);
let Some(meta) = attribute.meta() else { return };
// Lint attributes are always a metalist inside a
// metalist (even with just one lint).
let Some(meta_item_list) = meta.meta_item_list() else { return };
for meta_list in meta_item_list {
// Convert Path to String
let Some(meta_item) = meta_list.meta_item() else { return };
let ident: &str = &meta_item
.path
.segments
.iter()
.map(|segment| segment.ident.as_str())
.collect::<Vec<&str>>()
.join("::");
let Ok(lints) = store.find_lints(
// Lint attributes can only have literals
ident,
) else {
return;
};
for lint in lints {
self.dont_need_to_run.swap_remove(&lint);
}
}
}
}
}
pub struct LintLevelsBuilder<'s, P> {
sess: &'s Session,
features: &'s Features,
@@ -934,7 +1043,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
}
pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers { shallow_lint_levels_on, ..*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) {