Skip to content

Commit d5ef3a7

Browse files
committed
marramiau miau miau 🎉
1 parent ab8f98e commit d5ef3a7

File tree

4 files changed

+166
-51
lines changed

4 files changed

+166
-51
lines changed

compiler/rustc_lint/src/builtin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ use rustc_trait_selection::traits::{self, misc::type_allowed_to_implement_copy};
7373

7474
use crate::nonstandard_style::{method_context, MethodLateContext};
7575

76-
use std::fmt::Write;
7776
use std::default::Default;
77+
use std::fmt::Write;
7878

7979
// hardwired lints from rustc_lint_defs
8080
pub use rustc_session::lint::builtin::*;

compiler/rustc_lint/src/late.rs

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
//! upon. As the ast is traversed, this keeps track of the current lint level
1515
//! for all lint attributes.
1616
17-
use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore, LintId};
17+
use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore};
1818
use rustc_data_structures::stack::ensure_sufficient_stack;
1919
use rustc_data_structures::sync::{join, Lrc};
2020
use rustc_hir as hir;
@@ -25,7 +25,7 @@ use rustc_middle::hir::nested_filter;
2525
use rustc_middle::ty::{self, TyCtxt};
2626
use rustc_session::lint::LintPass;
2727
use rustc_session::Session;
28-
use rustc_span::Span;
28+
use rustc_span::{Span, Symbol};
2929

3030
use std::any::Any;
3131
use std::cell::Cell;
@@ -363,21 +363,43 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>(
363363
// Note: `passes` is often empty. In that case, it's faster to run
364364
// `builtin_lints` directly rather than bundling it up into the
365365
// `RuntimeCombinedLateLintPass`.
366-
let store = unerased_lint_store(tcx.sess);
367-
368-
if store.late_module_passes.is_empty() {
369-
late_lint_mod_inner(tcx, module_def_id, context, builtin_lints);
370-
} else {
371-
let passes: Vec<_> = store
372-
.late_module_passes
373-
.iter()
374-
.map(|mk_pass| (mk_pass)(tcx))
366+
let store = unerased_lint_store(tcx.sess);
367+
368+
if store.late_module_passes.is_empty() {
369+
late_lint_mod_inner(tcx, module_def_id, context, builtin_lints);
370+
} else {
371+
let passes: Vec<_> =
372+
store.late_module_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect();
373+
374+
// Filter unused lints
375+
let (lints_to_emit, lints_allowed) = &**tcx.lints_that_can_emit(());
376+
// let lints_to_emit = &lints_that_can_emit.0;
377+
// let lints_allowed = &lints_that_can_emit.1;
378+
let passes_lints: Vec<_> = passes.iter().map(|pass| LintPass::get_lints(pass)).collect();
379+
380+
// Now, we'll filtered passes in a way that discards any lint that
381+
let mut filtered_passes: Vec<Box<dyn LateLintPass<'tcx>>> = passes
382+
.into_iter()
383+
.enumerate()
384+
.filter_map(|(i, pass)| {
385+
if passes_lints[i].iter().any(|&lint| {
386+
let symbol = Symbol::intern(lint.name);
387+
// ^^^ Expensive, but more expensive would be having to
388+
// cast every element to &str
389+
390+
lints_to_emit.contains(&symbol)
391+
|| (!lints_allowed.contains(&symbol)
392+
&& lint.default_level > crate::Level::Allow)
393+
}) {
394+
Some(pass)
395+
} else {
396+
None
397+
}
398+
})
375399
.collect();
376-
let emittable_lints = tcx.lints_that_can_emit(());
377-
let mut filtered_passes: Vec<Box<dyn LateLintPass<'tcx>>> = passes.into_iter().filter(|pass| {
378-
LintPass::get_lints(pass).iter().any(|&lint| emittable_lints.contains(&LintId::of(lint)))
379-
}).collect();
380-
filtered_passes.push(Box::new(builtin_lints));
400+
401+
filtered_passes.push(Box::new(builtin_lints));
402+
381403
let pass = RuntimeCombinedLateLintPass { passes: &mut filtered_passes[..] };
382404
late_lint_mod_inner(tcx, module_def_id, context, pass);
383405
}
@@ -427,11 +449,36 @@ fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) {
427449
only_module: false,
428450
};
429451

