diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6f3f22b..a5c322a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -93,7 +93,7 @@ jobs: - name: Record Rust version run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" - name: Upload to codecov.io in ${{ matrix.crate }} - uses: codecov/codecov-action@v5.5.2 + uses: codecov/codecov-action@v7.0.0 with: disable_search: true env_vars: OS,RUST diff --git a/crates/syn-sem-name/AGENTS.md b/crates/syn-sem-name/AGENTS.md index e2127fe..19acb64 100644 --- a/crates/syn-sem-name/AGENTS.md +++ b/crates/syn-sem-name/AGENTS.md @@ -27,8 +27,19 @@ Rust namespaces must stay separate: Generic parameters should be represented as definitions, not recovered through ad hoc syntax ancestry. +Definition-attached scopes should keep roles separate. `DefScopes::path` is for +path-reachable children such as enum variants, `DefScopes::generic` is for +lexical generic parameters, and `DefScopes::body` is for value-body bindings. + Name resolution should be use-site based, scope-aware, and namespace-aware. +## Future Considerations + +Struct fields are not currently modeled as definitions or scopes. If field +modeling becomes necessary, add a dedicated field/member concept instead of +forcing fields into `DefScopes::path`, because fields are not path-reachable +children like enum variants. + ## Primary Public Items - `NameDb`: name-resolution database containing scopes, definitions, and diff --git a/crates/syn-sem-name/src/db.rs b/crates/syn-sem-name/src/db.rs index 75b59d8..7932dc4 100644 --- a/crates/syn-sem-name/src/db.rs +++ b/crates/syn-sem-name/src/db.rs @@ -85,8 +85,7 @@ impl<'cx> NameDb<'cx> { name, kind, parent_scope, - child_scope: None, - generic_scope: None, + scopes: Default::default(), target: None, visibility, origin, @@ -123,14 +122,19 @@ impl<'cx> NameDb<'cx> { id } - /// 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 a path scope that contains its path-reachable members. + pub fn set_path_scope(&mut self, def: DefId, path_scope: ScopeId) { + self.defs[def.index()].scopes.path = Some(path_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); + self.defs[def.index()].scopes.generic = Some(generic_scope); + } + + /// Links a definition to the scope containing its value body. + pub fn set_body_scope(&mut self, def: DefId, body_scope: ScopeId) { + self.defs[def.index()].scopes.body = Some(body_scope); } /// Follows `DefKind::Use` alias definitions to their underlying definition. @@ -208,8 +212,7 @@ impl<'cx> NameDb<'cx> { name, kind: DefKind::Use, parent_scope, - child_scope: None, - generic_scope: None, + scopes: Default::default(), target: Some(target), visibility, origin, @@ -365,13 +368,13 @@ impl<'cx> NameDb<'cx> { }; let glob_target = self.follow_aliases(*glob_candidate); - let Some(child_scope) = self[glob_target].child_scope else { + let Some(path_scope) = self[glob_target].scopes.path 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 (&name, binding) in self[path_scope].bindings.map(namespace) { for def in binding.iter() { if self.is_visible_from(def, import_data.scope) { visible.push((namespace, name, def)); @@ -524,10 +527,10 @@ impl<'cx> NameDb<'cx> { }; let target = self.follow_aliases(*def); - let Some(child_scope) = self[target].child_scope else { + let Some(path_scope) = self[target].scopes.path else { return CandidateResolution::NotFound; }; - current_scope = child_scope; + current_scope = path_scope; current_def = Some(target); index += 1; } @@ -764,7 +767,7 @@ mod tests { let mut db = NameDb::default(); let root = db.root_scope(); - let body = db.add_scope(ScopeKind::FunctionBody, Some(root)); + let body = db.add_scope(ScopeKind::Function, Some(root)); let outer = db.add_def( root, @@ -805,12 +808,12 @@ mod tests { 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 generic_scope = db.add_scope(ScopeKind::Generic, Some(root)); + let body = db.add_scope(ScopeKind::Function, Some(generic_scope)); let type_param = db.add_def( generic_scope, - DefKind::TypeParam, + DefKind::GenericType, Some(t), Visibility::Private, Origin::Untracked, @@ -839,7 +842,7 @@ mod tests { let mut db = NameDb::default(); let root = db.root_scope(); - let body = db.add_scope(ScopeKind::FunctionBody, Some(root)); + let body = db.add_scope(ScopeKind::Function, Some(root)); let block = db.add_scope(ScopeKind::Block, Some(body)); let local_struct = db.add_def( @@ -878,7 +881,7 @@ mod tests { let root = db.root_scope(); let type_param = db.add_def( root, - DefKind::TypeParam, + DefKind::GenericType, Some(t), Visibility::Private, Origin::Untracked, @@ -915,11 +918,11 @@ mod tests { let mut db = NameDb::default(); let root = db.root_scope(); - let generic_scope = db.add_scope(ScopeKind::GenericParams, Some(root)); + let generic_scope = db.add_scope(ScopeKind::Generic, Some(root)); let const_param = db.add_def( generic_scope, - DefKind::ConstParam, + DefKind::GenericConst, Some(n), Visibility::Private, Origin::Untracked, @@ -950,7 +953,7 @@ mod tests { Origin::Untracked, ); let scope = db.add_scope(ScopeKind::Module, Some(parent)); - db.set_child_scope(def, scope); + db.set_path_scope(def, scope); (def, scope) } @@ -1359,7 +1362,7 @@ mod tests { Origin::Untracked, ); let enum_scope = db.add_scope(ScopeKind::Item, Some(a_scope)); - db.set_child_scope(enum_def, enum_scope); + db.set_path_scope(enum_def, enum_scope); db.add_def( enum_scope, DefKind::Variant, diff --git a/crates/syn-sem-name/src/def.rs b/crates/syn-sem-name/src/def.rs index 2d682f6..bb955c4 100644 --- a/crates/syn-sem-name/src/def.rs +++ b/crates/syn-sem-name/src/def.rs @@ -15,17 +15,8 @@ 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, + /// Scopes owned by or directly attached to this definition. + pub scopes: DefScopes, /// Definition this definition aliases. /// @@ -39,6 +30,31 @@ pub struct Def<'cx> { pub origin: Origin, } +/// Scopes owned by or directly attached to a definition. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct DefScopes { + /// Scope used when path resolution descends through this definition. + /// + /// This is for names reachable with paths or imports. For example, `enum E { V }` gives the + /// `E` definition a path scope containing variant `V`, so `use crate::E::V;` can descend from + /// `E` into that scope. Definitions without path-reachable children leave this unset. + pub path: Option, + + /// Scope containing lexical generic parameters owned by this definition. + /// + /// For example, `fn f() {}` gives the `f` definition a generic scope containing `T`. + /// Generic parameters are lexical names, not path-reachable children, so this is separate from + /// [`Self::path`]. Definitions without generic parameters leave this unset. + pub generic: Option, + + /// Scope containing the value body owned by this definition. + /// + /// For example, `fn f(x: i32) { let y = x; }` gives the `f` definition a body scope containing + /// parameter binding `x`; the block inside the body then gets its own nested block scope for + /// names such as `y`. Definitions without a collected body leave this unset. + pub body: Option, +} + /// Kind of definition stored in the name database. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DefKind { @@ -60,12 +76,21 @@ pub enum DefKind { /// A type alias item. TypeAlias, - /// A function or method. + /// A function item. Fn, - /// A constant item or associated constant. + /// A constant item. Const, + /// An associated type item. + AssocType, + + /// An associated function item. + AssocFn, + + /// An associated constant item. + AssocConst, + /// A static item. Static, @@ -75,14 +100,14 @@ pub enum DefKind { /// A local variable or pattern binding. Local, - /// A type generic parameter. - TypeParam, + /// A generic type parameter. + GenericType, - /// A const generic parameter. - ConstParam, + /// A generic const parameter. + GenericConst, - /// A lifetime generic parameter. - LifetimeParam, + /// A generic lifetime parameter. + GenericLifetime, /// A `use` item or imported binding. Use, @@ -104,18 +129,23 @@ impl DefKind { | Self::Enum | Self::Trait | Self::TypeAlias - | Self::TypeParam => &[Namespace::Type], + | Self::AssocType + | Self::GenericType => &[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] - } + Self::Fn + | Self::Const + | Self::AssocFn + | Self::AssocConst + | Self::Static + | Self::Local + | Self::GenericConst => &[Namespace::Value], // Lifetime namespace - Self::LifetimeParam => &[Namespace::Lifetime], + Self::GenericLifetime => &[Namespace::Lifetime], // Macro namespace Self::Macro => &[Namespace::Macro], diff --git a/crates/syn-sem-name/src/scope.rs b/crates/syn-sem-name/src/scope.rs index 77e695a..782addf 100644 --- a/crates/syn-sem-name/src/scope.rs +++ b/crates/syn-sem-name/src/scope.rs @@ -40,11 +40,11 @@ pub enum ScopeKind { /// Item body or item member scope. Item, - /// Generic parameter scope. - GenericParams, + /// Scope containing generic type, const, and lifetime parameter bindings. + Generic, - /// Function body scope. - FunctionBody, + /// Function scope containing parameter bindings and enclosing the function block scope. + Function, /// Block expression or statement block scope. Block, diff --git a/crates/syn-sem-name/tests/ast.rs b/crates/syn-sem-name/tests/ast.rs index e0fb136..7b26d94 100644 --- a/crates/syn-sem-name/tests/ast.rs +++ b/crates/syn-sem-name/tests/ast.rs @@ -61,14 +61,10 @@ impl<'cx> AstNameCollector<'cx> { fn collect_fn(&mut self, parent_scope: ScopeId, item: &ast::ItemFn<'cx>) { self.add_named(parent_scope, DefKind::Fn, item.sig.ident.inner); - let generic_scope = self - .db - .add_scope(ScopeKind::GenericParams, Some(parent_scope)); + let generic_scope = self.db.add_scope(ScopeKind::Generic, Some(parent_scope)); self.collect_generics_into(generic_scope, &item.generics); - let body_scope = self - .db - .add_scope(ScopeKind::FunctionBody, Some(generic_scope)); + let body_scope = self.db.add_scope(ScopeKind::Function, Some(generic_scope)); for param in item.sig.params.iter().skip(1) { self.collect_pat(body_scope, param.pat.pat); @@ -90,9 +86,7 @@ impl<'cx> AstNameCollector<'cx> { } fn collect_generics(&mut self, parent_scope: ScopeId, generics: &ast::Generics<'cx>) { - let generic_scope = self - .db - .add_scope(ScopeKind::GenericParams, Some(parent_scope)); + let generic_scope = self.db.add_scope(ScopeKind::Generic, Some(parent_scope)); self.collect_generics_into(generic_scope, generics); } @@ -100,10 +94,10 @@ impl<'cx> AstNameCollector<'cx> { for param in generics.params { match param { ast::GenericParam::Type(param) => { - self.add_named(scope, DefKind::TypeParam, param.ident.inner); + self.add_named(scope, DefKind::GenericType, param.ident.inner); } ast::GenericParam::Const(param) => { - self.add_named(scope, DefKind::ConstParam, param.ident.inner); + self.add_named(scope, DefKind::GenericConst, param.ident.inner); } ast::GenericParam::Unsupported(_) => {} } @@ -218,7 +212,7 @@ fn resolves_function_generics_params_and_locals_from_ast() { ); let db = collect_names(&scx, text); - let generic_scope = scope(&db, ScopeKind::GenericParams, 0); + let generic_scope = scope(&db, ScopeKind::Generic, 0); let block_scope = scope(&db, ScopeKind::Block, 0); expect_def( @@ -226,14 +220,14 @@ fn resolves_function_generics_params_and_locals_from_ast() { block_scope, Namespace::Type, scx.common.intern("T"), - DefKind::TypeParam, + DefKind::GenericType, ); expect_def( &db, block_scope, Namespace::Value, scx.common.intern("N"), - DefKind::ConstParam, + DefKind::GenericConst, ); expect_def( &db, @@ -254,7 +248,7 @@ fn resolves_function_generics_params_and_locals_from_ast() { generic_scope, Namespace::Type, scx.common.intern("T"), - DefKind::TypeParam, + DefKind::GenericType, ); } @@ -289,7 +283,7 @@ fn resolves_local_item_declared_inside_function_from_ast() { block_scope, Namespace::Type, scx.common.intern("T"), - DefKind::TypeParam, + DefKind::GenericType, ); expect_def( &db, @@ -327,7 +321,7 @@ fn keeps_type_and_value_namespaces_separate_from_ast() { block_scope, Namespace::Type, scx.common.intern("T"), - DefKind::TypeParam, + DefKind::GenericType, ); expect_def( &db, diff --git a/crates/syn-sem-top/src/names.rs b/crates/syn-sem-top/src/names.rs index 30971a1..0006363 100644 --- a/crates/syn-sem-top/src/names.rs +++ b/crates/syn-sem-top/src/names.rs @@ -58,47 +58,15 @@ impl<'tcx> NameCollector<'tcx> { /// 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_def( - scope, - DefKind::Const, - item.ident.inner, - self.visibility_from_ast(scope, &item.vis), - ); - } + ast::Item::Const(item) => self.collect_const(scope, item), ast::Item::Enum(item) => self.collect_enum(scope, item), - ast::Item::Fn(item) => { - self.collect_fn(scope, item, self.visibility_from_ast(scope, &item.vis)) - } + ast::Item::Fn(item) => self.collect_fn(scope, item), ast::Item::Impl(item) => self.collect_impl(scope, item), ast::Item::Mod(item) => self.collect_mod_from_ast(scope, item), - ast::Item::Struct(item) => { - let def = self.add_named_def( - scope, - DefKind::Struct, - item.ident.inner, - self.visibility_from_ast(scope, &item.vis), - ); - self.collect_generic_scope(def, scope, &item.generics); - } + ast::Item::Struct(item) => self.collect_struct(scope, item), ast::Item::Trait(item) => self.collect_trait(scope, item), - ast::Item::Type(item) => { - let def = self.add_named_def( - scope, - DefKind::TypeAlias, - item.ident.inner, - self.visibility_from_ast(scope, &item.vis), - ); - self.collect_generic_scope(def, scope, &item.generics); - } - ast::Item::Use(item) => { - self.collect_use_tree( - scope, - Vec::new(), - &item.tree, - self.visibility_from_ast(scope, &item.vis), - ); - } + ast::Item::Type(item) => self.collect_type(scope, item), + ast::Item::Use(item) => self.collect_use(scope, item), } } @@ -108,14 +76,11 @@ impl<'tcx> NameCollector<'tcx> { item: &ast::ItemMod<'tcx>, path: &ModulePath, ) -> Result<()> { - let module_def = self.add_named_def( - parent_scope, - DefKind::Module, - item.ident.inner, - self.visibility_from_ast(parent_scope, &item.vis), - ); + let visibility = self.visibility_from_ast(parent_scope, &item.vis); + let module_def = + self.add_named_def(parent_scope, DefKind::Module, item.ident.inner, visibility); let module_scope = self.db.add_scope(ScopeKind::Module, Some(parent_scope)); - self.db.set_child_scope(module_def, module_scope); + self.db.set_path_scope(module_def, module_scope); let module_dir = path.child_dir(item); if let Some(items) = item.items { @@ -141,14 +106,11 @@ impl<'tcx> NameCollector<'tcx> { } 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 visibility = self.visibility_from_ast(parent_scope, &item.vis); + let module_def = + self.add_named_def(parent_scope, DefKind::Module, item.ident.inner, visibility); let module_scope = self.db.add_scope(ScopeKind::Module, Some(parent_scope)); - self.db.set_child_scope(module_def, module_scope); + self.db.set_path_scope(module_def, module_scope); if let Some(items) = item.items { for item in items { @@ -157,23 +119,58 @@ impl<'tcx> NameCollector<'tcx> { } } + /// For `const C: usize = 0;`, this creates the following definition and scope hierarchy: + /// + /// ```text + /// parent_scope + /// └─ DefKind::Const C + /// ``` + fn collect_const(&mut self, parent_scope: ScopeId, item: &ast::ItemConst<'tcx>) { + // DefKind::Const + let visibility = self.visibility_from_ast(parent_scope, &item.vis); + self.add_named_def(parent_scope, DefKind::Const, item.ident.inner, visibility); + } + + /// For `enum E { V }`, this creates the following definition and scope hierarchy: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::Enum E + /// │ ├─ scopes.generic -> Generic scope + /// │ └─ scopes.path -> Item scope + /// └─ Generic scope + /// └─ Item scope + /// └─ DefKind::Variant V + /// ``` + /// + /// Without generics, the hierarchy is still not flattened into `parent_scope`: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::Enum E + /// │ └─ scopes.path -> Item scope + /// └─ Item scope + /// └─ DefKind::Variant V + /// ``` fn collect_enum(&mut self, parent_scope: ScopeId, item: &ast::ItemEnum<'tcx>) { - // Enum Def + // DefKind::Enum 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); + let generic_scope = self.create_generic_scope(parent_scope, &item.generics); + if let Some(generic_scope) = generic_scope { + self.db.set_generic_scope(enum_def, generic_scope); + } + let path_parent_scope = generic_scope.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); + // DefKind::Variant + let path_scope = self.db.add_scope(ScopeKind::Item, Some(path_parent_scope)); + self.db.set_path_scope(enum_def, path_scope); for variant in item.variants { self.add_named_def( - item_scope, + path_scope, DefKind::Variant, variant.ident.inner, visibility, @@ -181,57 +178,204 @@ impl<'tcx> NameCollector<'tcx> { } } + /// For `fn f(x: T) { let y = x; }`, this creates the following definition and scope + /// hierarchy: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::Fn f + /// │ ├─ scopes.generic -> Generic scope + /// │ └─ scopes.body -> Function scope + /// └─ Generic scope + /// └─ Function scope + /// ├─ DefKind::Local x + /// └─ Block scope + /// └─ DefKind::Local y + /// ``` + /// + /// Without generics, the function scope is attached directly under `parent_scope`: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::Fn f + /// │ └─ scopes.body -> Function scope + /// └─ Function scope + /// ├─ DefKind::Local x + /// └─ Block scope + /// └─ DefKind::Local y + /// ``` + fn collect_fn(&mut self, parent_scope: ScopeId, item: &ast::ItemFn<'tcx>) { + // DefKind::Fn + let visibility = self.visibility_from_ast(parent_scope, &item.vis); + let fn_def = + self.add_named_def(parent_scope, DefKind::Fn, item.sig.ident.inner, visibility); + + // Generic scope + let generic_scope = self.create_generic_scope(parent_scope, &item.generics); + if let Some(generic_scope) = generic_scope { + self.db.set_generic_scope(fn_def, generic_scope); + } + let function_parent_scope = generic_scope.unwrap_or(parent_scope); + + // Block scope + let function_scope = + self.create_function_body_scope(function_parent_scope, &item.sig, &item.block); + self.db.set_body_scope(fn_def, function_scope); + } + + /// For `struct S;`, this creates the following definition and scope hierarchy: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::Struct S + /// │ └─ scopes.generic -> Generic scope + /// └─ Generic scope + /// ``` + /// + /// Without generics: + /// + /// ```text + /// parent_scope + /// └─ DefKind::Struct S + /// ``` + fn collect_struct(&mut self, parent_scope: ScopeId, item: &ast::ItemStruct<'tcx>) { + // DefKind::Struct + let visibility = self.visibility_from_ast(parent_scope, &item.vis); + let def = self.add_named_def(parent_scope, DefKind::Struct, item.ident.inner, visibility); + + // Generic scope + if let Some(generic_scope) = self.create_generic_scope(parent_scope, &item.generics) { + self.db.set_generic_scope(def, generic_scope); + } + } + + /// For `trait Tr { ... }`, this creates the following definition and scope hierarchy: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::Trait Tr + /// │ └─ scopes.generic -> Generic scope + /// └─ Generic scope + /// └─ Trait scope + /// ``` + /// + /// Without trait generics: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::Trait Tr + /// └─ Trait scope + /// ``` + /// + /// Trait items inside the `Trait` scope are collected by [`Self::collect_trait_item`]. fn collect_trait(&mut self, parent_scope: ScopeId, item: &ast::ItemTrait<'tcx>) { - let trait_def = self.add_named_def( - parent_scope, - DefKind::Trait, - item.ident.inner, - self.visibility_from_ast(parent_scope, &item.vis), - ); - let trait_parent_scope = self - .collect_generic_scope(trait_def, parent_scope, &item.generics) - .unwrap_or(parent_scope); + // Trait Def + let visibility = self.visibility_from_ast(parent_scope, &item.vis); + let trait_def = + self.add_named_def(parent_scope, DefKind::Trait, item.ident.inner, visibility); + + // Generic scope + let generic_scope = self.create_generic_scope(parent_scope, &item.generics); + if let Some(generic_scope) = generic_scope { + self.db.set_generic_scope(trait_def, generic_scope); + } + let trait_parent_scope = generic_scope.unwrap_or(parent_scope); + + // Trait scope let trait_scope = self .db .add_scope(ScopeKind::Trait, Some(trait_parent_scope)); + // Assoc items for item in item.items { - match item { - ast::TraitItem::Const(item) => { - let def = self.add_named_def( - trait_scope, - DefKind::Const, - item.ident.inner, - Visibility::Private, - ); - self.collect_generic_scope(def, trait_scope, &item.generics); + self.collect_trait_item(trait_scope, item); + } + } + + /// For trait items `const C`, `type Assoc`, and `fn f(&self) {}`, this creates the + /// following definition and scope hierarchy: + /// + /// ```text + /// trait_scope + /// ├─ DefKind::AssocConst C + /// ├─ DefKind::AssocType Assoc + /// │ └─ scopes.generic -> Generic scope + /// ├─ Generic scope + /// ├─ DefKind::AssocFn f + /// │ ├─ scopes.generic -> Generic scope + /// │ └─ scopes.body -> Function scope + /// └─ Generic scope + /// └─ Function scope + /// └─ Block scope + /// ``` + fn collect_trait_item(&mut self, trait_scope: ScopeId, item: &ast::TraitItem<'tcx>) { + match item { + ast::TraitItem::Const(item) => { + let def = self.add_named_def( + trait_scope, + DefKind::AssocConst, + item.ident.inner, + Visibility::Private, + ); + if let Some(generic_scope) = self.create_generic_scope(trait_scope, &item.generics) + { + self.db.set_generic_scope(def, generic_scope); } - ast::TraitItem::Fn(item) => { - let def = self.add_named_def( - trait_scope, - DefKind::Fn, - 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::Fn(item) => { + let def = self.add_named_def( + trait_scope, + DefKind::AssocFn, + item.sig.ident.inner, + Visibility::Private, + ); + let generic_scope = self.create_generic_scope(trait_scope, &item.sig.generics); + if let Some(generic_scope) = generic_scope { + self.db.set_generic_scope(def, generic_scope); } - ast::TraitItem::Type(item) => { - let def = self.add_named_def( - trait_scope, - DefKind::TypeAlias, - item.ident.inner, - Visibility::Private, - ); - self.collect_generic_scope(def, trait_scope, &item.generics); + let function_parent_scope = generic_scope.unwrap_or(trait_scope); + if let Some(block) = &item.default { + let function_scope = + self.create_function_body_scope(function_parent_scope, &item.sig, block); + self.db.set_body_scope(def, function_scope); + } + } + ast::TraitItem::Type(item) => { + let def = self.add_named_def( + trait_scope, + DefKind::AssocType, + item.ident.inner, + Visibility::Private, + ); + if let Some(generic_scope) = self.create_generic_scope(trait_scope, &item.generics) + { + self.db.set_generic_scope(def, generic_scope); } } } } + /// For `impl S { ... }`, this creates the following definition and scope hierarchy: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::Impl + /// │ └─ scopes.generic -> Generic scope + /// └─ Generic scope + /// └─ Impl scope + /// ``` + /// + /// Without impl generics: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::Impl + /// └─ Impl scope + /// ``` + /// + /// Impl items inside the `Impl` scope are collected by [`Self::collect_impl_item`]. fn collect_impl(&mut self, parent_scope: ScopeId, item: &ast::ItemImpl<'tcx>) { + // DefKind::Impl let impl_def = self.db.add_def( parent_scope, DefKind::Impl, @@ -240,147 +384,140 @@ impl<'tcx> NameCollector<'tcx> { Origin::Untracked, ); - 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) => { - let def = self.add_named_def( - impl_scope, - DefKind::Const, - item.ident.inner, - Visibility::Private, - ); - self.collect_generic_scope(def, impl_scope, &item.generics); - } - ast::ImplItem::Fn(item) => { - let def = self.add_named_def( - impl_scope, - DefKind::Fn, - 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) => { - let def = self.add_named_def( - impl_scope, - DefKind::TypeAlias, - item.ident.inner, - Visibility::Private, - ); - self.collect_generic_scope(def, impl_scope, &item.generics); - } - } + // Generic scope + let generic_scope = self.create_generic_scope(parent_scope, &item.generics); + if let Some(generic_scope) = generic_scope { + self.db.set_generic_scope(impl_def, generic_scope); } - } + let impl_parent_scope = generic_scope.unwrap_or(parent_scope); - fn collect_fn( - &mut self, - parent_scope: ScopeId, - item: &ast::ItemFn<'tcx>, - visibility: Visibility, - ) { - 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(body_parent_scope)); + // Impl scope + let impl_scope = self.db.add_scope(ScopeKind::Impl, Some(impl_parent_scope)); - for param in item.sig.params.iter().skip(1) { - self.collect_pat(body_scope, param.pat.pat); + // Assoc items + for item in item.items { + self.collect_impl_item(impl_scope, item); } - - self.collect_block(body_scope, &item.block); } - 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_from_ast(block_scope, item), - ast::Stmt::Expr(_) => {} + /// For impl items `const C`, `type Assoc`, and `fn f(&self) {}`, this creates the + /// following definition and scope hierarchy: + /// + /// ```text + /// impl_scope + /// ├─ DefKind::AssocConst C + /// ├─ DefKind::AssocType Assoc + /// │ └─ scopes.generic -> Generic scope + /// ├─ Generic scope + /// ├─ DefKind::AssocFn f + /// │ ├─ scopes.generic -> Generic scope + /// │ └─ scopes.body -> Function scope + /// └─ Generic scope + /// └─ Function scope + /// └─ Block scope + /// ``` + fn collect_impl_item(&mut self, impl_scope: ScopeId, item: &ast::ImplItem<'tcx>) { + match item { + ast::ImplItem::Const(item) => { + let def = self.add_named_def( + impl_scope, + DefKind::AssocConst, + item.ident.inner, + Visibility::Private, + ); + if let Some(generic_scope) = self.create_generic_scope(impl_scope, &item.generics) { + self.db.set_generic_scope(def, generic_scope); + } } - } - } - - /// 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.db.set_generic_scope(def, generic_scope); - - for param in generics.params { - match param { - ast::GenericParam::Type(param) => { - self.add_named_def( - generic_scope, - DefKind::TypeParam, - param.ident.inner, - Visibility::Private, - ); + ast::ImplItem::Fn(item) => { + let def = self.add_named_def( + impl_scope, + DefKind::AssocFn, + item.sig.ident.inner, + Visibility::Private, + ); + let generic_scope = self.create_generic_scope(impl_scope, &item.sig.generics); + if let Some(generic_scope) = generic_scope { + self.db.set_generic_scope(def, generic_scope); } - ast::GenericParam::Const(param) => { - self.add_named_def( - generic_scope, - DefKind::ConstParam, - param.ident.inner, - Visibility::Private, - ); + let function_parent_scope = generic_scope.unwrap_or(impl_scope); + let function_scope = + self.create_function_body_scope(function_parent_scope, &item.sig, &item.block); + self.db.set_body_scope(def, function_scope); + } + ast::ImplItem::Type(item) => { + let def = self.add_named_def( + impl_scope, + DefKind::AssocType, + item.ident.inner, + Visibility::Private, + ); + if let Some(generic_scope) = self.create_generic_scope(impl_scope, &item.generics) { + self.db.set_generic_scope(def, generic_scope); } - 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_def(scope, DefKind::Local, pat.ident.inner, Visibility::Private); - } - ast::Pat::Reference(pat) => self.collect_pat(scope, pat.pat), - ast::Pat::Slice(pat) => { - for elem in pat.elems { - self.collect_pat(scope, elem); - } - } - ast::Pat::Struct(pat) => { - for field in pat.fields { - self.collect_pat(scope, field.pat); - } - } - ast::Pat::Tuple(pat) => { - for elem in pat.elems { - self.collect_pat(scope, elem); - } - } - ast::Pat::Type(pat) => self.collect_pat(scope, pat.pat), - ast::Pat::Lit(_) | ast::Pat::Path(_) | ast::Pat::Rest(_) => {} + /// For `type Alias = T;`, this creates the following definition and scope hierarchy: + /// + /// ```text + /// parent_scope + /// ├─ DefKind::TypeAlias Alias + /// │ └─ scopes.generic -> Generic scope + /// └─ Generic scope + /// ``` + /// + /// Without generics: + /// + /// ```text + /// parent_scope + /// └─ DefKind::TypeAlias Alias + /// ``` + fn collect_type(&mut self, parent_scope: ScopeId, item: &ast::ItemType<'tcx>) { + // DefKind::TypeAlias + let visibility = self.visibility_from_ast(parent_scope, &item.vis); + let def = self.add_named_def( + parent_scope, + DefKind::TypeAlias, + item.ident.inner, + visibility, + ); + + // Generic scope + if let Some(generic_scope) = self.create_generic_scope(parent_scope, &item.generics) { + self.db.set_generic_scope(def, generic_scope); } } + /// For `use crate::a::B;`, this records the following import before import resolution: + /// + /// ```text + /// scope + /// └─ Import { source_path: [crate, a, B], kind: Single, visibility } + /// ``` + /// + /// After import resolution, the receiving scope gets an alias definition: + /// + /// ```text + /// scope + /// └─ DefKind::Use B -> target DefKind::Struct B + /// ``` + fn collect_use(&mut self, scope: ScopeId, item: &ast::ItemUse<'tcx>) { + let visibility = self.visibility_from_ast(scope, &item.vis); + self.collect_use_tree(scope, Vec::new(), &item.tree, visibility); + } + + /// For `use crate::a::{B, C as D, *};`, this flattens the use tree into import records: + /// + /// ```text + /// scope + /// ├─ Import { source_path: [crate, a, B], kind: Single, visibility } + /// ├─ Import { source_path: [crate, a, C], kind: Rename(D), visibility } + /// └─ Import { source_path: [crate, a], kind: Glob, visibility } + /// ``` + /// + /// Nested `Path` nodes extend `prefix`; `Group` nodes clone the current prefix for each branch. fn collect_use_tree( &mut self, scope: ScopeId, @@ -433,6 +570,113 @@ impl<'tcx> NameCollector<'tcx> { } } + /// For `{ let x = 0; struct Local; x }`, this creates the following scope hierarchy: + /// + /// ```text + /// parent_scope + /// └─ Block scope + /// ├─ DefKind::Local x + /// └─ DefKind::Struct Local + /// ``` + fn collect_block(&mut self, parent_scope: ScopeId, block: &ast::Block<'tcx>) { + // Block scope + 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_from_ast(block_scope, item), + ast::Stmt::Expr(_) => {} + } + } + } + + /// For pattern `(a, S { b })`, this collects local bindings into the current scope: + /// + /// ```text + /// scope + /// ├─ DefKind::Local a + /// └─ DefKind::Local b + /// ``` + fn collect_pat(&mut self, scope: ScopeId, pat: &ast::Pat<'tcx>) { + match pat { + ast::Pat::Ident(pat) => { + 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) => { + for elem in pat.elems { + self.collect_pat(scope, elem); + } + } + ast::Pat::Struct(pat) => { + for field in pat.fields { + self.collect_pat(scope, field.pat); + } + } + ast::Pat::Tuple(pat) => { + for elem in pat.elems { + self.collect_pat(scope, elem); + } + } + ast::Pat::Type(pat) => self.collect_pat(scope, pat.pat), + ast::Pat::Lit(_) | ast::Pat::Path(_) | ast::Pat::Rest(_) => {} + } + } + + /// Creates a `Generic` scope, then collects generic parameter defs. + fn create_generic_scope( + &mut self, + parent_scope: ScopeId, + generics: &ast::Generics<'tcx>, + ) -> Option { + if generics.params.is_empty() { + return None; + } + + let generic_scope = self.db.add_scope(ScopeKind::Generic, Some(parent_scope)); + + for param in generics.params { + match param { + ast::GenericParam::Type(param) => { + self.add_named_def( + generic_scope, + DefKind::GenericType, + param.ident.inner, + Visibility::Private, + ); + } + ast::GenericParam::Const(param) => { + self.add_named_def( + generic_scope, + DefKind::GenericConst, + param.ident.inner, + Visibility::Private, + ); + } + ast::GenericParam::Unsupported(_) => {} + } + } + + Some(generic_scope) + } + + fn create_function_body_scope( + &mut self, + parent_scope: ScopeId, + sig: &ast::Signature<'tcx>, + block: &ast::Block<'tcx>, + ) -> ScopeId { + let function_scope = self.db.add_scope(ScopeKind::Function, Some(parent_scope)); + + for param in sig.params.iter().skip(1) { + self.collect_pat(function_scope, param.pat.pat); + } + + self.collect_block(function_scope, block); + function_scope + } + fn add_named_def( &mut self, scope: ScopeId, @@ -491,7 +735,8 @@ impl<'tcx> NameCollector<'tcx> { .expect("restricted visibility path segment must resolve unambiguously"); let target = self.db.follow_aliases(def); scope = self.db[target] - .child_scope + .scopes + .path .expect("restricted visibility path segment must name a scope-bearing item"); } @@ -655,16 +900,7 @@ impl ModulePath { mod tests { use super::*; use crate::TopCx; - use syn_sem_name::{ImportKind, Namespace, ResolveResult}; - - fn scope(db: &NameDb<'_>, kind: ScopeKind, nth: usize) -> ScopeId { - db.scopes() - .iter() - .filter(|scope| scope.kind == kind) - .nth(nth) - .unwrap() - .id - } + use syn_sem_name::{DefId, Import, ImportKind, ImportStatus, Namespace, ResolveResult}; fn expect_kind( db: &NameDb<'_>, @@ -679,6 +915,94 @@ mod tests { assert_eq!(db[def].kind, kind); } + fn get_direct_def<'tcx>( + tcx: &'tcx TopCx<'tcx>, + db: &NameDb<'tcx>, + scope: ScopeId, + namespace: Namespace, + name: &str, + ) -> DefId { + let name = tcx.common.intern(name); + let binding = db + .binding(scope, namespace, name) + .unwrap_or_else(|| panic!("expected direct binding for {name:?} in {namespace:?}")); + let mut defs = binding.iter(); + assert_eq!(defs.len(), 1); + defs.next().unwrap() + } + + fn get_unique_child_scope(db: &NameDb<'_>, parent: ScopeId, kind: ScopeKind) -> ScopeId { + let mut scopes = db + .scopes() + .iter() + .filter(|scope| scope.parent == Some(parent) && scope.kind == kind) + .map(|scope| scope.id); + let scope = scopes.next().unwrap(); + assert!( + scopes.next().is_none(), + "expected exactly one {kind:?} child scope under {parent:?}" + ); + scope + } + + fn get_single_def(db: &NameDb<'_>, kind: DefKind) -> DefId { + let mut defs = db + .defs() + .iter() + .filter(|def| def.kind == kind) + .map(|def| def.id); + let def = defs.next().unwrap(); + assert!(defs.next().is_none(), "expected exactly one {kind:?} def"); + def + } + + fn get_import<'a, 'tcx>( + tcx: &'tcx TopCx<'tcx>, + db: &'a NameDb<'tcx>, + scope: ScopeId, + source_path: &[&str], + ) -> &'a Import<'tcx> { + let source_path: Vec<_> = source_path + .iter() + .map(|segment| tcx.common.intern(segment)) + .collect(); + let mut imports = db + .imports() + .iter() + .filter(|import| import.scope == scope && import.source_path == source_path); + let import = imports.next().unwrap(); + assert!( + imports.next().is_none(), + "expected exactly one import for {source_path:?} in {scope:?}" + ); + import + } + + fn get_module_scope<'tcx>( + tcx: &'tcx TopCx<'tcx>, + db: &NameDb<'tcx>, + parent: ScopeId, + name: &str, + ) -> ScopeId { + let def = get_direct_def(tcx, db, parent, Namespace::Type, name); + assert_eq!(db[def].kind, DefKind::Module); + db[def].scopes.path.unwrap() + } + + 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_lexical( db: &NameDb<'_>, mut scope: ScopeId, @@ -702,6 +1026,8 @@ mod tests { } } + /// Verifies the top-level analysis path collects modules, generic parameters, function + /// parameters, and block locals into a lexically searchable name database. #[test] fn collects_names_from_top_context() { let tcx = TopCx::default(); @@ -726,7 +1052,10 @@ mod tests { let db = semantics.names(); let root = db.root_scope(); - let block = scope(db, ScopeKind::Block, 0); + let load_def = get_direct_def(&tcx, db, root, Namespace::Value, "load"); + assert_eq!(db[load_def].kind, DefKind::Fn); + let function_scope = db[load_def].scopes.body.unwrap(); + let block = get_unique_child_scope(db, function_scope, ScopeKind::Block); expect_kind( db, @@ -740,14 +1069,14 @@ mod tests { block, Namespace::Type, tcx.common.intern("T"), - DefKind::TypeParam, + DefKind::GenericType, ); expect_kind( db, block, Namespace::Value, tcx.common.intern("N"), - DefKind::ConstParam, + DefKind::GenericConst, ); expect_kind( db, @@ -765,6 +1094,8 @@ mod tests { ); } + /// Verifies grouped `use` trees are flattened into single, rename, and glob import records + /// before import resolution creates alias definitions. #[test] fn collects_import_declarations() { let tcx = TopCx::default(); @@ -779,22 +1110,242 @@ mod tests { tcx.insert_virtual_file(entry_path, text).unwrap(); let semantics = tcx.analyze(entry_path).unwrap(); let db = semantics.names(); + let root = db.root_scope(); assert_eq!(db.imports().len(), 3); + let ab = get_import(&tcx, db, root, &["a", "b"]); + assert_eq!(ab.kind, ImportKind::Single); + let ac = get_import(&tcx, db, root, &["a", "c"]); + assert_eq!(ac.kind, ImportKind::Rename(tcx.common.intern("d"))); + let a = get_import(&tcx, db, root, &["a"]); + assert_eq!(a.kind, ImportKind::Glob); + } + + /// Verifies restricted visibility syntax is converted into the correct visibility scopes and + /// applied when collected imports are resolved. + #[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 = get_module_scope(&tcx, db, root, "a"); + let child_scope = get_module_scope(&tcx, db, a_scope, "child"); + let b_scope = get_module_scope(&tcx, db, root, "b"); + + assert_eq!( + follow_aliases_kind(&tcx, db, child_scope, Namespace::Type, "InA"), + DefKind::Struct + ); assert_eq!( - db.imports()[0].source_path, - vec![tcx.common.intern("a"), tcx.common.intern("b")] + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "CrateVisible"), + DefKind::Struct ); - assert_eq!(db.imports()[0].kind, ImportKind::Single); assert_eq!( - db.imports()[1].source_path, - vec![tcx.common.intern("a"), tcx.common.intern("c")] + follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "SuperVisible"), + DefKind::Struct + ); + assert_eq!( + resolve_lexical(db, b_scope, Namespace::Type, tcx.common.intern("InA")), + ResolveResult::NotFound + ); + + let in_a_import = get_import(&tcx, db, b_scope, &["crate", "a", "InA"]); + assert_eq!(in_a_import.status, ImportStatus::NotFound); + } + + /// Verifies `pub(in ...)` rejects anchors other than `crate`, `self`, and `super`. + #[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); + } + + /// Verifies `pub(in ...)` rejects restricted visibility paths whose module segments do not + /// resolve. + #[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); + } + + /// Verifies item and member definitions carry the expected `DefScopes` links for generic, + /// path, and body scopes. + #[test] + fn collects_def_scope_links_for_items_and_members() { + let tcx = TopCx::default(); + + let entry_path = tcx.common.intern("def_scopes.rs"); + let text = tcx.common.intern( + r#" + struct S; + + enum E { + V, + } + + trait Tr { + const C: usize; + type Assoc; + fn m(y: Y) { + let z = y; + } + } + + impl S { + fn make(q: Q) { + let r = q; + } + } + + fn f(n: N) { + let local = n; + } + "#, ); + + 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 = get_direct_def(&tcx, db, root, Namespace::Type, "S"); + assert_eq!(db[s_def].kind, DefKind::Struct); + let s_generic_scope = db[s_def].scopes.generic.unwrap(); + assert_eq!(db[s_generic_scope].kind, ScopeKind::Generic); + assert!(db[s_def].scopes.path.is_none()); + assert!(db[s_def].scopes.body.is_none()); + + let e_def = get_direct_def(&tcx, db, root, Namespace::Type, "E"); + assert_eq!(db[e_def].kind, DefKind::Enum); + let e_generic_scope = db[e_def].scopes.generic.unwrap(); + let e_path_scope = db[e_def].scopes.path.unwrap(); + assert_eq!(db[e_generic_scope].kind, ScopeKind::Generic); + assert_eq!(db[e_path_scope].kind, ScopeKind::Item); + assert_eq!(db[e_path_scope].parent, Some(e_generic_scope)); + let variant_def = get_direct_def(&tcx, db, e_path_scope, Namespace::Type, "V"); + assert_eq!(db[variant_def].kind, DefKind::Variant); + + let trait_def = get_direct_def(&tcx, db, root, Namespace::Type, "Tr"); + assert_eq!(db[trait_def].kind, DefKind::Trait); + let trait_generic_scope = db[trait_def].scopes.generic.unwrap(); + let trait_scope = get_unique_child_scope(db, trait_generic_scope, ScopeKind::Trait); + let c_def = get_direct_def(&tcx, db, trait_scope, Namespace::Value, "C"); + assert_eq!(db[c_def].kind, DefKind::AssocConst); + let assoc_def = get_direct_def(&tcx, db, trait_scope, Namespace::Type, "Assoc"); + assert_eq!(db[assoc_def].kind, DefKind::AssocType); assert_eq!( - db.imports()[1].kind, - ImportKind::Rename(tcx.common.intern("d")) + db[db[assoc_def].scopes.generic.unwrap()].kind, + ScopeKind::Generic + ); + let m_def = get_direct_def(&tcx, db, trait_scope, Namespace::Value, "m"); + assert_eq!(db[m_def].kind, DefKind::AssocFn); + let m_generic_scope = db[m_def].scopes.generic.unwrap(); + let m_function_scope = db[m_def].scopes.body.unwrap(); + assert_eq!(db[m_function_scope].kind, ScopeKind::Function); + assert_eq!(db[m_function_scope].parent, Some(m_generic_scope)); + + let impl_def = get_single_def(db, DefKind::Impl); + let impl_generic_scope = db[impl_def].scopes.generic.unwrap(); + let impl_scope = get_unique_child_scope(db, impl_generic_scope, ScopeKind::Impl); + let make_def = get_direct_def(&tcx, db, impl_scope, Namespace::Value, "make"); + assert_eq!(db[make_def].kind, DefKind::AssocFn); + let make_generic_scope = db[make_def].scopes.generic.unwrap(); + let make_function_scope = db[make_def].scopes.body.unwrap(); + assert_eq!(db[make_function_scope].kind, ScopeKind::Function); + assert_eq!(db[make_function_scope].parent, Some(make_generic_scope)); + + let f_def = get_direct_def(&tcx, db, root, Namespace::Value, "f"); + assert_eq!(db[f_def].kind, DefKind::Fn); + let f_generic_scope = db[f_def].scopes.generic.unwrap(); + let f_function_scope = db[f_def].scopes.body.unwrap(); + assert_eq!(db[f_function_scope].kind, ScopeKind::Function); + assert_eq!(db[f_function_scope].parent, Some(f_generic_scope)); + } + + /// Verifies function parameters are direct bindings of the `Function` scope, while `let` + /// bindings inside the body are direct bindings of the nested `Block` scope. + #[test] + fn collects_function_and_block_bindings_in_separate_scopes() { + let tcx = TopCx::default(); + + let entry_path = tcx.common.intern("function_scopes.rs"); + let text = tcx.common.intern( + r#" + fn f(x: i32) { + let y = x; + } + "#, ); - assert_eq!(db.imports()[2].source_path, vec![tcx.common.intern("a")]); - assert_eq!(db.imports()[2].kind, ImportKind::Glob); + + 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 f_def = get_direct_def(&tcx, db, root, Namespace::Value, "f"); + assert_eq!(db[f_def].kind, DefKind::Fn); + let function_scope = db[f_def].scopes.body.unwrap(); + let block_scope = get_unique_child_scope(db, function_scope, ScopeKind::Block); + + let x_def = get_direct_def(&tcx, db, function_scope, Namespace::Value, "x"); + assert_eq!(db[x_def].kind, DefKind::Local); + let y_def = get_direct_def(&tcx, db, block_scope, Namespace::Value, "y"); + assert_eq!(db[y_def].kind, DefKind::Local); + assert!(db + .binding(function_scope, Namespace::Value, tcx.common.intern("y")) + .is_none()); + assert!(db + .binding(block_scope, Namespace::Value, tcx.common.intern("x")) + .is_none()); } } diff --git a/crates/syn-sem-top/tests/file/a1.rs b/crates/syn-sem-top/tests/file/a1.rs index 17881b5..e62ad13 100644 --- a/crates/syn-sem-top/tests/file/a1.rs +++ b/crates/syn-sem-top/tests/file/a1.rs @@ -5,11 +5,13 @@ // Logical path: ~::a1::b1 // Physical path: ~/a1/b1.rs or ~/a1/b1/mod.rs. We have the first one. mod b1; +use b1::B1 as FromB1; // Logical path: ~::a1::c1 // Physical path: ~/c1.rs #[path = "c1.rs"] mod c1; +use c1::C1 as FromC1; // Logical path: ~::a1::dx // Physical path: ~/d1 diff --git a/crates/syn-sem-top/tests/file/a1/b1.rs b/crates/syn-sem-top/tests/file/a1/b1.rs index a30281a..fbb878c 100644 --- a/crates/syn-sem-top/tests/file/a1/b1.rs +++ b/crates/syn-sem-top/tests/file/a1/b1.rs @@ -2,6 +2,9 @@ // Logical path: ~::a1::b1 // Physical path: ~/a1/b1.rs +pub struct B1; + // Logical path: ~::a1::b1::b2 // Physical path: ~/a1/b1/b2.rs or ~/a1/b1/b2/mod.rs. We have the first one. -mod b2; +pub mod b2; +use b2::B2 as FromB2; diff --git a/crates/syn-sem-top/tests/file/a1/b1/b2.rs b/crates/syn-sem-top/tests/file/a1/b1/b2.rs index b4d4543..3a6b355 100644 --- a/crates/syn-sem-top/tests/file/a1/b1/b2.rs +++ b/crates/syn-sem-top/tests/file/a1/b1/b2.rs @@ -1,3 +1,5 @@ // From a1/b1.rs // Logical path: ~::a1::b1::b2 // Physical path: ~/a1/b1/b2.rs + +pub struct B2; diff --git a/crates/syn-sem-top/tests/file/c1.rs b/crates/syn-sem-top/tests/file/c1.rs index 14a9d66..8cc49dd 100644 --- a/crates/syn-sem-top/tests/file/c1.rs +++ b/crates/syn-sem-top/tests/file/c1.rs @@ -1,3 +1,5 @@ // From a1.rs // Logical path: ~::a1::c1 // Physical path: ~/c1.rs + +pub struct C1; diff --git a/crates/syn-sem-top/tests/name_resolution.rs b/crates/syn-sem-top/tests/name_resolution.rs index 9b7efeb..21edcb7 100644 --- a/crates/syn-sem-top/tests/name_resolution.rs +++ b/crates/syn-sem-top/tests/name_resolution.rs @@ -1,10 +1,12 @@ use std::fs; -use syn_sem_common::FilePath; -use syn_sem_name::{DefKind, ImportStatus, NameDb, Namespace, ResolveResult, ScopeId, ScopeKind}; + +use syn_sem_name::{DefKind, ImportStatus, NameDb, Namespace, ResolveResult, ScopeId}; use syn_sem_top::TopCx; +/// Verifies physical module files are loaded from the filesystem and `use` declarations across +/// those files resolve to the expected definitions. #[test] -fn resolves_names_from_physical_module_files() { +fn resolves_imports_from_physical_module_files() { let tcx = TopCx::default(); let entry_path = fixture("a1.rs"); @@ -13,354 +15,62 @@ fn resolves_names_from_physical_module_files() { let text = tcx.common.intern(&text); tcx.insert_virtual_file(entry_path, text).unwrap(); - let semantics = tcx.analyze(entry_path).unwrap(); - assert_fixture_modules(&tcx, semantics.names()); -} - -#[test] -fn resolves_names_from_virtual_module_files() { - let tcx = TopCx::default(); - - let entry_path = insert_virtual_fixture_tree(&tcx); - - let semantics = tcx.analyze(entry_path).unwrap(); - 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(); + let root = db.root_scope(); + 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 + resolve_kind(&tcx, db, root, Namespace::Type, "b1"), + DefKind::Module ); assert_eq!( - follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "a"), + resolve_kind(&tcx, db, root, Namespace::Type, "c1"), DefKind::Module ); assert_eq!( - follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "Local"), - DefKind::Struct + resolve_kind(&tcx, db, root, Namespace::Type, "dx"), + DefKind::Module ); 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 + resolve_kind(&tcx, db, root, Namespace::Type, "e1"), + DefKind::Module ); assert_eq!( - follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "CrateVisible"), + follow_aliases_kind(&tcx, db, root, Namespace::Type, "FromB1"), DefKind::Struct ); assert_eq!( - follow_aliases_kind(&tcx, db, b_scope, Namespace::Type, "SuperVisible"), + follow_aliases_kind(&tcx, db, root, Namespace::Type, "FromC1"), 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); + let b1_scope = get_module_scope(&tcx, db, root, "b1"); + let dx_scope = get_module_scope(&tcx, db, root, "dx"); + let e1_scope = get_module_scope(&tcx, db, root, "e1"); 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")); - tcx.insert_virtual_file(entry_path, text).unwrap(); - - for (path, text) in [ - ("a1/b1.rs", include_str!("file/a1/b1.rs")), - ("a1/b1/b2.rs", include_str!("file/a1/b1/b2.rs")), - ("c1.rs", include_str!("file/c1.rs")), - ("d1/d2.rs", include_str!("file/d1/d2.rs")), - ("a1/e1/e2.rs", include_str!("file/a1/e1/e2.rs")), - ("a1/e1/e4.rs", include_str!("file/a1/e1/e4.rs")), - ] { - let path = tcx.common.intern(path); - let text = tcx.common.intern(text); - tcx.insert_virtual_file(path, text).unwrap(); - } - - entry_path -} - -fn assert_fixture_modules<'tcx>(tcx: &'tcx TopCx<'tcx>, db: &NameDb<'tcx>) { - let root = db.root_scope(); - - assert_eq!( - resolve_kind(tcx, db, root, Namespace::Type, "b1"), + resolve_kind(&tcx, db, b1_scope, Namespace::Type, "b2"), DefKind::Module ); assert_eq!( - resolve_kind(tcx, db, root, Namespace::Type, "c1"), - DefKind::Module - ); - assert_eq!( - resolve_kind(tcx, db, root, Namespace::Type, "dx"), - DefKind::Module - ); - assert_eq!( - resolve_kind(tcx, db, root, Namespace::Type, "e1"), - DefKind::Module - ); - - let b1_scope = module_scope(db, root, 0); - let dx_scope = module_scope(db, root, 2); - let e1_scope = module_scope(db, root, 3); - - assert_eq!( - resolve_kind(tcx, db, b1_scope, Namespace::Type, "b2"), - DefKind::Module + follow_aliases_kind(&tcx, db, b1_scope, Namespace::Type, "FromB2"), + DefKind::Struct ); assert_eq!( - resolve_kind(tcx, db, dx_scope, Namespace::Type, "d2"), + resolve_kind(&tcx, db, dx_scope, Namespace::Type, "d2"), DefKind::Module ); assert_eq!( - resolve_kind(tcx, db, e1_scope, Namespace::Type, "e2"), + resolve_kind(&tcx, db, e1_scope, Namespace::Type, "e2"), DefKind::Module ); assert_eq!( - resolve_kind(tcx, db, e1_scope, Namespace::Type, "e3"), + resolve_kind(&tcx, db, e1_scope, Namespace::Type, "e3"), DefKind::Module ); } @@ -371,6 +81,17 @@ fn fixture(path: &str) -> std::path::PathBuf { .join(path) } +fn get_module_scope<'tcx>( + tcx: &'tcx TopCx<'tcx>, + db: &NameDb<'tcx>, + parent: ScopeId, + name: &str, +) -> ScopeId { + let def = resolve_def(tcx, db, parent, Namespace::Type, name); + assert_eq!(db[def].kind, DefKind::Module); + db[def].scopes.path.unwrap() +} + fn resolve_kind<'tcx>( tcx: &'tcx TopCx<'tcx>, db: &NameDb<'tcx>, @@ -378,10 +99,7 @@ fn resolve_kind<'tcx>( 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:?}"); - }; + let def = resolve_def(tcx, db, scope, namespace, name); db[def].kind } @@ -406,23 +124,10 @@ fn follow_aliases_kind<'tcx>( 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:?}"); - }; + let def = resolve_def(tcx, db, scope, namespace, name); 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, @@ -445,12 +150,3 @@ fn resolve_lexical( scope = parent; } } - -fn module_scope(db: &NameDb<'_>, parent: ScopeId, nth: usize) -> ScopeId { - db.scopes() - .iter() - .filter(|scope| scope.kind == ScopeKind::Module && scope.parent == Some(parent)) - .nth(nth) - .unwrap() - .id -}