diff --git a/crates/syn-sem-name/src/db.rs b/crates/syn-sem-name/src/db.rs index 165228f..75b59d8 100644 --- a/crates/syn-sem-name/src/db.rs +++ b/crates/syn-sem-name/src/db.rs @@ -1,22 +1,9 @@ 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}; -/// 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> { @@ -46,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()); @@ -88,6 +85,9 @@ impl<'cx> NameDb<'cx> { name, kind, parent_scope, + child_scope: None, + generic_scope: None, + target: None, visibility, origin, }); @@ -123,8 +123,147 @@ impl<'cx> NameDb<'cx> { id } - /// Resolves a single-segment name lexically in one namespace. - pub fn resolve_lexical( + /// 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.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 + /// 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 { + 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; + } + } + } + + 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, + generic_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) + } + + /// 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 binding validation requires at least one resolved binding"); + let target = self.follow_aliases(first); + 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); + } + + 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, namespace: Namespace, @@ -147,8 +286,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; @@ -160,19 +298,417 @@ impl<'cx> NameDb<'cx> { descendant = parent; } } + + /// 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]; + match import_data.kind { + ImportKind::Single | ImportKind::Rename(_) => { + let local_name = match self.import_local_name(import_data) { + ImportLocalName::Name(name) => name, + ImportLocalName::NoBinding => { + 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 candidates = + match self.resolve_import_path(import_data.scope, &import_data.source_path) { + CandidateResolution::Found(candidates) => candidates, + CandidateResolution::Ambiguous => return ImportResolve::Ambiguous, + CandidateResolution::NotFound => return ImportResolve::Pending, + }; + + 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, + import_binding.target, + ); + for namespace in import_binding.namespaces { + self.insert_unique_binding(import_data.scope, namespace, local_name, alias); + } + ImportResolve::Resolved + } + ImportKind::Glob => { + let candidates = + match self.resolve_import_path(import_data.scope, &import_data.source_path) { + CandidateResolution::Found(candidates) => candidates, + CandidateResolution::Ambiguous => return ImportResolve::Ambiguous, + CandidateResolution::NotFound => return ImportResolve::Pending, + }; + + let [(_, glob_candidate)] = candidates.as_slice() else { + return ImportResolve::Ambiguous; + }; + + let glob_target = self.follow_aliases(*glob_candidate); + let Some(child_scope) = self[glob_target].child_scope else { + return ImportResolve::Pending; + }; + + let mut visible = Vec::new(); + 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) { + visible.push((namespace, name, def)); + } + } + } + } + + let import_data = import_data.clone(); + for (namespace, name, def) in visible { + let target = self.follow_aliases(def); + let alias = self.get_or_insert_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 + } + } + } + + /// 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 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 candidates = match self.resolve_import_path(import.scope, parent) { + CandidateResolution::Found(candidates) => candidates, + CandidateResolution::Ambiguous => return ImportLocalName::Ambiguous, + CandidateResolution::NotFound => return ImportLocalName::Pending, + }; + + let import_binding = self.validate_import_binding_candidates(&candidates); + + let Some(name) = self[import_binding.target].name else { + return ImportLocalName::Pending; + }; + + name + } else { + terminal + } + } + ImportKind::Rename(name) => name, + ImportKind::Glob => return ImportLocalName::NoBinding, + }; + + if name.as_ref() == "_" { + ImportLocalName::NoBinding + } else { + ImportLocalName::Name(name) + } + } + + /// 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>]) -> CandidateResolution { + if path.is_empty() { + return CandidateResolution::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| 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 CandidateResolution::NotFound; + }; + current_scope = parent; + current_def = None; + index += 1; + continue; + } + _ => {} + } + + 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 candidates = match candidates { + CandidateResolution::Found(candidates) => candidates, + other => return other, + }; + + let visible = candidates + .into_iter() + .filter(|(_, def)| self.is_visible_from(*def, use_scope)) + .collect::>(); + + if visible.is_empty() { + return CandidateResolution::NotFound; + } + + if is_last { + return CandidateResolution::Found(visible); + } + + let [(_, def)] = visible.as_slice() else { + return CandidateResolution::Ambiguous; + }; + + let target = self.follow_aliases(*def); + let Some(child_scope) = self[target].child_scope else { + return CandidateResolution::NotFound; + }; + current_scope = child_scope; + current_def = Some(target); + index += 1; + } + + CandidateResolution::NotFound + } + + /// 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, is_lexical) { + CandidateResolution::Found(found) => defs.extend(found), + CandidateResolution::Ambiguous => return CandidateResolution::Ambiguous, + CandidateResolution::NotFound => {} + } + } + + if defs.is_empty() { + CandidateResolution::NotFound + } else { + defs.sort_by_key(|(_, def)| def.index()); + defs.dedup(); + 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>, + is_lexical: bool, + ) -> CandidateResolution { + if is_lexical { + match self.resolve_lexical(scope, namespace, name) { + 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| { + 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) + } + } + + 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?; + } + } } 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(), } } } +/// 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 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, + + /// Local name computation depends on an import that is not resolved yet. + Pending, +} + +/// 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 validate to target `V` with both the type +/// and value namespaces. +struct ValidatedImportBinding { + target: DefId, + namespaces: Vec, +} + +/// 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, +} + impl<'cx> Index for NameDb<'cx> { type Output = Scope<'cx>; @@ -208,144 +744,647 @@ 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, - ); + mod lexical_resolution { + use super::*; - assert_eq!( - db.resolve_lexical(body, Namespace::Value, x), - ResolveResult::Found(inner) - ); - assert_eq!( - db.resolve_lexical(root, Namespace::Value, x), - ResolveResult::Found(outer) - ); - } + // 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"); - #[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 body = db.add_scope(ScopeKind::FunctionBody, Some(root)); - 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, - ); + let outer = db.add_def( + root, + DefKind::Local, + Some(x), + Visibility::Private, + Origin::Untracked, + ); + let inner = db.add_def( + body, + DefKind::Local, + Some(x), + Visibility::Private, + Origin::Untracked, + ); - 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(body, Namespace::Value, x), + ResolveResult::Found(inner) + ); + assert_eq!( + db.resolve_lexical(root, Namespace::Value, x), + ResolveResult::Found(outer) + ); + } - #[test] - fn generic_scope_is_visible_from_function_body() { - let ccx = CommonCx::new(); - let t = ccx.intern("T"); + // 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 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, - ); + let type_param = db.add_def( + generic_scope, + DefKind::TypeParam, + Some(t), + Visibility::Private, + Origin::Untracked, + ); - assert_eq!( - db.resolve_lexical(body, Namespace::Type, t), - ResolveResult::Found(type_param) - ); + assert_eq!( + db.resolve_lexical(body, Namespace::Type, t), + ResolveResult::Found(type_param) + ); + } + + // 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::Untracked, + ); + + assert_eq!( + db.resolve_lexical(block, Namespace::Type, local), + ResolveResult::Found(local_struct) + ); + } } - #[test] - fn local_item_can_be_resolved_in_type_namespace() { - let ccx = CommonCx::new(); - let local = ccx.intern("Local"); + mod namespaces { + use super::*; - 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)); + // 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 local_struct = db.add_def( - block, - DefKind::Struct, - Some(local), - Visibility::Private, - Origin::Synthetic, - ); + let mut db = NameDb::default(); + let root = db.root_scope(); + let type_param = db.add_def( + root, + DefKind::TypeParam, + Some(t), + Visibility::Private, + Origin::Untracked, + ); + let local = db.add_def( + root, + DefKind::Local, + Some(t), + Visibility::Private, + Origin::Untracked, + ); - assert_eq!( - db.resolve_lexical(block, Namespace::Type, local), - ResolveResult::Found(local_struct) + 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) + ); + } + + // 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::Untracked, + ); + + 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>( + 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::Untracked, ); + let scope = db.add_scope(ScopeKind::Module, Some(parent)); + db.set_child_scope(def, scope); + (def, scope) } - #[test] - fn const_generic_lives_in_value_namespace() { - let ccx = CommonCx::new(); - let n = ccx.intern("N"); + 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.follow_aliases(def)].kind + } - let mut db = NameDb::default(); - let root = db.root_scope(); - let generic_scope = db.add_scope(ScopeKind::GenericParams, Some(root)); + mod import_resolution { + use super::*; - let const_param = db.add_def( - generic_scope, - DefKind::ConstParam, - Some(n), - Visibility::Private, - Origin::Synthetic, - ); + // 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("_"); - 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 - ); + 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::Untracked, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, s], + ImportKind::Single, + Visibility::Private, + Origin::Untracked, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, s], + ImportKind::Rename(t), + Visibility::Private, + Origin::Untracked, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, ccx.intern("self")], + ImportKind::Single, + Visibility::Private, + Origin::Untracked, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, s], + ImportKind::Rename(hidden), + Visibility::Private, + Origin::Untracked, + ); + + 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 + ); + } + + // 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::Untracked, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), missing, self_name], + ImportKind::Single, + Visibility::Private, + Origin::Untracked, + ); + + 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 + ); + } + + // 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::Untracked, + ); + db.add_def( + a_scope, + DefKind::Struct, + Some(private), + Visibility::Private, + Origin::Untracked, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, public], + ImportKind::Single, + Visibility::Public, + Origin::Untracked, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), b, public], + ImportKind::Single, + Visibility::Public, + Origin::Untracked, + ); + db.add_import( + d_scope, + vec![ccx.intern("super"), a], + ImportKind::Glob, + Visibility::Private, + Origin::Untracked, + ); + + 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 + ); + } + + // 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::Untracked, + ); + db.add_def( + b_scope, + DefKind::Struct, + Some(x), + Visibility::Public, + Origin::Untracked, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), a], + ImportKind::Glob, + Visibility::Private, + Origin::Untracked, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), b], + ImportKind::Glob, + Visibility::Private, + Origin::Untracked, + ); + db.add_import( + c_scope, + vec![ccx.intern("super"), a, missing], + ImportKind::Single, + Visibility::Private, + Origin::Untracked, + ); + + 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); + } + + // 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::Untracked, + ); + db.add_def( + a_scope, + DefKind::Const, + Some(x), + Visibility::Public, + Origin::Untracked, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, x], + ImportKind::Single, + Visibility::Private, + Origin::Untracked, + ); + + db.resolve_imports(); + } + + // 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::Untracked, + ); + 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::Untracked, + ); + db.add_import( + b_scope, + vec![ccx.intern("super"), a, e, v], + ImportKind::Single, + Visibility::Private, + Origin::Untracked, + ); + + 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..2d682f6 100644 --- a/crates/syn-sem-name/src/def.rs +++ b/crates/syn-sem-name/src/def.rs @@ -15,6 +15,23 @@ 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, + + /// 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. + pub target: Option, + /// Visibility of this definition. pub visibility: Visibility, @@ -81,18 +98,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 => &[], } } @@ -113,13 +141,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/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 0bdaa20..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,16 +66,6 @@ pub struct Bindings<'cx> { } impl<'cx> Bindings<'cx> { - /// Creates empty bindings. - pub fn new() -> Self { - Self::default() - } - - /// Inserts a definition under `name` in `namespace`. - pub fn insert(&mut self, namespace: Namespace, name: Name<'cx>, def: DefId) { - self.map_mut(namespace).entry(name).or_default().push(def); - } - /// Returns the binding for `name` in `namespace`. pub fn get(&self, namespace: Namespace, name: Name<'cx>) -> Option<&Binding> { self.map(namespace).get(&name) @@ -91,8 +81,35 @@ impl<'cx> Bindings<'cx> { } } + /// Creates empty bindings. + pub(crate) fn new() -> Self { + Self::default() + } + + /// Inserts a definition under `name` in `namespace`. + 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, 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 + } else { + binding.push(def); + true + } + } + /// 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, @@ -109,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() @@ -141,4 +148,14 @@ impl Binding { pub fn is_empty(&self) -> bool { 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(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..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, ) } } @@ -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/context.rs b/crates/syn-sem-top/src/context.rs index 1c8d1ef..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}; @@ -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. @@ -29,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 b482745..30971a1 100644 --- a/crates/syn-sem-top/src/names.rs +++ b/crates/syn-sem-top/src/names.rs @@ -3,136 +3,119 @@ 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>( +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)?; - } - 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, - ast_visibility(&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, ast_visibility(&item.vis)), + ast::Item::Fn(item) => { + 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( + let def = self.add_named_def( scope, DefKind::Struct, item.ident.inner, - ast_visibility(&item.vis), + 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( + let def = self.add_named_def( scope, DefKind::TypeAlias, item.ident.inner, - ast_visibility(&item.vis), + 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(scope, Vec::new(), &item.tree, ast_visibility(&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 item_scope = self.db.add_scope(ScopeKind::Item, Some(parent_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::Private, - ); - } - } - - fn collect_mod(&mut self, parent_scope: ScopeId, item: &ast::ItemMod<'tcx>) { - self.add_named( - parent_scope, - DefKind::Module, - item.ident.inner, - ast_visibility(&item.vis), - ); - let module_scope = self.db.add_scope(ScopeKind::Module, Some(parent_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( - &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(()) + self.collect_use_tree( + scope, + Vec::new(), + &item.tree, + self.visibility_from_ast(scope, &item.vis), + ); } } } - fn collect_mod_in_top( + fn collect_mod_from_module_tree( &mut self, - tcx: &'tcx TopCx<'tcx>, parent_scope: ScopeId, item: &ast::ItemMod<'tcx>, path: &ModulePath, ) -> Result<()> { - self.add_named( + let module_def = self.add_named_def( parent_scope, DefKind::Module, item.ident.inner, - ast_visibility(&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); let module_dir = path.child_dir(item); if let Some(items) = item.items { @@ -141,107 +124,156 @@ 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>) { + // 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); + + // 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, + DefKind::Variant, + variant.ident.inner, + visibility, + ); + } + } + fn collect_trait(&mut self, parent_scope: ScopeId, item: &ast::ItemTrait<'tcx>) { - self.add_named( + let trait_def = self.add_named_def( parent_scope, DefKind::Trait, item.ident.inner, - ast_visibility(&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); + 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( + 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( + 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, Visibility::Private, - Origin::Synthetic, + 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( + 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( + 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); } } } @@ -253,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); @@ -271,49 +302,47 @@ 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(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)); 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(_) => {} } } } - 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( - scope, + self.add_named_def( + generic_scope, DefKind::TypeParam, param.ident.inner, Visibility::Private, ); } ast::GenericParam::Const(param) => { - self.add_named( - scope, + self.add_named_def( + generic_scope, DefKind::ConstParam, param.ident.inner, Visibility::Private, @@ -322,12 +351,14 @@ impl<'tcx> NameCollector<'tcx> { ast::GenericParam::Unsupported(_) => {} } } + + Some(generic_scope) } 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) => { @@ -371,7 +402,7 @@ impl<'tcx> NameCollector<'tcx> { source_path, ImportKind::Single, visibility, - Origin::Synthetic, + Origin::Untracked, ); } ast::UseTree::Rename(tree) => { @@ -382,7 +413,7 @@ impl<'tcx> NameCollector<'tcx> { source_path, ImportKind::Rename(tree.rename.inner), visibility, - Origin::Synthetic, + Origin::Untracked, ); } ast::UseTree::Glob(_) => { @@ -391,7 +422,7 @@ impl<'tcx> NameCollector<'tcx> { prefix, ImportKind::Glob, visibility, - Origin::Synthetic, + Origin::Untracked, ); } ast::UseTree::Group(tree) => { @@ -402,7 +433,7 @@ impl<'tcx> NameCollector<'tcx> { } } - fn add_named( + fn add_named_def( &mut self, scope: ScopeId, kind: DefKind, @@ -410,14 +441,89 @@ 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 visibility_from_ast(&self, scope: ScopeId, vis: &ast::Visibility<'tcx>) -> Visibility { + match vis { + ast::Visibility::Public(_) => Visibility::Public, + ast::Visibility::Restricted(path) => { + Visibility::Restricted(self.resolve_restricted_visibility_scope(scope, path)) + } + ast::Visibility::Private => Visibility::Private, + } + } + + 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() + .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) + .expect("restricted visibility `super` must have a parent module") + } + _ => panic!("restricted visibility path must start with `crate`, `self`, or `super`"), + } + + for segment in segments { + assert!( + !segment.has_args(), + "restricted visibility path segments must not have arguments" + ); + + let binding = self.db[scope] + .bindings + .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 + .expect("restricted visibility path segment must name a scope-bearing item"); + } + + scope } -} -fn ast_visibility(vis: &ast::Visibility<'_>) -> Visibility { - match vis { - ast::Visibility::Public(_) => Visibility::Public, - ast::Visibility::Restricted(_) | ast::Visibility::Private => Visibility::Private, + 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?; + } } } @@ -442,18 +548,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 { @@ -557,12 +673,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 1ef83b1..9b7efeb 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,281 @@ 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!( + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "Public"), + DefKind::Struct + ); + assert_eq!( + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "Renamed"), + DefKind::Struct + ); + assert_eq!( + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "a"), + DefKind::Module + ); + assert_eq!( + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "Local"), + DefKind::Struct + ); + assert_eq!( + follow_aliases_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!( + follow_aliases_kind(&tcx, db, child_scope, Namespace::Type, "InA"), + DefKind::Struct + ); + assert_eq!( + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "CrateVisible"), + DefKind::Struct + ); + assert_eq!( + follow_aliases_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] +#[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(); + 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!( + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "V"), + DefKind::Variant + ); + assert_eq!( + follow_aliases_kind(&tcx, db, b_scope, Namespace::Value, "V"), + DefKind::Variant + ); +} + +#[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")); @@ -104,12 +379,73 @@ 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_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>, + scope: ScopeId, + namespace: Namespace, + name: &str, +) -> DefKind { + let name = tcx.common.intern(name); + let ResolveResult::Found(def) = resolve_lexical(db, scope, namespace, name) else { + panic!("expected {name:?} to resolve in {namespace:?}"); + }; + db[db.follow_aliases(def)].kind +} + +fn resolve_result<'tcx>( + tcx: &'tcx TopCx<'tcx>, + db: &NameDb<'tcx>, + scope: ScopeId, + namespace: Namespace, + name: &str, +) -> ResolveResult { + 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 { db.scopes() .iter()