diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index bb9bf8976f672..42cdb304966c7 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -1013,7 +1013,12 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { ); crate_id.map(|crate_id| { self.r.extern_crate_map.insert(local_def_id, crate_id); - self.r.expect_module(crate_id.as_def_id()) + let module = self.r.expect_module(crate_id.as_def_id()); + self.r.try_add_def_id_for_namespaced_crate( + crate_id.as_def_id(), + IdentKey::new(orig_ident), + ); + module }) } .map(|module| { diff --git a/compiler/rustc_resolve/src/error_helper.rs b/compiler/rustc_resolve/src/error_helper.rs index c8abc3556f8fe..14264612a9c51 100644 --- a/compiler/rustc_resolve/src/error_helper.rs +++ b/compiler/rustc_resolve/src/error_helper.rs @@ -1499,6 +1499,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Scope::ModuleGlobs(..) => { // Already handled in `ModuleNonGlobs`. } + Scope::NamespacedCrates(..) => { + // Do not suggest namespaced crates as ordinary in-module typo candidates. + } Scope::MacroUsePrelude => { suggestions.extend(this.macro_use_prelude.iter().filter_map( |(name, binding)| { diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index d5babe2e94a90..8798750c406ad 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -121,6 +121,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let module_only = matches!(scope_set, ScopeSet::Module(..)); let module_and_extern_prelude = matches!(scope_set, ScopeSet::ModuleAndExternPrelude(..)); let extern_prelude = matches!(scope_set, ScopeSet::ExternPrelude); + let visit_namespaced_crates = ns == TypeNS && !self.namespaced_crate_names.is_empty(); let mut scope = match ns { _ if module_only || module_and_extern_prelude => Scope::ModuleNonGlobs(module, None), _ if extern_prelude => Scope::ExternPreludeItems, @@ -151,6 +152,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { true } Scope::ModuleNonGlobs(..) | Scope::ModuleGlobs(..) => true, + Scope::NamespacedCrates(..) => visit_namespaced_crates, Scope::MacroUsePrelude => use_prelude || orig_ident_span.is_rust_2015(), Scope::BuiltinAttrs => true, Scope::ExternPreludeItems | Scope::ExternPreludeFlags => { @@ -194,15 +196,35 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { MacroRulesScope::Empty => Scope::ModuleNonGlobs(module, None), }, Scope::ModuleNonGlobs(module, lint_id) => Scope::ModuleGlobs(module, lint_id), - Scope::ModuleGlobs(..) if module_only => break, - Scope::ModuleGlobs(..) if module_and_extern_prelude => match ns { + Scope::ModuleGlobs(..) if module_only && !visit_namespaced_crates => break, + Scope::ModuleGlobs(module, lint_id) if module_only => { + Scope::NamespacedCrates(module, lint_id) + } + Scope::NamespacedCrates(..) if module_only => break, + Scope::ModuleGlobs(..) if module_and_extern_prelude && !visit_namespaced_crates => { + match ns { + TypeNS => { + ctxt.update_unchecked(|ctxt| ctxt.adjust(ExpnId::root())); + Scope::ExternPreludeItems + } + ValueNS | MacroNS => break, + } + } + Scope::ModuleGlobs(module, lint_id) if module_and_extern_prelude => { + Scope::NamespacedCrates(module, lint_id) + } + Scope::NamespacedCrates(..) if module_and_extern_prelude => match ns { TypeNS => { ctxt.update_unchecked(|ctxt| ctxt.adjust(ExpnId::root())); Scope::ExternPreludeItems } ValueNS | MacroNS => break, }, - Scope::ModuleGlobs(module, prev_lint_id) => { + Scope::ModuleGlobs(module, prev_lint_id) if visit_namespaced_crates => { + Scope::NamespacedCrates(module, prev_lint_id) + } + Scope::ModuleGlobs(module, prev_lint_id) + | Scope::NamespacedCrates(module, prev_lint_id) => { use_prelude = !module.no_implicit_prelude; match self.hygienic_lexical_parent(module, &mut ctxt, derive_fallback_lint_id) { Some((parent_module, lint_id)) => { @@ -709,6 +731,17 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Err(ControlFlow::Break(..)) => return binding, } } + Scope::NamespacedCrates(module, _) => { + match self.resolve_namespaced_crate_in_module( + module, + ident, + orig_ident_span, + finalize.is_some(), + ) { + Some(decl) => Ok(decl), + None => Err(Determinacy::Determined), + } + } Scope::MacroUsePrelude => match self.macro_use_prelude.get(&ident.name).cloned() { Some(decl) => Ok(decl), None => Err(Determinacy::determined(!self.graph_root.has_unexpanded_invocations())), diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 3b93f2d1fd130..fc55923a5edc5 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -93,6 +93,7 @@ mod macros; pub mod rustdoc; type Res = def::Res; +type NamespacedCrateNames = FxHashMap>; #[derive(Copy, Clone, PartialEq, Debug)] enum Determinacy { @@ -125,6 +126,9 @@ enum Scope<'ra> { /// The node ID is for reporting the `PROC_MACRO_DERIVE_RESOLUTION_FALLBACK` /// lint if it should be reported. ModuleGlobs(Module<'ra>, Option), + /// Namespaced extern crates available under the given module. + /// These have lower precedence than the module's regular non-glob and glob names. + NamespacedCrates(Module<'ra>, Option), /// Names introduced by `#[macro_use]` attributes on `extern crate` items. MacroUsePrelude, /// Built-in attributes. @@ -1526,6 +1530,10 @@ pub struct Resolver<'ra, 'tcx> { /// Needed because glob delegations exclude explicitly defined names. impl_binding_keys: FxHashMap> = default::fx_hash_map(), + /// Extern crates with names of the form `foo::bar`, keyed by root and child. + namespaced_crate_names: NamespacedCrateNames, + namespaced_crate_root_by_def_id: CacheRefCell>, + /// This is the `Span` where an `extern crate foo;` suggestion would be inserted, if `foo` /// could be a crate that wasn't imported. For diagnostics use only. current_crate_outer_attr_insert_span: Span, @@ -1797,6 +1805,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { invocation_parents.insert(LocalExpnId::ROOT, InvocationParent::ROOT); let extern_prelude = build_extern_prelude(tcx, attrs); + let namespaced_crate_names = build_namespaced_crate_names(tcx); let registered_tools = tcx.registered_tools(()); let edition = tcx.sess.edition(); @@ -1864,6 +1873,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { macro_reachable_adts: Default::default(), doc_link_resolutions: Default::default(), doc_link_traits_in_scope: Default::default(), + namespaced_crate_names, + namespaced_crate_root_by_def_id: Default::default(), current_crate_outer_attr_insert_span, disambiguators: Default::default(), delegation_infos: Default::default(), @@ -2117,6 +2128,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Scope::ModuleGlobs(..) => { // Already handled in `ModuleNonGlobs` (but see #144993). } + Scope::NamespacedCrates(..) => { + // This scope only resolves child crate roots, it does not introduce traits. + } Scope::StdLibPrelude => { if let Some(module) = this.prelude { this.get_mut().traits_in_module(module, assoc_item, &mut found_traits); @@ -2476,6 +2490,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { crate_id.map(|crate_id| { let def_id = crate_id.as_def_id(); let res = Res::Def(DefKind::Mod, def_id); + self.try_add_def_id_for_namespaced_crate(def_id, ident); self.arenas.new_pub_def_decl(res, DUMMY_SP, LocalExpnId::ROOT) }) } @@ -2486,6 +2501,28 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }) } + fn try_add_def_id_for_namespaced_crate(&self, def_id: DefId, ident: IdentKey) { + if self.namespaced_crate_names.contains_key(&ident.name) { + self.namespaced_crate_root_by_def_id.borrow_mut().insert(def_id, ident.name); + } + } + + fn resolve_namespaced_crate_in_module( + &self, + module: Module<'ra>, + ident: IdentKey, + orig_ident_span: Span, + finalize: bool, + ) -> Option> { + let def_id = module.opt_def_id()?; + let root_name = { + let names = self.namespaced_crate_root_by_def_id.borrow(); + names.get(&def_id).copied() + }?; + let full_name = self.namespaced_crate_names.get(&root_name)?.get(&ident.name).copied()?; + self.extern_prelude_get_flag(IdentKey::with_root_ctxt(full_name), orig_ident_span, finalize) + } + /// Rustdoc uses this to resolve doc link paths in a recoverable way. `PathResult<'a>` /// isn't something that can be returned because it can't be made to live that long, /// and also it's a private type. Fortunately rustdoc doesn't need to know the error, @@ -2724,6 +2761,27 @@ fn build_extern_prelude<'tcx, 'ra>( extern_prelude } +fn build_namespaced_crate_names(tcx: TyCtxt<'_>) -> NamespacedCrateNames { + let mut names: NamespacedCrateNames = FxHashMap::default(); + for (name, entry) in tcx.sess.opts.externs.iter() { + if !entry.add_prelude { + continue; + } + let Some((root, child)) = name.split_once("::") else { + continue; + }; + if tcx.sess.opts.externs.get(root).is_none() { + continue; + } + + names + .entry(Symbol::intern(root)) + .or_default() + .insert(Symbol::intern(child), Symbol::intern(name)); + } + names +} + fn names_to_string(names: impl Iterator) -> String { let mut result = String::new(); for (i, name) in names.enumerate().filter(|(_, name)| *name != kw::PathRoot) { diff --git a/tests/ui/resolve/open-ns-1.rs b/tests/ui/resolve/open-ns-1.rs index e77ddbe58122d..882c23c234e4c 100644 --- a/tests/ui/resolve/open-ns-1.rs +++ b/tests/ui/resolve/open-ns-1.rs @@ -1,3 +1,4 @@ +//@ check-pass //@ aux-crate:my_api=open-ns-my_api.rs //@ aux-crate:my_api::utils=open-ns-my_api_utils.rs //@ aux-crate:my_api::core=open-ns-my_api_core.rs @@ -6,14 +7,11 @@ use my_api::root_function; use my_api::utils::util; -//~^ ERROR unresolved import `my_api::utils` fn main() { let _ = root_function(); let _ = my_api::root_function(); let _ = my_api::utils::utils_helper(); - //~^ ERROR cannot find `utils` in `my_api` [E0433] let _ = util::util_mod_helper(); let _ = my_api::core::core_fn(); - //~^ ERROR cannot find `core` in `my_api` [E0433] } diff --git a/tests/ui/resolve/open-ns-1.stderr b/tests/ui/resolve/open-ns-1.stderr deleted file mode 100644 index 65b9c6a355ce0..0000000000000 --- a/tests/ui/resolve/open-ns-1.stderr +++ /dev/null @@ -1,22 +0,0 @@ -error[E0432]: unresolved import `my_api::utils` - --> $DIR/open-ns-1.rs:8:13 - | -LL | use my_api::utils::util; - | ^^^^^ could not find `utils` in `my_api` - -error[E0433]: cannot find `utils` in `my_api` - --> $DIR/open-ns-1.rs:14:21 - | -LL | let _ = my_api::utils::utils_helper(); - | ^^^^^ could not find `utils` in `my_api` - -error[E0433]: cannot find `core` in `my_api` - --> $DIR/open-ns-1.rs:17:21 - | -LL | let _ = my_api::core::core_fn(); - | ^^^^ could not find `core` in `my_api` - -error: aborting due to 3 previous errors - -Some errors have detailed explanations: E0432, E0433. -For more information about an error, try `rustc --explain E0432`. diff --git a/tests/ui/resolve/open-ns-12.rs b/tests/ui/resolve/open-ns-12.rs new file mode 100644 index 0000000000000..f1695f31f7542 --- /dev/null +++ b/tests/ui/resolve/open-ns-12.rs @@ -0,0 +1,14 @@ +//@ check-pass +//@ aux-crate: my_api=open-ns-my_api.rs +//@ aux-crate: my_api::utils=open-ns-my_api_utils.rs +//@ compile-flags: -Z namespaced-crates +//@ edition: 2024 + +extern crate my_api; + +use my_api::utils::utils_helper; + +fn main() { + let _ = utils_helper(); + let _ = my_api::utils::utils_helper(); +} diff --git a/tests/ui/resolve/open-ns-2.rs b/tests/ui/resolve/open-ns-2.rs index 6165a4102be07..40d0e0cc4802e 100644 --- a/tests/ui/resolve/open-ns-2.rs +++ b/tests/ui/resolve/open-ns-2.rs @@ -1,3 +1,4 @@ +//@ check-pass //@ aux-crate: my_api=open-ns-my_api.rs //@ aux-crate: my_api::utils=open-ns-my_api_utils.rs //@ aux-crate: my_api::core=open-ns-my_api_core.rs @@ -5,9 +6,7 @@ //@ edition: 2024 use my_api::core::{core_fn, core_fn2}; -//~^ ERROR unresolved import `my_api::core` [E0432] use my_api::utils::*; -//~^ ERROR unresolved import `my_api::utils` [E0432] use my_api::*; fn main() { diff --git a/tests/ui/resolve/open-ns-2.stderr b/tests/ui/resolve/open-ns-2.stderr deleted file mode 100644 index 0e221234c5179..0000000000000 --- a/tests/ui/resolve/open-ns-2.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0432]: unresolved import `my_api::core` - --> $DIR/open-ns-2.rs:7:13 - | -LL | use my_api::core::{core_fn, core_fn2}; - | ^^^^ could not find `core` in `my_api` - -error[E0432]: unresolved import `my_api::utils` - --> $DIR/open-ns-2.rs:9:13 - | -LL | use my_api::utils::*; - | ^^^^^ could not find `utils` in `my_api` - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/resolve/open-ns-4.rs b/tests/ui/resolve/open-ns-4.rs index 4db3ad4c80a1d..dde0391ca9e1e 100644 --- a/tests/ui/resolve/open-ns-4.rs +++ b/tests/ui/resolve/open-ns-4.rs @@ -1,12 +1,12 @@ -// This tests that namespaced crates are shadowed. +// Tests fully qualified paths through an existing root crate. //@ aux-crate: my_api=open-ns-my_api.rs //@ aux-crate: my_api::utils=open-ns-my_api_utils.rs //@ compile-flags: -Z namespaced-crates //@ edition: 2024 +//@ check-pass fn main() { let _ = my_api::root_function(); let _ = my_api::utils::utils_helper(); - //~^ ERROR cannot find `utils` in `my_api` [E0433] } diff --git a/tests/ui/resolve/open-ns-4.stderr b/tests/ui/resolve/open-ns-4.stderr deleted file mode 100644 index 2e6872c57986f..0000000000000 --- a/tests/ui/resolve/open-ns-4.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0433]: cannot find `utils` in `my_api` - --> $DIR/open-ns-4.rs:10:21 - | -LL | let _ = my_api::utils::utils_helper(); - | ^^^^^ could not find `utils` in `my_api` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0433`. diff --git a/tests/ui/resolve/open-ns-7.rs b/tests/ui/resolve/open-ns-7.rs index cf16c594fa40b..edb3b544b91fc 100644 --- a/tests/ui/resolve/open-ns-7.rs +++ b/tests/ui/resolve/open-ns-7.rs @@ -1,14 +1,13 @@ -// Tests that namespaced crates cannot be resolved if shadowed. +// Tests that a namespaced child crate resolves through an existing root crate. //@ aux-crate: my_api=open-ns-my_api.rs //@ aux-crate: my_api::utils=open-ns-my_api_utils.rs //@ compile-flags: -Z namespaced-crates //@ edition: 2024 +//@ check-pass use my_api::utils::utils_helper; -//~^ ERROR unresolved import `my_api::utils` [E0432] fn main() { let _ = my_api::utils::utils_helper(); - //~^ ERROR cannot find `utils` in `my_api` [E0433] } diff --git a/tests/ui/resolve/open-ns-7.stderr b/tests/ui/resolve/open-ns-7.stderr deleted file mode 100644 index b008547539831..0000000000000 --- a/tests/ui/resolve/open-ns-7.stderr +++ /dev/null @@ -1,16 +0,0 @@ -error[E0432]: unresolved import `my_api::utils` - --> $DIR/open-ns-7.rs:8:13 - | -LL | use my_api::utils::utils_helper; - | ^^^^^ could not find `utils` in `my_api` - -error[E0433]: cannot find `utils` in `my_api` - --> $DIR/open-ns-7.rs:12:21 - | -LL | let _ = my_api::utils::utils_helper(); - | ^^^^^ could not find `utils` in `my_api` - -error: aborting due to 2 previous errors - -Some errors have detailed explanations: E0432, E0433. -For more information about an error, try `rustc --explain E0432`.