430-
let hashmap = tcx.lints_that_can_emit(());
431-
432-
let mut filtered_passes: Vec<Box<dyn LateLintPass<'tcx>>> = passes.into_iter().filter(|pass| {
433-
LintPass::get_lints(pass).iter().any(|&lint| hashmap.contains(&LintId::of(lint)))
434-
}).collect();
452+
let (lints_to_emit, lints_allowed) = &**tcx.lints_that_can_emit(());
453+
// let lints_to_emit = &lints_that_can_emit.0;
454+
// let lints_allowed = &lints_that_can_emit.1;
455+
let passes_lints: Vec<_> = passes.iter().map(|pass| LintPass::get_lints(pass)).collect();
456+
457+
// Now, we'll filtered passes in a way that discards any lint that
458+
let mut filtered_passes: Vec<Box<dyn LateLintPass<'tcx>>> = passes
459+
.into_iter()
460+
.enumerate()
461+
.filter_map(|(i, pass)| {
462+
if passes_lints[i].iter().any(|&lint| {
463+
let symbol = Symbol::intern(
464+
&lint.name.to_lowercase()
465+
// Doing some calculations here to account for those separators
466+
[lint.name.find("::").unwrap_or(lint.name.len() - 2) + 2..],
467+
);
468+
469+
// ^^^ Expensive, but more expensive would be having to
470+
// cast every element to &str
471+
472+
lints_to_emit.contains(&symbol)
473+
|| (!lints_allowed.contains(&symbol)
474+
&& lint.default_level > crate::Level::Allow)
475+
}) {
476+
Some(pass)
477+
} else {
478+
None
479+
}
480+
})
481+
.collect();
435482

436483
// filtered_passes may be empty in case of `#[allow(all)]`
437484
if filtered_passes.is_empty() {

compiler/rustc_lint/src/levels.rs

Lines changed: 95 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
};
1616
use rustc_ast as ast;
1717
use rustc_ast_pretty::pprust;
18-
use rustc_data_structures::fx::FxIndexMap;
18+
use rustc_data_structures::{fx::FxIndexMap, sync::Lrc};
1919
use rustc_errors::{Diag, DiagMessage, LintDiagnostic, MultiSpan};
2020
use rustc_feature::{Features, GateIssue};
2121
use rustc_hir as hir;
@@ -153,30 +153,18 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp
153153

154154
/// Walk the whole crate collecting nodes where lint levels change
155155
/// (e.g. `#[allow]` attributes), and joins that list with the warn-by-default
156-
/// (and not allowed in the crate) and CLI lints. The final result is a builder
157-
/// that has information about just lints that can be emitted (leaving out
158-
/// globally-allowed lints)
159-
pub fn lints_that_can_emit(
160-
tcx: TyCtxt<'_>,
161-
(): ()
162-
) -> Vec<LintId> {
163-
let store = unerased_lint_store(&tcx.sess);
164-
165-
let specs = tcx.shallow_lint_levels_on(hir::CRATE_HIR_ID.owner);
166-
let lints = store.get_lints();
167-
168-
let mut hashmap: Vec<LintId> = Vec::new();
169-
hashmap.reserve((lints.len() >> 1) * usize::from(tcx.sess.opts.lint_cap.is_some())); // Avoid allocations
170-
171-
for &lint in lints {
172-
let lint_id = LintId::of(lint);
173-
let actual_level = specs.probe_for_lint_level(tcx, lint_id, hir::CRATE_HIR_ID).0.unwrap_or(lint.default_level);
174-
if actual_level > Level::Allow {
175-
hashmap.push(lint_id);
176-
}
177-
}
178-
179-
hashmap
156+
/// (and not allowed in the crate) and CLI lints. The returned value is a tuple
157+
/// of 1. The lints that will emit (or at least, should run), and 2.
158+
/// The lints that are allowed at the crate level and will not emit.
159+
pub fn lints_that_can_emit(tcx: TyCtxt<'_>, (): ()) -> Lrc<(Vec<Symbol>, Vec<Symbol>)> {
160+
// builder.add_command_line();
161+
// builder.add_id(hir::CRATE_HIR_ID);
162+
163+
let mut visitor = LintLevelMinimumVisitor::new(tcx);
164+
visitor.process_opts();
165+
tcx.hir().walk_attributes(&mut visitor);
166+
167+
Lrc::new((visitor.lints_to_emit, visitor.lints_allowed))
180168
}
181169

182170
#[instrument(level = "trace", skip(tcx), ret)]
@@ -478,6 +466,86 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'
478466
}
479467
}
480468

