From 5a6de3f020d91a4216103569c94ab68a2bbfe7f9 Mon Sep 17 00:00:00 2001 From: b-naber Date: Wed, 9 Apr 2025 13:41:25 +0000 Subject: [PATCH] add compiler support for RFC 3243 Co-authored-by: Eric Holk --- .../rustc_resolve/src/build_reduced_graph.rs | 132 ++++++++++- compiler/rustc_resolve/src/diagnostics.rs | 4 + compiler/rustc_resolve/src/ident.rs | 117 +++++++++- compiler/rustc_resolve/src/imports.rs | 4 +- compiler/rustc_resolve/src/lib.rs | 207 ++++++++++++++++-- compiler/rustc_resolve/src/macros.rs | 3 + compiler/rustc_session/src/options.rs | 2 +- compiler/rustc_session/src/output.rs | 2 +- .../resolve/auxiliary/open-ns-mod-my_api.rs | 9 + tests/ui/resolve/auxiliary/open-ns-my_api.rs | 3 + .../resolve/auxiliary/open-ns-my_api_core.rs | 15 ++ .../resolve/auxiliary/open-ns-my_api_utils.rs | 9 + tests/ui/resolve/open-ns-1.rs | 17 ++ tests/ui/resolve/open-ns-2.rs | 17 ++ tests/ui/resolve/open-ns-3.rs | 16 ++ tests/ui/resolve/open-ns-4.rs | 13 ++ tests/ui/resolve/open-ns-5.rs | 11 + tests/ui/resolve/open-ns-6.rs | 17 ++ tests/ui/resolve/open-ns-7.rs | 11 + tests/ui/unused-crate-deps/test-use-not-ok.rs | 11 + 20 files changed, 586 insertions(+), 34 deletions(-) create mode 100644 tests/ui/resolve/auxiliary/open-ns-mod-my_api.rs create mode 100644 tests/ui/resolve/auxiliary/open-ns-my_api.rs create mode 100644 tests/ui/resolve/auxiliary/open-ns-my_api_core.rs create mode 100644 tests/ui/resolve/auxiliary/open-ns-my_api_utils.rs create mode 100644 tests/ui/resolve/open-ns-1.rs create mode 100644 tests/ui/resolve/open-ns-2.rs create mode 100644 tests/ui/resolve/open-ns-3.rs create mode 100644 tests/ui/resolve/open-ns-4.rs create mode 100644 tests/ui/resolve/open-ns-5.rs create mode 100644 tests/ui/resolve/open-ns-6.rs create mode 100644 tests/ui/resolve/open-ns-7.rs create mode 100644 tests/ui/unused-crate-deps/test-use-not-ok.rs diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index cb328022c76db..b34797e105c88 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -23,8 +23,8 @@ use rustc_middle::metadata::ModChild; use rustc_middle::ty::Feed; use rustc_middle::{bug, ty}; use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind}; -use rustc_span::{Ident, Span, Symbol, kw, sym}; -use tracing::debug; +use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; +use tracing::{debug, instrument}; use crate::Namespace::{MacroNS, TypeNS, ValueNS}; use crate::def_collector::collect_definitions; @@ -70,6 +70,7 @@ impl<'ra, Id: Into> ToNameBinding<'ra> for (Res, ty::Visibility, Span impl<'ra, 'tcx> Resolver<'ra, 'tcx> { /// Defines `name` in namespace `ns` of module `parent` to be `def` if it is not yet defined; /// otherwise, reports an error. + #[instrument(level = "debug", skip(self, def))] pub(crate) fn define(&mut self, parent: Module<'ra>, ident: Ident, ns: Namespace, def: T) where T: ToNameBinding<'ra>, @@ -118,6 +119,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { return module.copied(); } + //if def_id.is_crate_root() && !def_id.is_local() { + // bug!( + // "expected module for external crate {:?} to be created via `create_module_for_external_crate`", + // def_id + // ); + //} + if !def_id.is_local() { // Query `def_kind` is not used because query system overhead is too expensive here. let def_kind = self.cstore().def_kind_untracked(def_id); @@ -143,6 +151,52 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { None } + /// Creates `Module` instance for an external crate. + /// `Module`s are usually created via `get_module`, but namespaced crates depend + /// on the crate name passed via `--extern`, whereas `get_module` uses the metadata's + /// crate name. We need a way to pass that name to a `Module` and this method does that. + #[instrument(level = "debug", skip(self))] + pub(crate) fn create_module_for_external_crate( + &mut self, + def_id: DefId, + ident: Ident, + finalize: bool, + ) -> Module<'ra> { + if let Some(module) = self.module_map.get(&def_id) { + return *module; + } + + if finalize { + self.crate_loader(|c| c.process_path_extern(ident.name, ident.span)); + } + + let def_kind = self.cstore().def_kind_untracked(def_id); + match def_kind { + DefKind::Mod => { + let parent = self + .tcx + .opt_parent(def_id) + .map(|parent_id| self.get_nearest_non_block_module(parent_id)); + // Query `expn_that_defined` is not used because + // hashing spans in its result is expensive. + let expn_id = self.cstore().expn_that_defined_untracked(def_id, self.tcx.sess); + let module = self.new_module( + parent, + ModuleKind::Def(def_kind, def_id, Some(ident.name)), + expn_id, + self.def_span(def_id), + // FIXME: Account for `#[no_implicit_prelude]` attributes. + parent.is_some_and(|module| module.no_implicit_prelude), + ); + + return module; + } + _ => { + bug!("expected DefKind::Mod for external crate"); + } + } + } + pub(crate) fn expn_def_scope(&mut self, expn_id: ExpnId) -> Module<'ra> { match expn_id.expn_data().macro_def_id { Some(def_id) => self.macro_def_scope(def_id), @@ -196,7 +250,33 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { visitor.parent_scope.macro_rules } + // Builds the reduced graph for an external crate. + // It builds the graph for each child module of the crate and it also checks whether + // namespaced crates with the same base name exist. If any exist it builds a graph for + // each of those crates. + #[instrument(level = "debug", skip(self))] pub(crate) fn build_reduced_graph_external(&mut self, module: Module<'ra>) { + // Check if `module`'s name is the base crate name for a namespaced crate + // (i.e. `my_api` for the namespaced crates `my_api::utils` and `my_api::core`), + // and process the extern crate for the namespaced crates if they exist. + if let Some(module_name) = module.kind.name() { + let namespace_crate_name = self + .namespaced_crate_names + .get(module_name.as_str()) + .map(|names| names.iter().map(|s| s.to_string().clone()).collect::>()); + + if let Some(namespaced_crate_names) = namespace_crate_name { + debug!(?namespaced_crate_names); + for namespaced_crate_name in namespaced_crate_names { + let parent_scope = ParentScope::module(module, self); + self.build_reduced_graph_for_namespaced_crate( + &namespaced_crate_name, + parent_scope, + ); + } + } + } + for child in self.tcx.module_children(module.def_id()) { let parent_scope = ParentScope::module(module, self); self.build_reduced_graph_for_external_crate_res(child, parent_scope) @@ -273,6 +353,43 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { | Res::Err => bug!("unexpected resolution: {:?}", res), } } + + // Builds the reduced graph for a namespaced external crate (crate names like `foo::bar`). + #[instrument(level = "debug", skip(self))] + fn build_reduced_graph_for_namespaced_crate( + &mut self, + name: &str, + parent_scope: ParentScope<'ra>, + ) { + let crate_id = self.crate_loader(|c| { + c.maybe_process_path_extern(Symbol::intern(&name)) + .expect(&format!("no crate_num for namespaced crate {}", name)) + }); + + let module = self.create_module_for_external_crate( + crate_id.as_def_id(), + Ident::from_str(name), + false, + ); + let ident = Ident::from_str( + name.split("::").nth(1).expect("namespaced crate name has unexpected form"), + ); + let parent = parent_scope.module; + let res = module.res().expect("namespaced crate has no Res").expect_non_local(); + let expansion = parent_scope.expansion; + + match res { + Res::Def(DefKind::Mod, _) => { + self.define( + parent, + ident, + TypeNS, + (module, ty::Visibility::::Public, DUMMY_SP, expansion), + ); + } + _ => bug!("expected namespaced crate to have Res Def(DefKind::Mod)"), + } + } } struct BuildReducedGraphVisitor<'a, 'ra, 'tcx> { @@ -469,6 +586,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { } } + #[instrument(level = "debug", skip(self))] fn build_reduced_graph_for_use_tree( &mut self, // This particular use tree @@ -482,11 +600,6 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { vis: ty::Visibility, root_span: Span, ) { - debug!( - "build_reduced_graph_for_use_tree(parent_prefix={:?}, use_tree={:?}, nested={})", - parent_prefix, use_tree, nested - ); - // Top level use tree reuses the item's id and list stems reuse their parent // use tree's ids, so in both cases their visibilities are already filled. if nested && !list_stem { @@ -589,7 +702,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { let crate_root = self.r.resolve_crate_root(source.ident); let crate_name = match crate_root.kind { ModuleKind::Def(.., name) => name, - ModuleKind::Block => unreachable!(), + ModuleKind::Block | ModuleKind::NamespaceCrate(..) => unreachable!(), }; // HACK(eddyb) unclear how good this is, but keeping `$crate` // in `source` breaks `tests/ui/imports/import-crate-var.rs`, @@ -897,6 +1010,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { } } + #[instrument(level = "debug", skip(self))] fn build_reduced_graph_for_extern_crate( &mut self, orig_name: Option, @@ -922,7 +1036,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'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()) + self.r.create_module_for_external_crate(crate_id.as_def_id(), ident, false) }) } .map(|module| { diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 363a75911ad43..54d3587a00941 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -222,6 +222,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // indirectly *calls* the resolver, and would cause a query cycle. ModuleKind::Def(kind, _, _) => kind.descr(parent.def_id()), ModuleKind::Block => "block", + ModuleKind::NamespaceCrate(..) => { + // `NamespaceCrate`s are virtual modules + unreachable!(); + } }; let (name, span) = diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 180d6af219d11..4912cf3445c0b 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -575,7 +575,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { None => Err(Determinacy::Determined), }, Scope::ExternPrelude => { - match this.extern_prelude_get(ident, finalize.is_some()) { + match this.extern_prelude_get( + ident, + finalize.is_some(), + Some(*parent_scope), + ) { Some(binding) => Ok((binding, Flags::empty())), None => Err(Determinacy::determined( this.graph_root.unexpanded_invocations.borrow().is_empty(), @@ -796,8 +800,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) } - /// Attempts to resolve `ident` in namespaces `ns` of `module`. - /// Invariant: if `finalize` is `Some`, expansion and import resolution must be complete. #[instrument(level = "debug", skip(self))] fn resolve_ident_in_module_unadjusted( &mut self, @@ -831,7 +833,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { assert_eq!(shadowing, Shadowing::Unrestricted); return if ns != TypeNS { Err((Determined, Weak::No)) - } else if let Some(binding) = self.extern_prelude_get(ident, finalize.is_some()) { + } else if let Some(binding) = + self.extern_prelude_get(ident, finalize.is_some(), Some(*parent_scope)) + { Ok(binding) } else if !self.graph_root.unexpanded_invocations.borrow().is_empty() { // Macro-expanded `extern crate` items can add names to extern prelude. @@ -866,9 +870,79 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } }; + let result = self.try_resolve_ident_in_module_unadjusted( + module, + ident, + ns, + parent_scope, + shadowing, + finalize, + ignore_binding, + ignore_import, + ); + + // If resolution failed for the module, check if we can resolve ident to a + // namespaced crate. Suppose we have the crate `my_api` and the namespaced + // crate `my_api::utils` as dependencies and `my_api` doesn't have a module + // named `utils`. The `Ident` `utils` will be unresolvable for the `Module` + // corresponding to `my_api`. If there's a crate named `my_api::utils` in the + // extern prelude we resolve `my_api::utils` to that crate then. If `my_api` + // does have a module named `utils` and a namespaced crate `my_api::utils` + // exists, then we report a name conflict error (via `build_reduced_graph_external`). + if let Err(err) = result { + if let Some(module_name) = module.kind.name() { + let Some(namespaced_crate_names) = + self.namespaced_crate_names.get(module_name.as_str()) + else { + return Err(err); + }; + + debug!(?namespaced_crate_names); + for namespaced_crate_name in namespaced_crate_names { + if namespaced_crate_name + .split("::") + .nth(1) + .expect("namespaced crate name has wrong form") + == ident.as_str() + { + return self.resolve_namespaced_crate( + namespaced_crate_name, + finalize, + module.parent.map(|m| ParentScope::module(m, self)), + ); + } + } + + return Err(err); + } else { + return Err(err); + } + } + + result + } + + /// Attempts to resolve `ident` in namespaces `ns` of `module`. + /// Invariant: if `finalize` is `Some`, expansion and import resolution must be complete. + #[instrument(level = "debug", skip(self))] + fn try_resolve_ident_in_module_unadjusted( + &mut self, + module: Module<'ra>, + ident: Ident, + ns: Namespace, + parent_scope: &ParentScope<'ra>, + shadowing: Shadowing, + finalize: Option, + // This binding should be ignored during in-module resolution, so that we don't get + // "self-confirming" import resolutions during import validation and checking. + ignore_binding: Option>, + ignore_import: Option>, + ) -> Result, (Determinacy, Weak)> { let key = BindingKey::new(ident, ns); - let resolution = - self.resolution(module, key).try_borrow_mut().map_err(|_| (Determined, Weak::No))?; // This happens when there is a cycle of imports. + let resolution = self.resolution(module, key).try_borrow_mut().map_err(|_| { + debug!("got resolution error for module {:?}", module); + (Determined, Weak::No) + })?; // This happens when there is a cycle of imports. // If the primary binding is unusable, search further and return the shadowed glob // binding if it exists. What we really want here is having two separate scopes in @@ -1034,6 +1108,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } + debug!("no resolution"); // --- From now on we have no resolution. --- // Now we are in situation when new item/import can appear only from a glob or a macro @@ -1100,6 +1175,34 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Err((Determined, Weak::No)) } + // Resolves a namespaced crate. + // In general namespaced crates, i.e. those with names of the form `foo::bar`, + // are resolved in the following way. When intializing the `Resolver`, we + // collect information on them in the field `namespaced_crate_names`, + // which maps the base crate name to a list of namespaced crates with that + // base. Suppose we have a base crate named `my_api` and two namespaced + // crates named `my_api::utils` and `my_api::core`. When building the reduced + // graph for the base crate `my_api` (in `build_reduced_graph_for_external`), + // we check whether any namespaced crates exist for it. If any do, we use + // `build_reduced_graph_for_namespaced_crate` to build a graph for them. + // In the case in which the base crate isn't a dependency and we try to resolve + // `my_api` in the path `my_api::utils`, then we resolve the base name to a virtual + // module `ModuleKind::NamespaceCrate`. `resolve_ident_in_module` + #[instrument(skip(self, finalize))] + fn resolve_namespaced_crate( + &mut self, + namespaced_crate_name: &str, + finalize: Option, + parent_scope: Option>, + ) -> Result, (Determinacy, Weak)> { + self.extern_prelude_get( + Ident::from_str(namespaced_crate_name), + finalize.is_some(), + parent_scope, + ) + .ok_or((Determinacy::Determined, Weak::No)) + } + /// Validate a local resolution (from ribs). #[instrument(level = "debug", skip(self, all_ribs))] fn validate_res_from_ribs( @@ -1550,6 +1653,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ); } + debug!(?module); let binding = if let Some(module) = module { self.resolve_ident_in_module( module, @@ -1597,6 +1701,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) }; + debug!("resolve_path_with_ribs binding: {:?}", binding); match binding { Ok(binding) => { if segment_idx == 1 { diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 762e08b2be5f0..0cc7d18fab9f6 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -22,7 +22,7 @@ use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::hygiene::LocalExpnId; use rustc_span::{Ident, Span, Symbol, kw, sym}; use smallvec::SmallVec; -use tracing::debug; +use tracing::{debug, instrument}; use crate::Determinacy::{self, *}; use crate::Namespace::*; @@ -783,6 +783,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Segment::names_to_string(&import.module_path), module_to_string(import.parent_scope.module).unwrap_or_else(|| "???".to_string()), ); + let module = if let Some(module) = import.imported_module.get() { module } else { @@ -876,6 +877,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { /// /// Optionally returns an unresolved import error. This error is buffered and used to /// consolidate multiple unresolved import errors into a single diagnostic. + #[instrument(level = "debug", skip(self))] fn finalize_import(&mut self, import: Import<'ra>) -> Option { let ignore_binding = match &import.kind { ImportKind::Single { target_bindings, .. } => target_bindings[TypeNS].get(), diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index b121755acd9c3..4f9b82f116a3b 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -60,18 +60,18 @@ use rustc_metadata::creader::{CStore, CrateLoader}; use rustc_middle::metadata::ModChild; use rustc_middle::middle::privacy::EffectiveVisibilities; use rustc_middle::query::Providers; -use rustc_middle::span_bug; use rustc_middle::ty::{ self, DelegationFnSig, Feed, MainDefinition, RegisteredTools, ResolverGlobalCtxt, ResolverOutputs, TyCtxt, TyCtxtFeed, }; +use rustc_middle::{bug, span_bug}; use rustc_query_system::ich::StableHashingContext; use rustc_session::lint::builtin::PRIVATE_MACRO_USE; use rustc_session::lint::{BuiltinLintDiag, LintBuffer}; use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind, SyntaxContext, Transparency}; use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; use smallvec::{SmallVec, smallvec}; -use tracing::debug; +use tracing::{debug, instrument}; type Res = def::Res; @@ -508,6 +508,14 @@ enum ModuleKind { /// * A trait or an enum (it implicitly contains associated types, methods and variant /// constructors). Def(DefKind, DefId, Option), + /// A virtual module for an external crate that only exists in a namespaced crate name + /// and whose crate name cannot be resolved using the CrateLoader. This variant allows + /// the `Resolver` to infer that the namespaced crate needs to be loaded when resolving + /// paths. + /// Example: Suppose we have the namespaced crate `my_api::utils`, but `my_api` is not a + /// dependency. In that case we resolve the `Ident` `my_api` to a `Module` of kind + /// `NamespaceCrate`. + NamespaceCrate(Symbol, DefId), } impl ModuleKind { @@ -516,6 +524,7 @@ impl ModuleKind { match *self { ModuleKind::Block => None, ModuleKind::Def(.., name) => name, + ModuleKind::NamespaceCrate(name, _) => Some(name), } } } @@ -614,6 +623,7 @@ impl<'ra> ModuleData<'ra> { ) -> Self { let is_foreign = match kind { ModuleKind::Def(_, def_id, _) => !def_id.is_local(), + ModuleKind::NamespaceCrate(..) => true, ModuleKind::Block => false, }; ModuleData { @@ -668,6 +678,7 @@ impl<'ra> Module<'ra> { fn res(self) -> Option { match self.kind { ModuleKind::Def(kind, def_id, _) => Some(Res::Def(kind, def_id)), + ModuleKind::NamespaceCrate(_, def_id) => Some(Res::Def(DefKind::Mod, def_id)), _ => None, } } @@ -680,6 +691,7 @@ impl<'ra> Module<'ra> { fn opt_def_id(self) -> Option { match self.kind { ModuleKind::Def(_, def_id, _) => Some(def_id), + ModuleKind::NamespaceCrate(_, def_id) => Some(def_id), _ => None, } } @@ -926,6 +938,9 @@ impl<'ra> NameBindingData<'ra> { { def_id.is_crate_root() } + NameBindingKind::Module(module) if let ModuleKind::NamespaceCrate(..) = module.kind => { + true + } _ => false, } } @@ -999,7 +1014,7 @@ impl<'ra> NameBindingData<'ra> { } } -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] struct ExternPreludeEntry<'ra> { binding: Option>, introduced_by_item: bool, @@ -1043,6 +1058,9 @@ pub struct Resolver<'ra, 'tcx> { prelude: Option>, extern_prelude: FxIndexMap>, + // Extern crates with names of the form `foo::bar` + namespaced_crate_names: FxHashMap<&'tcx str, Vec<&'tcx str>>, + /// N.B., this is used only for better diagnostics, not name resolution itself. field_names: LocalDefIdMap>, @@ -1437,14 +1455,53 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut invocation_parents = FxHashMap::default(); invocation_parents.insert(LocalExpnId::ROOT, InvocationParent::ROOT); + let namespaced_crate_names: FxHashMap<&str, Vec<&str>> = tcx + .sess + .opts + .externs + .iter() + .filter(|(name, _)| is_namespaced_crate(name)) + .map(|(name, _)| { + let main_crate_name = name + .split("::") + .nth(0) + .expect(&format!("namespaced crate name has unexpected form {}", name)); + (main_crate_name, name.as_str()) + }) + .fold( + FxHashMap::default(), // Start with an empty map + |mut acc_map, (main_name, full_name)| { + acc_map.entry(main_name).or_insert_with(Vec::new).push(full_name); // Push the full namespaced name onto the Vec + acc_map + }, + ); + + // We use the main crate name for namespaced crate names (i.e. `foo` in `foo::bar`) + // in `extern_prelude` to allow us to resolve the `Ident` corresponding to the main crate. + // See the documentation of `build_reduced_graph_external` for how we resolve + // namespaced crate names. let mut extern_prelude: FxIndexMap> = tcx .sess .opts .externs .iter() .filter(|(_, entry)| entry.add_prelude) - .map(|(name, _)| (Ident::from_str(name), Default::default())) + .map(|(name, _)| { + if is_namespaced_crate(name) { + let crate_name = + name.split("::").nth(0).expect("namespaced crate name should contain '::'"); + + if !namespaced_crate_names.contains_key(crate_name) { + panic!("{} should be in `namespaced_crates`", name); + } + + (Ident::from_str(crate_name), Default::default()) + } else { + (Ident::from_str(name), Default::default()) + } + }) .collect(); + debug!(?extern_prelude); if !attr::contains_name(attrs, sym::no_core) { extern_prelude.insert(Ident::with_dummy_span(sym::core), Default::default()); @@ -1468,6 +1525,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { graph_root, prelude: None, extern_prelude, + namespaced_crate_names, field_names: Default::default(), field_visibility_spans: FxHashMap::default(), @@ -1869,6 +1927,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { BindingKey { ident, ns, disambiguator } } + #[instrument(level = "debug", skip(self))] fn resolutions(&mut self, module: Module<'ra>) -> &'ra Resolutions<'ra> { if module.populate_on_access.get() { module.populate_on_access.set(false); @@ -1907,6 +1966,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { false } + #[instrument(level = "debug", skip(self))] fn record_use(&mut self, ident: Ident, used_binding: NameBinding<'ra>, used: Used) { self.record_use_inner(ident, used_binding, used, used_binding.warn_ambiguity); } @@ -1933,6 +1993,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { self.ambiguity_errors.push(ambiguity_error); } } + if let NameBindingKind::Import { import, binding } = used_binding.kind { if let ImportKind::MacroUse { warn_private: true } = import.kind { self.lint_buffer().buffer_lint( @@ -1976,8 +2037,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } + #[instrument(skip(self), level = "debug")] fn resolve_crate_root(&mut self, ident: Ident) -> Module<'ra> { - debug!("resolve_crate_root({:?})", ident); let mut ctxt = ident.span.ctxt(); let mark = if ident.name == kw::DollarCrate { // When resolving `$crate` from a `macro_rules!` invoked in a `macro`, @@ -2078,6 +2139,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { vis.is_accessible_from(module.nearest_parent_mod(), self.tcx) } + #[instrument(level = "debug", skip(self, module))] fn set_binding_parent_module(&mut self, binding: NameBinding<'ra>, module: Module<'ra>) { if let Some(old_module) = self.binding_parent_modules.insert(binding, module) { if module != old_module { @@ -2106,7 +2168,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } - fn extern_prelude_get(&mut self, ident: Ident, finalize: bool) -> Option> { + #[instrument(level = "debug", skip(self))] + fn extern_prelude_get( + &mut self, + ident: Ident, + finalize: bool, + parent_scope: Option>, + ) -> Option> { if ident.is_path_segment_keyword() { // Make sure `self`, `super` etc produce an error when passed to here. return None; @@ -2117,29 +2185,70 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { Some(if let Some(binding) = entry.binding { if finalize { if !entry.is_import() { - self.crate_loader(|c| c.process_path_extern(ident.name, ident.span)); + match binding.kind { + NameBindingKind::Module(module) => match module.kind { + ModuleKind::NamespaceCrate(..) => { + // This is a virtual module + } + _ => { + self.crate_loader(|c| { + c.process_path_extern(ident.name, ident.span) + }); + } + }, + _ => { + self.crate_loader(|c| { + c.process_path_extern(ident.name, ident.span) + }); + } + } } else if entry.introduced_by_item { self.record_use(ident, binding, Used::Other); } } binding } else { - let crate_id = if finalize { - let Some(crate_id) = - self.crate_loader(|c| c.process_path_extern(ident.name, ident.span)) - else { - return Some(self.dummy_binding); - }; - crate_id + let crate_root = if finalize { + if let Some(crate_id) = + self.crate_loader(|c| c.maybe_process_path_extern(ident.name)) + { + self.create_module_for_external_crate(crate_id.as_def_id(), ident, finalize) + } else { + if self.namespaced_crate_names.contains_key(ident.name.as_str()) { + self.create_namespaced_crate_module(ident, parent_scope) + } else { + // no crate found. Run `process_path_extern` to output error message + self.crate_loader(|c| c.process_path_extern(ident.name, ident.span)); + return Some(self.dummy_binding); + } + } } else { - self.crate_loader(|c| c.maybe_process_path_extern(ident.name))? + let opt_crate_id = + self.crate_loader(|c| c.maybe_process_path_extern(ident.name)); + + // This could be a namespaced crate + if opt_crate_id.is_none() { + if self.namespaced_crate_names.contains_key(ident.name.as_str()) { + self.create_namespaced_crate_module(ident, parent_scope) + } else { + return None; + } + } else { + self.create_module_for_external_crate( + opt_crate_id.unwrap().as_def_id(), + ident, + finalize, + ) + } }; - let crate_root = self.expect_module(crate_id.as_def_id()); + + debug!(?crate_root); let vis = ty::Visibility::::Public; (crate_root, vis, DUMMY_SP, LocalExpnId::ROOT).to_name_binding(self.arenas) }) }); + debug!(?binding); if let Some(entry) = self.extern_prelude.get_mut(&norm_ident) { entry.binding = binding; } @@ -2147,6 +2256,68 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { binding } + #[instrument(level = "debug", skip(self))] + fn create_namespaced_crate_module( + &mut self, + ident: Ident, + parent_scope: Option>, + ) -> Module<'ra> { + if parent_scope.is_none() { + bug!("namespaced crate should have a parent") + } + + let Some(namespaced_crate_names) = self + .namespaced_crate_names + .get(ident.name.as_str()) + .map(|cnames| cnames.iter().map(|cname| cname.to_string().clone()).collect::>()) + else { + bug!("shouldn't have called `create_namespaced_crate_module` for {}", ident) + }; + + // Make sure the namespaced crates exist + let mut crate_nums = vec![]; + for crate_name in &namespaced_crate_names { + let Some(crate_num) = + self.crate_loader(|c| c.maybe_process_path_extern(Symbol::intern(&crate_name))) + else { + // FIXME: This isn't a bug, this should be reported as an error just like any other + // time a crate is not found. + bug!( + "namespaced crate {} doesn't exist. namespaced_crate_names: {:?}", + crate_name, + namespaced_crate_names + ); + }; + + crate_nums.push(crate_num); + } + + // When we have multiple namespaced crates with the same main crate name, we use + // the first namespaced crate's DefId. + // FIXME We never use a `NamespaceCrate`'s DefId in the compiler, but rustdoc might + // depend on it? We would need to propagate the full path in which an `Ident` appears to + // identify the correct underlying namespaced crate. + let namespaced_crate_def_id = crate_nums.iter().nth(0).unwrap().as_def_id(); + let parent_module = parent_scope.unwrap().module; + let namespaced_crate = self.expect_module(namespaced_crate_def_id); + let span = self.def_span(namespaced_crate_def_id); + let kind = ModuleKind::NamespaceCrate(ident.name, namespaced_crate_def_id); + let expn_id = namespaced_crate.expansion; + let no_implicit_prelude = parent_module.no_implicit_prelude; + + let namespaced_crate_module = + self.new_module(Some(parent_module), kind, expn_id, span, no_implicit_prelude); + + #[cfg(debug_assertions)] + match &namespaced_crate_module.kind { + ModuleKind::NamespaceCrate(name, _) => debug!("NameSpaceCrate: {:?}", name), + _ => unreachable!(), + } + + debug!(?namespaced_crate_module); + namespaced_crate_module + } + /// 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, @@ -2266,6 +2437,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } +pub(crate) fn is_namespaced_crate(crate_name: &str) -> bool { + crate_name.contains("::") +} + fn names_to_string(names: impl Iterator) -> String { let mut result = String::new(); for (i, name) in names.filter(|name| *name != kw::PathRoot).enumerate() { diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index c58f84805720d..49b06fdc9b348 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -1077,6 +1077,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { "the crate root".to_string() } } + ModuleKind::NamespaceCrate(name, _) => { + format!("{}", name) + } ModuleKind::Block => "this scope".to_string(), }; self.tcx.sess.psess.buffer_lint( diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 36eee5f308656..dd3f4dbef912d 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2335,7 +2335,7 @@ options! { mutable_noalias: bool = (true, parse_bool, [TRACKED], "emit noalias metadata for mutable references (default: yes)"), namespaced_crates: bool = (false, parse_bool, [TRACKED], - "allow crates to be namespaced by other crates (default: no)"), + "allow crates to be nested in other crates (default: no)"), next_solver: NextSolverConfig = (NextSolverConfig::default(), parse_next_solver_config, [TRACKED], "enable and configure the next generation trait solver used by rustc"), nll_facts: bool = (false, parse_bool, [UNTRACKED], diff --git a/compiler/rustc_session/src/output.rs b/compiler/rustc_session/src/output.rs index 46dae9144cd40..633394fdc49eb 100644 --- a/compiler/rustc_session/src/output.rs +++ b/compiler/rustc_session/src/output.rs @@ -62,7 +62,7 @@ pub fn validate_crate_name(sess: &Session, crate_name: Symbol, span: Option String { + "my_api root!".to_string() +} diff --git a/tests/ui/resolve/auxiliary/open-ns-my_api.rs b/tests/ui/resolve/auxiliary/open-ns-my_api.rs new file mode 100644 index 0000000000000..be4bf31f0fbcd --- /dev/null +++ b/tests/ui/resolve/auxiliary/open-ns-my_api.rs @@ -0,0 +1,3 @@ +pub fn root_function() -> String { + "my_api root!".to_string() +} diff --git a/tests/ui/resolve/auxiliary/open-ns-my_api_core.rs b/tests/ui/resolve/auxiliary/open-ns-my_api_core.rs new file mode 100644 index 0000000000000..41418f1516f60 --- /dev/null +++ b/tests/ui/resolve/auxiliary/open-ns-my_api_core.rs @@ -0,0 +1,15 @@ +// #![crate_name = "my_api::core"] + +pub mod util { + pub fn core_mod_fn() -> String { + format!("core_fn from my_api::core::util",) + } +} + +pub fn core_fn() -> String { + format!("core_fn from my_api::core!",) +} + +pub fn core_fn2() -> String { + format!("core_fn2 from my_api::core!",) +} diff --git a/tests/ui/resolve/auxiliary/open-ns-my_api_utils.rs b/tests/ui/resolve/auxiliary/open-ns-my_api_utils.rs new file mode 100644 index 0000000000000..8ac0f46549322 --- /dev/null +++ b/tests/ui/resolve/auxiliary/open-ns-my_api_utils.rs @@ -0,0 +1,9 @@ +pub mod util { + pub fn util_mod_helper() -> String { + format!("Helper from my_api::utils::util",) + } +} + +pub fn utils_helper() -> String { + format!("Helper from my_api::utils!",) +} diff --git a/tests/ui/resolve/open-ns-1.rs b/tests/ui/resolve/open-ns-1.rs new file mode 100644 index 0000000000000..0268990b17e0b --- /dev/null +++ b/tests/ui/resolve/open-ns-1.rs @@ -0,0 +1,17 @@ +//@ build-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 +//@ compile-flags: -Z namespaced-crates +//@ edition: 2024 + +use my_api::root_function; +use my_api::utils::util; + +fn main() { + let _ = root_function(); + let _ = my_api::root_function(); + let _ = my_api::utils::utils_helper(); + let _ = util::util_mod_helper(); + let _ = my_api::core::core_fn(); +} diff --git a/tests/ui/resolve/open-ns-2.rs b/tests/ui/resolve/open-ns-2.rs new file mode 100644 index 0000000000000..5cc0ea38fd1d3 --- /dev/null +++ b/tests/ui/resolve/open-ns-2.rs @@ -0,0 +1,17 @@ +//@ build-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 +//@ compile-flags: -Z namespaced-crates +//@ edition: 2024 + +use my_api::core::{core_fn, core_fn2}; +use my_api::utils::*; +use my_api::*; + +fn main() { + let _ = root_function(); + let _ = utils_helper(); + let _ = core_fn(); + let _ = core_fn2(); +} diff --git a/tests/ui/resolve/open-ns-3.rs b/tests/ui/resolve/open-ns-3.rs new file mode 100644 index 0000000000000..f95bfc84d66ab --- /dev/null +++ b/tests/ui/resolve/open-ns-3.rs @@ -0,0 +1,16 @@ +// This test should fail with `utils` being defined multiple times, since open-ns-mod-my_api.rs +// includes a `mod utils` and we also include open-ns-my_api_utils.rs as a namespaced crate at +// my_api::utils. + +//@ aux-crate: my_api::utils=open-ns-my_api_utils.rs +//@ compile-flags: -Z namespaced-crates +//@ edition: 2024 +//@ build-pass + +fn main() { + // FIXME test should fail with conflict here, but unsure how to do error annotation + // in auxiliary crate. + // let _ = my_api::root_function(); + // let _ = my_api::utils::root_helper(); + let _ = my_api::utils::utils_helper(); +} diff --git a/tests/ui/resolve/open-ns-4.rs b/tests/ui/resolve/open-ns-4.rs new file mode 100644 index 0000000000000..0c0138d782946 --- /dev/null +++ b/tests/ui/resolve/open-ns-4.rs @@ -0,0 +1,13 @@ +// This test makes sure namespaced crates work if we don't use any use statements but instead fully +// qualify all references. + +//@ 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 +//@ build-pass + +fn main() { + let _ = my_api::root_function(); + let _ = my_api::utils::utils_helper(); +} diff --git a/tests/ui/resolve/open-ns-5.rs b/tests/ui/resolve/open-ns-5.rs new file mode 100644 index 0000000000000..7cfd46faf8e98 --- /dev/null +++ b/tests/ui/resolve/open-ns-5.rs @@ -0,0 +1,11 @@ +// This test makes sure namespaced crates work if we don't use any use statements but instead fully +// qualify all references and we only have a single namespaced crate with no parent crate. + +//@ aux-crate: my_api::utils=open-ns-my_api_utils.rs +//@ compile-flags: -Z namespaced-crates +//@ edition: 2024 +//@ build-pass + +fn main() { + let _ = my_api::utils::utils_helper(); +} diff --git a/tests/ui/resolve/open-ns-6.rs b/tests/ui/resolve/open-ns-6.rs new file mode 100644 index 0000000000000..a980a0e657122 --- /dev/null +++ b/tests/ui/resolve/open-ns-6.rs @@ -0,0 +1,17 @@ +// Tests that namespaced crate names work inside macros. + +//@ aux-crate: my_api::utils=open-ns-my_api_utils.rs +//@ compile-flags: -Z namespaced-crates +//@ edition: 2024 +//@ build-pass + +macro_rules! import_and_call { + ($import_path:path, $fn_name:ident) => {{ + use $import_path; + $fn_name(); + }}; +} + +fn main() { + import_and_call!(my_api::utils::utils_helper, utils_helper) +} diff --git a/tests/ui/resolve/open-ns-7.rs b/tests/ui/resolve/open-ns-7.rs new file mode 100644 index 0000000000000..02d2e0c2e64bf --- /dev/null +++ b/tests/ui/resolve/open-ns-7.rs @@ -0,0 +1,11 @@ +//@ aux-crate: my_api::utils=open-ns-my_api_utils.rs +//@ compile-flags: -Z namespaced-crates +//@ edition: 2024 +//@ build-pass + +use my_api; // FIXME can be resolved even though it (maybe?) shouldn't be +use my_api::utils::utils_helper; + +fn main() { + let _ = utils_helper(); +} diff --git a/tests/ui/unused-crate-deps/test-use-not-ok.rs b/tests/ui/unused-crate-deps/test-use-not-ok.rs new file mode 100644 index 0000000000000..97f029971b2bd --- /dev/null +++ b/tests/ui/unused-crate-deps/test-use-not-ok.rs @@ -0,0 +1,11 @@ +//@ edition:2018 +//@ check-pass +//@ aux-crate:bar=bar.rs +//@ compile-flags:--test + +fn main() {} + +#[test] +fn test_bar() { + assert_eq!(bar::BAR, "bar"); +}