From 7b1dc475b027022c24c75183868cbbec5a28e2b9 Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <barracuda@macos-powerpc.org>
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 <noreply@anthropic.com>
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

