From 37d7d36a51f72aff88b95ff699b1cf3d85e1b22a Mon Sep 17 00:00:00 2001 From: yukang Date: Sun, 12 Oct 2025 13:39:08 +0800 Subject: [PATCH] uplift module inspect --- compiler/rustc_hir/src/intravisit.rs | 1 + compiler/rustc_hir_analysis/src/check/mod.rs | 2 + compiler/rustc_lint/src/late.rs | 36 ++++---- compiler/rustc_lint/src/lib.rs | 13 +++ compiler/rustc_lint/src/module_inception.rs | 92 +++++++++++++++++++ library/core/src/async_iter/mod.rs | 1 + library/core/src/future/mod.rs | 1 + library/std/src/sys/process/unix/mod.rs | 1 + library/test/src/lib.rs | 1 + src/bootstrap/src/core/config/mod.rs | 2 +- tests/ui/expr/weird-exprs.rs | 1 + tests/ui/imports/issue-62767.rs | 1 + tests/ui/lint/module-inception-allow.rs | 24 +++++ .../convoluted-locals-131474-with-mods.rs | 1 + .../ui/lint/non-local-defs/module_as_local.rs | 1 + tests/ui/structs-enums/rec-align-u64.rs | 2 + 16 files changed, 159 insertions(+), 21 deletions(-) create mode 100644 compiler/rustc_lint/src/module_inception.rs create mode 100644 tests/ui/lint/module-inception-allow.rs diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index eb682f32111a5..2540f1ebf02e1 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -529,6 +529,7 @@ pub fn walk_param<'v, V: Visitor<'v>>(visitor: &mut V, param: &'v Param<'v>) -> pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) -> V::Result { let Item { owner_id: _, kind, span: _, vis_span: _, has_delayed_lints: _ } = item; + try_visit!(visitor.visit_id(item.hir_id())); match *kind { ItemKind::ExternCrate(orig_name, ident) => { diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index 0166c3b980de3..648d3166e32ac 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -63,6 +63,8 @@ a type parameter). */ pub mod always_applicable; +#[cfg_attr(bootstrap, allow(unknown_lints))] +#[allow(module_inception)] mod check; mod compare_impl_item; mod entry; diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index ccfba715a1be3..ab38e0c340c23 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -405,14 +405,6 @@ fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>( } fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { - // Note: `passes` is often empty. - let passes: Vec<_> = - unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); - - if passes.is_empty() { - return; - } - let context = LateContext { tcx, enclosing_body: None, @@ -426,19 +418,23 @@ fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { let lints_that_dont_need_to_run = tcx.lints_that_dont_need_to_run(()); - let mut filtered_passes: Vec>> = passes - .into_iter() - .filter(|pass| { - let lints = (**pass).get_lints(); - // Lintless passes are always in - lints.is_empty() || - // If the pass doesn't have a single needed lint, omit it - !lints.iter().all(|lint| lints_that_dont_need_to_run.contains(&LintId::of(lint))) - }) - .collect(); + let mut passes: Vec>> = + vec![Box::new(crate::BuiltinCombinedLateLintPass::new()), Box::new(HardwiredLints)]; + + // Filter out passes whose lints are all in the "don't need to run" set + passes.retain(|pass| { + let lints = (**pass).get_lints(); + // Lintless passes are always in + lints.is_empty() || + // If the pass doesn't have a single needed lint, omit it + !lints.iter().all(|lint| lints_that_dont_need_to_run.contains(&LintId::of(lint))) + }); + + if passes.is_empty() { + return; + } - filtered_passes.push(Box::new(HardwiredLints)); - let pass = RuntimeCombinedLateLintPass { passes: &mut filtered_passes[..] }; + let pass = RuntimeCombinedLateLintPass { passes: &mut passes }; late_lint_crate_inner(tcx, context, pass); } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 9bb53fea54a18..abad9447fa0a5 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -59,6 +59,7 @@ pub mod lifetime_syntax; mod lints; mod macro_expr_fragment_specifier_2024_migration; mod map_unit_fn; +mod module_inception; mod multiple_supertrait_upcastable; mod non_ascii_idents; mod non_fmt_panic; @@ -100,6 +101,7 @@ use let_underscore::*; use lifetime_syntax::*; use macro_expr_fragment_specifier_2024_migration::*; use map_unit_fn::*; +use module_inception::*; use multiple_supertrait_upcastable::*; use non_ascii_idents::*; use non_fmt_panic::NonPanicFmt; @@ -185,6 +187,16 @@ early_lint_methods!( ] ); +late_lint_methods!( + declare_combined_late_lint_pass, + [ + BuiltinCombinedLateLintPass, + [ + ModuleInception: ModuleInception::new(), + ] + ] +); + late_lint_methods!( declare_combined_late_lint_pass, [ @@ -273,6 +285,7 @@ fn register_builtins(store: &mut LintStore) { store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); + store.register_lints(&BuiltinCombinedLateLintPass::get_lints()); store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); store.register_lints(&foreign_modules::get_lints()); store.register_lints(&HardwiredLints::lint_vec()); diff --git a/compiler/rustc_lint/src/module_inception.rs b/compiler/rustc_lint/src/module_inception.rs new file mode 100644 index 0000000000000..27ba6f95ab5df --- /dev/null +++ b/compiler/rustc_lint/src/module_inception.rs @@ -0,0 +1,92 @@ +//! Lint for detecting modules that have the same name as their parent module. + +use rustc_hir::{Body, Item, ItemKind}; +use rustc_session::{declare_lint, impl_lint_pass}; +use rustc_span::Symbol; + +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `module_inception` lint detects modules that have the same name as their parent module. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// mod foo { + /// mod foo { + /// pub fn bar() {} + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// A typical beginner mistake is to have `mod foo;` and again `mod foo { .. }` + /// in `foo.rs`. The expectation is that items inside the inner `mod foo { .. }` + /// are then available through `foo::x`, but they are only available through + /// `foo::foo::x`. If this is done on purpose, it would be better to choose a + /// more representative module name. + pub MODULE_INCEPTION, + Warn, + "modules that have the same name as their parent module" +} + +struct ModInfo { + name: Symbol, + /// How many bodies are between this module and the current lint pass position. + /// + /// Only the most recently seen module is updated when entering/exiting a body. + in_body_count: u32, +} + +pub(crate) struct ModuleInception { + /// The module path the lint pass is in. + modules: Vec, +} + +impl ModuleInception { + pub(crate) fn new() -> Self { + Self { modules: Vec::new() } + } +} + +impl_lint_pass!(ModuleInception => [MODULE_INCEPTION]); + +impl<'tcx> LateLintPass<'tcx> for ModuleInception { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Mod(ident, _) = item.kind { + // Check if this module has the same name as its parent module + if let [.., prev] = &*self.modules + && prev.name == ident.name + && prev.in_body_count == 0 + && !item.span.from_expansion() + { + cx.span_lint(MODULE_INCEPTION, item.span, |lint| { + lint.primary_message("module has the same name as its containing module"); + }); + } + + self.modules.push(ModInfo { name: ident.name, in_body_count: 0 }); + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if matches!(item.kind, ItemKind::Mod(..)) { + self.modules.pop(); + } + } + + fn check_body(&mut self, _: &LateContext<'tcx>, _: &Body<'tcx>) { + if let [.., last] = &mut *self.modules { + last.in_body_count += 1; + } + } + + fn check_body_post(&mut self, _: &LateContext<'tcx>, _: &Body<'tcx>) { + if let [.., last] = &mut *self.modules { + last.in_body_count -= 1; + } + } +} diff --git a/library/core/src/async_iter/mod.rs b/library/core/src/async_iter/mod.rs index a5b03b7dd4f14..bc9a8eaccf0fb 100644 --- a/library/core/src/async_iter/mod.rs +++ b/library/core/src/async_iter/mod.rs @@ -121,6 +121,7 @@ //! warning: unused result that must be used: async iterators do nothing unless polled //! ``` +#[allow(module_inception)] mod async_iter; mod from_iter; diff --git a/library/core/src/future/mod.rs b/library/core/src/future/mod.rs index 2b16a568b4031..c7dd2d344c8e3 100644 --- a/library/core/src/future/mod.rs +++ b/library/core/src/future/mod.rs @@ -13,6 +13,7 @@ use crate::ptr::NonNull; use crate::task::Context; mod async_drop; +#[allow(module_inception)] mod future; mod into_future; mod join; diff --git a/library/std/src/sys/process/unix/mod.rs b/library/std/src/sys/process/unix/mod.rs index cda1bf74f1cad..7b5d10864abba 100644 --- a/library/std/src/sys/process/unix/mod.rs +++ b/library/std/src/sys/process/unix/mod.rs @@ -16,6 +16,7 @@ cfg_select! { pub use unsupported::output; } _ => { + #[allow(module_inception)] mod unix; use unix as imp; } diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs index d554807bbde70..111120cc24f30 100644 --- a/library/test/src/lib.rs +++ b/library/test/src/lib.rs @@ -40,6 +40,7 @@ pub use self::types::TestName::*; pub use self::types::*; // Module to be used by rustc to compile tests in libtest +#[allow(module_inception)] pub mod test { pub use crate::bench::Bencher; pub use crate::cli::{TestOpts, parse_opts}; diff --git a/src/bootstrap/src/core/config/mod.rs b/src/bootstrap/src/core/config/mod.rs index 007ed4aaba13f..b62038c25e0c6 100644 --- a/src/bootstrap/src/core/config/mod.rs +++ b/src/bootstrap/src/core/config/mod.rs @@ -19,7 +19,7 @@ //! - Utility enums for specific configuration options. //! - Helper functions for managing configuration values. -#[expect(clippy::module_inception)] +#[allow(unknown_lints, module_inception)] mod config; pub mod flags; pub mod target_selection; diff --git a/tests/ui/expr/weird-exprs.rs b/tests/ui/expr/weird-exprs.rs index 7db92d4606713..cd1bdda739b19 100644 --- a/tests/ui/expr/weird-exprs.rs +++ b/tests/ui/expr/weird-exprs.rs @@ -10,6 +10,7 @@ #![allow(uncommon_codepoints, confusable_idents)] #![allow(unused_imports)] #![allow(unreachable_patterns)] +#![allow(module_inception)] #![recursion_limit = "256"] diff --git a/tests/ui/imports/issue-62767.rs b/tests/ui/imports/issue-62767.rs index eb5569e003da0..32a867e0f1479 100644 --- a/tests/ui/imports/issue-62767.rs +++ b/tests/ui/imports/issue-62767.rs @@ -1,4 +1,5 @@ //@ check-pass +#![allow(module_inception)] // Minimized case from #62767. mod m { diff --git a/tests/ui/lint/module-inception-allow.rs b/tests/ui/lint/module-inception-allow.rs new file mode 100644 index 0000000000000..6715891aaa42c --- /dev/null +++ b/tests/ui/lint/module-inception-allow.rs @@ -0,0 +1,24 @@ +#![allow(module_inception)] + +pub mod foo { + pub mod foo { + pub fn bar() {} + } +} + +pub mod baz { + // This is fine - different name + pub fn qux() {} +} + +pub mod outer { + pub mod outer { + pub fn inner() {} + } + + pub mod different { + pub fn func() {} + } +} + +fn main() {} diff --git a/tests/ui/lint/non-local-defs/convoluted-locals-131474-with-mods.rs b/tests/ui/lint/non-local-defs/convoluted-locals-131474-with-mods.rs index 72fd056d461ad..20ac92262dcdb 100644 --- a/tests/ui/lint/non-local-defs/convoluted-locals-131474-with-mods.rs +++ b/tests/ui/lint/non-local-defs/convoluted-locals-131474-with-mods.rs @@ -4,6 +4,7 @@ // Similar to https://github.com/rust-lang/rust/issues/131474 //@ check-pass +#![allow(module_inception)] pub mod tmp { pub mod tmp { diff --git a/tests/ui/lint/non-local-defs/module_as_local.rs b/tests/ui/lint/non-local-defs/module_as_local.rs index bb215026c7d40..43cb0e5a48efd 100644 --- a/tests/ui/lint/non-local-defs/module_as_local.rs +++ b/tests/ui/lint/non-local-defs/module_as_local.rs @@ -17,6 +17,7 @@ fn simple_one() { fn simple_two() { mod posts { + #[allow(module_inception)] pub mod posts { #[allow(non_camel_case_types)] pub struct table {} diff --git a/tests/ui/structs-enums/rec-align-u64.rs b/tests/ui/structs-enums/rec-align-u64.rs index df219892d7374..6d0ab50a42fd1 100644 --- a/tests/ui/structs-enums/rec-align-u64.rs +++ b/tests/ui/structs-enums/rec-align-u64.rs @@ -1,6 +1,8 @@ //@ run-pass #![allow(dead_code)] #![allow(unused_unsafe)] +#![allow(module_inception)] + // Issue #2303