From 6eed10877da7d5b9f49d22a004a1b83803949b51 Mon Sep 17 00:00:00 2001 From: ecoricemon Date: Sat, 30 May 2026 02:56:09 +0900 Subject: [PATCH 1/5] refactor: Apply `use` resolve --- crates/syn-sem-name/src/db.rs | 701 +++++++++++++++++++- crates/syn-sem-name/src/def.rs | 11 + crates/syn-sem-name/src/scope.rs | 16 + crates/syn-sem-top/src/context.rs | 1 + crates/syn-sem-top/src/names.rs | 112 +++- crates/syn-sem-top/tests/name_resolution.rs | 168 ++++- 6 files changed, 983 insertions(+), 26 deletions(-) diff --git a/crates/syn-sem-name/src/db.rs b/crates/syn-sem-name/src/db.rs index 165228f..d6f48f4 100644 --- a/crates/syn-sem-name/src/db.rs +++ b/crates/syn-sem-name/src/db.rs @@ -1,9 +1,16 @@ use crate::{ - Def, DefId, DefKind, Import, ImportId, ImportKind, ImportStatus, Name, Namespace, Origin, - Scope, ScopeId, ScopeKind, Visibility, + Binding, Def, DefId, DefKind, Import, ImportId, ImportKind, ImportStatus, Name, Namespace, + Origin, Scope, ScopeId, ScopeKind, Visibility, }; use std::ops::{Index, IndexMut}; +const ALL_NAMESPACES: [Namespace; 4] = [ + Namespace::Type, + Namespace::Value, + Namespace::Macro, + Namespace::Lifetime, +]; + /// Result of resolving a name. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ResolveResult { @@ -88,6 +95,8 @@ impl<'cx> NameDb<'cx> { name, kind, parent_scope, + child_scope: None, + target: None, visibility, origin, }); @@ -101,6 +110,52 @@ impl<'cx> NameDb<'cx> { id } + /// Adds an import alias definition without binding it. + pub fn add_import_def( + &mut self, + parent_scope: ScopeId, + name: Option>, + visibility: Visibility, + origin: Origin, + target: DefId, + ) -> DefId { + let id = DefId::new(self.defs.len()); + self.defs.push(Def { + id, + name, + kind: DefKind::Use, + parent_scope, + child_scope: None, + target: Some(target), + visibility, + origin, + }); + id + } + + /// Returns an existing matching import alias or creates one. + pub fn get_or_add_import_def( + &mut self, + parent_scope: ScopeId, + name: Option>, + visibility: Visibility, + origin: Origin, + target: DefId, + ) -> DefId { + if let Some(def) = self.defs.iter().find(|def| { + def.kind == DefKind::Use + && def.parent_scope == parent_scope + && def.name == name + && def.visibility == visibility + && def.origin == origin + && def.target == Some(target) + }) { + return def.id; + } + + self.add_import_def(parent_scope, name, visibility, origin, target) + } + /// Adds an unresolved import. pub fn add_import( &mut self, @@ -123,6 +178,79 @@ impl<'cx> NameDb<'cx> { id } + /// Returns a mutable definition. + pub fn def_mut(&mut self, def: DefId) -> &mut Def<'cx> { + &mut self.defs[def.index()] + } + + /// Returns a mutable import. + pub fn import_mut(&mut self, import: ImportId) -> &mut Import<'cx> { + &mut self.imports[import.index()] + } + + /// Links a definition to a child scope that contains its importable members. + pub fn set_child_scope(&mut self, def: DefId, child_scope: ScopeId) { + self.def_mut(def).child_scope = Some(child_scope); + } + + /// Inserts a binding unless that exact definition is already present. + pub fn insert_unique_binding( + &mut self, + scope: ScopeId, + namespace: Namespace, + name: Name<'cx>, + def: DefId, + ) -> bool { + self[scope].bindings.insert_unique(namespace, name, def) + } + + /// Follows import aliases and returns the underlying target definition. + pub fn resolve_target(&self, mut def: DefId) -> DefId { + let mut remaining = self.defs.len(); + while remaining > 0 { + let Some(target) = self[def].target else { + return def; + }; + def = target; + remaining -= 1; + } + def + } + + /// Resolves all currently collected imports to local-crate bindings. + pub fn resolve_imports(&mut self) { + loop { + let mut changed = false; + for index in 0..self.imports.len() { + if self.imports[index].status != ImportStatus::Unresolved { + continue; + } + + match self.resolve_import(ImportId::new(index)) { + ImportResolve::Resolved => { + self.imports[index].status = ImportStatus::Resolved; + changed = true; + } + ImportResolve::Ambiguous => { + self.imports[index].status = ImportStatus::Ambiguous; + changed = true; + } + ImportResolve::Pending => {} + } + } + + if !changed { + break; + } + } + + for import in &mut self.imports { + if import.status == ImportStatus::Unresolved { + import.status = ImportStatus::NotFound; + } + } + } + /// Resolves a single-segment name lexically in one namespace. pub fn resolve_lexical( &self, @@ -160,6 +288,293 @@ impl<'cx> NameDb<'cx> { descendant = parent; } } + + fn resolve_import(&mut self, import: ImportId) -> ImportResolve { + let import_data = self[import].clone(); + match import_data.kind { + ImportKind::Single | ImportKind::Rename(_) => { + let Some(local_name) = self.import_local_name(&import_data) else { + return match self.resolve_path(import_data.scope, &import_data.source_path) { + PathResolve::Found(_) => ImportResolve::Resolved, + PathResolve::Ambiguous => ImportResolve::Ambiguous, + PathResolve::NotFound => ImportResolve::Pending, + }; + }; + + let bindings = match self.resolve_path(import_data.scope, &import_data.source_path) + { + PathResolve::Found(bindings) => bindings, + PathResolve::Ambiguous => return ImportResolve::Ambiguous, + PathResolve::NotFound => return ImportResolve::Pending, + }; + + let target = self.resolve_target(bindings[0].1); + let alias = self.get_or_add_import_def( + import_data.scope, + Some(local_name), + import_data.visibility, + import_data.origin, + target, + ); + for (namespace, _) in bindings { + self.insert_unique_binding(import_data.scope, namespace, local_name, alias); + } + ImportResolve::Resolved + } + ImportKind::Glob => { + let bindings = match self.resolve_path(import_data.scope, &import_data.source_path) + { + PathResolve::Found(bindings) => bindings, + PathResolve::Ambiguous => return ImportResolve::Ambiguous, + PathResolve::NotFound => return ImportResolve::Pending, + }; + + let [(_, target)] = bindings.as_slice() else { + return ImportResolve::Ambiguous; + }; + let target = self.resolve_target(*target); + let Some(child_scope) = self[target].child_scope else { + return ImportResolve::Pending; + }; + + let mut visible = Vec::new(); + for namespace in ALL_NAMESPACES { + for (&name, binding) in self[child_scope].bindings.map(namespace) { + for def in binding.iter() { + if self.is_visible_from(def, import_data.scope) { + visible.push((namespace, name, def)); + } + } + } + } + + for (namespace, name, def) in visible { + let target = self.resolve_target(def); + let alias = self.get_or_add_import_def( + import_data.scope, + Some(name), + import_data.visibility, + import_data.origin, + target, + ); + self.insert_unique_binding(import_data.scope, namespace, name, alias); + } + + ImportResolve::Resolved + } + } + } + + fn import_local_name(&self, import: &Import<'cx>) -> Option> { + let name = match import.kind { + ImportKind::Single => { + let terminal = *import.source_path.last()?; + if terminal.as_ref() == "self" { + let parent = &import.source_path[..import.source_path.len().saturating_sub(1)]; + let PathResolve::Found(bindings) = self.resolve_path(import.scope, parent) + else { + return Some(terminal); + }; + let [(_, def)] = bindings.as_slice() else { + return Some(terminal); + }; + self[self.resolve_target(*def)].name? + } else { + terminal + } + } + ImportKind::Rename(name) => name, + ImportKind::Glob => return None, + }; + + (name.as_ref() != "_").then_some(name) + } + + fn resolve_path(&self, scope: ScopeId, path: &[Name<'cx>]) -> PathResolve { + if path.is_empty() { + return PathResolve::NotFound; + } + + let use_scope = scope; + let mut current_scope = self.nearest_module_scope(scope); + let mut current_def = None; + let mut index = 0; + + while index < path.len() { + let segment = path[index]; + let is_last = index + 1 == path.len(); + + match segment.as_ref() { + "crate" if index == 0 => { + current_scope = self.root_scope(); + current_def = None; + index += 1; + continue; + } + "self" => { + if is_last { + return current_def + .map(|def| PathResolve::Found(vec![(Namespace::Type, def)])) + .unwrap_or(PathResolve::NotFound); + } + index += 1; + continue; + } + "super" => { + let Some(parent) = self.parent_module_scope(current_scope) else { + return PathResolve::NotFound; + }; + current_scope = parent; + current_def = None; + index += 1; + continue; + } + _ => {} + } + + let bindings = if is_last { + self.resolve_name_all(current_scope, segment, index == 0) + } else { + self.resolve_name_in_namespace(current_scope, Namespace::Type, segment, index == 0) + }; + + let bindings = match bindings { + NameResolve::Found(bindings) => bindings, + NameResolve::Ambiguous => return PathResolve::Ambiguous, + NameResolve::NotFound => return PathResolve::NotFound, + }; + + let visible = bindings + .into_iter() + .filter(|(_, def)| self.is_visible_from(*def, use_scope)) + .collect::>(); + + if visible.is_empty() { + return PathResolve::NotFound; + } + + if is_last { + return PathResolve::Found(visible); + } + + let [(_, def)] = visible.as_slice() else { + return PathResolve::Ambiguous; + }; + let target = self.resolve_target(*def); + let Some(child_scope) = self[target].child_scope else { + return PathResolve::NotFound; + }; + current_scope = child_scope; + current_def = Some(target); + index += 1; + } + + PathResolve::NotFound + } + + fn resolve_name_all(&self, scope: ScopeId, name: Name<'cx>, lexical: bool) -> NameResolve { + let mut defs = Vec::new(); + for namespace in ALL_NAMESPACES { + match self.resolve_name_in_namespace(scope, namespace, name, lexical) { + NameResolve::Found(found) => defs.extend(found), + NameResolve::Ambiguous => return NameResolve::Ambiguous, + NameResolve::NotFound => {} + } + } + + if defs.is_empty() { + NameResolve::NotFound + } else { + defs.sort_by_key(|(_, def)| def.index()); + defs.dedup(); + NameResolve::Found(defs) + } + } + + fn resolve_name_in_namespace( + &self, + scope: ScopeId, + namespace: Namespace, + name: Name<'cx>, + lexical: bool, + ) -> NameResolve { + if lexical { + match self.resolve_lexical(scope, namespace, name) { + ResolveResult::Found(def) => NameResolve::Found(vec![(namespace, def)]), + ResolveResult::Ambiguous(_) => NameResolve::Ambiguous, + ResolveResult::NotFound => NameResolve::NotFound, + } + } else { + self[scope] + .bindings + .get(namespace, name) + .map(|binding| self.binding_result(namespace, binding)) + .unwrap_or(NameResolve::NotFound) + } + } + + fn binding_result(&self, namespace: Namespace, binding: &Binding) -> NameResolve { + let defs = binding.iter().collect::>(); + match defs.len() { + 0 => NameResolve::NotFound, + 1 => NameResolve::Found(vec![(namespace, defs[0])]), + _ => NameResolve::Ambiguous, + } + } + + fn is_visible_from(&self, def: DefId, scope: ScopeId) -> bool { + let def = &self[def]; + match def.visibility { + Visibility::Public => true, + Visibility::Restricted(ancestor) => { + self.is_descendant_scope(self.nearest_module_scope(scope), ancestor) + } + Visibility::Private => { + let defining_module = self.nearest_module_scope(def.parent_scope); + self.is_descendant_scope(self.nearest_module_scope(scope), defining_module) + } + } + } + + fn nearest_module_scope(&self, mut scope: ScopeId) -> ScopeId { + loop { + if matches!(self[scope].kind, ScopeKind::CrateRoot | ScopeKind::Module) { + return scope; + } + let Some(parent) = self[scope].parent else { + return scope; + }; + scope = parent; + } + } + + fn parent_module_scope(&self, scope: ScopeId) -> Option { + let mut scope = self[scope].parent?; + loop { + if matches!(self[scope].kind, ScopeKind::CrateRoot | ScopeKind::Module) { + return Some(scope); + } + scope = self[scope].parent?; + } + } +} + +enum ImportResolve { + Resolved, + Ambiguous, + Pending, +} + +enum PathResolve { + Found(Vec<(Namespace, DefId)>), + Ambiguous, + NotFound, +} + +enum NameResolve { + Found(Vec<(Namespace, DefId)>), + Ambiguous, + NotFound, } impl Default for NameDb<'_> { @@ -348,4 +763,286 @@ mod tests { ResolveResult::NotFound ); } + + fn module<'cx>( + db: &mut NameDb<'cx>, + parent: ScopeId, + name: Name<'cx>, + visibility: Visibility, + ) -> (DefId, ScopeId) { + let def = db.add_def( + parent, + DefKind::Module, + Some(name), + visibility, + Origin::Synthetic, + ); + let scope = db.add_scope(ScopeKind::Module, Some(parent)); + db.set_child_scope(def, scope); + (def, scope) + } + + fn target_kind( + db: &NameDb<'_>, + scope: ScopeId, + namespace: Namespace, + name: Name<'_>, + ) -> DefKind { + let ResolveResult::Found(def) = db.resolve_lexical(scope, namespace, name) else { + panic!("expected {name:?} to resolve in {namespace:?}"); + }; + db[db.resolve_target(def)].kind + } + + #[test] + fn resolves_single_rename_self_and_underscore_imports() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let s = ccx.intern("S"); + let t = ccx.intern("T"); + let hidden = ccx.intern("_"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + db.add_def( + a_scope, + DefKind::Struct, + Some(s), + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, s], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, s], + ImportKind::Rename(t), + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, ccx.intern("self")], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, s], + ImportKind::Rename(hidden), + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + assert!(db + .imports() + .iter() + .all(|import| import.status == ImportStatus::Resolved)); + assert_eq!( + target_kind(&db, b_scope, Namespace::Type, s), + DefKind::Struct + ); + assert_eq!( + target_kind(&db, b_scope, Namespace::Type, t), + DefKind::Struct + ); + assert_eq!( + target_kind(&db, b_scope, Namespace::Type, a), + DefKind::Module + ); + assert_eq!( + db.resolve_lexical(b_scope, Namespace::Type, hidden), + ResolveResult::NotFound + ); + } + + #[test] + fn resolves_chained_reexports_and_globs_with_visibility() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let c = ccx.intern("c"); + let d = ccx.intern("d"); + let public = ccx.intern("Public"); + let private = ccx.intern("Private"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + let (_, c_scope) = module(&mut db, root, c, Visibility::Public); + let (_, d_scope) = module(&mut db, root, d, Visibility::Public); + db.add_def( + a_scope, + DefKind::Struct, + Some(public), + Visibility::Public, + Origin::Synthetic, + ); + db.add_def( + a_scope, + DefKind::Struct, + Some(private), + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, public], + ImportKind::Single, + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), b, public], + ImportKind::Single, + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + d_scope, + vec![ccx.intern("super"), a], + ImportKind::Glob, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + assert!(db + .imports() + .iter() + .all(|import| import.status == ImportStatus::Resolved)); + assert_eq!( + target_kind(&db, c_scope, Namespace::Type, public), + DefKind::Struct + ); + assert_eq!( + target_kind(&db, d_scope, Namespace::Type, public), + DefKind::Struct + ); + assert_eq!( + db.resolve_lexical(d_scope, Namespace::Type, private), + ResolveResult::NotFound + ); + } + + #[test] + fn import_resolution_reports_ambiguity_and_not_found() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let c = ccx.intern("c"); + let x = ccx.intern("X"); + let missing = ccx.intern("Missing"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + let (_, c_scope) = module(&mut db, root, c, Visibility::Public); + db.add_def( + a_scope, + DefKind::Struct, + Some(x), + Visibility::Public, + Origin::Synthetic, + ); + db.add_def( + b_scope, + DefKind::Struct, + Some(x), + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), a], + ImportKind::Glob, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), b], + ImportKind::Glob, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), a, missing], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + let ResolveResult::Ambiguous(defs) = db.resolve_lexical(c_scope, Namespace::Type, x) else { + panic!("expected imported globs to make {x:?} ambiguous"); + }; + assert_eq!(defs.len(), 2); + assert_eq!(db.imports()[2].status, ImportStatus::NotFound); + } + + #[test] + fn imported_enum_variant_keeps_type_and_value_namespaces() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let e = ccx.intern("E"); + let v = ccx.intern("V"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + let enum_def = db.add_def( + a_scope, + DefKind::Enum, + Some(e), + Visibility::Public, + Origin::Synthetic, + ); + let enum_scope = db.add_scope(ScopeKind::Item, Some(a_scope)); + db.set_child_scope(enum_def, enum_scope); + db.add_def( + enum_scope, + DefKind::Variant, + Some(v), + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, e, v], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + assert_eq!( + target_kind(&db, b_scope, Namespace::Type, v), + DefKind::Variant + ); + assert_eq!( + target_kind(&db, b_scope, Namespace::Value, v), + DefKind::Variant + ); + } } diff --git a/crates/syn-sem-name/src/def.rs b/crates/syn-sem-name/src/def.rs index 7048b5b..9cfa119 100644 --- a/crates/syn-sem-name/src/def.rs +++ b/crates/syn-sem-name/src/def.rs @@ -15,6 +15,17 @@ pub struct Def<'cx> { /// Scope that owns this definition. pub parent_scope: ScopeId, + /// Scope containing this definition's importable children. + /// + /// Modules and item-like definitions such as enums can expose names through a child scope. + /// Definitions without importable children leave this unset. + pub child_scope: Option, + + /// Definition this definition aliases. + /// + /// Import definitions use this to point at their resolved target. + pub target: Option, + /// Visibility of this definition. pub visibility: Visibility, diff --git a/crates/syn-sem-name/src/scope.rs b/crates/syn-sem-name/src/scope.rs index 0bdaa20..0b05b54 100644 --- a/crates/syn-sem-name/src/scope.rs +++ b/crates/syn-sem-name/src/scope.rs @@ -76,6 +76,17 @@ impl<'cx> Bindings<'cx> { self.map_mut(namespace).entry(name).or_default().push(def); } + /// Inserts a definition unless the same definition is already bound there. + pub fn insert_unique(&mut self, namespace: Namespace, name: Name<'cx>, def: DefId) -> bool { + let binding = self.map_mut(namespace).entry(name).or_default(); + if binding.contains(def) { + false + } else { + binding.push(def); + true + } + } + /// Returns the binding for `name` in `namespace`. pub fn get(&self, namespace: Namespace, name: Name<'cx>) -> Option<&Binding> { self.map(namespace).get(&name) @@ -141,4 +152,9 @@ impl Binding { pub fn is_empty(&self) -> bool { self.defs.is_empty() } + + /// Returns whether this binding already contains `def`. + pub fn contains(&self, def: DefId) -> bool { + self.defs.contains(&def) + } } diff --git a/crates/syn-sem-top/src/context.rs b/crates/syn-sem-top/src/context.rs index 1c8d1ef..781d548 100644 --- a/crates/syn-sem-top/src/context.rs +++ b/crates/syn-sem-top/src/context.rs @@ -8,6 +8,7 @@ use syn_sem_common::{CommonCx, FilePath, Result, SourceText}; /// `TopCx` owns shared infrastructure and phase contexts. Phase contexts borrow the owned common /// context through the top-level root, keeping lower-level contexts from owning one another. pub struct TopCx<'tcx> { + /// Syntax parsing and source storage context. pub syntax: SyntaxCx<'tcx>, /// Shared common infrastructure context. diff --git a/crates/syn-sem-top/src/names.rs b/crates/syn-sem-top/src/names.rs index b482745..61655d6 100644 --- a/crates/syn-sem-top/src/names.rs +++ b/crates/syn-sem-top/src/names.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use syn_sem_ast as ast; use syn_sem_common::{FilePath, Result}; use syn_sem_name::{ - DefId, DefKind, ImportKind, Name, NameDb, Origin, ScopeId, ScopeKind, Visibility, + DefId, DefKind, ImportKind, Name, NameDb, Namespace, Origin, ScopeId, ScopeKind, Visibility, }; pub(crate) fn collect_names_in_top<'tcx>( @@ -17,6 +17,7 @@ pub(crate) fn collect_names_in_top<'tcx>( for item in file.items { collector.collect_item_in_top(tcx, root, item, &path)?; } + collector.db.resolve_imports(); Ok(collector.db) } @@ -33,11 +34,13 @@ impl<'tcx> NameCollector<'tcx> { scope, DefKind::Const, item.ident.inner, - ast_visibility(&item.vis), + self.ast_visibility(scope, &item.vis), ); } ast::Item::Enum(item) => self.collect_enum(scope, item), - ast::Item::Fn(item) => self.collect_fn(scope, item, ast_visibility(&item.vis)), + ast::Item::Fn(item) => { + self.collect_fn(scope, item, self.ast_visibility(scope, &item.vis)) + } ast::Item::Impl(item) => self.collect_impl(scope, item), ast::Item::Mod(item) => self.collect_mod(scope, item), ast::Item::Struct(item) => { @@ -45,7 +48,7 @@ impl<'tcx> NameCollector<'tcx> { scope, DefKind::Struct, item.ident.inner, - ast_visibility(&item.vis), + self.ast_visibility(scope, &item.vis), ); self.collect_generics(scope, &item.generics); } @@ -55,24 +58,26 @@ impl<'tcx> NameCollector<'tcx> { scope, DefKind::TypeAlias, item.ident.inner, - ast_visibility(&item.vis), + self.ast_visibility(scope, &item.vis), ); self.collect_generics(scope, &item.generics); } ast::Item::Use(item) => { - self.collect_use_tree(scope, Vec::new(), &item.tree, ast_visibility(&item.vis)); + self.collect_use_tree( + scope, + Vec::new(), + &item.tree, + self.ast_visibility(scope, &item.vis), + ); } } } fn collect_enum(&mut self, parent_scope: ScopeId, item: &ast::ItemEnum<'tcx>) { - self.add_named( - parent_scope, - DefKind::Enum, - item.ident.inner, - ast_visibility(&item.vis), - ); + let visibility = self.ast_visibility(parent_scope, &item.vis); + let enum_def = self.add_named(parent_scope, DefKind::Enum, item.ident.inner, visibility); let item_scope = self.db.add_scope(ScopeKind::Item, Some(parent_scope)); + self.db.set_child_scope(enum_def, item_scope); self.collect_generics_into(item_scope, &item.generics); for variant in item.variants { @@ -80,19 +85,20 @@ impl<'tcx> NameCollector<'tcx> { item_scope, DefKind::Variant, variant.ident.inner, - Visibility::Private, + visibility, ); } } fn collect_mod(&mut self, parent_scope: ScopeId, item: &ast::ItemMod<'tcx>) { - self.add_named( + let module_def = self.add_named( parent_scope, DefKind::Module, item.ident.inner, - ast_visibility(&item.vis), + self.ast_visibility(parent_scope, &item.vis), ); let module_scope = self.db.add_scope(ScopeKind::Module, Some(parent_scope)); + self.db.set_child_scope(module_def, module_scope); if let Some(items) = item.items { for item in items { @@ -126,13 +132,14 @@ impl<'tcx> NameCollector<'tcx> { item: &ast::ItemMod<'tcx>, path: &ModulePath, ) -> Result<()> { - self.add_named( + let module_def = self.add_named( parent_scope, DefKind::Module, item.ident.inner, - ast_visibility(&item.vis), + self.ast_visibility(parent_scope, &item.vis), ); let module_scope = self.db.add_scope(ScopeKind::Module, Some(parent_scope)); + self.db.set_child_scope(module_def, module_scope); let module_dir = path.child_dir(item); if let Some(items) = item.items { @@ -162,7 +169,7 @@ impl<'tcx> NameCollector<'tcx> { parent_scope, DefKind::Trait, item.ident.inner, - ast_visibility(&item.vis), + self.ast_visibility(parent_scope, &item.vis), ); let trait_scope = self.db.add_scope(ScopeKind::Trait, Some(parent_scope)); self.collect_generics_into(trait_scope, &item.generics); @@ -412,12 +419,71 @@ impl<'tcx> NameCollector<'tcx> { self.db .add_def(scope, kind, Some(name), visibility, Origin::Synthetic) } -} -fn ast_visibility(vis: &ast::Visibility<'_>) -> Visibility { - match vis { - ast::Visibility::Public(_) => Visibility::Public, - ast::Visibility::Restricted(_) | ast::Visibility::Private => Visibility::Private, + fn ast_visibility(&self, scope: ScopeId, vis: &ast::Visibility<'tcx>) -> Visibility { + match vis { + ast::Visibility::Public(_) => Visibility::Public, + ast::Visibility::Restricted(path) => self + .visibility_scope(scope, path) + .map(Visibility::Restricted) + .unwrap_or(Visibility::Private), + ast::Visibility::Private => Visibility::Private, + } + } + + fn visibility_scope(&self, scope: ScopeId, path: &ast::Path<'tcx>) -> Option { + let mut scope = self.nearest_module_scope(scope); + let mut segments = path.segments.iter(); + let first = segments.next()?; + + match first.ident.inner.as_ref() { + "crate" => scope = self.db.root_scope(), + "self" => {} + "super" => scope = self.parent_module_scope(scope)?, + _ => return None, + } + + for segment in segments { + if segment.has_args() { + return None; + } + let binding = self.db[scope] + .bindings + .get(Namespace::Type, segment.ident.inner)?; + let def = binding.single()?; + let target = self.db.resolve_target(def); + scope = self.db[target].child_scope?; + } + + Some(scope) + } + + fn nearest_module_scope(&self, mut scope: ScopeId) -> ScopeId { + loop { + if matches!( + self.db[scope].kind, + ScopeKind::CrateRoot | ScopeKind::Module + ) { + return scope; + } + let Some(parent) = self.db[scope].parent else { + return scope; + }; + scope = parent; + } + } + + fn parent_module_scope(&self, scope: ScopeId) -> Option { + let mut scope = self.db[scope].parent?; + loop { + if matches!( + self.db[scope].kind, + ScopeKind::CrateRoot | ScopeKind::Module + ) { + return Some(scope); + } + scope = self.db[scope].parent?; + } } } diff --git a/crates/syn-sem-top/tests/name_resolution.rs b/crates/syn-sem-top/tests/name_resolution.rs index 1ef83b1..f0d8832 100644 --- a/crates/syn-sem-top/tests/name_resolution.rs +++ b/crates/syn-sem-top/tests/name_resolution.rs @@ -1,6 +1,6 @@ use std::fs; use syn_sem_common::FilePath; -use syn_sem_name::{DefKind, NameDb, Namespace, ResolveResult, ScopeId, ScopeKind}; +use syn_sem_name::{DefKind, ImportStatus, NameDb, Namespace, ResolveResult, ScopeId, ScopeKind}; use syn_sem_top::TopCx; #[test] @@ -27,6 +27,148 @@ fn resolves_names_from_virtual_module_files() { assert_fixture_modules(&tcx, semantics.names()); } +#[test] +fn resolves_local_use_paths_from_top_context() { + let tcx = TopCx::default(); + let entry_path = tcx.common.intern("use_paths.rs"); + let text = tcx.common.intern( + r#" + mod a; + + mod b { + use crate::a::Public; + use crate::a::{self, Public as Renamed}; + + mod inner { + pub(super) struct Local; + } + + use self::inner::Local; + use super::a::Public as FromSuper; + } + "#, + ); + tcx.insert_virtual_file(entry_path, text).unwrap(); + tcx.insert_virtual_file( + tcx.common.intern("use_paths/a.rs"), + tcx.common.intern("pub struct Public;"), + ) + .unwrap(); + + let semantics = tcx.analyze(entry_path).unwrap(); + let db = semantics.names(); + assert!(db + .imports() + .iter() + .all(|import| import.status == ImportStatus::Resolved)); + + let b_scope = module_scope(db, db.root_scope(), 1); + assert_eq!( + resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "Public"), + DefKind::Struct + ); + assert_eq!( + resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "Renamed"), + DefKind::Struct + ); + assert_eq!( + resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "a"), + DefKind::Module + ); + assert_eq!( + resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "Local"), + DefKind::Struct + ); + assert_eq!( + resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "FromSuper"), + DefKind::Struct + ); +} + +#[test] +fn applies_restricted_visibility_to_imports() { + let tcx = TopCx::default(); + let entry_path = tcx.common.intern("visibility.rs"); + let text = tcx.common.intern( + r#" + mod a { + pub(crate) struct CrateVisible; + pub(super) struct SuperVisible; + pub(in crate::a) struct InA; + + pub mod child { + use super::InA; + } + } + + mod b { + use crate::a::CrateVisible; + use crate::a::SuperVisible; + use crate::a::InA; + } + "#, + ); + tcx.insert_virtual_file(entry_path, text).unwrap(); + + let semantics = tcx.analyze(entry_path).unwrap(); + let db = semantics.names(); + let root = db.root_scope(); + let a_scope = module_scope(db, root, 0); + let child_scope = module_scope(db, a_scope, 0); + let b_scope = module_scope(db, root, 1); + + assert_eq!( + resolve_target_kind(&tcx, db, child_scope, Namespace::Type, "InA"), + DefKind::Struct + ); + assert_eq!( + resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "CrateVisible"), + DefKind::Struct + ); + assert_eq!( + resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "SuperVisible"), + DefKind::Struct + ); + assert_eq!( + resolve_result(&tcx, db, b_scope, Namespace::Type, "InA"), + ResolveResult::NotFound + ); + assert_eq!(db.imports()[3].status, ImportStatus::NotFound); +} + +#[test] +fn imports_enum_variants_in_type_and_value_namespaces() { + let tcx = TopCx::default(); + let entry_path = tcx.common.intern("variants.rs"); + let text = tcx.common.intern( + r#" + mod a { + pub enum E { + V, + } + } + + mod b { + use crate::a::E::V; + } + "#, + ); + tcx.insert_virtual_file(entry_path, text).unwrap(); + + let semantics = tcx.analyze(entry_path).unwrap(); + let db = semantics.names(); + let b_scope = module_scope(db, db.root_scope(), 1); + + assert_eq!( + resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "V"), + DefKind::Variant + ); + assert_eq!( + resolve_target_kind(&tcx, db, b_scope, Namespace::Value, "V"), + DefKind::Variant + ); +} + fn insert_virtual_fixture_tree<'tcx>(tcx: &'tcx TopCx<'tcx>) -> FilePath<'tcx> { let entry_path = tcx.common.intern("a1.rs"); let text = tcx.common.intern(include_str!("file/a1.rs")); @@ -110,6 +252,30 @@ fn resolve_kind<'tcx>( db[def].kind } +fn resolve_target_kind<'tcx>( + tcx: &'tcx TopCx<'tcx>, + db: &NameDb<'tcx>, + scope: ScopeId, + namespace: Namespace, + name: &str, +) -> DefKind { + let name = tcx.common.intern(name); + let ResolveResult::Found(def) = db.resolve_lexical(scope, namespace, name) else { + panic!("expected {name:?} to resolve in {namespace:?}"); + }; + db[db.resolve_target(def)].kind +} + +fn resolve_result<'tcx>( + tcx: &'tcx TopCx<'tcx>, + db: &NameDb<'tcx>, + scope: ScopeId, + namespace: Namespace, + name: &str, +) -> ResolveResult { + db.resolve_lexical(scope, namespace, tcx.common.intern(name)) +} + fn module_scope(db: &NameDb<'_>, parent: ScopeId, nth: usize) -> ScopeId { db.scopes() .iter() From c7a3502f31982cab5102f3d64de2356397b47a98 Mon Sep 17 00:00:00 2001 From: ecoricemon Date: Fri, 5 Jun 2026 16:54:55 +0900 Subject: [PATCH 2/5] wip: Organizing syn-sem-name --- crates/syn-sem-name/src/db.rs | 489 +++++++++++++------- crates/syn-sem-name/src/def.rs | 11 + crates/syn-sem-name/src/import.rs | 4 +- crates/syn-sem-name/src/namespace.rs | 12 + crates/syn-sem-name/src/scope.rs | 65 +-- crates/syn-sem-name/tests/ast.rs | 25 +- crates/syn-sem-top/src/names.rs | 27 +- crates/syn-sem-top/tests/name_resolution.rs | 53 ++- 8 files changed, 468 insertions(+), 218 deletions(-) diff --git a/crates/syn-sem-name/src/db.rs b/crates/syn-sem-name/src/db.rs index d6f48f4..d2f7076 100644 --- a/crates/syn-sem-name/src/db.rs +++ b/crates/syn-sem-name/src/db.rs @@ -4,26 +4,6 @@ use crate::{ }; use std::ops::{Index, IndexMut}; -const ALL_NAMESPACES: [Namespace; 4] = [ - Namespace::Type, - Namespace::Value, - Namespace::Macro, - Namespace::Lifetime, -]; - -/// Result of resolving a name. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ResolveResult { - /// Name resolved to one definition. - Found(DefId), - - /// Name resolved to multiple candidate definitions. - Ambiguous(Vec), - - /// Name was not found. - NotFound, -} - /// Name-resolution database. #[derive(Debug, Clone)] pub struct NameDb<'cx> { @@ -53,6 +33,16 @@ impl<'cx> NameDb<'cx> { &self.imports } + /// Returns the binding for `name` directly declared in `scope` and `namespace`. + pub fn binding( + &self, + scope: ScopeId, + namespace: Namespace, + name: Name<'cx>, + ) -> Option<&Binding> { + self[scope].bindings.get(namespace, name) + } + /// Adds a scope under `parent`. pub fn add_scope(&mut self, kind: ScopeKind, parent: Option) -> ScopeId { let id = ScopeId::new(self.scopes.len()); @@ -110,52 +100,6 @@ impl<'cx> NameDb<'cx> { id } - /// Adds an import alias definition without binding it. - pub fn add_import_def( - &mut self, - parent_scope: ScopeId, - name: Option>, - visibility: Visibility, - origin: Origin, - target: DefId, - ) -> DefId { - let id = DefId::new(self.defs.len()); - self.defs.push(Def { - id, - name, - kind: DefKind::Use, - parent_scope, - child_scope: None, - target: Some(target), - visibility, - origin, - }); - id - } - - /// Returns an existing matching import alias or creates one. - pub fn get_or_add_import_def( - &mut self, - parent_scope: ScopeId, - name: Option>, - visibility: Visibility, - origin: Origin, - target: DefId, - ) -> DefId { - if let Some(def) = self.defs.iter().find(|def| { - def.kind == DefKind::Use - && def.parent_scope == parent_scope - && def.name == name - && def.visibility == visibility - && def.origin == origin - && def.target == Some(target) - }) { - return def.id; - } - - self.add_import_def(parent_scope, name, visibility, origin, target) - } - /// Adds an unresolved import. pub fn add_import( &mut self, @@ -178,34 +122,16 @@ impl<'cx> NameDb<'cx> { id } - /// Returns a mutable definition. - pub fn def_mut(&mut self, def: DefId) -> &mut Def<'cx> { - &mut self.defs[def.index()] - } - - /// Returns a mutable import. - pub fn import_mut(&mut self, import: ImportId) -> &mut Import<'cx> { - &mut self.imports[import.index()] - } - /// Links a definition to a child scope that contains its importable members. pub fn set_child_scope(&mut self, def: DefId, child_scope: ScopeId) { - self.def_mut(def).child_scope = Some(child_scope); + self.defs[def.index()].child_scope = Some(child_scope); } - /// Inserts a binding unless that exact definition is already present. - pub fn insert_unique_binding( - &mut self, - scope: ScopeId, - namespace: Namespace, - name: Name<'cx>, - def: DefId, - ) -> bool { - self[scope].bindings.insert_unique(namespace, name, def) - } - - /// Follows import aliases and returns the underlying target definition. - pub fn resolve_target(&self, mut def: DefId) -> DefId { + /// Follows `DefKind::Use` alias definitions to their underlying definition. + /// + /// For example, if `use b::C` points at `pub use a::C`, and that re-export points at the + /// original struct `C`, this returns the struct definition. + pub fn follow_aliases(&self, mut def: DefId) -> DefId { let mut remaining = self.defs.len(); while remaining > 0 { let Some(target) = self[def].target else { @@ -251,8 +177,67 @@ impl<'cx> NameDb<'cx> { } } - /// Resolves a single-segment name lexically in one namespace. - pub fn resolve_lexical( + fn get_or_insert_import_def( + &mut self, + parent_scope: ScopeId, + name: Option>, + visibility: Visibility, + origin: Origin, + target: DefId, + ) -> DefId { + if let Some(def) = self.defs.iter().find(|def| { + def.kind == DefKind::Use + && def.parent_scope == parent_scope + && def.name == name + && def.visibility == visibility + && def.origin == origin + && def.target == Some(target) + }) { + return def.id; + } + + let id = DefId::new(self.defs.len()); + self.defs.push(Def { + id, + name, + kind: DefKind::Use, + parent_scope, + child_scope: None, + target: Some(target), + visibility, + origin, + }); + id + } + + fn insert_unique_binding( + &mut self, + scope: ScopeId, + namespace: Namespace, + name: Name<'cx>, + def: DefId, + ) -> bool { + self[scope].bindings.insert_unique(namespace, name, def) + } + + fn to_import_alias_target(&self, bindings: &[(Namespace, DefId)]) -> Option { + let (_, first) = *bindings + .first() + .expect("import alias target requires at least one resolved binding"); + let target = self.follow_aliases(first); + let mut namespaces = Vec::with_capacity(bindings.len()); + + for &(namespace, def) in bindings { + if self.follow_aliases(def) != target { + return None; + } + namespaces.push(namespace); + } + + Some(ImportAliasTarget { target, namespaces }) + } + + fn resolve_lexical( &self, mut scope: ScopeId, namespace: Namespace, @@ -275,8 +260,7 @@ impl<'cx> NameDb<'cx> { } } - /// Returns whether `descendant` is equal to or nested inside `ancestor`. - pub fn is_descendant_scope(&self, mut descendant: ScopeId, ancestor: ScopeId) -> bool { + fn is_descendant_scope(&self, mut descendant: ScopeId, ancestor: ScopeId) -> bool { loop { if descendant == ancestor { return true; @@ -289,56 +273,79 @@ impl<'cx> NameDb<'cx> { } } + /// Resolves one collected `use` declaration into local bindings. + /// + /// The original [`Import`] entry remains the source-level record. Successful resolution adds + /// one or more [`DefKind::Use`] alias definitions and binds those alias definitions in the + /// scope that contains the `use`. + /// + /// For example, `use a::b::C` where `C` is a struct resolves the path to + /// `(Namespace::Type, DefId(C))`, creates a `DefKind::Use` alias named `C` whose target is the + /// original struct definition, then inserts that alias into the type namespace of the `use` + /// scope. fn resolve_import(&mut self, import: ImportId) -> ImportResolve { - let import_data = self[import].clone(); + let import_data = &self[import]; match import_data.kind { ImportKind::Single | ImportKind::Rename(_) => { - let Some(local_name) = self.import_local_name(&import_data) else { - return match self.resolve_path(import_data.scope, &import_data.source_path) { - PathResolve::Found(_) => ImportResolve::Resolved, - PathResolve::Ambiguous => ImportResolve::Ambiguous, - PathResolve::NotFound => ImportResolve::Pending, - }; + let local_name = match self.import_local_name(import_data) { + ImportLocalName::Name(name) => name, + ImportLocalName::NoBinding => { + let bindings = + self.resolve_import_path(import_data.scope, &import_data.source_path); + return match bindings { + LookupResolve::Found(_) => ImportResolve::Resolved, + LookupResolve::Ambiguous => ImportResolve::Ambiguous, + LookupResolve::NotFound => ImportResolve::Pending, + }; + } + ImportLocalName::Ambiguous => return ImportResolve::Ambiguous, + ImportLocalName::Pending => return ImportResolve::Pending, }; - let bindings = match self.resolve_path(import_data.scope, &import_data.source_path) - { - PathResolve::Found(bindings) => bindings, - PathResolve::Ambiguous => return ImportResolve::Ambiguous, - PathResolve::NotFound => return ImportResolve::Pending, + let bindings = + match self.resolve_import_path(import_data.scope, &import_data.source_path) { + LookupResolve::Found(bindings) => bindings, + LookupResolve::Ambiguous => return ImportResolve::Ambiguous, + LookupResolve::NotFound => return ImportResolve::Pending, + }; + + let Some(alias_target) = self.to_import_alias_target(&bindings) else { + return ImportResolve::Ambiguous; }; - let target = self.resolve_target(bindings[0].1); - let alias = self.get_or_add_import_def( + let import_data = import_data.clone(); + let alias = self.get_or_insert_import_def( import_data.scope, Some(local_name), import_data.visibility, import_data.origin, - target, + alias_target.target, ); - for (namespace, _) in bindings { + for namespace in alias_target.namespaces { self.insert_unique_binding(import_data.scope, namespace, local_name, alias); } ImportResolve::Resolved } ImportKind::Glob => { - let bindings = match self.resolve_path(import_data.scope, &import_data.source_path) - { - PathResolve::Found(bindings) => bindings, - PathResolve::Ambiguous => return ImportResolve::Ambiguous, - PathResolve::NotFound => return ImportResolve::Pending, - }; + // `use a::b::*;` imports each visible binding from the target's child scope. Each + // imported child gets its own local `DefKind::Use` alias in the `use` scope. + let bindings = + match self.resolve_import_path(import_data.scope, &import_data.source_path) { + LookupResolve::Found(bindings) => bindings, + LookupResolve::Ambiguous => return ImportResolve::Ambiguous, + LookupResolve::NotFound => return ImportResolve::Pending, + }; let [(_, target)] = bindings.as_slice() else { return ImportResolve::Ambiguous; }; - let target = self.resolve_target(*target); + let target = self.follow_aliases(*target); let Some(child_scope) = self[target].child_scope else { return ImportResolve::Pending; }; let mut visible = Vec::new(); - for namespace in ALL_NAMESPACES { + for namespace in Namespace::all() { for (&name, binding) in self[child_scope].bindings.map(namespace) { for def in binding.iter() { if self.is_visible_from(def, import_data.scope) { @@ -348,9 +355,10 @@ impl<'cx> NameDb<'cx> { } } + let import_data = import_data.clone(); for (namespace, name, def) in visible { - let target = self.resolve_target(def); - let alias = self.get_or_add_import_def( + let target = self.follow_aliases(def); + let alias = self.get_or_insert_import_def( import_data.scope, Some(name), import_data.visibility, @@ -365,34 +373,66 @@ impl<'cx> NameDb<'cx> { } } - fn import_local_name(&self, import: &Import<'cx>) -> Option> { + /// Returns the local binding name introduced by an import. + /// + /// For example, + /// - `use a::b::C` introduces `C` + /// - `use a::b::C as D` introduces `D` + /// - `use crate::a::{self}` introduces `a` + /// - Glob imports and underscore imports such as `use a::b::*` or `use a::b::C as _` introduce + /// no single local name. + /// + /// A trailing `self` depends on resolving its parent path. If that parent path is ambiguous or + /// not found yet, the result preserves that state instead of treating `self` as a local name. + fn import_local_name(&self, import: &Import<'cx>) -> ImportLocalName<'cx> { let name = match import.kind { ImportKind::Single => { - let terminal = *import.source_path.last()?; + let Some(&terminal) = import.source_path.last() else { + return ImportLocalName::Pending; + }; + if terminal.as_ref() == "self" { let parent = &import.source_path[..import.source_path.len().saturating_sub(1)]; - let PathResolve::Found(bindings) = self.resolve_path(import.scope, parent) - else { - return Some(terminal); + + let bindings = match self.resolve_import_path(import.scope, parent) { + LookupResolve::Found(bindings) => bindings, + LookupResolve::Ambiguous => return ImportLocalName::Ambiguous, + LookupResolve::NotFound => return ImportLocalName::Pending, + }; + + let Some(alias_target) = self.to_import_alias_target(&bindings) else { + return ImportLocalName::Ambiguous; }; - let [(_, def)] = bindings.as_slice() else { - return Some(terminal); + + let Some(name) = self[alias_target.target].name else { + return ImportLocalName::Pending; }; - self[self.resolve_target(*def)].name? + + name } else { terminal } } ImportKind::Rename(name) => name, - ImportKind::Glob => return None, + ImportKind::Glob => return ImportLocalName::NoBinding, }; - (name.as_ref() != "_").then_some(name) + if name.as_ref() == "_" { + ImportLocalName::NoBinding + } else { + ImportLocalName::Name(name) + } } - fn resolve_path(&self, scope: ScopeId, path: &[Name<'cx>]) -> PathResolve { + /// Resolves an import path from `scope` into visible candidate definitions. + /// + /// For example, resolving `crate::a::C` starts at the root scope, finds module `a`, follows + /// `a`'s child scope, then resolves `C` in that child scope. If the terminal name exists in + /// multiple namespaces with the same target, such as an enum variant, the result contains each + /// namespace-target pair. + fn resolve_import_path(&self, scope: ScopeId, path: &[Name<'cx>]) -> LookupResolve { if path.is_empty() { - return PathResolve::NotFound; + return LookupResolve::NotFound; } let use_scope = scope; @@ -414,15 +454,15 @@ impl<'cx> NameDb<'cx> { "self" => { if is_last { return current_def - .map(|def| PathResolve::Found(vec![(Namespace::Type, def)])) - .unwrap_or(PathResolve::NotFound); + .map(|def| LookupResolve::Found(vec![(Namespace::Type, def)])) + .unwrap_or(LookupResolve::NotFound); } index += 1; continue; } "super" => { let Some(parent) = self.parent_module_scope(current_scope) else { - return PathResolve::NotFound; + return LookupResolve::NotFound; }; current_scope = parent; current_def = None; @@ -439,9 +479,8 @@ impl<'cx> NameDb<'cx> { }; let bindings = match bindings { - NameResolve::Found(bindings) => bindings, - NameResolve::Ambiguous => return PathResolve::Ambiguous, - NameResolve::NotFound => return PathResolve::NotFound, + LookupResolve::Found(bindings) => bindings, + other => return other, }; let visible = bindings @@ -450,44 +489,45 @@ impl<'cx> NameDb<'cx> { .collect::>(); if visible.is_empty() { - return PathResolve::NotFound; + return LookupResolve::NotFound; } if is_last { - return PathResolve::Found(visible); + return LookupResolve::Found(visible); } let [(_, def)] = visible.as_slice() else { - return PathResolve::Ambiguous; + return LookupResolve::Ambiguous; }; - let target = self.resolve_target(*def); + + let target = self.follow_aliases(*def); let Some(child_scope) = self[target].child_scope else { - return PathResolve::NotFound; + return LookupResolve::NotFound; }; current_scope = child_scope; current_def = Some(target); index += 1; } - PathResolve::NotFound + LookupResolve::NotFound } - fn resolve_name_all(&self, scope: ScopeId, name: Name<'cx>, lexical: bool) -> NameResolve { + fn resolve_name_all(&self, scope: ScopeId, name: Name<'cx>, lexical: bool) -> LookupResolve { let mut defs = Vec::new(); - for namespace in ALL_NAMESPACES { + for namespace in Namespace::all() { match self.resolve_name_in_namespace(scope, namespace, name, lexical) { - NameResolve::Found(found) => defs.extend(found), - NameResolve::Ambiguous => return NameResolve::Ambiguous, - NameResolve::NotFound => {} + LookupResolve::Found(found) => defs.extend(found), + LookupResolve::Ambiguous => return LookupResolve::Ambiguous, + LookupResolve::NotFound => {} } } if defs.is_empty() { - NameResolve::NotFound + LookupResolve::NotFound } else { defs.sort_by_key(|(_, def)| def.index()); defs.dedup(); - NameResolve::Found(defs) + LookupResolve::Found(defs) } } @@ -497,28 +537,28 @@ impl<'cx> NameDb<'cx> { namespace: Namespace, name: Name<'cx>, lexical: bool, - ) -> NameResolve { + ) -> LookupResolve { if lexical { match self.resolve_lexical(scope, namespace, name) { - ResolveResult::Found(def) => NameResolve::Found(vec![(namespace, def)]), - ResolveResult::Ambiguous(_) => NameResolve::Ambiguous, - ResolveResult::NotFound => NameResolve::NotFound, + ResolveResult::Found(def) => LookupResolve::Found(vec![(namespace, def)]), + ResolveResult::Ambiguous(_) => LookupResolve::Ambiguous, + ResolveResult::NotFound => LookupResolve::NotFound, } } else { self[scope] .bindings .get(namespace, name) .map(|binding| self.binding_result(namespace, binding)) - .unwrap_or(NameResolve::NotFound) + .unwrap_or(LookupResolve::NotFound) } } - fn binding_result(&self, namespace: Namespace, binding: &Binding) -> NameResolve { + fn binding_result(&self, namespace: Namespace, binding: &Binding) -> LookupResolve { let defs = binding.iter().collect::>(); match defs.len() { - 0 => NameResolve::NotFound, - 1 => NameResolve::Found(vec![(namespace, defs[0])]), - _ => NameResolve::Ambiguous, + 0 => LookupResolve::NotFound, + 1 => LookupResolve::Found(vec![(namespace, defs[0])]), + _ => LookupResolve::Ambiguous, } } @@ -559,19 +599,49 @@ impl<'cx> NameDb<'cx> { } } +/// Result of resolving a name. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ResolveResult { + /// Name resolved to one definition. + Found(DefId), + + /// Name resolved to multiple candidate definitions. + Ambiguous(Vec), + + /// Name was not found. + NotFound, +} + enum ImportResolve { Resolved, Ambiguous, Pending, } -enum PathResolve { - Found(Vec<(Namespace, DefId)>), +enum ImportLocalName<'cx> { + /// Import introduces this local binding name. + Name(Name<'cx>), + + /// Import introduces no single local binding, such as a glob or underscore import. + NoBinding, + + /// Local name computation found an ambiguous path. Ambiguous, - NotFound, + + /// Local name computation depends on an import that is not resolved yet. + Pending, +} + +/// Normalized target information for creating one import alias definition. +/// +/// For example, `use a::E::V` for an enum variant can produce one alias target for `V` with both +/// the type and value namespaces. +struct ImportAliasTarget { + target: DefId, + namespaces: Vec, } -enum NameResolve { +enum LookupResolve { Found(Vec<(Namespace, DefId)>), Ambiguous, NotFound, @@ -580,8 +650,10 @@ enum NameResolve { impl Default for NameDb<'_> { /// Creates a name database with a crate-root scope. fn default() -> Self { + let root_scope = Scope::new(ScopeId::new(0), ScopeKind::CrateRoot, None); + Self { - scopes: vec![Scope::new(ScopeId::new(0), ScopeKind::CrateRoot, None)], + scopes: vec![root_scope], defs: Vec::new(), imports: Vec::new(), } @@ -791,7 +863,7 @@ mod tests { let ResolveResult::Found(def) = db.resolve_lexical(scope, namespace, name) else { panic!("expected {name:?} to resolve in {namespace:?}"); }; - db[db.resolve_target(def)].kind + db[db.follow_aliases(def)].kind } #[test] @@ -867,6 +939,45 @@ mod tests { ); } + #[test] + fn self_import_preserves_parent_path_failure() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let missing = ccx.intern("missing"); + let self_name = ccx.intern("self"); + + module(&mut db, root, a, Visibility::Public); + module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + + db.add_import( + b_scope, + vec![ccx.intern("super"), a, self_name], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), missing, self_name], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + assert_eq!(db.imports()[0].status, ImportStatus::Ambiguous); + assert_eq!(db.imports()[1].status, ImportStatus::NotFound); + assert_eq!( + db.resolve_lexical(b_scope, Namespace::Type, self_name), + ResolveResult::NotFound + ); + } + #[test] fn resolves_chained_reexports_and_globs_with_visibility() { let ccx = CommonCx::new(); @@ -998,6 +1109,52 @@ mod tests { assert_eq!(db.imports()[2].status, ImportStatus::NotFound); } + #[test] + fn single_import_is_ambiguous_when_namespaces_resolve_to_distinct_targets() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let x = ccx.intern("X"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + db.add_def( + a_scope, + DefKind::Struct, + Some(x), + Visibility::Public, + Origin::Synthetic, + ); + db.add_def( + a_scope, + DefKind::Const, + Some(x), + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, x], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + assert_eq!(db.imports()[0].status, ImportStatus::Ambiguous); + assert_eq!( + db.resolve_lexical(b_scope, Namespace::Type, x), + ResolveResult::NotFound + ); + assert_eq!( + db.resolve_lexical(b_scope, Namespace::Value, x), + ResolveResult::NotFound + ); + } + #[test] fn imported_enum_variant_keeps_type_and_value_namespaces() { let ccx = CommonCx::new(); diff --git a/crates/syn-sem-name/src/def.rs b/crates/syn-sem-name/src/def.rs index 9cfa119..785e043 100644 --- a/crates/syn-sem-name/src/def.rs +++ b/crates/syn-sem-name/src/def.rs @@ -92,18 +92,29 @@ impl DefKind { /// Returns the default namespaces populated by this definition kind. pub const fn namespaces(self) -> &'static [Namespace] { match self { + // Type namespace Self::Module | Self::Struct | Self::Enum | Self::Trait | Self::TypeAlias | Self::TypeParam => &[Namespace::Type], + + // Type & Value namespaces Self::Variant => &[Namespace::Type, Namespace::Value], + + // Value namespace Self::Fn | Self::Const | Self::Static | Self::Local | Self::ConstParam => { &[Namespace::Value] } + + // Lifetime namespace Self::LifetimeParam => &[Namespace::Lifetime], + + // Macro namespace Self::Macro => &[Namespace::Macro], + + // None Self::Field | Self::Use | Self::Impl => &[], } } diff --git a/crates/syn-sem-name/src/import.rs b/crates/syn-sem-name/src/import.rs index 7aa8f77..be73f15 100644 --- a/crates/syn-sem-name/src/import.rs +++ b/crates/syn-sem-name/src/import.rs @@ -1,10 +1,10 @@ -use crate::{Name, Origin, ScopeId, Visibility}; +use crate::{ImportId, Name, Origin, ScopeId, Visibility}; /// Import declaration collected during name resolution. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Import<'cx> { /// Import id. - pub id: crate::ImportId, + pub id: ImportId, /// Scope that receives the imported binding. pub scope: ScopeId, diff --git a/crates/syn-sem-name/src/namespace.rs b/crates/syn-sem-name/src/namespace.rs index 960c4d7..4b4887b 100644 --- a/crates/syn-sem-name/src/namespace.rs +++ b/crates/syn-sem-name/src/namespace.rs @@ -13,3 +13,15 @@ pub enum Namespace { /// Lifetime namespace. Lifetime, } + +impl Namespace { + /// Returns all Rust namespaces handled by the name database. + pub const fn all() -> [Self; 4] { + [ + Namespace::Type, + Namespace::Value, + Namespace::Macro, + Namespace::Lifetime, + ] + } +} diff --git a/crates/syn-sem-name/src/scope.rs b/crates/syn-sem-name/src/scope.rs index 0b05b54..77e695a 100644 --- a/crates/syn-sem-name/src/scope.rs +++ b/crates/syn-sem-name/src/scope.rs @@ -18,7 +18,7 @@ pub struct Scope<'cx> { impl<'cx> Scope<'cx> { /// Creates an empty scope. - pub fn new(id: ScopeId, kind: ScopeKind, parent: Option) -> Self { + pub(crate) fn new(id: ScopeId, kind: ScopeKind, parent: Option) -> Self { Self { id, parent, @@ -66,18 +66,39 @@ pub struct Bindings<'cx> { } impl<'cx> Bindings<'cx> { + /// Returns the binding for `name` in `namespace`. + pub fn get(&self, namespace: Namespace, name: Name<'cx>) -> Option<&Binding> { + self.map(namespace).get(&name) + } + + /// Returns the map for `namespace`. + pub fn map(&self, namespace: Namespace) -> &Map, Binding> { + match namespace { + Namespace::Type => &self.types, + Namespace::Value => &self.values, + Namespace::Macro => &self.macros, + Namespace::Lifetime => &self.lifetimes, + } + } + /// Creates empty bindings. - pub fn new() -> Self { + pub(crate) fn new() -> Self { Self::default() } /// Inserts a definition under `name` in `namespace`. - pub fn insert(&mut self, namespace: Namespace, name: Name<'cx>, def: DefId) { + pub(crate) fn insert(&mut self, namespace: Namespace, name: Name<'cx>, def: DefId) { self.map_mut(namespace).entry(name).or_default().push(def); } - /// Inserts a definition unless the same definition is already bound there. - pub fn insert_unique(&mut self, namespace: Namespace, name: Name<'cx>, def: DefId) -> bool { + /// Inserts a definition unless the same definition is already bound there, then returns true + /// if the given definition is successfully inserted. + pub(crate) fn insert_unique( + &mut self, + namespace: Namespace, + name: Name<'cx>, + def: DefId, + ) -> bool { let binding = self.map_mut(namespace).entry(name).or_default(); if binding.contains(def) { false @@ -87,23 +108,8 @@ impl<'cx> Bindings<'cx> { } } - /// Returns the binding for `name` in `namespace`. - pub fn get(&self, namespace: Namespace, name: Name<'cx>) -> Option<&Binding> { - self.map(namespace).get(&name) - } - - /// Returns the map for `namespace`. - pub fn map(&self, namespace: Namespace) -> &Map, Binding> { - match namespace { - Namespace::Type => &self.types, - Namespace::Value => &self.values, - Namespace::Macro => &self.macros, - Namespace::Lifetime => &self.lifetimes, - } - } - /// Returns the mutable map for `namespace`. - pub fn map_mut(&mut self, namespace: Namespace) -> &mut Map, Binding> { + pub(crate) fn map_mut(&mut self, namespace: Namespace) -> &mut Map, Binding> { match namespace { Namespace::Type => &mut self.types, Namespace::Value => &mut self.values, @@ -120,16 +126,6 @@ pub struct Binding { } impl Binding { - /// Creates an empty binding. - pub fn new() -> Self { - Self::default() - } - - /// Appends a definition to this binding. - pub fn push(&mut self, def: DefId) { - self.defs.push(def); - } - /// Iterates definitions attached to this binding. pub fn iter(&self) -> impl ExactSizeIterator + '_ { self.defs.iter().copied() @@ -153,8 +149,13 @@ impl Binding { self.defs.is_empty() } + /// Appends a definition to this binding. + pub(crate) fn push(&mut self, def: DefId) { + self.defs.push(def); + } + /// Returns whether this binding already contains `def`. - pub fn contains(&self, def: DefId) -> bool { + pub(crate) fn contains(&self, def: DefId) -> bool { self.defs.contains(&def) } } diff --git a/crates/syn-sem-name/tests/ast.rs b/crates/syn-sem-name/tests/ast.rs index 0901051..67502fc 100644 --- a/crates/syn-sem-name/tests/ast.rs +++ b/crates/syn-sem-name/tests/ast.rs @@ -175,13 +175,36 @@ fn expect_def( name: Name<'_>, kind: DefKind, ) -> DefId { - let ResolveResult::Found(def) = db.resolve_lexical(scope, namespace, name) else { + let ResolveResult::Found(def) = resolve_lexical(db, scope, namespace, name) else { panic!("expected {name:?} to resolve in {namespace:?}"); }; assert_eq!(db[def].kind, kind); def } +fn resolve_lexical( + db: &NameDb<'_>, + mut scope: ScopeId, + namespace: Namespace, + name: Name<'_>, +) -> ResolveResult { + loop { + if let Some(binding) = db.binding(scope, namespace, name) { + let mut defs = binding.iter(); + return match defs.len() { + 0 => ResolveResult::NotFound, + 1 => ResolveResult::Found(defs.next().unwrap()), + _ => ResolveResult::Ambiguous(defs.collect()), + }; + } + + let Some(parent) = db[scope].parent else { + return ResolveResult::NotFound; + }; + scope = parent; + } +} + #[test] fn resolves_function_generics_params_and_locals_from_ast() { let ccx = CommonCx::new(); diff --git a/crates/syn-sem-top/src/names.rs b/crates/syn-sem-top/src/names.rs index 61655d6..9650528 100644 --- a/crates/syn-sem-top/src/names.rs +++ b/crates/syn-sem-top/src/names.rs @@ -451,7 +451,7 @@ impl<'tcx> NameCollector<'tcx> { .bindings .get(Namespace::Type, segment.ident.inner)?; let def = binding.single()?; - let target = self.db.resolve_target(def); + let target = self.db.follow_aliases(def); scope = self.db[target].child_scope?; } @@ -623,12 +623,35 @@ mod tests { name: Name<'_>, kind: DefKind, ) { - let ResolveResult::Found(def) = db.resolve_lexical(scope, namespace, name) else { + let ResolveResult::Found(def) = resolve_lexical(db, scope, namespace, name) else { panic!("expected {name:?} to resolve in {namespace:?}"); }; assert_eq!(db[def].kind, kind); } + fn resolve_lexical( + db: &NameDb<'_>, + mut scope: ScopeId, + namespace: Namespace, + name: Name<'_>, + ) -> ResolveResult { + loop { + if let Some(binding) = db.binding(scope, namespace, name) { + let mut defs = binding.iter(); + return match defs.len() { + 0 => ResolveResult::NotFound, + 1 => ResolveResult::Found(defs.next().unwrap()), + _ => ResolveResult::Ambiguous(defs.collect()), + }; + } + + let Some(parent) = db[scope].parent else { + return ResolveResult::NotFound; + }; + scope = parent; + } + } + #[test] fn collects_names_from_top_context() { let tcx = TopCx::default(); diff --git a/crates/syn-sem-top/tests/name_resolution.rs b/crates/syn-sem-top/tests/name_resolution.rs index f0d8832..fb80e02 100644 --- a/crates/syn-sem-top/tests/name_resolution.rs +++ b/crates/syn-sem-top/tests/name_resolution.rs @@ -64,23 +64,23 @@ fn resolves_local_use_paths_from_top_context() { let b_scope = module_scope(db, db.root_scope(), 1); assert_eq!( - resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "Public"), + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "Public"), DefKind::Struct ); assert_eq!( - resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "Renamed"), + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "Renamed"), DefKind::Struct ); assert_eq!( - resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "a"), + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "a"), DefKind::Module ); assert_eq!( - resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "Local"), + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "Local"), DefKind::Struct ); assert_eq!( - resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "FromSuper"), + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "FromSuper"), DefKind::Struct ); } @@ -118,15 +118,15 @@ fn applies_restricted_visibility_to_imports() { let b_scope = module_scope(db, root, 1); assert_eq!( - resolve_target_kind(&tcx, db, child_scope, Namespace::Type, "InA"), + follow_aliases_kind(&tcx, db, child_scope, Namespace::Type, "InA"), DefKind::Struct ); assert_eq!( - resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "CrateVisible"), + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "CrateVisible"), DefKind::Struct ); assert_eq!( - resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "SuperVisible"), + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "SuperVisible"), DefKind::Struct ); assert_eq!( @@ -160,11 +160,11 @@ fn imports_enum_variants_in_type_and_value_namespaces() { let b_scope = module_scope(db, db.root_scope(), 1); assert_eq!( - resolve_target_kind(&tcx, db, b_scope, Namespace::Type, "V"), + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "V"), DefKind::Variant ); assert_eq!( - resolve_target_kind(&tcx, db, b_scope, Namespace::Value, "V"), + follow_aliases_kind(&tcx, db, b_scope, Namespace::Value, "V"), DefKind::Variant ); } @@ -246,13 +246,13 @@ fn resolve_kind<'tcx>( name: &str, ) -> DefKind { let name = tcx.common.intern(name); - let ResolveResult::Found(def) = db.resolve_lexical(scope, namespace, name) else { + let ResolveResult::Found(def) = resolve_lexical(db, scope, namespace, name) else { panic!("expected {name:?} to resolve in {namespace:?}"); }; db[def].kind } -fn resolve_target_kind<'tcx>( +fn follow_aliases_kind<'tcx>( tcx: &'tcx TopCx<'tcx>, db: &NameDb<'tcx>, scope: ScopeId, @@ -260,10 +260,10 @@ fn resolve_target_kind<'tcx>( name: &str, ) -> DefKind { let name = tcx.common.intern(name); - let ResolveResult::Found(def) = db.resolve_lexical(scope, namespace, name) else { + let ResolveResult::Found(def) = resolve_lexical(db, scope, namespace, name) else { panic!("expected {name:?} to resolve in {namespace:?}"); }; - db[db.resolve_target(def)].kind + db[db.follow_aliases(def)].kind } fn resolve_result<'tcx>( @@ -273,7 +273,30 @@ fn resolve_result<'tcx>( namespace: Namespace, name: &str, ) -> ResolveResult { - db.resolve_lexical(scope, namespace, tcx.common.intern(name)) + resolve_lexical(db, scope, namespace, tcx.common.intern(name)) +} + +fn resolve_lexical( + db: &NameDb<'_>, + mut scope: ScopeId, + namespace: Namespace, + name: syn_sem_name::Name<'_>, +) -> ResolveResult { + loop { + if let Some(binding) = db.binding(scope, namespace, name) { + let mut defs = binding.iter(); + return match defs.len() { + 0 => ResolveResult::NotFound, + 1 => ResolveResult::Found(defs.next().unwrap()), + _ => ResolveResult::Ambiguous(defs.collect()), + }; + } + + let Some(parent) = db[scope].parent else { + return ResolveResult::NotFound; + }; + scope = parent; + } } fn module_scope(db: &NameDb<'_>, parent: ScopeId, nth: usize) -> ScopeId { From 66c0e538749aa748f9dab31f45f30fe25c2ddb9e Mon Sep 17 00:00:00 2001 From: ecoricemon Date: Sat, 6 Jun 2026 09:35:03 +0900 Subject: [PATCH 3/5] wip: Organize syn-sem-name --- crates/syn-sem-name/src/db.rs | 1278 +++++++++++++++++++-------------- 1 file changed, 728 insertions(+), 550 deletions(-) diff --git a/crates/syn-sem-name/src/db.rs b/crates/syn-sem-name/src/db.rs index d2f7076..5a0b891 100644 --- a/crates/syn-sem-name/src/db.rs +++ b/crates/syn-sem-name/src/db.rs @@ -220,23 +220,42 @@ impl<'cx> NameDb<'cx> { self[scope].bindings.insert_unique(namespace, name, def) } - fn to_import_alias_target(&self, bindings: &[(Namespace, DefId)]) -> Option { - let (_, first) = *bindings + /// Validates path lookup candidates for one local import binding. + /// + /// Every candidate must resolve through aliases to the same final target. The returned value + /// keeps that target and the namespaces where the import should be bound. For example, enum + /// variant `V` from `use a::E::V;` can validate as one target with both type and value + /// namespaces. + /// + /// Empty candidates or candidates with different final targets are caller invariant + /// violations and panic. + fn validate_import_binding_candidates( + &self, + candidates: &[(Namespace, DefId)], + ) -> ValidatedImportBinding { + let (_, first) = *candidates .first() - .expect("import alias target requires at least one resolved binding"); + .expect("import binding validation requires at least one resolved binding"); let target = self.follow_aliases(first); - let mut namespaces = Vec::with_capacity(bindings.len()); - - for &(namespace, def) in bindings { - if self.follow_aliases(def) != target { - return None; - } + let mut namespaces = Vec::with_capacity(candidates.len()); + + for &(namespace, def) in candidates { + assert_eq!( + self.follow_aliases(def), + target, + "import binding candidates must resolve to one final target" + ); namespaces.push(namespace); } - Some(ImportAliasTarget { target, namespaces }) + ValidatedImportBinding { target, namespaces } } + /// Resolves one name by walking lexical parent scopes. + /// + /// This is a low-level lookup primitive: it checks only the requested namespace and stops at + /// the first scope with a matching binding. It does not apply visibility, statement order, or + /// path-specific rules such as `crate`, `self`, or `super`. fn resolve_lexical( &self, mut scope: ScopeId, @@ -290,57 +309,56 @@ impl<'cx> NameDb<'cx> { let local_name = match self.import_local_name(import_data) { ImportLocalName::Name(name) => name, ImportLocalName::NoBinding => { - let bindings = - self.resolve_import_path(import_data.scope, &import_data.source_path); - return match bindings { - LookupResolve::Found(_) => ImportResolve::Resolved, - LookupResolve::Ambiguous => ImportResolve::Ambiguous, - LookupResolve::NotFound => ImportResolve::Pending, + return match self + .resolve_import_path(import_data.scope, &import_data.source_path) + { + CandidateResolution::Found(_) => ImportResolve::Resolved, + CandidateResolution::Ambiguous => ImportResolve::Ambiguous, + CandidateResolution::NotFound => ImportResolve::Pending, }; } ImportLocalName::Ambiguous => return ImportResolve::Ambiguous, ImportLocalName::Pending => return ImportResolve::Pending, }; - let bindings = + let candidates = match self.resolve_import_path(import_data.scope, &import_data.source_path) { - LookupResolve::Found(bindings) => bindings, - LookupResolve::Ambiguous => return ImportResolve::Ambiguous, - LookupResolve::NotFound => return ImportResolve::Pending, + CandidateResolution::Found(candidates) => candidates, + CandidateResolution::Ambiguous => return ImportResolve::Ambiguous, + CandidateResolution::NotFound => return ImportResolve::Pending, }; - let Some(alias_target) = self.to_import_alias_target(&bindings) else { - return ImportResolve::Ambiguous; - }; + let import_binding = self.validate_import_binding_candidates(&candidates); + // Creates or reuses the local `DefKind::Use` definition that points at the final + // target, such as the original `C` definition for `use a::b::C`. let import_data = import_data.clone(); let alias = self.get_or_insert_import_def( import_data.scope, Some(local_name), import_data.visibility, import_data.origin, - alias_target.target, + import_binding.target, ); - for namespace in alias_target.namespaces { + for namespace in import_binding.namespaces { self.insert_unique_binding(import_data.scope, namespace, local_name, alias); } ImportResolve::Resolved } ImportKind::Glob => { - // `use a::b::*;` imports each visible binding from the target's child scope. Each - // imported child gets its own local `DefKind::Use` alias in the `use` scope. - let bindings = + let candidates = match self.resolve_import_path(import_data.scope, &import_data.source_path) { - LookupResolve::Found(bindings) => bindings, - LookupResolve::Ambiguous => return ImportResolve::Ambiguous, - LookupResolve::NotFound => return ImportResolve::Pending, + CandidateResolution::Found(candidates) => candidates, + CandidateResolution::Ambiguous => return ImportResolve::Ambiguous, + CandidateResolution::NotFound => return ImportResolve::Pending, }; - let [(_, target)] = bindings.as_slice() else { + let [(_, glob_candidate)] = candidates.as_slice() else { return ImportResolve::Ambiguous; }; - let target = self.follow_aliases(*target); - let Some(child_scope) = self[target].child_scope else { + + let glob_target = self.follow_aliases(*glob_candidate); + let Some(child_scope) = self[glob_target].child_scope else { return ImportResolve::Pending; }; @@ -394,17 +412,15 @@ impl<'cx> NameDb<'cx> { if terminal.as_ref() == "self" { let parent = &import.source_path[..import.source_path.len().saturating_sub(1)]; - let bindings = match self.resolve_import_path(import.scope, parent) { - LookupResolve::Found(bindings) => bindings, - LookupResolve::Ambiguous => return ImportLocalName::Ambiguous, - LookupResolve::NotFound => return ImportLocalName::Pending, + let candidates = match self.resolve_import_path(import.scope, parent) { + CandidateResolution::Found(candidates) => candidates, + CandidateResolution::Ambiguous => return ImportLocalName::Ambiguous, + CandidateResolution::NotFound => return ImportLocalName::Pending, }; - let Some(alias_target) = self.to_import_alias_target(&bindings) else { - return ImportLocalName::Ambiguous; - }; + let import_binding = self.validate_import_binding_candidates(&candidates); - let Some(name) = self[alias_target.target].name else { + let Some(name) = self[import_binding.target].name else { return ImportLocalName::Pending; }; @@ -430,9 +446,9 @@ impl<'cx> NameDb<'cx> { /// `a`'s child scope, then resolves `C` in that child scope. If the terminal name exists in /// multiple namespaces with the same target, such as an enum variant, the result contains each /// namespace-target pair. - fn resolve_import_path(&self, scope: ScopeId, path: &[Name<'cx>]) -> LookupResolve { + fn resolve_import_path(&self, scope: ScopeId, path: &[Name<'cx>]) -> CandidateResolution { if path.is_empty() { - return LookupResolve::NotFound; + return CandidateResolution::NotFound; } let use_scope = scope; @@ -454,15 +470,15 @@ impl<'cx> NameDb<'cx> { "self" => { if is_last { return current_def - .map(|def| LookupResolve::Found(vec![(Namespace::Type, def)])) - .unwrap_or(LookupResolve::NotFound); + .map(|def| CandidateResolution::Found(vec![(Namespace::Type, def)])) + .unwrap_or(CandidateResolution::NotFound); } index += 1; continue; } "super" => { let Some(parent) = self.parent_module_scope(current_scope) else { - return LookupResolve::NotFound; + return CandidateResolution::NotFound; }; current_scope = parent; current_def = None; @@ -472,93 +488,107 @@ impl<'cx> NameDb<'cx> { _ => {} } - let bindings = if is_last { - self.resolve_name_all(current_scope, segment, index == 0) + let candidates = if is_last { + self.resolve_name_in_all_namespaces(current_scope, segment, index == 0) } else { self.resolve_name_in_namespace(current_scope, Namespace::Type, segment, index == 0) }; - let bindings = match bindings { - LookupResolve::Found(bindings) => bindings, + let candidates = match candidates { + CandidateResolution::Found(candidates) => candidates, other => return other, }; - let visible = bindings + let visible = candidates .into_iter() .filter(|(_, def)| self.is_visible_from(*def, use_scope)) .collect::>(); if visible.is_empty() { - return LookupResolve::NotFound; + return CandidateResolution::NotFound; } if is_last { - return LookupResolve::Found(visible); + return CandidateResolution::Found(visible); } let [(_, def)] = visible.as_slice() else { - return LookupResolve::Ambiguous; + return CandidateResolution::Ambiguous; }; let target = self.follow_aliases(*def); let Some(child_scope) = self[target].child_scope else { - return LookupResolve::NotFound; + return CandidateResolution::NotFound; }; current_scope = child_scope; current_def = Some(target); index += 1; } - LookupResolve::NotFound + CandidateResolution::NotFound } - fn resolve_name_all(&self, scope: ScopeId, name: Name<'cx>, lexical: bool) -> LookupResolve { + /// Resolves one name across all namespaces. + /// + /// This is used for terminal import path segments, where a name can legally resolve in more + /// than one namespace. For example, an enum variant can produce both type and value namespace + /// candidates that point at the same definition. + fn resolve_name_in_all_namespaces( + &self, + scope: ScopeId, + name: Name<'cx>, + is_lexical: bool, + ) -> CandidateResolution { let mut defs = Vec::new(); for namespace in Namespace::all() { - match self.resolve_name_in_namespace(scope, namespace, name, lexical) { - LookupResolve::Found(found) => defs.extend(found), - LookupResolve::Ambiguous => return LookupResolve::Ambiguous, - LookupResolve::NotFound => {} + match self.resolve_name_in_namespace(scope, namespace, name, is_lexical) { + CandidateResolution::Found(found) => defs.extend(found), + CandidateResolution::Ambiguous => return CandidateResolution::Ambiguous, + CandidateResolution::NotFound => {} } } if defs.is_empty() { - LookupResolve::NotFound + CandidateResolution::NotFound } else { defs.sort_by_key(|(_, def)| def.index()); defs.dedup(); - LookupResolve::Found(defs) + CandidateResolution::Found(defs) } } + /// Resolves one name in one namespace for an import path segment. + /// + /// When `is_lexical` is true, lookup walks parent scopes with [`Self::resolve_lexical`]. When + /// false, lookup checks only the current scope's binding map. Import paths use lexical lookup + /// for their first ordinary segment and current-scope lookup after descending into a child + /// scope. fn resolve_name_in_namespace( &self, scope: ScopeId, namespace: Namespace, name: Name<'cx>, - lexical: bool, - ) -> LookupResolve { - if lexical { + is_lexical: bool, + ) -> CandidateResolution { + if is_lexical { match self.resolve_lexical(scope, namespace, name) { - ResolveResult::Found(def) => LookupResolve::Found(vec![(namespace, def)]), - ResolveResult::Ambiguous(_) => LookupResolve::Ambiguous, - ResolveResult::NotFound => LookupResolve::NotFound, + ResolveResult::Found(def) => CandidateResolution::Found(vec![(namespace, def)]), + ResolveResult::Ambiguous(_) => CandidateResolution::Ambiguous, + ResolveResult::NotFound => CandidateResolution::NotFound, } } else { self[scope] .bindings .get(namespace, name) - .map(|binding| self.binding_result(namespace, binding)) - .unwrap_or(LookupResolve::NotFound) - } - } - - fn binding_result(&self, namespace: Namespace, binding: &Binding) -> LookupResolve { - let defs = binding.iter().collect::>(); - match defs.len() { - 0 => LookupResolve::NotFound, - 1 => LookupResolve::Found(vec![(namespace, defs[0])]), - _ => LookupResolve::Ambiguous, + .map(|binding| { + let mut defs = binding.iter(); + match defs.len() { + 0 => CandidateResolution::NotFound, + 1 => CandidateResolution::Found(vec![(namespace, defs.next().unwrap())]), + _ => CandidateResolution::Ambiguous, + } + }) + .unwrap_or(CandidateResolution::NotFound) } } @@ -632,18 +662,30 @@ enum ImportLocalName<'cx> { Pending, } -/// Normalized target information for creating one import alias definition. +/// Validated information for binding one resolved import locally. +/// +/// `target` is the final non-`Use` definition after following aliases, such as a module, struct, +/// enum, function, or variant. `namespaces` lists where the import should be bound locally. /// -/// For example, `use a::E::V` for an enum variant can produce one alias target for `V` with both -/// the type and value namespaces. -struct ImportAliasTarget { +/// For example, `use a::E::V` for an enum variant can validate to target `V` with both the type +/// and value namespaces. +struct ValidatedImportBinding { target: DefId, namespaces: Vec, } -enum LookupResolve { +/// Result of resolving namespace-tagged definition candidates. +/// +/// For example, resolving `V` in `use a::E::V` can find the same enum variant in both the type +/// and value namespaces, producing `Found([(Type, V), (Value, V)])`. +enum CandidateResolution { + /// Lookup found one or more namespace-tagged candidates. Found(Vec<(Namespace, DefId)>), + + /// Lookup found multiple candidates where a single candidate was required. Ambiguous, + + /// Lookup found no candidates. NotFound, } @@ -695,145 +737,196 @@ mod tests { use super::*; use syn_sem_common::CommonCx; - #[test] - fn lexical_resolution_prefers_inner_scope() { - let ccx = CommonCx::new(); - let x = ccx.intern("x"); - - let mut db = NameDb::default(); - let root = db.root_scope(); - let body = db.add_scope(ScopeKind::FunctionBody, Some(root)); - - let outer = db.add_def( - root, - DefKind::Local, - Some(x), - Visibility::Private, - Origin::Synthetic, - ); - let inner = db.add_def( - body, - DefKind::Local, - Some(x), - Visibility::Private, - Origin::Synthetic, - ); - - assert_eq!( - db.resolve_lexical(body, Namespace::Value, x), - ResolveResult::Found(inner) - ); - assert_eq!( - db.resolve_lexical(root, Namespace::Value, x), - ResolveResult::Found(outer) - ); - } - - #[test] - fn namespaces_are_independent() { - let ccx = CommonCx::new(); - let t = ccx.intern("T"); - - let mut db = NameDb::default(); - let root = db.root_scope(); - let type_param = db.add_def( - root, - DefKind::TypeParam, - Some(t), - Visibility::Private, - Origin::Synthetic, - ); - let local = db.add_def( - root, - DefKind::Local, - Some(t), - Visibility::Private, - Origin::Synthetic, - ); - - assert_eq!( - db.resolve_lexical(root, Namespace::Type, t), - ResolveResult::Found(type_param) - ); - assert_eq!( - db.resolve_lexical(root, Namespace::Value, t), - ResolveResult::Found(local) - ); - } - - #[test] - fn generic_scope_is_visible_from_function_body() { - let ccx = CommonCx::new(); - let t = ccx.intern("T"); - - let mut db = NameDb::default(); - let root = db.root_scope(); - let generic_scope = db.add_scope(ScopeKind::GenericParams, Some(root)); - let body = db.add_scope(ScopeKind::FunctionBody, Some(generic_scope)); - - let type_param = db.add_def( - generic_scope, - DefKind::TypeParam, - Some(t), - Visibility::Private, - Origin::Synthetic, - ); - - assert_eq!( - db.resolve_lexical(body, Namespace::Type, t), - ResolveResult::Found(type_param) - ); - } + mod lexical_resolution { + use super::*; + + // Covers lexical lookup from this code shape: + // + // let x = outer; + // + // fn f() { + // let x = inner; + // x + // } + // + // Lookup from the function body finds the inner `x`; lookup from root finds the outer `x`. + #[test] + fn lexical_resolution_prefers_inner_scope() { + let ccx = CommonCx::new(); + let x = ccx.intern("x"); + + let mut db = NameDb::default(); + let root = db.root_scope(); + let body = db.add_scope(ScopeKind::FunctionBody, Some(root)); + + let outer = db.add_def( + root, + DefKind::Local, + Some(x), + Visibility::Private, + Origin::Synthetic, + ); + let inner = db.add_def( + body, + DefKind::Local, + Some(x), + Visibility::Private, + Origin::Synthetic, + ); + + assert_eq!( + db.resolve_lexical(body, Namespace::Value, x), + ResolveResult::Found(inner) + ); + assert_eq!( + db.resolve_lexical(root, Namespace::Value, x), + ResolveResult::Found(outer) + ); + } - #[test] - fn local_item_can_be_resolved_in_type_namespace() { - let ccx = CommonCx::new(); - let local = ccx.intern("Local"); - - let mut db = NameDb::default(); - let root = db.root_scope(); - let body = db.add_scope(ScopeKind::FunctionBody, Some(root)); - let block = db.add_scope(ScopeKind::Block, Some(body)); - - let local_struct = db.add_def( - block, - DefKind::Struct, - Some(local), - Visibility::Private, - Origin::Synthetic, - ); + // Covers lexical lookup from this code shape: + // + // fn f() { + // let _: T; + // } + // + // The function body can see names declared in the generic-parameter scope. + #[test] + fn generic_scope_is_visible_from_function_body() { + let ccx = CommonCx::new(); + let t = ccx.intern("T"); + + let mut db = NameDb::default(); + let root = db.root_scope(); + let generic_scope = db.add_scope(ScopeKind::GenericParams, Some(root)); + let body = db.add_scope(ScopeKind::FunctionBody, Some(generic_scope)); + + let type_param = db.add_def( + generic_scope, + DefKind::TypeParam, + Some(t), + Visibility::Private, + Origin::Synthetic, + ); + + assert_eq!( + db.resolve_lexical(body, Namespace::Type, t), + ResolveResult::Found(type_param) + ); + } - assert_eq!( - db.resolve_lexical(block, Namespace::Type, local), - ResolveResult::Found(local_struct) - ); + // Covers lexical lookup from this code shape: + // + // fn f() { + // { + // struct Local; + // let _: Local; + // } + // } + // + // The block-local item is visible from the same block in the type namespace. + #[test] + fn local_item_can_be_resolved_in_type_namespace() { + let ccx = CommonCx::new(); + let local = ccx.intern("Local"); + + let mut db = NameDb::default(); + let root = db.root_scope(); + let body = db.add_scope(ScopeKind::FunctionBody, Some(root)); + let block = db.add_scope(ScopeKind::Block, Some(body)); + + let local_struct = db.add_def( + block, + DefKind::Struct, + Some(local), + Visibility::Private, + Origin::Synthetic, + ); + + assert_eq!( + db.resolve_lexical(block, Namespace::Type, local), + ResolveResult::Found(local_struct) + ); + } } - #[test] - fn const_generic_lives_in_value_namespace() { - let ccx = CommonCx::new(); - let n = ccx.intern("N"); - - let mut db = NameDb::default(); - let root = db.root_scope(); - let generic_scope = db.add_scope(ScopeKind::GenericParams, Some(root)); - - let const_param = db.add_def( - generic_scope, - DefKind::ConstParam, - Some(n), - Visibility::Private, - Origin::Synthetic, - ); + mod namespaces { + use super::*; + + // Covers namespace lookup from this test DB state: + // + // type namespace: + // T -> type parameter + // + // value namespace: + // T -> local binding + // + // The same spelling can resolve to different definitions in different namespaces. + #[test] + fn namespaces_are_independent() { + let ccx = CommonCx::new(); + let t = ccx.intern("T"); + + let mut db = NameDb::default(); + let root = db.root_scope(); + let type_param = db.add_def( + root, + DefKind::TypeParam, + Some(t), + Visibility::Private, + Origin::Synthetic, + ); + let local = db.add_def( + root, + DefKind::Local, + Some(t), + Visibility::Private, + Origin::Synthetic, + ); + + assert_eq!( + db.resolve_lexical(root, Namespace::Type, t), + ResolveResult::Found(type_param) + ); + assert_eq!( + db.resolve_lexical(root, Namespace::Value, t), + ResolveResult::Found(local) + ); + } - assert_eq!( - db.resolve_lexical(generic_scope, Namespace::Value, n), - ResolveResult::Found(const_param) - ); - assert_eq!( - db.resolve_lexical(generic_scope, Namespace::Type, n), - ResolveResult::NotFound - ); + // Covers namespace lookup from this code shape: + // + // fn f() { + // let _ = N; + // } + // + // The const parameter `N` lives in the value namespace, not the type namespace. + #[test] + fn const_generic_lives_in_value_namespace() { + let ccx = CommonCx::new(); + let n = ccx.intern("N"); + + let mut db = NameDb::default(); + let root = db.root_scope(); + let generic_scope = db.add_scope(ScopeKind::GenericParams, Some(root)); + + let const_param = db.add_def( + generic_scope, + DefKind::ConstParam, + Some(n), + Visibility::Private, + Origin::Synthetic, + ); + + assert_eq!( + db.resolve_lexical(generic_scope, Namespace::Value, n), + ResolveResult::Found(const_param) + ); + assert_eq!( + db.resolve_lexical(generic_scope, Namespace::Type, n), + ResolveResult::NotFound + ); + } } fn module<'cx>( @@ -866,340 +959,425 @@ mod tests { db[db.follow_aliases(def)].kind } - #[test] - fn resolves_single_rename_self_and_underscore_imports() { - let ccx = CommonCx::new(); - let mut db = NameDb::default(); - let root = db.root_scope(); - let a = ccx.intern("a"); - let b = ccx.intern("b"); - let s = ccx.intern("S"); - let t = ccx.intern("T"); - let hidden = ccx.intern("_"); - - let (_, a_scope) = module(&mut db, root, a, Visibility::Public); - let (_, b_scope) = module(&mut db, root, b, Visibility::Public); - db.add_def( - a_scope, - DefKind::Struct, - Some(s), - Visibility::Public, - Origin::Synthetic, - ); - db.add_import( - b_scope, - vec![ccx.intern("super"), a, s], - ImportKind::Single, - Visibility::Private, - Origin::Synthetic, - ); - db.add_import( - b_scope, - vec![ccx.intern("super"), a, s], - ImportKind::Rename(t), - Visibility::Private, - Origin::Synthetic, - ); - db.add_import( - b_scope, - vec![ccx.intern("super"), a, ccx.intern("self")], - ImportKind::Single, - Visibility::Private, - Origin::Synthetic, - ); - db.add_import( - b_scope, - vec![ccx.intern("super"), a, s], - ImportKind::Rename(hidden), - Visibility::Private, - Origin::Synthetic, - ); - - db.resolve_imports(); - - assert!(db - .imports() - .iter() - .all(|import| import.status == ImportStatus::Resolved)); - assert_eq!( - target_kind(&db, b_scope, Namespace::Type, s), - DefKind::Struct - ); - assert_eq!( - target_kind(&db, b_scope, Namespace::Type, t), - DefKind::Struct - ); - assert_eq!( - target_kind(&db, b_scope, Namespace::Type, a), - DefKind::Module - ); - assert_eq!( - db.resolve_lexical(b_scope, Namespace::Type, hidden), - ResolveResult::NotFound - ); - } - - #[test] - fn self_import_preserves_parent_path_failure() { - let ccx = CommonCx::new(); - let mut db = NameDb::default(); - let root = db.root_scope(); - let a = ccx.intern("a"); - let b = ccx.intern("b"); - let missing = ccx.intern("missing"); - let self_name = ccx.intern("self"); - - module(&mut db, root, a, Visibility::Public); - module(&mut db, root, a, Visibility::Public); - let (_, b_scope) = module(&mut db, root, b, Visibility::Public); - - db.add_import( - b_scope, - vec![ccx.intern("super"), a, self_name], - ImportKind::Single, - Visibility::Private, - Origin::Synthetic, - ); - db.add_import( - b_scope, - vec![ccx.intern("super"), missing, self_name], - ImportKind::Single, - Visibility::Private, - Origin::Synthetic, - ); - - db.resolve_imports(); - - assert_eq!(db.imports()[0].status, ImportStatus::Ambiguous); - assert_eq!(db.imports()[1].status, ImportStatus::NotFound); - assert_eq!( - db.resolve_lexical(b_scope, Namespace::Type, self_name), - ResolveResult::NotFound - ); - } - - #[test] - fn resolves_chained_reexports_and_globs_with_visibility() { - let ccx = CommonCx::new(); - let mut db = NameDb::default(); - let root = db.root_scope(); - let a = ccx.intern("a"); - let b = ccx.intern("b"); - let c = ccx.intern("c"); - let d = ccx.intern("d"); - let public = ccx.intern("Public"); - let private = ccx.intern("Private"); - - let (_, a_scope) = module(&mut db, root, a, Visibility::Public); - let (_, b_scope) = module(&mut db, root, b, Visibility::Public); - let (_, c_scope) = module(&mut db, root, c, Visibility::Public); - let (_, d_scope) = module(&mut db, root, d, Visibility::Public); - db.add_def( - a_scope, - DefKind::Struct, - Some(public), - Visibility::Public, - Origin::Synthetic, - ); - db.add_def( - a_scope, - DefKind::Struct, - Some(private), - Visibility::Private, - Origin::Synthetic, - ); - db.add_import( - b_scope, - vec![ccx.intern("super"), a, public], - ImportKind::Single, - Visibility::Public, - Origin::Synthetic, - ); - db.add_import( - c_scope, - vec![ccx.intern("super"), b, public], - ImportKind::Single, - Visibility::Public, - Origin::Synthetic, - ); - db.add_import( - d_scope, - vec![ccx.intern("super"), a], - ImportKind::Glob, - Visibility::Private, - Origin::Synthetic, - ); - - db.resolve_imports(); - - assert!(db - .imports() - .iter() - .all(|import| import.status == ImportStatus::Resolved)); - assert_eq!( - target_kind(&db, c_scope, Namespace::Type, public), - DefKind::Struct - ); - assert_eq!( - target_kind(&db, d_scope, Namespace::Type, public), - DefKind::Struct - ); - assert_eq!( - db.resolve_lexical(d_scope, Namespace::Type, private), - ResolveResult::NotFound - ); - } - - #[test] - fn import_resolution_reports_ambiguity_and_not_found() { - let ccx = CommonCx::new(); - let mut db = NameDb::default(); - let root = db.root_scope(); - let a = ccx.intern("a"); - let b = ccx.intern("b"); - let c = ccx.intern("c"); - let x = ccx.intern("X"); - let missing = ccx.intern("Missing"); - - let (_, a_scope) = module(&mut db, root, a, Visibility::Public); - let (_, b_scope) = module(&mut db, root, b, Visibility::Public); - let (_, c_scope) = module(&mut db, root, c, Visibility::Public); - db.add_def( - a_scope, - DefKind::Struct, - Some(x), - Visibility::Public, - Origin::Synthetic, - ); - db.add_def( - b_scope, - DefKind::Struct, - Some(x), - Visibility::Public, - Origin::Synthetic, - ); - db.add_import( - c_scope, - vec![ccx.intern("super"), a], - ImportKind::Glob, - Visibility::Private, - Origin::Synthetic, - ); - db.add_import( - c_scope, - vec![ccx.intern("super"), b], - ImportKind::Glob, - Visibility::Private, - Origin::Synthetic, - ); - db.add_import( - c_scope, - vec![ccx.intern("super"), a, missing], - ImportKind::Single, - Visibility::Private, - Origin::Synthetic, - ); - - db.resolve_imports(); - - let ResolveResult::Ambiguous(defs) = db.resolve_lexical(c_scope, Namespace::Type, x) else { - panic!("expected imported globs to make {x:?} ambiguous"); - }; - assert_eq!(defs.len(), 2); - assert_eq!(db.imports()[2].status, ImportStatus::NotFound); - } - - #[test] - fn single_import_is_ambiguous_when_namespaces_resolve_to_distinct_targets() { - let ccx = CommonCx::new(); - let mut db = NameDb::default(); - let root = db.root_scope(); - let a = ccx.intern("a"); - let b = ccx.intern("b"); - let x = ccx.intern("X"); - - let (_, a_scope) = module(&mut db, root, a, Visibility::Public); - let (_, b_scope) = module(&mut db, root, b, Visibility::Public); - db.add_def( - a_scope, - DefKind::Struct, - Some(x), - Visibility::Public, - Origin::Synthetic, - ); - db.add_def( - a_scope, - DefKind::Const, - Some(x), - Visibility::Public, - Origin::Synthetic, - ); - db.add_import( - b_scope, - vec![ccx.intern("super"), a, x], - ImportKind::Single, - Visibility::Private, - Origin::Synthetic, - ); + mod import_resolution { + use super::*; + + // Covers imports from this module shape: + // + // mod a { + // pub struct S; + // } + // + // mod b { + // use super::a::S; + // use super::a::S as T; + // use super::a::{self}; + // use super::a::S as _; + // } + // + // The first three introduce local bindings to the resolved target; `_` does not. + #[test] + fn resolves_single_rename_self_and_underscore_imports() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let s = ccx.intern("S"); + let t = ccx.intern("T"); + let hidden = ccx.intern("_"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + db.add_def( + a_scope, + DefKind::Struct, + Some(s), + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, s], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, s], + ImportKind::Rename(t), + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, ccx.intern("self")], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, s], + ImportKind::Rename(hidden), + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + assert!(db + .imports() + .iter() + .all(|import| import.status == ImportStatus::Resolved)); + assert_eq!( + target_kind(&db, b_scope, Namespace::Type, s), + DefKind::Struct + ); + assert_eq!( + target_kind(&db, b_scope, Namespace::Type, t), + DefKind::Struct + ); + assert_eq!( + target_kind(&db, b_scope, Namespace::Type, a), + DefKind::Module + ); + assert_eq!( + db.resolve_lexical(b_scope, Namespace::Type, hidden), + ResolveResult::NotFound + ); + } - db.resolve_imports(); + // Covers imports from this invalid test DB state: + // + // mod a {} + // mod a {} // invalid test DB state: `a` is ambiguous. + // + // mod b { + // use super::a::{self}; + // use super::missing::{self}; + // } + // + // The first import preserves the ambiguous parent-path failure; the second is not found. + #[test] + fn self_import_preserves_parent_path_failure() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let missing = ccx.intern("missing"); + let self_name = ccx.intern("self"); + + module(&mut db, root, a, Visibility::Public); + module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + + db.add_import( + b_scope, + vec![ccx.intern("super"), a, self_name], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), missing, self_name], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + assert_eq!(db.imports()[0].status, ImportStatus::Ambiguous); + assert_eq!(db.imports()[1].status, ImportStatus::NotFound); + assert_eq!( + db.resolve_lexical(b_scope, Namespace::Type, self_name), + ResolveResult::NotFound + ); + } - assert_eq!(db.imports()[0].status, ImportStatus::Ambiguous); - assert_eq!( - db.resolve_lexical(b_scope, Namespace::Type, x), - ResolveResult::NotFound - ); - assert_eq!( - db.resolve_lexical(b_scope, Namespace::Value, x), - ResolveResult::NotFound - ); - } + // Covers imports from this module shape: + // + // mod a { + // pub struct Public; + // struct Private; + // } + // + // mod b { + // pub use super::a::Public; + // } + // + // mod c { + // use super::b::Public; + // } + // + // mod d { + // use super::a::*; + // } + // + // Chained re-exports resolve to the original target, and globs skip private children. + #[test] + fn resolves_chained_reexports_and_globs_with_visibility() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let c = ccx.intern("c"); + let d = ccx.intern("d"); + let public = ccx.intern("Public"); + let private = ccx.intern("Private"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + let (_, c_scope) = module(&mut db, root, c, Visibility::Public); + let (_, d_scope) = module(&mut db, root, d, Visibility::Public); + db.add_def( + a_scope, + DefKind::Struct, + Some(public), + Visibility::Public, + Origin::Synthetic, + ); + db.add_def( + a_scope, + DefKind::Struct, + Some(private), + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, public], + ImportKind::Single, + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), b, public], + ImportKind::Single, + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + d_scope, + vec![ccx.intern("super"), a], + ImportKind::Glob, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + assert!(db + .imports() + .iter() + .all(|import| import.status == ImportStatus::Resolved)); + assert_eq!( + target_kind(&db, c_scope, Namespace::Type, public), + DefKind::Struct + ); + assert_eq!( + target_kind(&db, d_scope, Namespace::Type, public), + DefKind::Struct + ); + assert_eq!( + db.resolve_lexical(d_scope, Namespace::Type, private), + ResolveResult::NotFound + ); + } - #[test] - fn imported_enum_variant_keeps_type_and_value_namespaces() { - let ccx = CommonCx::new(); - let mut db = NameDb::default(); - let root = db.root_scope(); - let a = ccx.intern("a"); - let b = ccx.intern("b"); - let e = ccx.intern("E"); - let v = ccx.intern("V"); - - let (_, a_scope) = module(&mut db, root, a, Visibility::Public); - let (_, b_scope) = module(&mut db, root, b, Visibility::Public); - let enum_def = db.add_def( - a_scope, - DefKind::Enum, - Some(e), - Visibility::Public, - Origin::Synthetic, - ); - let enum_scope = db.add_scope(ScopeKind::Item, Some(a_scope)); - db.set_child_scope(enum_def, enum_scope); - db.add_def( - enum_scope, - DefKind::Variant, - Some(v), - Visibility::Public, - Origin::Synthetic, - ); - db.add_import( - b_scope, - vec![ccx.intern("super"), a, e, v], - ImportKind::Single, - Visibility::Private, - Origin::Synthetic, - ); + // Covers imports from this module shape: + // + // mod a { + // pub struct X; + // } + // + // mod b { + // pub struct X; + // } + // + // mod c { + // use super::a::*; + // use super::b::*; + // use super::a::Missing; + // } + // + // The two globs make `X` ambiguous in `c`; `Missing` reports not found. + #[test] + fn import_resolution_reports_ambiguity_and_not_found() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let c = ccx.intern("c"); + let x = ccx.intern("X"); + let missing = ccx.intern("Missing"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + let (_, c_scope) = module(&mut db, root, c, Visibility::Public); + db.add_def( + a_scope, + DefKind::Struct, + Some(x), + Visibility::Public, + Origin::Synthetic, + ); + db.add_def( + b_scope, + DefKind::Struct, + Some(x), + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), a], + ImportKind::Glob, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), b], + ImportKind::Glob, + Visibility::Private, + Origin::Synthetic, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), a, missing], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + let ResolveResult::Ambiguous(defs) = db.resolve_lexical(c_scope, Namespace::Type, x) + else { + panic!("expected imported globs to make {x:?} ambiguous"); + }; + assert_eq!(defs.len(), 2); + assert_eq!(db.imports()[2].status, ImportStatus::NotFound); + } - db.resolve_imports(); + // Covers imports from this module shape: + // + // mod a { + // pub struct X; + // pub const X: (); + // } + // + // mod b { + // use super::a::X; + // } + // + // This is an invalid DB state for a single import binding: the terminal candidates resolve + // to different final targets across namespaces, so validation must panic. + #[test] + #[should_panic(expected = "import binding candidates must resolve to one final target")] + fn single_import_panics_when_namespace_candidates_resolve_to_distinct_targets() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let x = ccx.intern("X"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + db.add_def( + a_scope, + DefKind::Struct, + Some(x), + Visibility::Public, + Origin::Synthetic, + ); + db.add_def( + a_scope, + DefKind::Const, + Some(x), + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, x], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + } - assert_eq!( - target_kind(&db, b_scope, Namespace::Type, v), - DefKind::Variant - ); - assert_eq!( - target_kind(&db, b_scope, Namespace::Value, v), - DefKind::Variant - ); + // Covers imports from this module shape: + // + // mod a { + // pub enum E { + // V, + // } + // } + // + // mod b { + // use super::a::E::V; + // } + // + // The variant `V` is imported into both type and value namespaces while still pointing at + // one final variant definition. + #[test] + fn imported_enum_variant_keeps_type_and_value_namespaces() { + let ccx = CommonCx::new(); + let mut db = NameDb::default(); + let root = db.root_scope(); + let a = ccx.intern("a"); + let b = ccx.intern("b"); + let e = ccx.intern("E"); + let v = ccx.intern("V"); + + let (_, a_scope) = module(&mut db, root, a, Visibility::Public); + let (_, b_scope) = module(&mut db, root, b, Visibility::Public); + let enum_def = db.add_def( + a_scope, + DefKind::Enum, + Some(e), + Visibility::Public, + Origin::Synthetic, + ); + let enum_scope = db.add_scope(ScopeKind::Item, Some(a_scope)); + db.set_child_scope(enum_def, enum_scope); + db.add_def( + enum_scope, + DefKind::Variant, + Some(v), + Visibility::Public, + Origin::Synthetic, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, e, v], + ImportKind::Single, + Visibility::Private, + Origin::Synthetic, + ); + + db.resolve_imports(); + + assert_eq!( + target_kind(&db, b_scope, Namespace::Type, v), + DefKind::Variant + ); + assert_eq!( + target_kind(&db, b_scope, Namespace::Value, v), + DefKind::Variant + ); + } } } From 1bb89f5d195d1ee0efe61e72221288a20e24c136 Mon Sep 17 00:00:00 2001 From: ecoricemon Date: Sat, 6 Jun 2026 12:53:09 +0900 Subject: [PATCH 4/5] wip: Organizing syn-sem-top --- crates/syn-sem-name/src/db.rs | 88 +++--- crates/syn-sem-name/src/def.rs | 13 +- crates/syn-sem-name/tests/ast.rs | 2 +- crates/syn-sem-top/src/context.rs | 4 +- crates/syn-sem-top/src/names.rs | 285 +++++++++++--------- crates/syn-sem-top/tests/name_resolution.rs | 34 +++ 6 files changed, 248 insertions(+), 178 deletions(-) diff --git a/crates/syn-sem-name/src/db.rs b/crates/syn-sem-name/src/db.rs index 5a0b891..3637737 100644 --- a/crates/syn-sem-name/src/db.rs +++ b/crates/syn-sem-name/src/db.rs @@ -629,6 +629,19 @@ impl<'cx> NameDb<'cx> { } } +impl Default for NameDb<'_> { + /// Creates a name database with a crate-root scope. + fn default() -> Self { + let root_scope = Scope::new(ScopeId::new(0), ScopeKind::CrateRoot, None); + + Self { + scopes: vec![root_scope], + defs: Vec::new(), + imports: Vec::new(), + } + } +} + /// Result of resolving a name. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ResolveResult { @@ -689,19 +702,6 @@ enum CandidateResolution { NotFound, } -impl Default for NameDb<'_> { - /// Creates a name database with a crate-root scope. - fn default() -> Self { - let root_scope = Scope::new(ScopeId::new(0), ScopeKind::CrateRoot, None); - - Self { - scopes: vec![root_scope], - defs: Vec::new(), - imports: Vec::new(), - } - } -} - impl<'cx> Index for NameDb<'cx> { type Output = Scope<'cx>; @@ -764,14 +764,14 @@ mod tests { DefKind::Local, Some(x), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); let inner = db.add_def( body, DefKind::Local, Some(x), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); assert_eq!( @@ -806,7 +806,7 @@ mod tests { DefKind::TypeParam, Some(t), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); assert_eq!( @@ -840,7 +840,7 @@ mod tests { DefKind::Struct, Some(local), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); assert_eq!( @@ -874,14 +874,14 @@ mod tests { DefKind::TypeParam, Some(t), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); let local = db.add_def( root, DefKind::Local, Some(t), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); assert_eq!( @@ -915,7 +915,7 @@ mod tests { DefKind::ConstParam, Some(n), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); assert_eq!( @@ -940,7 +940,7 @@ mod tests { DefKind::Module, Some(name), visibility, - Origin::Synthetic, + Origin::Untracked, ); let scope = db.add_scope(ScopeKind::Module, Some(parent)); db.set_child_scope(def, scope); @@ -994,35 +994,35 @@ mod tests { DefKind::Struct, Some(s), Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( b_scope, vec![ccx.intern("super"), a, s], ImportKind::Single, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( b_scope, vec![ccx.intern("super"), a, s], ImportKind::Rename(t), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( b_scope, vec![ccx.intern("super"), a, ccx.intern("self")], ImportKind::Single, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( b_scope, vec![ccx.intern("super"), a, s], ImportKind::Rename(hidden), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.resolve_imports(); @@ -1079,14 +1079,14 @@ mod tests { vec![ccx.intern("super"), a, self_name], ImportKind::Single, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( b_scope, vec![ccx.intern("super"), missing, self_name], ImportKind::Single, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.resolve_imports(); @@ -1140,35 +1140,35 @@ mod tests { DefKind::Struct, Some(public), Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); db.add_def( a_scope, DefKind::Struct, Some(private), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( b_scope, vec![ccx.intern("super"), a, public], ImportKind::Single, Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( c_scope, vec![ccx.intern("super"), b, public], ImportKind::Single, Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( d_scope, vec![ccx.intern("super"), a], ImportKind::Glob, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.resolve_imports(); @@ -1227,35 +1227,35 @@ mod tests { DefKind::Struct, Some(x), Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); db.add_def( b_scope, DefKind::Struct, Some(x), Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( c_scope, vec![ccx.intern("super"), a], ImportKind::Glob, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( c_scope, vec![ccx.intern("super"), b], ImportKind::Glob, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( c_scope, vec![ccx.intern("super"), a, missing], ImportKind::Single, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.resolve_imports(); @@ -1298,21 +1298,21 @@ mod tests { DefKind::Struct, Some(x), Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); db.add_def( a_scope, DefKind::Const, Some(x), Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( b_scope, vec![ccx.intern("super"), a, x], ImportKind::Single, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.resolve_imports(); @@ -1349,7 +1349,7 @@ mod tests { DefKind::Enum, Some(e), Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); let enum_scope = db.add_scope(ScopeKind::Item, Some(a_scope)); db.set_child_scope(enum_def, enum_scope); @@ -1358,14 +1358,14 @@ mod tests { DefKind::Variant, Some(v), Visibility::Public, - Origin::Synthetic, + Origin::Untracked, ); db.add_import( b_scope, vec![ccx.intern("super"), a, e, v], ImportKind::Single, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); db.resolve_imports(); diff --git a/crates/syn-sem-name/src/def.rs b/crates/syn-sem-name/src/def.rs index 785e043..10dcaa8 100644 --- a/crates/syn-sem-name/src/def.rs +++ b/crates/syn-sem-name/src/def.rs @@ -135,13 +135,12 @@ pub enum Visibility { /// Source origin associated with a definition or import. /// -/// The name crate deliberately does not depend on a concrete AST crate. Users can store their own -/// stable node index here and interpret it at the integration boundary. +/// This is the place where future source mapping can attach diagnostics, go-to-definition +/// information, or incremental invalidation data to `Def` and `Import` entries. For now, the name +/// database records untracked origins because no stable AST-node identity is wired through the +/// integration layer yet. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Origin { - /// No source node is associated with this entry. - Synthetic, - - /// Opaque AST node index owned by the caller. - AstNode(usize), + /// Source origin is not tracked for this entry. + Untracked, } diff --git a/crates/syn-sem-name/tests/ast.rs b/crates/syn-sem-name/tests/ast.rs index 67502fc..e0fb136 100644 --- a/crates/syn-sem-name/tests/ast.rs +++ b/crates/syn-sem-name/tests/ast.rs @@ -142,7 +142,7 @@ impl<'cx> AstNameCollector<'cx> { kind, Some(name), Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ) } } diff --git a/crates/syn-sem-top/src/context.rs b/crates/syn-sem-top/src/context.rs index 781d548..91350e9 100644 --- a/crates/syn-sem-top/src/context.rs +++ b/crates/syn-sem-top/src/context.rs @@ -1,4 +1,4 @@ -use crate::Semantics; +use crate::{NameCollector, Semantics}; use std::{fs, path::Path}; use syn_sem_ast::SyntaxCx; use syn_sem_common::{CommonCx, FilePath, Result, SourceText}; @@ -30,7 +30,7 @@ impl<'tcx> TopCx<'tcx> { /// Analyzes a previously inserted or read entry file. pub fn analyze(&'tcx self, entry_path: FilePath<'tcx>) -> Result> { let file = self.syntax.lookup_source(entry_path)?.ast(); - let names = crate::collect_names_in_top(self, entry_path, file)?; + let names = NameCollector::new(self).collect(entry_path, file)?; Ok(Semantics::new(self, names)) } diff --git a/crates/syn-sem-top/src/names.rs b/crates/syn-sem-top/src/names.rs index 9650528..a6582f8 100644 --- a/crates/syn-sem-top/src/names.rs +++ b/crates/syn-sem-top/src/names.rs @@ -6,59 +6,88 @@ use syn_sem_name::{ DefId, DefKind, ImportKind, Name, NameDb, Namespace, Origin, ScopeId, ScopeKind, Visibility, }; -pub(crate) fn collect_names_in_top<'tcx>( +pub(crate) struct NameCollector<'tcx> { tcx: &'tcx TopCx<'tcx>, - file_path: FilePath<'tcx>, - file: &ast::File<'tcx>, -) -> Result> { - let mut collector = NameCollector::default(); - let root = collector.db.root_scope(); - let path = ModulePath::root(PathBuf::from(&*file_path)); - for item in file.items { - collector.collect_item_in_top(tcx, root, item, &path)?; - } - collector.db.resolve_imports(); - Ok(collector.db) -} - -#[derive(Default)] -struct NameCollector<'tcx> { db: NameDb<'tcx>, } impl<'tcx> NameCollector<'tcx> { - fn collect_item(&mut self, scope: ScopeId, item: &ast::Item<'tcx>) { + pub(crate) fn new(tcx: &'tcx TopCx<'tcx>) -> Self { + Self { + tcx, + db: NameDb::default(), + } + } + + pub(crate) fn collect( + mut self, + file_path: FilePath<'tcx>, + file: &ast::File<'tcx>, + ) -> Result> { + let root = self.db.root_scope(); + let path = ModulePath::from_entry_file(PathBuf::from(&*file_path)); + for item in file.items { + self.collect_item_from_module_tree(root, item, &path)?; + } + self.db.resolve_imports(); + Ok(self.db) + } + + /// Collects one item while walking a crate's module tree. + /// + /// Unlike [`Self::collect_item_from_ast`], this variant follows `mod foo;` declarations to + /// their source files and continues collecting there. + fn collect_item_from_module_tree( + &mut self, + scope: ScopeId, + item: &ast::Item<'tcx>, + path: &ModulePath, + ) -> Result<()> { + match item { + ast::Item::Mod(item) => self.collect_mod_from_module_tree(scope, item, path), + _ => { + self.collect_item_from_ast(scope, item); + Ok(()) + } + } + } + + /// Collects one AST item into the current name database without loading extra files. + /// + /// Inline modules are collected recursively, declarations create `Def`s, and `use` trees are + /// recorded as imports to be resolved after collection finishes. + fn collect_item_from_ast(&mut self, scope: ScopeId, item: &ast::Item<'tcx>) { match item { ast::Item::Const(item) => { - self.add_named( + self.add_named_def( scope, DefKind::Const, item.ident.inner, - self.ast_visibility(scope, &item.vis), + self.visibility_from_ast(scope, &item.vis), ); } ast::Item::Enum(item) => self.collect_enum(scope, item), ast::Item::Fn(item) => { - self.collect_fn(scope, item, self.ast_visibility(scope, &item.vis)) + self.collect_fn(scope, item, self.visibility_from_ast(scope, &item.vis)) } ast::Item::Impl(item) => self.collect_impl(scope, item), - ast::Item::Mod(item) => self.collect_mod(scope, item), + ast::Item::Mod(item) => self.collect_mod_from_ast(scope, item), ast::Item::Struct(item) => { - self.add_named( + self.add_named_def( scope, DefKind::Struct, item.ident.inner, - self.ast_visibility(scope, &item.vis), + self.visibility_from_ast(scope, &item.vis), ); self.collect_generics(scope, &item.generics); } ast::Item::Trait(item) => self.collect_trait(scope, item), ast::Item::Type(item) => { - self.add_named( + self.add_named_def( scope, DefKind::TypeAlias, item.ident.inner, - self.ast_visibility(scope, &item.vis), + self.visibility_from_ast(scope, &item.vis), ); self.collect_generics(scope, &item.generics); } @@ -67,76 +96,23 @@ impl<'tcx> NameCollector<'tcx> { scope, Vec::new(), &item.tree, - self.ast_visibility(scope, &item.vis), + self.visibility_from_ast(scope, &item.vis), ); } } } - fn collect_enum(&mut self, parent_scope: ScopeId, item: &ast::ItemEnum<'tcx>) { - let visibility = self.ast_visibility(parent_scope, &item.vis); - let enum_def = self.add_named(parent_scope, DefKind::Enum, item.ident.inner, visibility); - let item_scope = self.db.add_scope(ScopeKind::Item, Some(parent_scope)); - self.db.set_child_scope(enum_def, item_scope); - self.collect_generics_into(item_scope, &item.generics); - - for variant in item.variants { - self.add_named( - item_scope, - DefKind::Variant, - variant.ident.inner, - visibility, - ); - } - } - - fn collect_mod(&mut self, parent_scope: ScopeId, item: &ast::ItemMod<'tcx>) { - let module_def = self.add_named( - parent_scope, - DefKind::Module, - item.ident.inner, - self.ast_visibility(parent_scope, &item.vis), - ); - let module_scope = self.db.add_scope(ScopeKind::Module, Some(parent_scope)); - self.db.set_child_scope(module_def, module_scope); - - if let Some(items) = item.items { - for item in items { - self.collect_item(module_scope, item); - } - } - } - - /// Top-level collection differs from AST-only collection only for out-of-line modules: - /// `mod foo;` may require loading and collecting names from another source file. - fn collect_item_in_top( + fn collect_mod_from_module_tree( &mut self, - tcx: &'tcx TopCx<'tcx>, - scope: ScopeId, - item: &ast::Item<'tcx>, - path: &ModulePath, - ) -> Result<()> { - match item { - ast::Item::Mod(item) => self.collect_mod_in_top(tcx, scope, item, path), - _ => { - self.collect_item(scope, item); - Ok(()) - } - } - } - - fn collect_mod_in_top( - &mut self, - tcx: &'tcx TopCx<'tcx>, parent_scope: ScopeId, item: &ast::ItemMod<'tcx>, path: &ModulePath, ) -> Result<()> { - let module_def = self.add_named( + let module_def = self.add_named_def( parent_scope, DefKind::Module, item.ident.inner, - self.ast_visibility(parent_scope, &item.vis), + self.visibility_from_ast(parent_scope, &item.vis), ); let module_scope = self.db.add_scope(ScopeKind::Module, Some(parent_scope)); self.db.set_child_scope(module_def, module_scope); @@ -148,28 +124,63 @@ impl<'tcx> NameCollector<'tcx> { module_dir, }; for item in items { - self.collect_item_in_top(tcx, module_scope, item, &path)?; + self.collect_item_from_module_tree(module_scope, item, &path)?; } - } else if let Some(file_path) = path.child_file(tcx, item)? { - let file = tcx.syntax.lookup_source(file_path)?.ast(); + } else if let Some(file_path) = path.child_file(self.tcx, item)? { + let file = self.tcx.syntax.lookup_source(file_path)?.ast(); let path = ModulePath { source_file: file_path.as_ref().into(), module_dir, }; for item in file.items { - self.collect_item_in_top(tcx, module_scope, item, &path)?; + self.collect_item_from_module_tree(module_scope, item, &path)?; } } Ok(()) } + fn collect_mod_from_ast(&mut self, parent_scope: ScopeId, item: &ast::ItemMod<'tcx>) { + let module_def = self.add_named_def( + parent_scope, + DefKind::Module, + item.ident.inner, + self.visibility_from_ast(parent_scope, &item.vis), + ); + let module_scope = self.db.add_scope(ScopeKind::Module, Some(parent_scope)); + self.db.set_child_scope(module_def, module_scope); + + if let Some(items) = item.items { + for item in items { + self.collect_item_from_ast(module_scope, item); + } + } + } + + fn collect_enum(&mut self, parent_scope: ScopeId, item: &ast::ItemEnum<'tcx>) { + let visibility = self.visibility_from_ast(parent_scope, &item.vis); + let enum_def = + self.add_named_def(parent_scope, DefKind::Enum, item.ident.inner, visibility); + let item_scope = self.db.add_scope(ScopeKind::Item, Some(parent_scope)); + self.db.set_child_scope(enum_def, item_scope); + self.collect_generics_into(item_scope, &item.generics); + + for variant in item.variants { + self.add_named_def( + item_scope, + DefKind::Variant, + variant.ident.inner, + visibility, + ); + } + } + fn collect_trait(&mut self, parent_scope: ScopeId, item: &ast::ItemTrait<'tcx>) { - self.add_named( + self.add_named_def( parent_scope, DefKind::Trait, item.ident.inner, - self.ast_visibility(parent_scope, &item.vis), + self.visibility_from_ast(parent_scope, &item.vis), ); let trait_scope = self.db.add_scope(ScopeKind::Trait, Some(parent_scope)); self.collect_generics_into(trait_scope, &item.generics); @@ -177,7 +188,7 @@ impl<'tcx> NameCollector<'tcx> { for item in item.items { match item { ast::TraitItem::Const(item) => { - self.add_named( + self.add_named_def( trait_scope, DefKind::Const, item.ident.inner, @@ -197,7 +208,7 @@ impl<'tcx> NameCollector<'tcx> { } } ast::TraitItem::Type(item) => { - self.add_named( + self.add_named_def( trait_scope, DefKind::TypeAlias, item.ident.inner, @@ -215,7 +226,7 @@ impl<'tcx> NameCollector<'tcx> { DefKind::Impl, None, Visibility::Private, - Origin::Synthetic, + Origin::Untracked, ); let impl_scope = self.db.add_scope(ScopeKind::Impl, Some(parent_scope)); @@ -224,7 +235,7 @@ impl<'tcx> NameCollector<'tcx> { for item in item.items { match item { ast::ImplItem::Const(item) => { - self.add_named( + self.add_named_def( impl_scope, DefKind::Const, item.ident.inner, @@ -242,7 +253,7 @@ impl<'tcx> NameCollector<'tcx> { self.collect_block(impl_scope, &item.block); } ast::ImplItem::Type(item) => { - self.add_named( + self.add_named_def( impl_scope, DefKind::TypeAlias, item.ident.inner, @@ -285,7 +296,7 @@ impl<'tcx> NameCollector<'tcx> { sig: &ast::Signature<'tcx>, visibility: Visibility, ) { - self.add_named(parent_scope, kind, sig.ident.inner, visibility); + self.add_named_def(parent_scope, kind, sig.ident.inner, visibility); } fn collect_block(&mut self, parent_scope: ScopeId, block: &ast::Block<'tcx>) { @@ -294,7 +305,7 @@ impl<'tcx> NameCollector<'tcx> { for stmt in block.stmts { match stmt { ast::Stmt::Local(local) => self.collect_pat(block_scope, &local.pat), - ast::Stmt::Item(item) => self.collect_item(block_scope, item), + ast::Stmt::Item(item) => self.collect_item_from_ast(block_scope, item), ast::Stmt::Expr(_) => {} } } @@ -311,7 +322,7 @@ impl<'tcx> NameCollector<'tcx> { for param in generics.params { match param { ast::GenericParam::Type(param) => { - self.add_named( + self.add_named_def( scope, DefKind::TypeParam, param.ident.inner, @@ -319,7 +330,7 @@ impl<'tcx> NameCollector<'tcx> { ); } ast::GenericParam::Const(param) => { - self.add_named( + self.add_named_def( scope, DefKind::ConstParam, param.ident.inner, @@ -334,7 +345,7 @@ impl<'tcx> NameCollector<'tcx> { fn collect_pat(&mut self, scope: ScopeId, pat: &ast::Pat<'tcx>) { match pat { ast::Pat::Ident(pat) => { - self.add_named(scope, DefKind::Local, pat.ident.inner, Visibility::Private); + self.add_named_def(scope, DefKind::Local, pat.ident.inner, Visibility::Private); } ast::Pat::Reference(pat) => self.collect_pat(scope, pat.pat), ast::Pat::Slice(pat) => { @@ -378,7 +389,7 @@ impl<'tcx> NameCollector<'tcx> { source_path, ImportKind::Single, visibility, - Origin::Synthetic, + Origin::Untracked, ); } ast::UseTree::Rename(tree) => { @@ -389,7 +400,7 @@ impl<'tcx> NameCollector<'tcx> { source_path, ImportKind::Rename(tree.rename.inner), visibility, - Origin::Synthetic, + Origin::Untracked, ); } ast::UseTree::Glob(_) => { @@ -398,7 +409,7 @@ impl<'tcx> NameCollector<'tcx> { prefix, ImportKind::Glob, visibility, - Origin::Synthetic, + Origin::Untracked, ); } ast::UseTree::Group(tree) => { @@ -409,7 +420,7 @@ impl<'tcx> NameCollector<'tcx> { } } - fn add_named( + fn add_named_def( &mut self, scope: ScopeId, kind: DefKind, @@ -417,45 +428,61 @@ impl<'tcx> NameCollector<'tcx> { visibility: Visibility, ) -> DefId { self.db - .add_def(scope, kind, Some(name), visibility, Origin::Synthetic) + .add_def(scope, kind, Some(name), visibility, Origin::Untracked) } - fn ast_visibility(&self, scope: ScopeId, vis: &ast::Visibility<'tcx>) -> Visibility { + fn visibility_from_ast(&self, scope: ScopeId, vis: &ast::Visibility<'tcx>) -> Visibility { match vis { ast::Visibility::Public(_) => Visibility::Public, - ast::Visibility::Restricted(path) => self - .visibility_scope(scope, path) - .map(Visibility::Restricted) - .unwrap_or(Visibility::Private), + ast::Visibility::Restricted(path) => { + Visibility::Restricted(self.resolve_restricted_visibility_scope(scope, path)) + } ast::Visibility::Private => Visibility::Private, } } - fn visibility_scope(&self, scope: ScopeId, path: &ast::Path<'tcx>) -> Option { + fn resolve_restricted_visibility_scope( + &self, + scope: ScopeId, + path: &ast::Path<'tcx>, + ) -> ScopeId { let mut scope = self.nearest_module_scope(scope); let mut segments = path.segments.iter(); - let first = segments.next()?; + let first = segments + .next() + .expect("restricted visibility path must have at least one segment"); match first.ident.inner.as_ref() { "crate" => scope = self.db.root_scope(), "self" => {} - "super" => scope = self.parent_module_scope(scope)?, - _ => return None, + "super" => { + scope = self + .parent_module_scope(scope) + .expect("restricted visibility `super` must have a parent module") + } + _ => panic!("restricted visibility path must start with `crate`, `self`, or `super`"), } for segment in segments { - if segment.has_args() { - return None; - } + assert!( + !segment.has_args(), + "restricted visibility path segments must not have arguments" + ); + let binding = self.db[scope] .bindings - .get(Namespace::Type, segment.ident.inner)?; - let def = binding.single()?; + .get(Namespace::Type, segment.ident.inner) + .expect("restricted visibility path segment must resolve"); + let def = binding + .single() + .expect("restricted visibility path segment must resolve unambiguously"); let target = self.db.follow_aliases(def); - scope = self.db[target].child_scope?; + scope = self.db[target] + .child_scope + .expect("restricted visibility path segment must name a scope-bearing item"); } - Some(scope) + scope } fn nearest_module_scope(&self, mut scope: ScopeId) -> ScopeId { @@ -508,18 +535,28 @@ fn path_attr(item: &ast::ItemMod<'_>) -> Option { }) } +/// Tracks filesystem locations while walking a Rust module tree. +/// +/// It records both the file currently being collected and the directory used to search for that +/// module's out-of-line child files, such as `foo.rs` or `foo/mod.rs`. struct ModulePath { + /// Source file currently being collected. source_file: PathBuf, + + /// Directory used to search for child modules declared from this module. module_dir: PathBuf, } impl ModulePath { - fn root(file_path: PathBuf) -> Self { + fn from_entry_file(file_path: PathBuf) -> Self { let source_dir = file_path.parent().unwrap_or_else(|| Path::new("")); - let module_dir = match file_path.file_stem().and_then(|stem| stem.to_str()) { - Some("lib" | "main" | "mod") => source_dir.to_path_buf(), - Some(stem) => source_dir.join(stem), - None => source_dir.to_path_buf(), + let stem = file_path + .file_stem() + .and_then(|stem| stem.to_str()) + .expect("root module path must be a Rust source file path"); + let module_dir = match stem { + "lib" | "main" | "mod" => source_dir.to_path_buf(), + stem => source_dir.join(stem), }; Self { diff --git a/crates/syn-sem-top/tests/name_resolution.rs b/crates/syn-sem-top/tests/name_resolution.rs index fb80e02..58118c3 100644 --- a/crates/syn-sem-top/tests/name_resolution.rs +++ b/crates/syn-sem-top/tests/name_resolution.rs @@ -136,6 +136,40 @@ fn applies_restricted_visibility_to_imports() { assert_eq!(db.imports()[3].status, ImportStatus::NotFound); } +#[test] +#[should_panic(expected = "restricted visibility path must start with `crate`, `self`, or `super`")] +fn invalid_restricted_visibility_anchor_panics() { + let tcx = TopCx::default(); + let entry_path = tcx.common.intern("invalid_visibility.rs"); + let text = tcx.common.intern( + r#" + mod a { + pub(in a) struct Invalid; + } + "#, + ); + tcx.insert_virtual_file(entry_path, text).unwrap(); + + let _ = tcx.analyze(entry_path); +} + +#[test] +#[should_panic(expected = "restricted visibility path segment must resolve")] +fn unresolved_restricted_visibility_path_panics() { + let tcx = TopCx::default(); + let entry_path = tcx.common.intern("unresolved_visibility.rs"); + let text = tcx.common.intern( + r#" + mod a { + pub(in crate::missing) struct Invalid; + } + "#, + ); + tcx.insert_virtual_file(entry_path, text).unwrap(); + + let _ = tcx.analyze(entry_path); +} + #[test] fn imports_enum_variants_in_type_and_value_namespaces() { let tcx = TopCx::default(); From 17bee09873bfbf1e8919c2d20e91d3c725ae86cd Mon Sep 17 00:00:00 2001 From: ecoricemon Date: Sat, 6 Jun 2026 16:55:11 +0900 Subject: [PATCH 5/5] wip: Fixing --- crates/syn-sem-name/src/db.rs | 7 ++ crates/syn-sem-name/src/def.rs | 6 ++ crates/syn-sem-top/src/names.rs | 109 ++++++++++--------- crates/syn-sem-top/tests/name_resolution.rs | 113 ++++++++++++++++++++ 4 files changed, 187 insertions(+), 48 deletions(-) diff --git a/crates/syn-sem-name/src/db.rs b/crates/syn-sem-name/src/db.rs index 3637737..75b59d8 100644 --- a/crates/syn-sem-name/src/db.rs +++ b/crates/syn-sem-name/src/db.rs @@ -86,6 +86,7 @@ impl<'cx> NameDb<'cx> { kind, parent_scope, child_scope: None, + generic_scope: None, target: None, visibility, origin, @@ -127,6 +128,11 @@ impl<'cx> NameDb<'cx> { self.defs[def.index()].child_scope = Some(child_scope); } + /// Links a definition to the scope containing its generic parameters. + pub fn set_generic_scope(&mut self, def: DefId, generic_scope: ScopeId) { + self.defs[def.index()].generic_scope = Some(generic_scope); + } + /// Follows `DefKind::Use` alias definitions to their underlying definition. /// /// For example, if `use b::C` points at `pub use a::C`, and that re-export points at the @@ -203,6 +209,7 @@ impl<'cx> NameDb<'cx> { kind: DefKind::Use, parent_scope, child_scope: None, + generic_scope: None, target: Some(target), visibility, origin, diff --git a/crates/syn-sem-name/src/def.rs b/crates/syn-sem-name/src/def.rs index 10dcaa8..2d682f6 100644 --- a/crates/syn-sem-name/src/def.rs +++ b/crates/syn-sem-name/src/def.rs @@ -21,6 +21,12 @@ pub struct Def<'cx> { /// Definitions without importable children leave this unset. pub child_scope: Option, + /// Scope containing this definition's generic parameters. + /// + /// Generic parameters are lexical names, not importable children. Definitions without generic + /// parameters leave this unset. + pub generic_scope: Option, + /// Definition this definition aliases. /// /// Import definitions use this to point at their resolved target. diff --git a/crates/syn-sem-top/src/names.rs b/crates/syn-sem-top/src/names.rs index a6582f8..30971a1 100644 --- a/crates/syn-sem-top/src/names.rs +++ b/crates/syn-sem-top/src/names.rs @@ -73,23 +73,23 @@ impl<'tcx> NameCollector<'tcx> { ast::Item::Impl(item) => self.collect_impl(scope, item), ast::Item::Mod(item) => self.collect_mod_from_ast(scope, item), ast::Item::Struct(item) => { - self.add_named_def( + let def = self.add_named_def( scope, DefKind::Struct, item.ident.inner, self.visibility_from_ast(scope, &item.vis), ); - self.collect_generics(scope, &item.generics); + self.collect_generic_scope(def, scope, &item.generics); } ast::Item::Trait(item) => self.collect_trait(scope, item), ast::Item::Type(item) => { - self.add_named_def( + let def = self.add_named_def( scope, DefKind::TypeAlias, item.ident.inner, self.visibility_from_ast(scope, &item.vis), ); - self.collect_generics(scope, &item.generics); + self.collect_generic_scope(def, scope, &item.generics); } ast::Item::Use(item) => { self.collect_use_tree( @@ -158,13 +158,19 @@ impl<'tcx> NameCollector<'tcx> { } fn collect_enum(&mut self, parent_scope: ScopeId, item: &ast::ItemEnum<'tcx>) { + // Enum Def let visibility = self.visibility_from_ast(parent_scope, &item.vis); let enum_def = self.add_named_def(parent_scope, DefKind::Enum, item.ident.inner, visibility); - let item_scope = self.db.add_scope(ScopeKind::Item, Some(parent_scope)); - self.db.set_child_scope(enum_def, item_scope); - self.collect_generics_into(item_scope, &item.generics); + // Generic scope + let item_parent_scope = self + .collect_generic_scope(enum_def, parent_scope, &item.generics) + .unwrap_or(parent_scope); + + // Variant Def + let item_scope = self.db.add_scope(ScopeKind::Item, Some(item_parent_scope)); + self.db.set_child_scope(enum_def, item_scope); for variant in item.variants { self.add_named_def( item_scope, @@ -176,52 +182,57 @@ impl<'tcx> NameCollector<'tcx> { } fn collect_trait(&mut self, parent_scope: ScopeId, item: &ast::ItemTrait<'tcx>) { - self.add_named_def( + let trait_def = self.add_named_def( parent_scope, DefKind::Trait, item.ident.inner, self.visibility_from_ast(parent_scope, &item.vis), ); - let trait_scope = self.db.add_scope(ScopeKind::Trait, Some(parent_scope)); - self.collect_generics_into(trait_scope, &item.generics); + let trait_parent_scope = self + .collect_generic_scope(trait_def, parent_scope, &item.generics) + .unwrap_or(parent_scope); + let trait_scope = self + .db + .add_scope(ScopeKind::Trait, Some(trait_parent_scope)); for item in item.items { match item { ast::TraitItem::Const(item) => { - self.add_named_def( + let def = self.add_named_def( trait_scope, DefKind::Const, item.ident.inner, Visibility::Private, ); - self.collect_generics(trait_scope, &item.generics); + self.collect_generic_scope(def, trait_scope, &item.generics); } ast::TraitItem::Fn(item) => { - self.collect_fn_signature( + let def = self.add_named_def( trait_scope, DefKind::Fn, - &item.sig, + item.sig.ident.inner, Visibility::Private, ); + self.collect_generic_scope(def, trait_scope, &item.sig.generics); if let Some(block) = &item.default { self.collect_block(trait_scope, block); } } ast::TraitItem::Type(item) => { - self.add_named_def( + let def = self.add_named_def( trait_scope, DefKind::TypeAlias, item.ident.inner, Visibility::Private, ); - self.collect_generics(trait_scope, &item.generics); + self.collect_generic_scope(def, trait_scope, &item.generics); } } } } fn collect_impl(&mut self, parent_scope: ScopeId, item: &ast::ItemImpl<'tcx>) { - self.db.add_def( + let impl_def = self.db.add_def( parent_scope, DefKind::Impl, None, @@ -229,37 +240,40 @@ impl<'tcx> NameCollector<'tcx> { Origin::Untracked, ); - let impl_scope = self.db.add_scope(ScopeKind::Impl, Some(parent_scope)); - self.collect_generics_into(impl_scope, &item.generics); + let impl_parent_scope = self + .collect_generic_scope(impl_def, parent_scope, &item.generics) + .unwrap_or(parent_scope); + let impl_scope = self.db.add_scope(ScopeKind::Impl, Some(impl_parent_scope)); for item in item.items { match item { ast::ImplItem::Const(item) => { - self.add_named_def( + let def = self.add_named_def( impl_scope, DefKind::Const, item.ident.inner, Visibility::Private, ); - self.collect_generics(impl_scope, &item.generics); + self.collect_generic_scope(def, impl_scope, &item.generics); } ast::ImplItem::Fn(item) => { - self.collect_fn_signature( + let def = self.add_named_def( impl_scope, DefKind::Fn, - &item.sig, + item.sig.ident.inner, Visibility::Private, ); + self.collect_generic_scope(def, impl_scope, &item.sig.generics); self.collect_block(impl_scope, &item.block); } ast::ImplItem::Type(item) => { - self.add_named_def( + let def = self.add_named_def( impl_scope, DefKind::TypeAlias, item.ident.inner, Visibility::Private, ); - self.collect_generics(impl_scope, &item.generics); + self.collect_generic_scope(def, impl_scope, &item.generics); } } } @@ -271,16 +285,15 @@ impl<'tcx> NameCollector<'tcx> { item: &ast::ItemFn<'tcx>, visibility: Visibility, ) { - self.collect_fn_signature(parent_scope, DefKind::Fn, &item.sig, visibility); - - let generic_scope = self - .db - .add_scope(ScopeKind::GenericParams, Some(parent_scope)); - self.collect_generics_into(generic_scope, &item.generics); + let fn_def = + self.add_named_def(parent_scope, DefKind::Fn, item.sig.ident.inner, visibility); + let body_parent_scope = self + .collect_generic_scope(fn_def, parent_scope, &item.generics) + .unwrap_or(parent_scope); let body_scope = self .db - .add_scope(ScopeKind::FunctionBody, Some(generic_scope)); + .add_scope(ScopeKind::FunctionBody, Some(body_parent_scope)); for param in item.sig.params.iter().skip(1) { self.collect_pat(body_scope, param.pat.pat); @@ -289,16 +302,6 @@ impl<'tcx> NameCollector<'tcx> { self.collect_block(body_scope, &item.block); } - fn collect_fn_signature( - &mut self, - parent_scope: ScopeId, - kind: DefKind, - sig: &ast::Signature<'tcx>, - visibility: Visibility, - ) { - self.add_named_def(parent_scope, kind, sig.ident.inner, visibility); - } - fn collect_block(&mut self, parent_scope: ScopeId, block: &ast::Block<'tcx>) { let block_scope = self.db.add_scope(ScopeKind::Block, Some(parent_scope)); @@ -311,19 +314,27 @@ impl<'tcx> NameCollector<'tcx> { } } - fn collect_generics(&mut self, parent_scope: ScopeId, generics: &ast::Generics<'tcx>) { + /// Creates and links a `GenericParams` scope for `def`, then collects generic parameter defs. + fn collect_generic_scope( + &mut self, + def: DefId, + parent_scope: ScopeId, + generics: &ast::Generics<'tcx>, + ) -> Option { + if generics.params.is_empty() { + return None; + } + let generic_scope = self .db .add_scope(ScopeKind::GenericParams, Some(parent_scope)); - self.collect_generics_into(generic_scope, generics); - } + self.db.set_generic_scope(def, generic_scope); - fn collect_generics_into(&mut self, scope: ScopeId, generics: &ast::Generics<'tcx>) { for param in generics.params { match param { ast::GenericParam::Type(param) => { self.add_named_def( - scope, + generic_scope, DefKind::TypeParam, param.ident.inner, Visibility::Private, @@ -331,7 +342,7 @@ impl<'tcx> NameCollector<'tcx> { } ast::GenericParam::Const(param) => { self.add_named_def( - scope, + generic_scope, DefKind::ConstParam, param.ident.inner, Visibility::Private, @@ -340,6 +351,8 @@ impl<'tcx> NameCollector<'tcx> { ast::GenericParam::Unsupported(_) => {} } } + + Some(generic_scope) } fn collect_pat(&mut self, scope: ScopeId, pat: &ast::Pat<'tcx>) { diff --git a/crates/syn-sem-top/tests/name_resolution.rs b/crates/syn-sem-top/tests/name_resolution.rs index 58118c3..9b7efeb 100644 --- a/crates/syn-sem-top/tests/name_resolution.rs +++ b/crates/syn-sem-top/tests/name_resolution.rs @@ -203,6 +203,105 @@ fn imports_enum_variants_in_type_and_value_namespaces() { ); } +#[test] +fn connects_generic_scopes_to_defs_consistently() { + let tcx = TopCx::default(); + let entry_path = tcx.common.intern("generics.rs"); + let text = tcx.common.intern( + r#" + struct S; + + enum E { + V, + } + + trait Tr { + fn m(); + } + + impl S { + fn make() {} + } + + fn f() {} + "#, + ); + tcx.insert_virtual_file(entry_path, text).unwrap(); + + let semantics = tcx.analyze(entry_path).unwrap(); + let db = semantics.names(); + let root = db.root_scope(); + + let s_def = resolve_def(&tcx, db, root, Namespace::Type, "S"); + let s_generic_scope = db[s_def].generic_scope.expect("S should have generics"); + assert_eq!(db[s_generic_scope].kind, ScopeKind::GenericParams); + assert_eq!( + resolve_result(&tcx, db, s_generic_scope, Namespace::Type, "T"), + ResolveResult::Found(resolve_def(&tcx, db, s_generic_scope, Namespace::Type, "T")) + ); + assert!(db[s_def].child_scope.is_none()); + + let e_def = resolve_def(&tcx, db, root, Namespace::Type, "E"); + let e_generic_scope = db[e_def].generic_scope.expect("E should have generics"); + let e_item_scope = db[e_def].child_scope.expect("E should expose variants"); + assert_eq!(db[e_generic_scope].kind, ScopeKind::GenericParams); + assert_eq!(db[e_item_scope].kind, ScopeKind::Item); + assert_eq!(db[e_item_scope].parent, Some(e_generic_scope)); + assert_eq!( + resolve_result(&tcx, db, e_generic_scope, Namespace::Type, "U"), + ResolveResult::Found(resolve_def(&tcx, db, e_generic_scope, Namespace::Type, "U")) + ); + assert_eq!( + resolve_result(&tcx, db, e_item_scope, Namespace::Type, "V"), + ResolveResult::Found(resolve_def(&tcx, db, e_item_scope, Namespace::Type, "V")) + ); + assert!(db + .binding(e_item_scope, Namespace::Type, tcx.common.intern("U")) + .is_none()); + + let trait_def = resolve_def(&tcx, db, root, Namespace::Type, "Tr"); + let trait_generic_scope = db[trait_def] + .generic_scope + .expect("Tr should have generics"); + assert_eq!(db[trait_generic_scope].kind, ScopeKind::GenericParams); + assert_eq!( + resolve_result(&tcx, db, trait_generic_scope, Namespace::Type, "W"), + ResolveResult::Found(resolve_def( + &tcx, + db, + trait_generic_scope, + Namespace::Type, + "W" + )) + ); + + let f_def = resolve_def(&tcx, db, root, Namespace::Value, "f"); + let f_generic_scope = db[f_def].generic_scope.expect("f should have generics"); + assert_eq!(db[f_generic_scope].kind, ScopeKind::GenericParams); + assert_eq!( + resolve_result(&tcx, db, f_generic_scope, Namespace::Type, "N"), + ResolveResult::Found(resolve_def(&tcx, db, f_generic_scope, Namespace::Type, "N")) + ); + + let impl_def = db + .defs() + .iter() + .find(|def| def.kind == DefKind::Impl) + .expect("impl def should be collected"); + let impl_generic_scope = impl_def.generic_scope.expect("impl should have generics"); + assert_eq!(db[impl_generic_scope].kind, ScopeKind::GenericParams); + assert_eq!( + resolve_result(&tcx, db, impl_generic_scope, Namespace::Type, "Y"), + ResolveResult::Found(resolve_def( + &tcx, + db, + impl_generic_scope, + Namespace::Type, + "Y" + )) + ); +} + fn insert_virtual_fixture_tree<'tcx>(tcx: &'tcx TopCx<'tcx>) -> FilePath<'tcx> { let entry_path = tcx.common.intern("a1.rs"); let text = tcx.common.intern(include_str!("file/a1.rs")); @@ -286,6 +385,20 @@ fn resolve_kind<'tcx>( db[def].kind } +fn resolve_def<'tcx>( + tcx: &'tcx TopCx<'tcx>, + db: &NameDb<'tcx>, + scope: ScopeId, + namespace: Namespace, + name: &str, +) -> syn_sem_name::DefId { + let name = tcx.common.intern(name); + let ResolveResult::Found(def) = resolve_lexical(db, scope, namespace, name) else { + panic!("expected {name:?} to resolve in {namespace:?}"); + }; + def +} + fn follow_aliases_kind<'tcx>( tcx: &'tcx TopCx<'tcx>, db: &NameDb<'tcx>,