From 7b1dc475b027022c24c75183868cbbec5a28e2b9 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Sat, 25 Jul 2026 16:44:24 +0000 Subject: [PATCH 21/25] resolve: local items shadow implicit extern crates in 2018+ use paths In edition 2018+, the first segment of a relative use path went straight to the implicit-crate (extern prelude) list. rustc gives module-local items precedence: a crate with `mod core;` plus `pub use core::X;` must resolve to the local module. Walk the owning module (conservatively - any walk failure keeps the old behaviour) and skip the implicit-crate lookup when a local type-namespace item of that name exists. Seen with colorgrad 0.7.2 (blocks xan). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GNXc7PddzJE4X1swqbe3Xn --- src/resolve/use.cpp | 74 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/src/resolve/use.cpp b/src/resolve/use.cpp index 9b6b9094..d333de37 100644 --- a/src/resolve/use.cpp +++ b/src/resolve/use.cpp @@ -72,16 +72,78 @@ void Resolve_Use(::AST::Crate& crate) // - Fun fact: The equivalent logic for non-use is gated on TARGETVER_LEAST_1_29 (but use is still special until 2018) if( crate.m_edition >= AST::Edition::Rust2018 ) { const auto& name = e.nodes.at(0).name(); - auto ec_it = AST::g_implicit_crates.find(name); - if(ec_it != AST::g_implicit_crates.end()) + + // Local items shadow the extern prelude: if the module containing this `use` + // statement (found by walking `base_path` from the crate root) has a local + // type-namespace item with this name, don't consult the implicit-crate list. + // Be conservative - if the walk fails for any reason, fall through to the + // previous (extern-crate-wins) behaviour. + bool has_local_shadow = false; { - DEBUG("Found implict crate " << name); - e.nodes.erase(e.nodes.begin()); - return AST::Path( ec_it->second, e.nodes); + const AST::Module* cur_mod = &crate.m_root_module; + bool walk_ok = true; + for( unsigned int i = 0; walk_ok && i < base_path.nodes().size(); i ++ ) + { + const auto& seg_name = base_path.nodes()[i].name(); + if( seg_name.size() > 0 && seg_name.c_str()[0] == '#' ) { + // Anon modules aren't indexed by name - can't safely continue the walk. + walk_ok = false; + break; + } + const AST::Module* next_mod = nullptr; + for(const auto& item : cur_mod->m_items) { + if( item->name == seg_name && item->data.is_Module() ) { + next_mod = &item->data.as_Module(); + break; + } + } + if( !next_mod ) { + walk_ok = false; + break; + } + cur_mod = next_mod; + } + if( walk_ok ) + { + for(const auto& item : cur_mod->m_items) { + if( item->name != name ) + continue; + TU_MATCH_HDRA( (item->data), {) + default: + break; + TU_ARMA(Module, i) { has_local_shadow = true; } + TU_ARMA(Crate, i) { has_local_shadow = true; } + TU_ARMA(Struct, i) { has_local_shadow = true; } + TU_ARMA(Enum, i) { has_local_shadow = true; } + TU_ARMA(Union, i) { has_local_shadow = true; } + TU_ARMA(Trait, i) { has_local_shadow = true; } + TU_ARMA(Type, i) { has_local_shadow = true; } + } + // Keep scanning on a name match in the value namespace only (e.g. a + // `fn core` shouldn't stop a later `mod core` from shadowing) + if( has_local_shadow ) + break; + } + } + } + + if( !has_local_shadow ) + { + auto ec_it = AST::g_implicit_crates.find(name); + if(ec_it != AST::g_implicit_crates.end()) + { + DEBUG("Found implict crate " << name); + e.nodes.erase(e.nodes.begin()); + return AST::Path( ec_it->second, e.nodes); + } + else + { + DEBUG("No implicit crate " << name); + } } else { - DEBUG("No implicit crate " << name); + DEBUG("Local item shadows implicit crate " << name); } } -- 2.43.0