diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 5d81370c35acf..5f84d5c8b9495 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -20,23 +20,23 @@ use rustc_ast::ptr::P; use rustc_ast::visit::{self as ast_visit, Visitor}; use rustc_ast::{self as ast, walk_list, HasAttrs}; use rustc_middle::ty::RegisteredTools; -use rustc_session::lint::{BufferedEarlyLint, LintBuffer}; +use rustc_session::lint::{BufferedEarlyLint, LintBuffer, LintPass}; use rustc_session::Session; use rustc_span::symbol::Ident; use rustc_span::Span; -macro_rules! run_early_passes { ($cx:expr, $f:ident, $($args:expr),*) => ({ - for pass in $cx.passes.iter_mut() { - pass.$f(&$cx.context, $($args),*); - } +macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({ + $cx.pass.$f(&$cx.context, $($args),*); }) } -pub struct EarlyContextAndPasses<'a> { +/// Implements the AST traversal for early lint passes. `T` provides the the +/// `check_*` methods. +pub struct EarlyContextAndPass<'a, T: EarlyLintPass> { context: EarlyContext<'a>, - passes: Vec, + pass: T, } -impl<'a> EarlyContextAndPasses<'a> { +impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> { // This always-inlined function is for the hot call site. #[inline(always)] fn inlined_check_id(&mut self, id: ast::NodeId) { @@ -70,27 +70,27 @@ impl<'a> EarlyContextAndPasses<'a> { self.inlined_check_id(id); debug!("early context: enter_attrs({:?})", attrs); - run_early_passes!(self, enter_lint_attrs, attrs); + lint_callback!(self, enter_lint_attrs, attrs); f(self); debug!("early context: exit_attrs({:?})", attrs); - run_early_passes!(self, exit_lint_attrs, attrs); + lint_callback!(self, exit_lint_attrs, attrs); self.context.builder.pop(push); } } -impl<'a> ast_visit::Visitor<'a> for EarlyContextAndPasses<'a> { +impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T> { fn visit_param(&mut self, param: &'a ast::Param) { self.with_lint_attrs(param.id, ¶m.attrs, |cx| { - run_early_passes!(cx, check_param, param); + lint_callback!(cx, check_param, param); ast_visit::walk_param(cx, param); }); } fn visit_item(&mut self, it: &'a ast::Item) { self.with_lint_attrs(it.id, &it.attrs, |cx| { - run_early_passes!(cx, check_item, it); + lint_callback!(cx, check_item, it); ast_visit::walk_item(cx, it); - run_early_passes!(cx, check_item_post, it); + lint_callback!(cx, check_item_post, it); }) } @@ -101,10 +101,10 @@ impl<'a> ast_visit::Visitor<'a> for EarlyContextAndPasses<'a> { } fn visit_pat(&mut self, p: &'a ast::Pat) { - run_early_passes!(self, check_pat, p); + lint_callback!(self, check_pat, p); self.check_id(p.id); ast_visit::walk_pat(self, p); - run_early_passes!(self, check_pat_post, p); + lint_callback!(self, check_pat_post, p); } fn visit_pat_field(&mut self, field: &'a ast::PatField) { @@ -120,7 +120,7 @@ impl<'a> ast_visit::Visitor<'a> for EarlyContextAndPasses<'a> { fn visit_expr(&mut self, e: &'a ast::Expr) { self.with_lint_attrs(e.id, &e.attrs, |cx| { - run_early_passes!(cx, check_expr, e); + lint_callback!(cx, check_expr, e); ast_visit::walk_expr(cx, e); }) } @@ -141,7 +141,7 @@ impl<'a> ast_visit::Visitor<'a> for EarlyContextAndPasses<'a> { // Note that statements get their attributes from // the AST struct that they wrap (e.g. an item) self.with_lint_attrs(s.id, s.attrs(), |cx| { - run_early_passes!(cx, check_stmt, s); + lint_callback!(cx, check_stmt, s); cx.check_id(s.id); }); // The visitor for the AST struct wrapped @@ -152,7 +152,7 @@ impl<'a> ast_visit::Visitor<'a> for EarlyContextAndPasses<'a> { } fn visit_fn(&mut self, fk: ast_visit::FnKind<'a>, span: Span, id: ast::NodeId) { - run_early_passes!(self, check_fn, fk, span, id); + lint_callback!(self, check_fn, fk, span, id); self.check_id(id); ast_visit::walk_fn(self, fk); @@ -180,37 +180,37 @@ impl<'a> ast_visit::Visitor<'a> for EarlyContextAndPasses<'a> { fn visit_variant(&mut self, v: &'a ast::Variant) { self.with_lint_attrs(v.id, &v.attrs, |cx| { - run_early_passes!(cx, check_variant, v); + lint_callback!(cx, check_variant, v); ast_visit::walk_variant(cx, v); }) } fn visit_ty(&mut self, t: &'a ast::Ty) { - run_early_passes!(self, check_ty, t); + lint_callback!(self, check_ty, t); self.check_id(t.id); ast_visit::walk_ty(self, t); } fn visit_ident(&mut self, ident: Ident) { - run_early_passes!(self, check_ident, ident); + lint_callback!(self, check_ident, ident); } fn visit_local(&mut self, l: &'a ast::Local) { self.with_lint_attrs(l.id, &l.attrs, |cx| { - run_early_passes!(cx, check_local, l); + lint_callback!(cx, check_local, l); ast_visit::walk_local(cx, l); }) } fn visit_block(&mut self, b: &'a ast::Block) { - run_early_passes!(self, check_block, b); + lint_callback!(self, check_block, b); self.check_id(b.id); ast_visit::walk_block(self, b); } fn visit_arm(&mut self, a: &'a ast::Arm) { self.with_lint_attrs(a.id, &a.attrs, |cx| { - run_early_passes!(cx, check_arm, a); + lint_callback!(cx, check_arm, a); ast_visit::walk_arm(cx, a); }) } @@ -229,19 +229,19 @@ impl<'a> ast_visit::Visitor<'a> for EarlyContextAndPasses<'a> { } fn visit_generic_arg(&mut self, arg: &'a ast::GenericArg) { - run_early_passes!(self, check_generic_arg, arg); + lint_callback!(self, check_generic_arg, arg); ast_visit::walk_generic_arg(self, arg); } fn visit_generic_param(&mut self, param: &'a ast::GenericParam) { self.with_lint_attrs(param.id, ¶m.attrs, |cx| { - run_early_passes!(cx, check_generic_param, param); + lint_callback!(cx, check_generic_param, param); ast_visit::walk_generic_param(cx, param); }); } fn visit_generics(&mut self, g: &'a ast::Generics) { - run_early_passes!(self, check_generics, g); + lint_callback!(self, check_generics, g); ast_visit::walk_generics(self, g); } @@ -250,18 +250,18 @@ impl<'a> ast_visit::Visitor<'a> for EarlyContextAndPasses<'a> { } fn visit_poly_trait_ref(&mut self, t: &'a ast::PolyTraitRef) { - run_early_passes!(self, check_poly_trait_ref, t); + lint_callback!(self, check_poly_trait_ref, t); ast_visit::walk_poly_trait_ref(self, t); } fn visit_assoc_item(&mut self, item: &'a ast::AssocItem, ctxt: ast_visit::AssocCtxt) { self.with_lint_attrs(item.id, &item.attrs, |cx| match ctxt { ast_visit::AssocCtxt::Trait => { - run_early_passes!(cx, check_trait_item, item); + lint_callback!(cx, check_trait_item, item); ast_visit::walk_assoc_item(cx, item, ctxt); } ast_visit::AssocCtxt::Impl => { - run_early_passes!(cx, check_impl_item, item); + lint_callback!(cx, check_impl_item, item); ast_visit::walk_assoc_item(cx, item, ctxt); } }); @@ -282,20 +282,49 @@ impl<'a> ast_visit::Visitor<'a> for EarlyContextAndPasses<'a> { } fn visit_attribute(&mut self, attr: &'a ast::Attribute) { - run_early_passes!(self, check_attribute, attr); + lint_callback!(self, check_attribute, attr); } fn visit_mac_def(&mut self, mac: &'a ast::MacroDef, id: ast::NodeId) { - run_early_passes!(self, check_mac_def, mac); + lint_callback!(self, check_mac_def, mac); self.check_id(id); } fn visit_mac_call(&mut self, mac: &'a ast::MacCall) { - run_early_passes!(self, check_mac, mac); + lint_callback!(self, check_mac, mac); ast_visit::walk_mac(self, mac); } } +// Combines multiple lint passes into a single pass, at runtime. Each +// `check_foo` method in `$methods` within this pass simply calls `check_foo` +// once per `$pass`. Compare with `declare_combined_early_lint_pass`, which is +// similar, but combines lint passes at compile time. +struct RuntimeCombinedEarlyLintPass<'a> { + passes: &'a mut [EarlyLintPassObject], +} + +#[allow(rustc::lint_pass_impl_without_macro)] +impl LintPass for RuntimeCombinedEarlyLintPass<'_> { + fn name(&self) -> &'static str { + panic!() + } +} + +macro_rules! impl_early_lint_pass { + ([], [$($(#[$attr:meta])* fn $f:ident($($param:ident: $arg:ty),*);)*]) => ( + impl EarlyLintPass for RuntimeCombinedEarlyLintPass<'_> { + $(fn $f(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) { + for pass in self.passes.iter_mut() { + pass.$f(context, $($param),*); + } + })* + } + ) +} + +crate::early_lint_methods!(impl_early_lint_pass, []); + /// Early lints work on different nodes - either on the crate root, or on freshly loaded modules. /// This trait generalizes over those nodes. pub trait EarlyCheckNode<'a>: Copy { @@ -303,7 +332,7 @@ pub trait EarlyCheckNode<'a>: Copy { fn attrs<'b>(self) -> &'b [ast::Attribute] where 'a: 'b; - fn check<'b>(self, cx: &mut EarlyContextAndPasses<'b>) + fn check<'b, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>) where 'a: 'b; } @@ -318,13 +347,13 @@ impl<'a> EarlyCheckNode<'a> for &'a ast::Crate { { &self.attrs } - fn check<'b>(self, cx: &mut EarlyContextAndPasses<'b>) + fn check<'b, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>) where 'a: 'b, { - run_early_passes!(cx, check_crate, self); + lint_callback!(cx, check_crate, self); ast_visit::walk_crate(cx, self); - run_early_passes!(cx, check_crate_post, self); + lint_callback!(cx, check_crate_post, self); } } @@ -338,7 +367,7 @@ impl<'a> EarlyCheckNode<'a> for (ast::NodeId, &'a [ast::Attribute], &'a [P(self, cx: &mut EarlyContextAndPasses<'b>) + fn check<'b, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>) where 'a: 'b, { @@ -356,21 +385,37 @@ pub fn check_ast_node<'a>( builtin_lints: impl EarlyLintPass + 'static, check_node: impl EarlyCheckNode<'a>, ) { + let context = EarlyContext::new( + sess, + !pre_expansion, + lint_store, + registered_tools, + lint_buffer.unwrap_or_default(), + ); + + // Note: `passes` is often empty. In that case, it's faster to run + // `builtin_lints` directly rather than bundling it up into the + // `RuntimeCombinedEarlyLintPass`. let passes = if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes }; - let mut passes: Vec = passes.iter().map(|p| (p)()).collect(); - passes.push(Box::new(builtin_lints)); - - let mut cx = EarlyContextAndPasses { - context: EarlyContext::new( - sess, - !pre_expansion, - lint_store, - registered_tools, - lint_buffer.unwrap_or_default(), - ), - passes, - }; + if passes.is_empty() { + check_ast_node_inner(sess, check_node, context, builtin_lints); + } else { + let mut passes: Vec<_> = passes.iter().map(|mk_pass| (mk_pass)()).collect(); + passes.push(Box::new(builtin_lints)); + let pass = RuntimeCombinedEarlyLintPass { passes: &mut passes[..] }; + check_ast_node_inner(sess, check_node, context, pass); + } +} + +pub fn check_ast_node_inner<'a, T: EarlyLintPass>( + sess: &Session, + check_node: impl EarlyCheckNode<'a>, + context: EarlyContext<'_>, + pass: T, +) { + let mut cx = EarlyContextAndPass { context, pass }; + cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx)); // All of the buffered lints should have been emitted at this point. diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index 8a50cb1f19ef5..e2876938d7056 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -23,6 +23,7 @@ use rustc_hir::intravisit as hir_visit; use rustc_hir::intravisit::Visitor; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, TyCtxt}; +use rustc_session::lint::LintPass; use rustc_span::Span; use std::any::Any; @@ -36,17 +37,17 @@ pub fn unerased_lint_store(tcx: TyCtxt<'_>) -> &LintStore { } macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({ - for pass in $cx.passes.iter_mut() { - pass.$f(&$cx.context, $($args),*); - } + $cx.pass.$f(&$cx.context, $($args),*); }) } -struct LateContextAndPasses<'tcx> { +/// Implements the AST traversal for late lint passes. `T` provides the the +/// `check_*` methods. +pub struct LateContextAndPass<'tcx, T: LateLintPass<'tcx>> { context: LateContext<'tcx>, - passes: Vec>, + pass: T, } -impl<'tcx> LateContextAndPasses<'tcx> { +impl<'tcx, T: LateLintPass<'tcx>> LateContextAndPass<'tcx, T> { /// Merge the lints specified by any lint attributes into the /// current lint context, call the provided function, then reset the /// lints in effect to their previous state. @@ -82,7 +83,7 @@ impl<'tcx> LateContextAndPasses<'tcx> { } } -impl<'tcx> hir_visit::Visitor<'tcx> for LateContextAndPasses<'tcx> { +impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPass<'tcx, T> { type NestedFilter = nested_filter::All; /// Because lints are scoped lexically, we want to walk nested @@ -302,6 +303,35 @@ impl<'tcx> hir_visit::Visitor<'tcx> for LateContextAndPasses<'tcx> { } } +// Combines multiple lint passes into a single pass, at runtime. Each +// `check_foo` method in `$methods` within this pass simply calls `check_foo` +// once per `$pass`. Compare with `declare_combined_late_lint_pass`, which is +// similar, but combines lint passes at compile time. +struct RuntimeCombinedLateLintPass<'a, 'tcx> { + passes: &'a mut [LateLintPassObject<'tcx>], +} + +#[allow(rustc::lint_pass_impl_without_macro)] +impl LintPass for RuntimeCombinedLateLintPass<'_, '_> { + fn name(&self) -> &'static str { + panic!() + } +} + +macro_rules! impl_late_lint_pass { + ([], [$($(#[$attr:meta])* fn $f:ident($($param:ident: $arg:ty),*);)*]) => { + impl<'tcx> LateLintPass<'tcx> for RuntimeCombinedLateLintPass<'_, 'tcx> { + $(fn $f(&mut self, context: &LateContext<'tcx>, $($param: $arg),*) { + for pass in self.passes.iter_mut() { + pass.$f(context, $($param),*); + } + })* + } + }; +} + +crate::late_lint_methods!(impl_late_lint_pass, []); + pub(super) fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( tcx: TyCtxt<'tcx>, module_def_id: LocalDefId, @@ -319,11 +349,27 @@ pub(super) fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( only_module: true, }; + // Note: `passes` is often empty. In that case, it's faster to run + // `builtin_lints` directly rather than bundling it up into the + // `RuntimeCombinedLateLintPass`. let mut passes: Vec<_> = - unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)(tcx)).collect(); - passes.push(Box::new(builtin_lints)); + unerased_lint_store(tcx).late_module_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); + if passes.is_empty() { + late_lint_mod_inner(tcx, module_def_id, context, builtin_lints); + } else { + passes.push(Box::new(builtin_lints)); + let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] }; + late_lint_mod_inner(tcx, module_def_id, context, pass); + } +} - let mut cx = LateContextAndPasses { context, passes }; +fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + module_def_id: LocalDefId, + context: LateContext<'tcx>, + pass: T, +) { + let mut cx = LateContextAndPass { context, pass }; let (module, _span, hir_id) = tcx.hir().get_module(module_def_id); cx.process_mod(module, hir_id); @@ -349,11 +395,26 @@ fn late_lint_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>(tcx: TyCtxt<'tcx>, builti only_module: false, }; - let mut passes = - unerased_lint_store(tcx).late_passes.iter().map(|p| (p)(tcx)).collect::>(); - passes.push(Box::new(builtin_lints)); + // Note: `passes` is often empty. In that case, it's faster to run + // `builtin_lints` directly rather than bundling it up into the + // `RuntimeCombinedLateLintPass`. + let mut passes: Vec<_> = + unerased_lint_store(tcx).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); + if passes.is_empty() { + late_lint_crate_inner(tcx, context, builtin_lints); + } else { + passes.push(Box::new(builtin_lints)); + let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] }; + late_lint_crate_inner(tcx, context, pass); + } +} - let mut cx = LateContextAndPasses { context, passes }; +fn late_lint_crate_inner<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + context: LateContext<'tcx>, + pass: T, +) { + let mut cx = LateContextAndPass { context, pass }; // Visit the whole crate. cx.with_lint_attrs(hir::CRATE_HIR_ID, |cx| { diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 1990a74841bc2..11022eb80ea5f 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -127,7 +127,6 @@ fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { late::late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new()); } -// See the comment on `BuiltinCombinedEarlyLintPass`, which is similar. early_lint_methods!( declare_combined_early_lint_pass, [ @@ -138,9 +137,6 @@ early_lint_methods!( ] ); -// Declare `BuiltinCombinedEarlyLintPass`, a lint pass that combines multiple -// lint passes into a single pass for maximum speed. Each `check_foo` method -// within this pass simply calls `check_foo` once per listed lint. early_lint_methods!( declare_combined_early_lint_pass, [ @@ -168,7 +164,6 @@ early_lint_methods!( // FIXME: Make a separate lint type which does not require typeck tables. -// See the comment on `BuiltinCombinedEarlyLintPass`, which is similar. late_lint_methods!( declare_combined_late_lint_pass, [ @@ -188,7 +183,6 @@ late_lint_methods!( ] ); -// See the comment on `BuiltinCombinedEarlyLintPass`, which is similar. late_lint_methods!( declare_combined_late_lint_pass, [ diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs index 00922cef38462..5558156a4b9ef 100644 --- a/compiler/rustc_lint/src/passes.rs +++ b/compiler/rustc_lint/src/passes.rs @@ -95,6 +95,11 @@ macro_rules! expand_combined_late_lint_pass_methods { ) } +/// Combines multiple lints passes into a single lint pass, at compile time, +/// for maximum speed. Each `check_foo` method in `$methods` within this pass +/// simply calls `check_foo` once per `$pass`. Compare with +/// `LateLintPassObjects`, which is similar, but combines lint passes at +/// runtime. #[macro_export] macro_rules! declare_combined_late_lint_pass { ([$v:vis $name:ident, [$($pass:ident: $constructor:expr,)*]], $methods:tt) => ( @@ -198,6 +203,11 @@ macro_rules! expand_combined_early_lint_pass_methods { ) } +/// Combines multiple lints passes into a single lint pass, at compile time, +/// for maximum speed. Each `check_foo` method in `$methods` within this pass +/// simply calls `check_foo` once per `$pass`. Compare with +/// `EarlyLintPassObjects`, which is similar, but combines lint passes at +/// runtime. #[macro_export] macro_rules! declare_combined_early_lint_pass { ([$v:vis $name:ident, [$($pass:ident: $constructor:expr,)*]], $methods:tt) => (