469+
/// Visitor with the only function of visiting every item-like in a crate and
470+
/// computing the highest level that every lint gets put to.
471+
///
472+
/// E.g., if a crate has a global #![allow(lint)] attribute, but a single item
473+
/// uses #[warn(lint)], this visitor will set that lint level as `Warn`
474+
struct LintLevelMinimumVisitor<'tcx> {
475+
tcx: TyCtxt<'tcx>,
476+
/// The actual list of detected lints.
477+
lints_to_emit: Vec<Symbol>,
478+
lints_allowed: Vec<Symbol>,
479+
}
480+
481+
impl<'tcx> LintLevelMinimumVisitor<'tcx> {
482+
pub fn new(tcx: TyCtxt<'tcx>) -> Self {
483+
Self {
484+
tcx,
485+
// That magic number is the current number of lints + some more for possible future lints
486+
lints_to_emit: Vec::with_capacity(230),
487+
lints_allowed: Vec::with_capacity(100),
488+
}
489+
}
490+
491+
fn process_opts(&mut self) {
492+
for (lint, level) in &self.tcx.sess.opts.lint_opts {
493+
if *level == Level::Allow {
494+
self.lints_allowed.push(Symbol::intern(&lint));
495+
} else {
496+
self.lints_to_emit.push(Symbol::intern(&lint));
497+
}
498+
}
499+
}
500+
}
501+
502+
impl<'tcx> Visitor<'tcx> for LintLevelMinimumVisitor<'tcx> {
503+
type NestedFilter = nested_filter::All;
504+
505+
fn nested_visit_map(&mut self) -> Self::Map {
506+
self.tcx.hir()
507+
}
508+
509+
fn visit_attribute(&mut self, attribute: &'tcx ast::Attribute) {
510+
if let Some(meta) = attribute.meta() {
511+
if [sym::warn, sym::deny, sym::forbid, sym::expect]
512+
.iter()
513+
.any(|kind| meta.has_name(*kind))
514+
{
515+
// SAFETY: Lint attributes are always a metalist inside a
516+
// metalist (even with just one lint).
517+
for meta_list in meta.meta_item_list().unwrap() {
518+
// If it's a tool lint (e.g. clippy::my_clippy_lint)
519+
if let ast::NestedMetaItem::MetaItem(meta_item) = meta_list {
520+
if meta_item.path.segments.len() == 1 {
521+
self.lints_to_emit.push(
522+
// SAFETY: Lint attributes can only have literals
523+
meta_list.ident().unwrap().name,
524+
);
525+
} else {
526+
self.lints_to_emit.push(meta_item.path.segments[1].ident.name);
527+
}
528+
}
529+
}
530+
// We handle #![allow]s differently, as these remove checking rather than adding.
531+
} else if meta.has_name(sym::allow)
532+
&& let ast::AttrStyle::Inner = attribute.style
533+
{
534+
for meta_list in meta.meta_item_list().unwrap() {
535+
// If it's a tool lint (e.g. clippy::my_clippy_lint)
536+
if let ast::NestedMetaItem::MetaItem(meta_item) = meta_list {
537+
if meta_item.path.segments.len() == 1 {
538+
self.lints_allowed.push(meta_list.name_or_empty())
539+
} else {
540+
self.lints_allowed.push(meta_item.path.segments[1].ident.name);
541+
}
542+
}
543+
}
544+
}
545+
}
546+
}
547+
}
548+
481549
pub struct LintLevelsBuilder<'s, P> {
482550
sess: &'s Session,
483551
features: &'s Features,
@@ -1161,7 +1229,8 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
11611229
}
11621230

11631231
pub(crate) fn provide(providers: &mut Providers) {
1164-
*providers = Providers { shallow_lint_levels_on, lint_expectations, lints_that_can_emit, ..*providers };
1232+
*providers =
1233+
Providers { shallow_lint_levels_on, lint_expectations, lints_that_can_emit, ..*providers };
11651234
}
11661235

11671236
pub fn parse_lint_and_tool_name(lint_name: &str) -> (Option<Symbol>, &str) {

compiler/rustc_middle/src/query/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ use rustc_hir::def_id::{
7171
};
7272
use rustc_hir::lang_items::{LangItem, LanguageItems};
7373
use rustc_hir::{Crate, ItemLocalId, ItemLocalMap, TraitCandidate};
74-
use rustc_lint_defs::LintId;
7574
use rustc_index::IndexVec;
7675
use rustc_macros::rustc_queries;
7776
use rustc_query_system::ich::StableHashingContext;
@@ -432,9 +431,9 @@ rustc_queries! {
432431
desc { "computing `#[expect]`ed lints in this crate" }
433432
}
434433

435-
query lints_that_can_emit(_: ()) -> &'tcx Vec<LintId> {
434+
query lints_that_can_emit(_: ()) -> &'tcx Lrc<(Vec<Symbol>, Vec<Symbol>)> {
436435
arena_cache
437-
desc { "Computing all lints that are not `[#allow]ed` / allow-by-default" }
436+
desc { "Computing all lints that are explicitly enabled or with a default level great than Allow" }
438437
}
439438

440439
query expn_that_defined(key: DefId) -> rustc_span::ExpnId {

0 commit comments

Comments
 (0)