From 85be339a40421d3234975cba7616a8e39aa4557b Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:00:49 +0800 Subject: [PATCH 01/18] feat(syntax): add package AST helpers --- crates/syntax/src/slang_ext.rs | 3 + crates/syntax/src/slang_ext/package.rs | 121 +++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 crates/syntax/src/slang_ext/package.rs diff --git a/crates/syntax/src/slang_ext.rs b/crates/syntax/src/slang_ext.rs index 7c063195..2f4cb89e 100644 --- a/crates/syntax/src/slang_ext.rs +++ b/crates/syntax/src/slang_ext.rs @@ -10,9 +10,12 @@ use utils::line_index::{TextRange, TextSize}; use crate::{has_text_range::HasTextRange, ptr::SyntaxNodePtr}; +pub mod package; pub mod token; pub mod trivia; +pub use package::*; + #[derive(Clone, Debug)] pub enum TokenAtOffset<'a> { None, diff --git a/crates/syntax/src/slang_ext/package.rs b/crates/syntax/src/slang_ext/package.rs new file mode 100644 index 00000000..7bf78794 --- /dev/null +++ b/crates/syntax/src/slang_ext/package.rs @@ -0,0 +1,121 @@ +use slang::{ + SyntaxKind, SyntaxNode, SyntaxToken, + ast::{self, AstNode, ModuleDeclaration, ModuleHeader, PackageImportDeclaration, SyntaxList}, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct PackageDeclaration<'a> { + inner: ast::ModuleDeclaration<'a>, +} + +impl<'a> PackageDeclaration<'a> { + #[inline] + pub fn from_module(decl: ast::ModuleDeclaration<'a>) -> Option { + matches!(decl, ModuleDeclaration::PackageDeclaration(_)).then_some(Self { inner: decl }) + } + + #[inline] + pub fn header(&self) -> PackageHeader<'a> { + PackageHeader::new(self.inner.header()).expect("package header") + } + + #[inline] + pub fn members(&self) -> SyntaxList<'a, ast::Member<'a>> { + self.inner.members() + } + + #[inline] + pub fn imports(&self) -> SyntaxList<'a, PackageImportDeclaration<'a>> { + self.inner.header().imports() + } + + #[inline] + pub fn endpackage(&self) -> Option> { + self.inner.endmodule() + } + + #[inline] + pub fn block_name(&self) -> Option> { + self.inner.block_name() + } + + #[inline] + pub fn into_module(self) -> ast::ModuleDeclaration<'a> { + self.inner + } +} + +impl<'a> AstNode<'a> for PackageDeclaration<'a> { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::PACKAGE_DECLARATION + } + + #[inline] + fn cast(syntax: SyntaxNode<'a>) -> Option { + let decl = ModuleDeclaration::cast(syntax)?; + Self::from_module(decl) + } + + #[inline] + fn syntax(&self) -> SyntaxNode<'a> { + self.inner.syntax() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct PackageHeader<'a> { + inner: ast::ModuleHeader<'a>, +} + +impl<'a> PackageHeader<'a> { + #[inline] + pub fn name(&self) -> Option> { + self.inner.name() + } + + #[inline] + pub fn lifetime(&self) -> Option> { + self.inner.lifetime() + } + + #[inline] + pub fn imports(&self) -> SyntaxList<'a, PackageImportDeclaration<'a>> { + self.inner.imports() + } + + #[inline] + pub fn semi(&self) -> Option> { + self.inner.semi() + } + + #[inline] + pub fn into_header(self) -> ast::ModuleHeader<'a> { + self.inner + } +} + +impl<'a> PackageHeader<'a> { + #[inline] + pub fn new(inner: ast::ModuleHeader<'a>) -> Option { + matches!(inner, ModuleHeader::PackageHeader(_)).then_some(Self { inner }) + } +} + +impl<'a> AstNode<'a> for PackageHeader<'a> { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::PACKAGE_HEADER + } + + #[inline] + fn cast(syntax: SyntaxNode<'a>) -> Option { + let header = ModuleHeader::cast(syntax)?; + Self::new(header) + } + + #[inline] + fn syntax(&self) -> SyntaxNode<'a> { + self.inner.syntax() + } +} From 1729c0a9dbeed89dc05a3a9d64867320fc8192a1 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:30:27 +0800 Subject: [PATCH 02/18] feat(hir): add package, struct, and typedef support --- crates/hir/Cargo.toml | 5 +- crates/hir/src/container.rs | 81 ++- crates/hir/src/db.rs | 32 + crates/hir/src/display.rs | 39 +- crates/hir/src/hir_def.rs | 3 + crates/hir/src/hir_def/aggregate.rs | 203 +++++++ crates/hir/src/hir_def/block.rs | 51 +- crates/hir/src/hir_def/declaration.rs | 12 +- crates/hir/src/hir_def/expr.rs | 18 +- crates/hir/src/hir_def/expr/data_ty.rs | 8 + crates/hir/src/hir_def/expr/declarator.rs | 4 +- crates/hir/src/hir_def/expr/timing_control.rs | 4 +- crates/hir/src/hir_def/file.rs | 138 ++++- crates/hir/src/hir_def/module.rs | 473 ++++++++++++++- .../hir/src/hir_def/module/instantiation.rs | 31 +- crates/hir/src/hir_def/package.rs | 548 ++++++++++++++++++ crates/hir/src/hir_def/proc.rs | 2 +- crates/hir/src/hir_def/stmt.rs | 4 +- crates/hir/src/hir_def/subroutine.rs | 320 +++++++++- crates/hir/src/hir_def/typedef.rs | 94 +++ crates/hir/src/region_tree.rs | 4 +- 21 files changed, 2009 insertions(+), 65 deletions(-) create mode 100644 crates/hir/src/hir_def/aggregate.rs create mode 100644 crates/hir/src/hir_def/package.rs create mode 100644 crates/hir/src/hir_def/typedef.rs diff --git a/crates/hir/Cargo.toml b/crates/hir/Cargo.toml index da716211..80e3f164 100644 --- a/crates/hir/Cargo.toml +++ b/crates/hir/Cargo.toml @@ -5,9 +5,7 @@ edition.workspace = true [dependencies] base-db.workspace = true -hashbrown = { version = "0.12.3", features = [ - "inline-more", -], default-features = false } +hashbrown = { version = "0.12.3", features = ["inline-more"], default-features = false } itertools.workspace = true la-arena.workspace = true proc-macro-utils.workspace = true @@ -19,4 +17,3 @@ syntax.workspace = true triomphe.workspace = true utils.workspace = true vfs.workspace = true -slang.workspace = true diff --git a/crates/hir/src/container.rs b/crates/hir/src/container.rs index 7f2dd959..142cb4e3 100644 --- a/crates/hir/src/container.rs +++ b/crates/hir/src/container.rs @@ -9,6 +9,7 @@ use crate::{ db::{HirDb, InternDb}, file::HirFileId, hir_def::{ + aggregate::{StructDef, StructId, StructSrc}, block::{Block, BlockId, BlockInfo, BlockSourceMap, BlockSrc, LocalBlockId}, declaration::{Declaration, DeclarationId, DeclarationSrc}, expr::{ @@ -18,7 +19,10 @@ use crate::{ }, file::{FileSourceMap, HirFile}, module::{Module, ModuleId, ModuleSourceMap}, + package::{Package, PackageId, PackageSourceMap}, stmt::{Stmt, StmtId, StmtSrc}, + subroutine::{Subroutine, SubroutineId, SubroutineSourceMap}, + typedef::{Typedef, TypedefId, TypedefSrc}, }, region_tree::RegionTree, }; @@ -26,9 +30,11 @@ use crate::{ define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum ContainerId { - HirFileId, - ModuleId, - BlockId, + HirFileId(HirFileId), + ModuleId(ModuleId), + PackageId(PackageId), + BlockId(BlockId), + SubroutineId(InModule), } } @@ -52,6 +58,28 @@ impl InContainer { } } +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct InSubroutine { + pub value: T, + pub subroutine: InModule, +} + +impl InSubroutine { + pub fn new(subroutine: InModule, value: T) -> Self { + Self { value, subroutine } + } + + pub fn with_value(self, value: U) -> InSubroutine { + InSubroutine { value, subroutine: self.subroutine } + } +} + +impl From> for InContainer { + fn from(item: InSubroutine) -> InContainer { + InContainer::new(ContainerId::SubroutineId(item.subroutine), item.value) + } +} + macro_rules! define_container_id { ($($name:ident[$id:ident : $ty:ty]),* $(,)?) => { $( @@ -87,6 +115,7 @@ macro_rules! define_container_id { define_container_id! { InFile[file_id: HirFileId], InModule[module_id: ModuleId], + InPackage[package_id: PackageId], InBlock[block_id: BlockId], } @@ -95,7 +124,9 @@ impl ContainerId { match self { ContainerId::HirFileId(file_id) => file_id.file_id(), ContainerId::ModuleId(module_id) => module_id.file_id(), + ContainerId::PackageId(package_id) => package_id.file_id(), ContainerId::BlockId(block_id) => block_id.file_id(db), + ContainerId::SubroutineId(loc) => loc.module_id.file_id(), } } @@ -103,7 +134,9 @@ impl ContainerId { match self { ContainerId::HirFileId(file_id) => file_id.to_container(db).into(), ContainerId::ModuleId(module_id) => module_id.to_container(db).into(), + ContainerId::PackageId(package_id) => package_id.to_container(db).into(), ContainerId::BlockId(block_id) => block_id.to_container(db).into(), + ContainerId::SubroutineId(loc) => loc.to_container(db).into(), } } @@ -111,7 +144,9 @@ impl ContainerId { match self { ContainerId::HirFileId(file_id) => file_id.to_container_src_map(db).into(), ContainerId::ModuleId(module_id) => module_id.to_container_src_map(db).into(), + ContainerId::PackageId(package_id) => package_id.to_container_src_map(db).into(), ContainerId::BlockId(block_id) => block_id.to_container_src_map(db).into(), + ContainerId::SubroutineId(loc) => loc.to_container_src_map(db).into(), } } } @@ -148,6 +183,22 @@ impl ModuleId { } } +impl PackageId { + pub fn file_id(&self) -> FileId { + self.file_id.file_id() + } + + #[inline] + pub fn to_container(&self, db: &dyn HirDb) -> Arc { + db.package(*self) + } + + #[inline] + pub fn to_container_src_map(&self, db: &dyn HirDb) -> Arc { + db.package_with_source_map(*self).1 + } +} + impl BlockId { pub fn file_id(&self, db: &dyn InternDb) -> FileId { self.lookup(db).src.file_id.file_id() @@ -164,14 +215,30 @@ impl BlockId { } } +impl InModule { + #[inline] + pub fn to_container(&self, db: &dyn HirDb) -> Arc { + db.subroutine(*self) + } + + #[inline] + pub fn to_container_src_map(&self, db: &dyn HirDb) -> Arc { + db.subroutine_with_source_map(*self).1 + } +} + impl_container! { #[derive(Debug, PartialEq, Eq, Clone)] pub enum { HirFile | FileSourceMap, Module | ModuleSourceMap, + Package | PackageSourceMap, Block | BlockSourceMap, + Subroutine | SubroutineSourceMap, } => { Declaration[DeclarationId | DeclarationSrc], + Typedef[TypedefId | TypedefSrc], + StructDef[StructId | StructSrc], Expr[ExprId | ExprSrc], EventExpr[EventExprId | EventExprSrc], Declarator[DeclId | DeclaratorSrc], @@ -186,7 +253,9 @@ impl Container { match self { Container::HirFile(_) => None, Container::Module(module) => module.name.as_ref(), + Container::Package(package) => package.name.as_ref(), Container::Block(block) => block.name.as_ref(), + Container::Subroutine(subroutine) => subroutine.name.as_ref(), } } } @@ -203,7 +272,9 @@ impl ContainerSrcMap { match self { ContainerSrcMap::FileSourceMap(file) => Some(&file.region_tree), ContainerSrcMap::ModuleSourceMap(module) => Some(&module.region_tree), + ContainerSrcMap::PackageSourceMap(package) => Some(&package.region_tree), ContainerSrcMap::BlockSourceMap(block) => Some(&block.region_tree), + ContainerSrcMap::SubroutineSourceMap(subroutine) => Some(&subroutine.region_tree), } } } @@ -221,7 +292,7 @@ pub struct ContainerParent<'db> { } impl ContainerParent<'_> { - pub fn start_from(db: &dyn InternDb, cont_id: ContainerId) -> ContainerParent { + pub fn start_from(db: &dyn InternDb, cont_id: ContainerId) -> ContainerParent<'_> { ContainerParent { db, cont_id: Some(cont_id) } } } @@ -234,7 +305,9 @@ impl Iterator for ContainerParent<'_> { self.cont_id = match self.cont_id? { ContainerId::HirFileId(_) => None, ContainerId::ModuleId(module_id) => Some(module_id.file_id.into()), + ContainerId::PackageId(package_id) => Some(package_id.file_id.into()), ContainerId::BlockId(block_id) => Some(block_id.lookup(self.db).cont_id), + ContainerId::SubroutineId(loc) => Some(loc.module_id.into()), }; next } diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index f1aa2a1c..38c70b88 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -1,14 +1,19 @@ use base_db::{impl_intern_key, impl_intern_lookup, salsa, source_db::SourceDb}; +use rustc_hash::FxHashMap; use syntax::SyntaxTree; use triomphe::Arc; use crate::{ + container::InModule, file::HirFileId, hir_def::{ + Ident, block::{self, Block, BlockId, BlockLoc, BlockSourceMap}, expr::data_ty::{BuiltinDataTy, BuiltinDataTyId}, file::{self, FileSourceMap, HirFile}, module::{self, Module, ModuleId, ModuleSourceMap}, + package::{self, Package, PackageId, PackageSourceMap}, + subroutine::{self, Subroutine, SubroutineId, SubroutineSourceMap}, }, scope::{BlockScope, ModuleScope, UnitScope}, }; @@ -45,11 +50,30 @@ pub trait HirDb: InternDb { fn module(&self, module_id: ModuleId) -> Arc; + #[salsa::invoke(package::package_with_source_map_query)] + fn package_with_source_map( + &self, + package_id: PackageId, + ) -> (Arc, Arc); + + fn package(&self, package_id: PackageId) -> Arc; + + #[salsa::invoke(package::packages_by_name_query)] + fn packages_by_name(&self) -> Arc>>; + #[salsa::invoke(block::block_with_source_map_query)] fn block_with_source_map(&self, block_id: BlockId) -> (Arc, Arc); fn block(&self, block_id: BlockId) -> Arc; + #[salsa::invoke(subroutine::subroutine_with_source_map_query)] + fn subroutine_with_source_map( + &self, + subroutine: InModule, + ) -> (Arc, Arc); + + fn subroutine(&self, subroutine_id: InModule) -> Arc; + #[salsa::invoke(UnitScope::unit_scope_query)] fn unit_scope(&self) -> Arc; @@ -75,6 +99,14 @@ fn module(db: &dyn HirDb, module_id: ModuleId) -> Arc { db.module_with_source_map(module_id).0 } +fn package(db: &dyn HirDb, package_id: PackageId) -> Arc { + db.package_with_source_map(package_id).0 +} + fn block(db: &dyn HirDb, block_id: BlockId) -> Arc { db.block_with_source_map(block_id).0 } + +fn subroutine(db: &dyn HirDb, subroutine_id: InModule) -> Arc { + db.subroutine_with_source_map(subroutine_id).0 +} diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index eef3c018..c67c10cc 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -6,9 +6,10 @@ use triomphe::Arc; use utils::get::GetRef; use crate::{ - container::{InContainer, InModule}, + container::{ContainerId, InContainer, InModule}, db::HirDb, hir_def::{ + aggregate::StructKind, expr::{ Arg, AssignOp, BinaryOp, Expr, ExprId, IncDecOp, Selector, StreamOp, UnaryOp, data_ty::{BuiltinDataTy, DataTy, Dimension, IntKind, NamedDataTy, Real, VecKind}, @@ -130,11 +131,47 @@ impl HirDisplay for InContainer { Real::RealTime => f.write_str("realtime"), }, BuiltinDataTy::String => f.write_str("string"), + BuiltinDataTy::Void => f.write_str("void"), }, DataTy::Named(named) => match named { NamedDataTy::Ident(expr_id) => self.with_value(expr_id).hir_fmt(f), NamedDataTy::Field(expr_id) => self.with_value(expr_id).hir_fmt(f), }, + DataTy::Struct(struct_ref) => { + let cont = struct_ref.cont_id.to_container(f.db); + let def = cont.get(struct_ref.value); + let keyword = match def.kind { + StructKind::Struct => "struct", + StructKind::Union => "union", + }; + f.write_str(keyword)?; + if let Some(name) = &def.name { + f.write_str(" ")?; + f.write_str(name.as_str())?; + } + Ok(()) + } + DataTy::Class(class_ref) => { + f.write_str("class")?; + let class_name = match class_ref.cont_id { + ContainerId::HirFileId(file_id) => { + file_id.to_container(f.db).classes.get(class_ref.value).name.clone() + } + ContainerId::ModuleId(module_id) => { + module_id.to_container(f.db).classes.get(class_ref.value).name.clone() + } + ContainerId::PackageId(package_id) => { + package_id.to_container(f.db).classes.get(class_ref.value).name.clone() + } + ContainerId::BlockId(_) => None, + ContainerId::SubroutineId(_) => None, + }; + if let Some(name) = class_name { + f.write_str(" ")?; + f.write_str(name.as_str())?; + } + Ok(()) + } } } } diff --git a/crates/hir/src/hir_def.rs b/crates/hir/src/hir_def.rs index e834cd62..246aa36e 100644 --- a/crates/hir/src/hir_def.rs +++ b/crates/hir/src/hir_def.rs @@ -1,13 +1,16 @@ +pub mod aggregate; pub mod block; pub mod declaration; pub mod expr; pub mod file; pub mod literal; pub mod module; +pub mod package; pub mod proc; pub mod stmt; pub mod subroutine; pub mod ty; +pub mod typedef; use la_arena::{Arena, Idx, RawIdx}; use smol_str::{SmolStr, ToSmolStr}; diff --git a/crates/hir/src/hir_def/aggregate.rs b/crates/hir/src/hir_def/aggregate.rs new file mode 100644 index 00000000..e823c5f4 --- /dev/null +++ b/crates/hir/src/hir_def/aggregate.rs @@ -0,0 +1,203 @@ +use la_arena::Idx; +use smallvec::SmallVec; +use syntax::{ + SyntaxKind, TokenKind, + ast::{self, AstNode, DataType, StructUnionType}, + ptr::{SyntaxNodePtr, SyntaxTokenPtr}, + slang_ext::AstNodeExt, +}; +use utils::text_edit::TextRange; + +use super::{Ident, expr::data_ty::DataTy, lower_ident_opt}; +use crate::{ + container::{ContainerId, InContainer}, + source_map::{IsNamedSrc, IsSrc, ToAstNode}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum StructKind { + Struct, + Union, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StructMember { + pub name: Option, + pub ty: Option>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StructDef { + pub kind: StructKind, + pub name: Option, + pub packed: bool, + pub signing: Option, + pub tagged: bool, + pub members: SmallVec<[StructMember; 4]>, +} + +pub type StructId = Idx; + +pub(crate) fn lower_struct_def( + struct_ty: StructUnionType, + container_id: ContainerId, + mut lower_data_ty: impl FnMut(DataType) -> DataTy, +) -> StructDef { + let kind = match struct_ty { + StructUnionType::StructType(_) => StructKind::Struct, + StructUnionType::UnionType(_) => StructKind::Union, + }; + + let packed = struct_ty.packed().is_some(); + let tagged = struct_ty + .tagged_or_soft() + .map(|tok| tok.kind() == TokenKind::TAGGED_KEYWORD) + .unwrap_or(false); + let signing = struct_ty.signing().and_then(|tok| match tok.kind() { + TokenKind::SIGNED_KEYWORD => Some(true), + TokenKind::UNSIGNED_KEYWORD => Some(false), + _ => None, + }); + + let mut members = SmallVec::<[StructMember; 4]>::new(); + for member in struct_ty.members().children() { + let member_ty = lower_data_ty(member.type_()); + for declarator in member.declarators().children() { + let name = lower_ident_opt(declarator.name()); + let ty = InContainer::new(container_id, member_ty); + members.push(StructMember { name, ty: Some(ty) }); + } + } + + StructDef { kind, name: None, packed, signing, tagged, members } +} + +pub(crate) fn lower_class_def( + class_decl: ast::ClassDeclaration, + container_id: ContainerId, + mut lower_data_ty: impl FnMut(ast::DataType) -> DataTy, +) -> ClassDef { + let name = lower_ident_opt(class_decl.name()); + let mut members = SmallVec::<[ClassMember; 4]>::new(); + + for item in class_decl.items().children() { + if let ast::Member::ClassPropertyDeclaration(prop) = item + && let Some(data_decl) = prop.declaration().as_data_declaration() + { + let member_ty = lower_data_ty(data_decl.type_()); + for declarator in data_decl.declarators().children() { + let member_name = lower_ident_opt(declarator.name()); + members.push(ClassMember { + name: member_name, + kind: ClassMemberKind::Property, + ty: Some(InContainer::new(container_id, member_ty)), + }); + } + } + } + + ClassDef { name, members } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct StructSrc { + pub node: SyntaxNodePtr, +} + +impl IsSrc for StructSrc { + #[inline] + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + #[inline] + fn range(&self) -> TextRange { + self.node.range() + } +} + +impl<'a> ToAstNode<'a, ast::StructUnionType<'a>> for StructSrc { + fn to_node(&self, tree: &'a syntax::SyntaxTree) -> Option> { + let mut node = self.node.to_node(tree)?; + while !ast::StructUnionType::can_cast(node.kind()) { + node = node.children().find_map(|elem| elem.as_node()).unwrap(); + } + ast::StructUnionType::cast(node) + } +} + +impl From> for StructSrc { + fn from(node: ast::StructUnionType<'_>) -> Self { + StructSrc { node: AstNodeExt::to_ptr(&node) } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ClassMemberKind { + Property, + Method, + Typedef, + Unknown, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ClassMember { + pub name: Option, + pub kind: ClassMemberKind, + pub ty: Option>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ClassDef { + pub name: Option, + pub members: SmallVec<[ClassMember; 4]>, +} + +pub type ClassId = Idx; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ClassSrc { + pub node: SyntaxNodePtr, + pub name: Option, +} + +impl IsSrc for ClassSrc { + #[inline] + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + #[inline] + fn range(&self) -> TextRange { + self.node.range() + } +} + +impl IsNamedSrc for ClassSrc { + #[inline] + fn name_kind(&self) -> Option { + self.name.map(|name| name.kind()) + } + + #[inline] + fn name_range(&self) -> Option { + self.name.map(|name| name.range()) + } +} + +impl<'a> ToAstNode<'a, ast::ClassDeclaration<'a>> for ClassSrc { + fn to_node(&self, tree: &'a syntax::SyntaxTree) -> Option> { + let mut node = self.node.to_node(tree)?; + while !ast::ClassDeclaration::can_cast(node.kind()) { + node = node.children().find_map(|elem| elem.as_node()).unwrap(); + } + ast::ClassDeclaration::cast(node) + } +} + +impl From> for ClassSrc { + fn from(node: ast::ClassDeclaration<'_>) -> Self { + let name = node.name().map(SyntaxTokenPtr::from_token); + ClassSrc { node: AstNodeExt::to_ptr(&node), name } + } +} diff --git a/crates/hir/src/hir_def/block.rs b/crates/hir/src/hir_def/block.rs index 340c06d8..fb0d75f3 100644 --- a/crates/hir/src/hir_def/block.rs +++ b/crates/hir/src/hir_def/block.rs @@ -16,17 +16,20 @@ use utils::{ use super::{ Ident, + aggregate::{StructDef, StructId, StructSrc, lower_struct_def}, + alloc_idx_and_src, declaration::{ Declaration, DeclarationId, DeclarationSrc, LowerDeclaration, impl_lower_declaration, }, expr::{ - Expr, ExprSrc, + Expr, ExprSrc, LowerExpr, declarator::{Declarator, DeclaratorSrc, impl_lower_decl}, impl_lower_expr, timing_control::{EventExpr, EventExprSrc, impl_lower_event_expr}, }, lower_ident_opt, stmt::{LowerStmt, Stmt, StmtId, StmtKind, StmtSrc, impl_lower_stmt}, + typedef::{Typedef, TypedefId, TypedefSrc, lower_typedef_data_ty}, }; use crate::{ container::{ContainerId, InFile}, @@ -44,6 +47,8 @@ define_container! { kind: BlockKind, declarations: [Declaration], + typedefs: [Typedef], + structs: [StructDef], exprs: [Expr], event_exprs: [EventExpr], decls: [Declarator], @@ -61,6 +66,8 @@ define_container! { region_tree: RegionTree, declaration_srcs: [Declaration | DeclarationSrc], + typedef_srcs: [Typedef | TypedefSrc], + struct_srcs: [StructDef | StructSrc], expr_srcs: [Expr | ExprSrc], event_expr_srcs: [EventExpr | EventExprSrc], decl_srcs: [Declarator | DeclaratorSrc], @@ -75,6 +82,8 @@ impl BlockSourceMap { pub fn item_to_ptr(&self, item: &BlockItem) -> SyntaxNodePtr { match item { BlockItem::DeclarationId(idx) => self.get(*idx).ptr(), + BlockItem::TypedefId(idx) => self.get(*idx).ptr(), + BlockItem::StructId(idx) => self.get(*idx).node, BlockItem::StmtId(idx) => self.get(*idx).node, } } @@ -148,6 +157,8 @@ define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone)] pub enum BlockItem { DeclarationId, + TypedefId, + StructId, StmtId, } } @@ -158,7 +169,8 @@ pub struct BlockInfo { pub block_id: BlockId, } -pub struct LocalBlockId(StmtId); +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct LocalBlockId(pub StmtId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct BlockId(pub salsa::InternId); @@ -187,6 +199,40 @@ impl_lower_stmt!(LowerBlockCtx<'_>, block_id, block, block_source_map); impl_lower_declaration!(LowerBlockCtx<'_>, block, block_source_map); impl LowerBlockCtx<'_> { + fn lower_struct_type(&mut self, struct_ty: ast::StructUnionType) -> StructId { + let container_id = ContainerId::BlockId(self.block_id); + let struct_def = lower_struct_def(struct_ty.clone(), container_id, |ty| { + self.expr_ctx().lower_data_ty(ty) + }); + + alloc_idx_and_src! { + struct_def => self.block.structs, + struct_ty => self.block_source_map.struct_srcs, + } + } + + fn lower_typedef(&mut self, typedef: ast::TypedefDeclaration) -> TypedefId { + let name = lower_ident_opt(typedef.name()); + + let typedef_id = alloc_idx_and_src! { + Typedef { name, ty: None } => self.block.typedefs, + typedef => self.block_source_map.typedef_srcs, + }; + + let data_ty = typedef.type_(); + let lowered_ty = lower_typedef_data_ty( + self, + data_ty, + ContainerId::BlockId(self.block_id), + |ctx, struct_ty| ctx.lower_struct_type(struct_ty), + |ctx, ty| ctx.expr_ctx().lower_data_ty(ty), + ); + + self.block.typedefs[typedef_id].ty = Some(lowered_ty); + + typedef_id + } + pub(crate) fn lower_block(&mut self, block: ast::BlockStatement) { // TODO: label? end_block_name? self.block.name = block.block_name().and_then(|name| lower_ident_opt(name.name())); @@ -201,6 +247,7 @@ impl LowerBlockCtx<'_> { let idx = match_ast! { node.syntax(), ast::Statement[it] => self.stmt_ctx().lower_stmt(it).into(), ast::DataDeclaration[it] => self.declaration_ctx().lower_data_decl(it).into(), + ast::TypedefDeclaration[it] => self.lower_typedef(it).into(), _ => unimplemented!("{:?}", node.syntax().kind()), }; self.block_source_map.items.push(idx); diff --git a/crates/hir/src/hir_def/declaration.rs b/crates/hir/src/hir_def/declaration.rs index 13e1454b..727a951c 100644 --- a/crates/hir/src/hir_def/declaration.rs +++ b/crates/hir/src/hir_def/declaration.rs @@ -35,14 +35,20 @@ define_enum_deriving_from! { } pub type DeclarationId = Idx; -define_src!(DeclarationSrc(ast::DataDeclaration, ast::NetDeclaration, ast::ParameterDeclaration)); +define_src!(DeclarationSrc( + ast::DataDeclaration, + ast::NetDeclaration, + ast::ParameterDeclaration, + ast::LocalVariableDeclaration +)); impl DeclarationSrc { pub fn ptr(&self) -> SyntaxNodePtr { match self { DeclarationSrc::DataDeclaration(ptr) | DeclarationSrc::NetDeclaration(ptr) - | DeclarationSrc::ParameterDeclaration(ptr) => *ptr, + | DeclarationSrc::ParameterDeclaration(ptr) + | DeclarationSrc::LocalVariableDeclaration(ptr) => *ptr, } } } @@ -116,7 +122,7 @@ pub(crate) trait LowerDeclaration: LowerDecl + LowerEventExpr { pub(in crate::hir_def) macro impl_lower_declaration($ctx:ty, $data:ident, $src_map:ident) { impl $crate::hir_def::declaration::LowerDeclaration for $ctx { - fn declaration_ctx(&mut self) -> $crate::hir_def::declaration::LowerDeclarationCtx { + fn declaration_ctx(&mut self) -> $crate::hir_def::declaration::LowerDeclarationCtx<'_> { $crate::hir_def::declaration::LowerDeclarationCtx { db: self.db, declarations: &mut self.$data.declarations, diff --git a/crates/hir/src/hir_def/expr.rs b/crates/hir/src/hir_def/expr.rs index 69676d02..78ce0ded 100644 --- a/crates/hir/src/hir_def/expr.rs +++ b/crates/hir/src/hir_def/expr.rs @@ -10,7 +10,9 @@ use super::literal::{Literal, lower_literal}; use crate::{ db::InternDb, define_src, - hir_def::{Ident, alloc_idx_and_src, literal::lower_integer_vector, lower_ident_opt}, + hir_def::{ + Ident, alloc_idx_and_src, literal::lower_integer_vector, lower_ident, lower_ident_opt, + }, source_map::SourceMap, }; @@ -263,13 +265,13 @@ pub enum Selector { } pub(crate) trait LowerExpr { - fn expr_ctx(&mut self) -> LowerExprCtx; + fn expr_ctx(&mut self) -> LowerExprCtx<'_>; } pub(in crate::hir_def) macro impl_lower_expr { ($ctx:ty $(,$data:ident, $src_map:ident)?) => { impl $crate::hir_def::expr::LowerExpr for $ctx { - fn expr_ctx(&mut self) -> $crate::hir_def::expr::LowerExprCtx { + fn expr_ctx(&mut self) -> $crate::hir_def::expr::LowerExprCtx<'_> { $crate::hir_def::expr::LowerExprCtx { db: self.db, exprs: &mut self.$($data.)?exprs, @@ -418,7 +420,15 @@ impl LowerExprCtx<'_> { _ => unreachable!("lower_name: {:?}", scoped.right().syntax().kind()), } } - _ => unimplemented!("lower_name: {:?}", name.syntax().kind()), + ast::Name::KeywordName(keyword) => { + let ident = lower_ident(keyword.keyword()); + Some(ident.map_or(Expr::Missing, Expr::Ident)) + } + ast::Name::ClassName(class_name) => { + let ident = lower_ident_opt(class_name.identifier()); + Some(ident.map_or(Expr::Missing, Expr::Ident)) + } + ast::Name::EmptyIdentifierName(_) => Some(Expr::Missing), } } diff --git a/crates/hir/src/hir_def/expr/data_ty.rs b/crates/hir/src/hir_def/expr/data_ty.rs index 0432ef51..0e78b14a 100644 --- a/crates/hir/src/hir_def/expr/data_ty.rs +++ b/crates/hir/src/hir_def/expr/data_ty.rs @@ -6,11 +6,17 @@ use syntax::{ }; use super::{ExprId, LowerExprCtx, Selector}; +use crate::{ + container::InContainer, + hir_def::aggregate::{ClassId, StructId}, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DataTy { Builtin(BuiltinDataTyId), Named(NamedDataTy), + Struct(InContainer), + Class(InContainer), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -22,6 +28,7 @@ pub enum BuiltinDataTy { Vector { kind: VecKind, signing: bool, dimensions: SmallVec<[Option; 2]> }, Real(Real), String, + Void, } impl Default for BuiltinDataTy { @@ -94,6 +101,7 @@ impl LowerExprCtx<'_> { RealType(_) => BuiltinDataTy::Real(Real::Real), ShortRealType(_) => BuiltinDataTy::Real(Real::ShortReal), RealTimeType(_) => BuiltinDataTy::Real(Real::RealTime), + VoidType(_) => BuiltinDataTy::Void, _ => unimplemented!("{:?}", ty.syntax().kind()), } } diff --git a/crates/hir/src/hir_def/expr/declarator.rs b/crates/hir/src/hir_def/expr/declarator.rs index b1ee9b3b..1682f808 100644 --- a/crates/hir/src/hir_def/expr/declarator.rs +++ b/crates/hir/src/hir_def/expr/declarator.rs @@ -46,13 +46,13 @@ pub(crate) struct LowerDeclCtx<'a> { } pub(crate) trait LowerDecl: LowerExpr { - fn decl_ctx(&mut self) -> LowerDeclCtx; + fn decl_ctx(&mut self) -> LowerDeclCtx<'_>; } pub(in crate::hir_def) macro impl_lower_decl { ($ctx:ty $(,$data:ident, $src_map:ident)?) => { impl $crate::hir_def::expr::declarator::LowerDecl for $ctx { - fn decl_ctx(&mut self) -> $crate::hir_def::expr::declarator::LowerDeclCtx { + fn decl_ctx(&mut self) -> $crate::hir_def::expr::declarator::LowerDeclCtx<'_> { $crate::hir_def::expr::declarator::LowerDeclCtx { db: self.db, decls: &mut self.$($data.)?decls, diff --git a/crates/hir/src/hir_def/expr/timing_control.rs b/crates/hir/src/hir_def/expr/timing_control.rs index 9e73e3ab..da98c896 100644 --- a/crates/hir/src/hir_def/expr/timing_control.rs +++ b/crates/hir/src/hir_def/expr/timing_control.rs @@ -58,13 +58,13 @@ pub(crate) struct LowerEventExprCtx<'a> { } pub(crate) trait LowerEventExpr: LowerExpr { - fn event_expr_ctx(&mut self) -> LowerEventExprCtx; + fn event_expr_ctx(&mut self) -> LowerEventExprCtx<'_>; } pub(in crate::hir_def) macro impl_lower_event_expr { ($ctx:ty $(,$data:ident, $src_map:ident)?) => { impl $crate::hir_def::expr::timing_control::LowerEventExpr for $ctx { - fn event_expr_ctx(&mut self) -> $crate::hir_def::expr::timing_control::LowerEventExprCtx { + fn event_expr_ctx(&mut self) -> $crate::hir_def::expr::timing_control::LowerEventExprCtx<'_> { $crate::hir_def::expr::timing_control::LowerEventExprCtx { db: self.db, event_exprs: &mut self.$($data.)?event_exprs, diff --git a/crates/hir/src/hir_def/file.rs b/crates/hir/src/hir_def/file.rs index fddd3b55..66196f1f 100644 --- a/crates/hir/src/hir_def/file.rs +++ b/crates/hir/src/hir_def/file.rs @@ -4,27 +4,38 @@ use smallvec::SmallVec; use syntax::{ ast::{self, AstNode}, ptr::SyntaxNodePtr, + slang_ext::PackageDeclaration, }; use triomphe::Arc; use utils::{define_enum_deriving_from, get::Get}; use super::{ + aggregate::{ + ClassDef, ClassId, ClassSrc, StructDef, StructId, StructSrc, lower_class_def, + lower_struct_def, + }, alloc_idx_and_src, block::{BlockInfo, BlockSrc, LocalBlockId}, declaration::{ Declaration, DeclarationId, DeclarationSrc, LowerDeclaration, impl_lower_declaration, }, expr::{ - Expr, ExprSrc, + Expr, ExprSrc, LowerExpr, declarator::{Declarator, DeclaratorSrc, impl_lower_decl}, impl_lower_expr, timing_control::{EventExpr, EventExprSrc, impl_lower_event_expr}, }, module::{LocalModuleId, ModuleInfo, ModuleSrc}, + package::{ + LocalPackageId, PackageImport, PackageImportId, PackageImportItem, PackageImportSrc, + PackageInfo, PackageSrc, lower_package_import_item, + }, proc::{LowerProc, LowerProcCtx, Proc, ProcId, ProcSrc}, stmt::{Stmt, StmtId, StmtSrc, impl_lower_stmt}, + typedef::{Typedef, TypedefId, TypedefSrc, lower_typedef_data_ty}, }; use crate::{ + container::ContainerId, db::{HirDb, InternDb}, file::HirFileId, hir_def::lower_ident_opt, @@ -36,8 +47,14 @@ define_container! { #[derive(Default, Debug, PartialEq, Eq)] pub struct HirFile { modules: [ModuleInfo], + packages: [PackageInfo], procs: [Proc], + typedefs: [Typedef], + structs: [StructDef], + classes: [ClassDef], + package_imports: [PackageImport], + declarations: [Declaration], exprs: [Expr], event_exprs: [EventExpr], @@ -56,9 +73,14 @@ define_container! { region_tree: RegionTree, module_srcs: [ModuleInfo | ModuleSrc], + package_srcs: [PackageInfo | PackageSrc], proc_srcs: [Proc | ProcSrc], declaration_srcs: [Declaration | DeclarationSrc], + typedef_srcs: [Typedef | TypedefSrc], + struct_srcs: [StructDef | StructSrc], + class_srcs: [ClassDef | ClassSrc], + package_import_srcs: [PackageImport | PackageImportSrc], expr_srcs: [Expr | ExprSrc], event_expr_srcs: [EventExpr | EventExprSrc], decl_srcs: [Declarator | DeclaratorSrc], @@ -73,8 +95,13 @@ define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum FileItem { LocalModuleId, + LocalPackageId, ProcId, DeclarationId, + TypedefId, + StructId, + ClassId, + PackageImportId, } } @@ -82,8 +109,13 @@ impl FileSourceMap { pub fn item_to_ptr(&self, item: &FileItem) -> SyntaxNodePtr { match item { FileItem::LocalModuleId(idx) => self.get(*idx).node, + FileItem::LocalPackageId(idx) => self.get(*idx).node, FileItem::ProcId(idx) => self.get(*idx).0, FileItem::DeclarationId(idx) => self.get(*idx).ptr(), + FileItem::TypedefId(idx) => self.get(*idx).ptr(), + FileItem::StructId(idx) => self.get(*idx).node, + FileItem::ClassId(idx) => self.get(*idx).node, + FileItem::PackageImportId(idx) => self.get(*idx).node, } } } @@ -129,25 +161,95 @@ impl LowerProc for LowerFileCtx<'_> { } impl LowerFileCtx<'_> { + fn lower_struct_type(&mut self, struct_ty: ast::StructUnionType) -> StructId { + let container_id = ContainerId::HirFileId(self.file_id); + let struct_def = lower_struct_def(struct_ty.clone(), container_id, |ty| { + self.expr_ctx().lower_data_ty(ty) + }); + + alloc_idx_and_src! { + struct_def => self.file.structs, + struct_ty => self.file_source_map.struct_srcs, + } + } + + fn lower_class_decl(&mut self, class_decl: ast::ClassDeclaration) -> ClassId { + let container_id = ContainerId::HirFileId(self.file_id); + let class_def = lower_class_def( + class_decl.clone(), + container_id, + |ty| self.expr_ctx().lower_data_ty(ty), + ); + + alloc_idx_and_src! { + class_def => self.file.classes, + class_decl => self.file_source_map.class_srcs, + } + } + + fn lower_package_import(&mut self, import: ast::PackageImportDeclaration) -> PackageImportId { + let mut items = SmallVec::<[PackageImportItem; 2]>::new(); + for item in import.items().children() { + if let Some(lowered) = lower_package_import_item(item) { + items.push(lowered); + } + } + + alloc_idx_and_src! { + PackageImport { items } => self.file.package_imports, + import => self.file_source_map.package_import_srcs, + } + } + + fn lower_typedef(&mut self, typedef: ast::TypedefDeclaration) -> TypedefId { + let name = lower_ident_opt(typedef.name()); + let typedef_id = alloc_idx_and_src! { + Typedef { name, ty: None } => self.file.typedefs, + typedef => self.file_source_map.typedef_srcs, + }; + + let data_ty = typedef.type_(); + let lowered_ty = lower_typedef_data_ty( + self, + data_ty, + ContainerId::HirFileId(self.file_id), + |ctx, struct_ty| ctx.lower_struct_type(struct_ty), + |ctx, ty| ctx.expr_ctx().lower_data_ty(ty), + ); + + self.file.typedefs[typedef_id].ty = Some(lowered_ty); + + typedef_id + } + pub(crate) fn lower_file(&mut self, root: ast::CompilationUnit) { for member in root.members().children() { use ast::Member::*; let idx = match member { ModuleDeclaration(decl) => { - let name = lower_ident_opt(decl.header().name()); + if let Some(package_decl) = PackageDeclaration::from_module(decl) { + self.register_package_decl(package_decl, None).into() + } else { + let name = lower_ident_opt(decl.header().name()); - alloc_idx_and_src! { - ModuleInfo { name } => self.file.modules, - decl => self.file_source_map.module_srcs, + alloc_idx_and_src! { + ModuleInfo { name } => self.file.modules, + decl => self.file_source_map.module_srcs, + } + .into() } - .into() } ProceduralBlock(proc) => self.proc_ctx().lower_proc(proc).into(), DataDeclaration(data_decl) => { self.declaration_ctx().lower_data_decl(data_decl).into() } NetDeclaration(net_decl) => self.declaration_ctx().lower_net_decl(net_decl).into(), + PackageImportDeclaration(import_decl) => { + self.lower_package_import(import_decl).into() + } + ClassDeclaration(class_decl) => self.lower_class_decl(class_decl).into(), EmptyMember(_x) => continue, + TypedefDeclaration(typedef_decl) => self.lower_typedef(typedef_decl).into(), _ => unimplemented!("{:?}", member.syntax().kind()), }; self.file_source_map.items.push(idx); @@ -159,6 +261,30 @@ impl LowerFileCtx<'_> { } } +impl LowerFileCtx<'_> { + fn register_package_decl( + &mut self, + package_decl: PackageDeclaration, + parent: Option, + ) -> LocalPackageId { + let name = lower_ident_opt(package_decl.header().name()); + let local_package_id = alloc_idx_and_src! { + PackageInfo { name, parent } => self.file.packages, + package_decl => self.file_source_map.package_srcs, + }; + + for member in package_decl.members().children() { + if let ast::Member::ModuleDeclaration(module_decl) = member + && let Some(nested_pkg) = PackageDeclaration::from_module(module_decl) + { + self.register_package_decl(nested_pkg, Some(local_package_id)); + } + } + + local_package_id + } +} + pub(crate) fn hir_file_with_source_map_query( db: &dyn HirDb, file_id: HirFileId, diff --git a/crates/hir/src/hir_def/module.rs b/crates/hir/src/hir_def/module.rs index 8434a7bc..256a344b 100644 --- a/crates/hir/src/hir_def/module.rs +++ b/crates/hir/src/hir_def/module.rs @@ -9,6 +9,8 @@ use port::{ PortRefId, PortRefSrc, PortSrcs, Ports, }; use proc_macro_utils::define_container; +use rustc_hash::FxHashMap; +use smallvec::SmallVec; use syntax::{ ast::{self, AstNode, PortList}, ptr::SyntaxNodePtr, @@ -21,22 +23,37 @@ use utils::{ use super::{ HirData, Ident, + aggregate::{ + ClassDef, ClassId, ClassSrc, StructDef, StructId, StructSrc, lower_class_def, + lower_struct_def, + }, + alloc_idx_and_src, block::{BlockInfo, BlockSrc, LocalBlockId}, declaration::{ Declaration, DeclarationId, DeclarationSrc, LowerDeclaration, impl_lower_declaration, }, expr::{ - Expr, ExprSrc, + Expr, ExprSrc, LowerExpr, declarator::{DeclId, Declarator, DeclaratorSrc, impl_lower_decl}, impl_lower_expr, timing_control::{EventExpr, EventExprSrc, impl_lower_event_expr}, }, + lower_ident_opt, + package::{ + PackageImport, PackageImportId, PackageImportItem, PackageImportSrc, + lower_package_import_item, + }, proc::{LowerProc, LowerProcCtx, Proc, ProcId, ProcSrc}, stmt::{Stmt, StmtId, StmtSrc, impl_lower_stmt}, + subroutine::{ + LowerSubroutineBodyCtx, Subroutine, SubroutineId, SubroutineSourceMap, SubroutineSrc, + lower_subroutine, lower_subroutine_body, + }, ty::NetKind, + typedef::{Typedef, TypedefId, TypedefSrc, lower_typedef_data_ty}, }; use crate::{ - container::InFile, + container::{ContainerId, InFile, InModule}, db::{HirDb, InternDb}, define_src_with_name, file::HirFileId, @@ -62,6 +79,12 @@ define_container! { cont_assigns: [ContAssign], declarations: [Declaration], + typedefs: [Typedef], + structs: [StructDef], + classes: [ClassDef], + package_imports: [PackageImport], + subroutines: [Subroutine], + subroutine_source_maps: FxHashMap, instantiations: [Instantiation], inst_param_assigns: [ParamAssign], @@ -94,6 +117,11 @@ define_container! { assign_srcs: [ContAssign | ContAssignSrc], declaration_srcs: [Declaration | DeclarationSrc], + typedef_srcs: [Typedef | TypedefSrc], + struct_srcs: [StructDef | StructSrc], + class_srcs: [ClassDef | ClassSrc], + package_import_srcs: [PackageImport | PackageImportSrc], + subroutine_srcs: [Subroutine | SubroutineSrc], instantiation_srcs: [Instantiation | InstantiationSrc], inst_param_assign_srcs: [ParamAssign | ParamAssignSrc], @@ -144,9 +172,14 @@ impl ModuleSourceMap { match item { ModuleItem::ContAssignId(idx) => self.get(*idx).0, ModuleItem::DeclarationId(idx) => self.get(*idx).ptr(), - ModuleItem::InstantiationId(idx) => self.get(*idx).0, + ModuleItem::StructId(idx) => self.get(*idx).node, + ModuleItem::ClassId(idx) => self.get(*idx).node, + ModuleItem::PackageImportId(idx) => self.get(*idx).node, + ModuleItem::InstantiationId(idx) => self.get(*idx).into(), ModuleItem::ProcId(idx) => self.get(*idx).0, ModuleItem::PortDeclId(idx) => self.get(*idx).ptr(), + ModuleItem::TypedefId(idx) => self.get(*idx).ptr(), + ModuleItem::SubroutineId(idx) => self.get(*idx).0, } } } @@ -154,11 +187,16 @@ impl ModuleSourceMap { define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone)] pub enum ModuleItem { - ContAssignId, - DeclarationId, - InstantiationId, - ProcId, - PortDeclId, + ContAssignId(ContAssignId), + DeclarationId(DeclarationId), + StructId(StructId), + ClassId(ClassId), + PackageImportId(PackageImportId), + InstantiationId(InstantiationId), + ProcId(ProcId), + PortDeclId(PortDeclId), + TypedefId(TypedefId), + SubroutineId(SubroutineId), } } @@ -213,6 +251,97 @@ impl LowerProc for LowerModuleCtx<'_> { } impl LowerModuleCtx<'_> { + fn lower_struct_type(&mut self, struct_ty: ast::StructUnionType) -> StructId { + let container_id = ContainerId::ModuleId(self.module_id); + let struct_def = lower_struct_def(struct_ty.clone(), container_id, |ty| { + self.expr_ctx().lower_data_ty(ty) + }); + + alloc_idx_and_src! { + struct_def => self.module.structs, + struct_ty => self.module_source_map.struct_srcs, + } + } + + fn lower_class_decl(&mut self, class_decl: ast::ClassDeclaration) -> ClassId { + let container_id = ContainerId::ModuleId(self.module_id); + let class_def = lower_class_def( + class_decl.clone(), + container_id, + |ty| self.expr_ctx().lower_data_ty(ty), + ); + + alloc_idx_and_src! { + class_def => self.module.classes, + class_decl => self.module_source_map.class_srcs, + } + } + + fn lower_package_import(&mut self, import: ast::PackageImportDeclaration) -> PackageImportId { + let mut items = SmallVec::<[PackageImportItem; 2]>::new(); + for item in import.items().children() { + if let Some(lowered) = lower_package_import_item(item) { + items.push(lowered); + } + } + + alloc_idx_and_src! { + PackageImport { items } => self.module.package_imports, + import => self.module_source_map.package_import_srcs, + } + } + + fn lower_typedef(&mut self, typedef: ast::TypedefDeclaration) -> TypedefId { + let name = lower_ident_opt(typedef.name()); + + let typedef_id = alloc_idx_and_src! { + Typedef { name, ty: None } => self.module.typedefs, + typedef => self.module_source_map.typedef_srcs, + }; + + let data_ty = typedef.type_(); + let lowered_ty = lower_typedef_data_ty( + self, + data_ty, + ContainerId::ModuleId(self.module_id), + |ctx, struct_ty| ctx.lower_struct_type(struct_ty), + |ctx, ty| ctx.expr_ctx().lower_data_ty(ty), + ); + + self.module.typedefs[typedef_id].ty = Some(lowered_ty); + + typedef_id + } + + fn lower_subroutine_decl(&mut self, func: ast::FunctionDeclaration) -> Option { + let subroutine = lower_subroutine(&func, |ty| self.expr_ctx().lower_data_ty(ty))?; + + let subroutine_id = alloc_idx_and_src! { + subroutine => self.module.subroutines, + func => self.module_source_map.subroutine_srcs, + }; + + if func.end().is_some() { + let subroutine_loc = InModule::new(self.module_id, subroutine_id); + let subroutine = &mut self.module.subroutines[subroutine_id]; + let mut subroutine_source_map = SubroutineSourceMap::default(); + let mut ctx = LowerSubroutineBodyCtx { + db: self.db, + file_id: self.file_id, + subroutine_loc, + subroutine, + subroutine_source_map: &mut subroutine_source_map, + region_tree: RegionTreeBuilder::new(), + }; + lower_subroutine_body(&mut ctx, func); + self.module.subroutine_source_maps.insert(subroutine_id, subroutine_source_map); + } + + self.module.subroutines[subroutine_id].shrink_to_fit(); + + Some(subroutine_id) + } + pub(crate) fn lower_module_decl(&mut self, decl: ast::ModuleDeclaration) { let header = decl.header(); if let Some(param_ports) = header.parameters() { @@ -240,24 +369,347 @@ impl LowerModuleCtx<'_> { for member in decl.members().children() { use ast::Member::*; let idx = match member { + // Assignments ContinuousAssign(assign) => self.lower_continuous_assign(assign).into(), + + // Declarations DataDeclaration(data_decl) => { self.declaration_ctx().lower_data_decl(data_decl).into() } NetDeclaration(net_decl) => self.declaration_ctx().lower_net_decl(net_decl).into(), + LocalVariableDeclaration(_local_decl) => { + // Local variable declarations shouldn't appear at module level, + // they appear in function/task bodies + continue; + } ParameterDeclarationStatement(param_decl) => { self.declaration_ctx().lower_param_decl_base(param_decl.parameter()).into() } + TypedefDeclaration(typedef_decl) => self.lower_typedef(typedef_decl).into(), + NetTypeDeclaration(_net_type_decl) => { + // TODO: implement net type declaration lowering + continue; + } + ForwardTypedefDeclaration(_fwd_typedef) => { + // TODO: implement forward typedef lowering + continue; + } + UserDefinedNetDeclaration(_udn_decl) => { + // TODO: implement user defined net declaration lowering + continue; + } + GenvarDeclaration(_genvar_decl) => { + // TODO: implement genvar declaration lowering + continue; + } + + // Instantiations HierarchyInstantiation(instantiation) => { self.lower_instantiation(instantiation).into() } - FunctionDeclaration(_fn_decl) => todo!(), + PrimitiveInstantiation(instantiation) => { + self.lower_primitive_instantiation(instantiation).into() + } + CheckerInstantiation(_checker_inst) => { + // TODO: implement checker instantiation lowering + continue; + } + + // Subroutines + FunctionDeclaration(fn_decl) => match self.lower_subroutine_decl(fn_decl) { + Some(sub_id) => sub_id.into(), + None => continue, + }, + + // Procedural blocks ProceduralBlock(proc) => self.proc_ctx().lower_proc(proc).into(), + // Ports PortDeclaration(port) => self.lower_port_decl(port).into(), ExplicitAnsiPort(_) | ImplicitAnsiPort(_) => unreachable!(), + + // Imports + PackageImportDeclaration(import_decl) => { + self.lower_package_import(import_decl).into() + } + + // Aggregates + ClassDeclaration(class_decl) => self.lower_class_decl(class_decl).into(), + + // Nested modules/interfaces/programs + ModuleDeclaration(_nested_module) => { + // TODO: handle nested module declarations + continue; + } + + // Generate constructs + GenerateRegion(_gen_region) => { + // TODO: implement generate region lowering + continue; + } + GenerateBlock(_gen_block) => { + // TODO: implement generate block lowering + continue; + } + IfGenerate(_if_gen) => { + // TODO: implement if generate lowering + continue; + } + CaseGenerate(_case_gen) => { + // TODO: implement case generate lowering + continue; + } + LoopGenerate(_loop_gen) => { + // TODO: implement loop generate lowering + continue; + } + + // Timing and clocking + TimeUnitsDeclaration(_time_units) => { + // TODO: implement time units declaration lowering + continue; + } + ClockingDeclaration(_clocking_decl) => { + // TODO: implement clocking declaration lowering + continue; + } + DefaultClockingReference(_default_clocking) => { + // TODO: implement default clocking reference lowering + continue; + } + ClockingItem(_clocking_item) => { + // TODO: implement clocking item lowering + continue; + } + + // Assertions and properties + PropertyDeclaration(_prop_decl) => { + // TODO: implement property declaration lowering + continue; + } + SequenceDeclaration(_seq_decl) => { + // TODO: implement sequence declaration lowering + continue; + } + ImmediateAssertionMember(_assert_member) => { + // TODO: implement immediate assertion lowering + continue; + } + ConcurrentAssertionMember(_assert_member) => { + // TODO: implement concurrent assertion lowering + continue; + } + + // Coverage + CovergroupDeclaration(_covergroup) => { + // TODO: implement covergroup lowering + continue; + } + Coverpoint(_coverpoint) => { + // TODO: implement coverpoint lowering + continue; + } + CoverCross(_cover_cross) => { + // TODO: implement cover cross lowering + continue; + } + CoverageBins(_coverage_bins) => { + // TODO: implement coverage bins lowering + continue; + } + BinsSelection(_bins_selection) => { + // TODO: implement bins selection lowering + continue; + } + CoverageOption(_coverage_option) => { + // TODO: implement coverage option lowering + continue; + } + + // Specify blocks + SpecifyBlock(_specify_block) => { + // TODO: implement specify block lowering + continue; + } + PathDeclaration(_path_decl) => { + // TODO: implement path declaration lowering + continue; + } + ConditionalPathDeclaration(_cond_path) => { + // TODO: implement conditional path declaration lowering + continue; + } + IfNonePathDeclaration(_if_none_path) => { + // TODO: implement if none path declaration lowering + continue; + } + SystemTimingCheck(_timing_check) => { + // TODO: implement system timing check lowering + continue; + } + SpecparamDeclaration(_specparam) => { + // TODO: implement specparam declaration lowering + continue; + } + PulseStyleDeclaration(_pulse_style) => { + // TODO: implement pulse style declaration lowering + continue; + } + DefaultSkewItem(_default_skew) => { + // TODO: implement default skew item lowering + continue; + } + + // DPI and external + DPIImport(_dpi_import) => { + // TODO: implement DPI import lowering + continue; + } + DPIExport(_dpi_export) => { + // TODO: implement DPI export lowering + continue; + } + ExternInterfaceMethod(_extern_method) => { + // TODO: implement extern interface method lowering + continue; + } + ExternModuleDecl(_extern_module) => { + // TODO: implement extern module declaration lowering + continue; + } + ExternUdpDecl(_extern_udp) => { + // TODO: implement extern UDP declaration lowering + continue; + } + + // UDP + UdpDeclaration(_udp_decl) => { + // TODO: implement UDP declaration lowering + continue; + } + + // Defparam + DefParam(_defparam) => { + // TODO: implement defparam lowering + continue; + } + + // Net alias + NetAlias(_net_alias) => { + // TODO: implement net alias lowering + continue; + } + + // Modport + ModportDeclaration(_modport_decl) => { + // TODO: implement modport declaration lowering + continue; + } + ModportClockingPort(_modport_clocking) => { + // TODO: implement modport clocking port lowering + continue; + } + ModportSimplePortList(_modport_simple) => { + // TODO: implement modport simple port list lowering + continue; + } + ModportSubroutinePortList(_modport_subroutine) => { + // TODO: implement modport subroutine port list lowering + continue; + } + + // Class members (shouldn't appear in module but handle anyway) + ClassPropertyDeclaration(_class_prop) => { + // Class property shouldn't appear directly in module + continue; + } + ClassMethodDeclaration(_class_method) => { + // Class method shouldn't appear directly in module + continue; + } + ClassMethodPrototype(_class_method_proto) => { + // Class method prototype shouldn't appear directly in module + continue; + } + + // Checker + CheckerDeclaration(_checker_decl) => { + // TODO: implement checker declaration lowering + continue; + } + CheckerDataDeclaration(_checker_data) => { + // TODO: implement checker data declaration lowering + continue; + } + + // Constraints + ConstraintDeclaration(_constraint_decl) => { + // TODO: implement constraint declaration lowering + continue; + } + ConstraintPrototype(_constraint_proto) => { + // TODO: implement constraint prototype lowering + continue; + } + + // Config + ConfigDeclaration(_config_decl) => { + // TODO: implement config declaration lowering + continue; + } + + // Bind + BindDirective(_bind_directive) => { + // TODO: implement bind directive lowering + continue; + } + + // Package exports + PackageExportDeclaration(_pkg_export) => { + // TODO: implement package export declaration lowering + continue; + } + PackageExportAllDeclaration(_pkg_export_all) => { + // TODO: implement package export all declaration lowering + continue; + } + + // Library + LibraryDeclaration(_lib_decl) => { + // TODO: implement library declaration lowering + continue; + } + LibraryIncludeStatement(_lib_include) => { + // TODO: implement library include statement lowering + continue; + } + + // Let declaration + LetDeclaration(_let_decl) => { + // TODO: implement let declaration lowering + continue; + } + + // Default disable + DefaultDisableDeclaration(_default_disable) => { + // TODO: implement default disable declaration lowering + continue; + } + + // Elaboration system task + ElabSystemTask(_elab_task) => { + // TODO: implement elaboration system task lowering + continue; + } + + // Anonymous program + AnonymousProgram(_anon_program) => { + // TODO: implement anonymous program lowering + continue; + } + + // Empty member - skip EmptyMember(_) => continue, - _ => unimplemented!("unhandled module member: {:?}", member.syntax().kind()), }; self.module_source_map.items.push(idx); self.region_tree.handle_node(member.syntax()); @@ -292,6 +744,7 @@ pub(crate) fn module_with_source_map_query( }; lower_ctx.lower_module_decl(ast_module); + module.subroutine_source_maps.shrink_to_fit(); module.shrink_to_fit(); module_source_map.shrink_to_fit(); (Arc::new(module), Arc::new(module_source_map)) diff --git a/crates/hir/src/hir_def/module/instantiation.rs b/crates/hir/src/hir_def/module/instantiation.rs index ef9237ca..c4f98315 100644 --- a/crates/hir/src/hir_def/module/instantiation.rs +++ b/crates/hir/src/hir_def/module/instantiation.rs @@ -21,7 +21,16 @@ pub struct Instantiation { pub type InstantiationId = Idx; -define_src!(InstantiationSrc(ast::HierarchyInstantiation)); +define_src!(InstantiationSrc(ast::HierarchyInstantiation, ast::PrimitiveInstantiation)); + +impl From for syntax::ptr::SyntaxNodePtr { + fn from(src: InstantiationSrc) -> Self { + match src { + InstantiationSrc::HierarchyInstantiation(ptr) => ptr, + InstantiationSrc::PrimitiveInstantiation(ptr) => ptr, + } + } +} #[derive(Debug, PartialEq, Eq, Clone)] pub struct Instance { @@ -77,6 +86,26 @@ impl LowerModuleCtx<'_> { } } + pub(crate) fn lower_primitive_instantiation( + &mut self, + inst: ast::PrimitiveInstantiation, + ) -> InstantiationId { + let module_name = lower_ident_opt(inst.type_()); + let param_assigns = SmallVec::new(); + + let next_instantiation_id = self.module.instantiations.nxt_idx(); + let instances = inst + .instances() + .children() + .map(|hier| self.lower_instance(hier, next_instantiation_id)) + .collect(); + + alloc_idx_and_src! { + Instantiation { module_name, param_assigns, instances } => self.module.instantiations, + inst => self.module_source_map.instantiation_srcs, + } + } + fn lower_param_assign( &mut self, assigns: Option, diff --git a/crates/hir/src/hir_def/package.rs b/crates/hir/src/hir_def/package.rs new file mode 100644 index 00000000..aeb8b2e8 --- /dev/null +++ b/crates/hir/src/hir_def/package.rs @@ -0,0 +1,548 @@ +use la_arena::Idx; +use proc_macro_utils::define_container; +use rustc_hash::FxHashMap; +use smallvec::SmallVec; +use syntax::{ + SyntaxKind, TokenKind, + ast::{self, AstNode}, + ptr::{SyntaxNodePtr, SyntaxTokenPtr}, + slang_ext::{AstNodeExt, PackageDeclaration}, +}; +use triomphe::Arc; +use utils::{ + define_enum_deriving_from, + get::{Get, GetRef}, + text_edit::TextRange, +}; + +use super::{ + Ident, + aggregate::{ClassDef, ClassId, StructDef, StructId, lower_class_def, lower_struct_def}, + alloc_idx_and_src, + block::{BlockInfo, LocalBlockId}, + declaration::{Declaration, DeclarationId, LowerDeclaration}, + expr::{Expr, LowerExpr, declarator::Declarator, timing_control::EventExpr}, + lower_ident, lower_ident_opt, + proc::{LowerProc, LowerProcCtx, Proc, ProcId}, + stmt::{Stmt, StmtId, impl_lower_stmt}, + subroutine::{Subroutine, SubroutineId, SubroutineSrc, lower_subroutine}, + typedef::{Typedef, TypedefId, lower_typedef_data_ty}, +}; +use crate::{ + container::{ContainerId, InFile}, + db::{HirDb, InternDb}, + file::HirFileId, + hir_def::Arena, + region_tree::RegionTree, + source_map::{IsNamedSrc, IsSrc, SourceMap, ToAstNode}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum PackageImportMember { + All, + Named(Ident), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PackageImportItem { + pub package: Ident, + pub member: PackageImportMember, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PackageImport { + pub items: SmallVec<[PackageImportItem; 2]>, +} + +pub type PackageImportId = Idx; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PackageExportItem { + pub package: Ident, + pub member: PackageImportMember, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PackageExport { + All, + Items(SmallVec<[PackageExportItem; 2]>), +} + +pub type PackageExportId = Idx; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct PackageImportSrc { + pub node: SyntaxNodePtr, +} + +impl IsSrc for PackageImportSrc { + #[inline] + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + #[inline] + fn range(&self) -> TextRange { + self.node.range() + } +} + +impl IsNamedSrc for PackageImportSrc { + #[inline] + fn name_kind(&self) -> Option { + None + } + + #[inline] + fn name_range(&self) -> Option { + None + } +} + +impl<'a> ToAstNode<'a, ast::PackageImportDeclaration<'a>> for PackageImportSrc { + fn to_node(&self, tree: &'a syntax::SyntaxTree) -> Option> { + let mut node = self.node.to_node(tree)?; + while !ast::PackageImportDeclaration::can_cast(node.kind()) { + node = node.children().find_map(|elem| elem.as_node()).unwrap(); + } + ast::PackageImportDeclaration::cast(node) + } +} + +impl From> for PackageImportSrc { + fn from(node: ast::PackageImportDeclaration<'_>) -> Self { + PackageImportSrc { node: AstNodeExt::to_ptr(&node) } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct PackageExportSrc { + pub node: SyntaxNodePtr, +} + +impl IsSrc for PackageExportSrc { + #[inline] + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + #[inline] + fn range(&self) -> TextRange { + self.node.range() + } +} + +impl IsNamedSrc for PackageExportSrc { + #[inline] + fn name_kind(&self) -> Option { + None + } + + #[inline] + fn name_range(&self) -> Option { + None + } +} + +impl<'a> ToAstNode<'a, syntax::ast::Member<'a>> for PackageExportSrc { + fn to_node(&self, tree: &'a syntax::SyntaxTree) -> Option> { + let node = self.node.to_node(tree)?; + syntax::ast::Member::cast(node) + } +} + +impl From> for PackageExportSrc { + fn from(node: syntax::ast::Member<'_>) -> Self { + PackageExportSrc { node: AstNodeExt::to_ptr(&node) } + } +} + +impl From> for PackageExportSrc { + fn from(node: ast::PackageExportDeclaration<'_>) -> Self { + PackageExportSrc { node: AstNodeExt::to_ptr(&node) } + } +} + +impl From> for PackageExportSrc { + fn from(node: ast::PackageExportAllDeclaration<'_>) -> Self { + PackageExportSrc { node: AstNodeExt::to_ptr(&node) } + } +} + +pub fn lower_package_import_item(item: ast::PackageImportItem) -> Option { + let package = lower_ident(item.package())?; + + let member = match item.item()?.kind() { + TokenKind::STAR => PackageImportMember::All, + _ => PackageImportMember::Named(lower_ident(item.item())?), + }; + + Some(PackageImportItem { package, member }) +} + +define_container! { + #[derive(Default, Debug, PartialEq, Eq)] + pub struct Package { + name: Option, + + declarations: [Declaration], + typedefs: [Typedef], + structs: [StructDef], + classes: [ClassDef], + procs: [Proc], + subroutines: [Subroutine], + exports: [PackageExport], + + exprs: [Expr], + event_exprs: [EventExpr], + decls: [Declarator], + stmts: [Stmt] => { + [StmtId | Stmt], + [LocalBlockId | BlockInfo], + }, + } +} + +define_container! { + #[derive(Default, Debug, PartialEq, Eq)] + pub struct PackageSourceMap { + items: Vec, + region_tree: RegionTree, + + declaration_srcs: [Declaration | super::declaration::DeclarationSrc], + typedef_srcs: [Typedef | super::typedef::TypedefSrc], + struct_srcs: [StructDef | super::aggregate::StructSrc], + class_srcs: [ClassDef | super::aggregate::ClassSrc], + package_export_srcs: [PackageExport | PackageExportSrc], + subroutine_srcs: [Subroutine | SubroutineSrc], + + proc_srcs: [Proc | super::proc::ProcSrc], + + expr_srcs: [Expr | super::expr::ExprSrc], + event_expr_srcs: [EventExpr | super::expr::timing_control::EventExprSrc], + decl_srcs: [Declarator | super::expr::declarator::DeclaratorSrc], + stmt_srcs: [Stmt | super::stmt::StmtSrc] => { + [StmtId | super::stmt::StmtSrc], + [LocalBlockId | super::block::BlockSrc], + }, + } +} + +define_enum_deriving_from! { + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] + pub enum PackageItem { + DeclarationId, + TypedefId, + StructId, + ClassId, + ProcId, + PackageExportId, + SubroutineId, + } +} + +impl PackageSourceMap { + pub(crate) fn ptr(&self, item: PackageItem) -> SyntaxNodePtr { + match item { + PackageItem::DeclarationId(idx) => self.declaration_srcs.get(idx).ptr(), + PackageItem::TypedefId(idx) => self.typedef_srcs.get(idx).node, + PackageItem::StructId(idx) => self.struct_srcs.get(idx).node, + PackageItem::ClassId(idx) => self.class_srcs.get(idx).node, + PackageItem::ProcId(idx) => self.proc_srcs.get(idx).0, + PackageItem::PackageExportId(idx) => self.package_export_srcs.get(idx).node, + PackageItem::SubroutineId(idx) => self.subroutine_srcs.get(idx).0, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PackageInfo { + pub name: Option, + pub parent: Option, +} + +pub type LocalPackageId = Idx; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct PackageSrc { + pub node: SyntaxNodePtr, + pub name: Option, +} + +impl IsSrc for PackageSrc { + #[inline] + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + #[inline] + fn range(&self) -> TextRange { + self.node.range() + } +} + +impl IsNamedSrc for PackageSrc { + #[inline] + fn name_kind(&self) -> Option { + self.name.map(|name| name.kind()) + } + + #[inline] + fn name_range(&self) -> Option { + self.name.map(|name| name.range()) + } +} + +impl<'a> ToAstNode<'a, PackageDeclaration<'a>> for PackageSrc { + fn to_node(&self, tree: &'a syntax::SyntaxTree) -> Option> { + let mut node = self.node.to_node(tree)?; + while !PackageDeclaration::can_cast(node.kind()) { + node = node.children().find_map(|elem| elem.as_node())?; + } + PackageDeclaration::cast(node) + } +} + +impl From> for PackageSrc { + fn from(node: PackageDeclaration<'_>) -> Self { + let name = node.header().name().map(SyntaxTokenPtr::from_token); + PackageSrc { node: AstNodeExt::to_ptr(&node), name } + } +} + +pub type PackageData = Arc<(Package, PackageSourceMap)>; +pub type PackageId = InFile; + +pub(crate) struct LowerPackageCtx<'a> { + pub(crate) db: &'a dyn InternDb, + pub(crate) file_id: HirFileId, + pub(crate) package_id: PackageId, + pub(crate) package: &'a mut Package, + pub(crate) package_source_map: &'a mut PackageSourceMap, + pub(crate) region_tree: crate::region_tree::RegionTreeBuilder, +} + +use super::{ + declaration::impl_lower_declaration, + expr::{declarator::impl_lower_decl, impl_lower_expr, timing_control::impl_lower_event_expr}, +}; + +impl_lower_expr!(LowerPackageCtx<'_>, package, package_source_map); +impl_lower_decl!(LowerPackageCtx<'_>, package, package_source_map); +impl_lower_event_expr!(LowerPackageCtx<'_>, package, package_source_map); +impl_lower_stmt!(LowerPackageCtx<'_>, package_id, package, package_source_map); +impl_lower_declaration!(LowerPackageCtx<'_>, package, package_source_map); + +impl LowerProc for LowerPackageCtx<'_> { + fn proc_ctx(&mut self) -> LowerProcCtx<'_> { + LowerProcCtx { + db: self.db, + file_id: self.file_id, + cont_id: self.package_id.into(), + + procs: &mut self.package.procs, + proc_srcs: &mut self.package_source_map.proc_srcs, + + stmts: &mut self.package.stmts, + stmt_srcs: &mut self.package_source_map.stmt_srcs, + + exprs: &mut self.package.exprs, + expr_srcs: &mut self.package_source_map.expr_srcs, + + event_exprs: &mut self.package.event_exprs, + event_expr_srcs: &mut self.package_source_map.event_expr_srcs, + + decls: &mut self.package.decls, + decl_srcs: &mut self.package_source_map.decl_srcs, + } + } +} + +impl LowerPackageCtx<'_> { + fn lower_struct_type(&mut self, struct_ty: ast::StructUnionType) -> StructId { + let container_id = ContainerId::PackageId(self.package_id); + let struct_def = lower_struct_def(struct_ty.clone(), container_id, |ty| { + self.expr_ctx().lower_data_ty(ty) + }); + + alloc_idx_and_src! { + struct_def => self.package.structs, + struct_ty => self.package_source_map.struct_srcs, + } + } + + fn lower_typedef(&mut self, typedef_decl: ast::TypedefDeclaration) -> TypedefId { + let name = lower_ident_opt(typedef_decl.name()); + + let typedef_id = alloc_idx_and_src! { + Typedef { name, ty: None } => self.package.typedefs, + typedef_decl => self.package_source_map.typedef_srcs, + }; + + let data_ty = typedef_decl.type_(); + let lowered_ty = lower_typedef_data_ty( + self, + data_ty, + ContainerId::PackageId(self.package_id), + |ctx, struct_ty| ctx.lower_struct_type(struct_ty), + |ctx, ty| ctx.expr_ctx().lower_data_ty(ty), + ); + + self.package.typedefs[typedef_id].ty = Some(lowered_ty); + + typedef_id + } + + fn lower_subroutine_decl(&mut self, func: ast::FunctionDeclaration) -> Option { + let subroutine = lower_subroutine(&func, |ty| self.expr_ctx().lower_data_ty(ty))?; + + let subroutine_id = alloc_idx_and_src! { + subroutine => self.package.subroutines, + func => self.package_source_map.subroutine_srcs, + }; + + Some(subroutine_id) + } + + fn lower_package_export(&mut self, export: ast::PackageExportDeclaration) -> PackageExportId { + let mut items = SmallVec::<[PackageExportItem; 2]>::new(); + for item in export.items().children() { + if let Some(lowered) = lower_package_import_item(item) { + let PackageImportItem { package, member } = lowered; + items.push(PackageExportItem { package, member }); + } + } + + alloc_idx_and_src! { + PackageExport::Items(items) => self.package.exports, + export => self.package_source_map.package_export_srcs, + } + } + + fn lower_package_export_all( + &mut self, + export: ast::PackageExportAllDeclaration, + ) -> PackageExportId { + alloc_idx_and_src! { + PackageExport::All => self.package.exports, + export => self.package_source_map.package_export_srcs, + } + } + + fn lower_class_decl(&mut self, class_decl: ast::ClassDeclaration) -> ClassId { + let container_id = ContainerId::PackageId(self.package_id); + let class_def = lower_class_def( + class_decl.clone(), + container_id, + |ty| self.expr_ctx().lower_data_ty(ty), + ); + + alloc_idx_and_src! { + class_def => self.package.classes, + class_decl => self.package_source_map.class_srcs, + } + } + + pub(crate) fn lower_package_decl(&mut self, decl: PackageDeclaration) { + for member in decl.members().children() { + let item = match member { + ast::Member::DataDeclaration(data_decl) => { + Some(self.declaration_ctx().lower_data_decl(data_decl).into()) + } + ast::Member::NetDeclaration(net_decl) => { + Some(self.declaration_ctx().lower_net_decl(net_decl).into()) + } + ast::Member::ParameterDeclarationStatement(param_decl) => Some( + self.declaration_ctx().lower_param_decl_base(param_decl.parameter()).into(), + ), + ast::Member::TypedefDeclaration(typedef_decl) => { + Some(self.lower_typedef(typedef_decl).into()) + } + ast::Member::ClassDeclaration(class_decl) => { + Some(self.lower_class_decl(class_decl).into()) + } + ast::Member::ProceduralBlock(proc) => Some(self.proc_ctx().lower_proc(proc).into()), + ast::Member::FunctionDeclaration(fn_decl) => { + self.lower_subroutine_decl(fn_decl).map(|sub_id| sub_id.into()) + } + ast::Member::PackageExportDeclaration(export_decl) => { + Some(self.lower_package_export(export_decl).into()) + } + ast::Member::PackageExportAllDeclaration(export_all_decl) => { + Some(self.lower_package_export_all(export_all_decl).into()) + } + ast::Member::EmptyMember(_) + | ast::Member::TimeUnitsDeclaration(_) + | ast::Member::PackageImportDeclaration(_) => None, + _ => None, + }; + + if let Some(item) = item { + self.package_source_map.items.push(item); + } + + self.region_tree.handle_node(member.syntax()); + } + + if let Some(end_token) = decl.endpackage() { + self.region_tree.stage(Some(end_token)); + } + + self.package_source_map.region_tree = self.region_tree.finish(); + } +} + +pub(crate) fn package_with_source_map_query( + db: &dyn HirDb, + package_id: PackageId, +) -> (Arc, Arc) { + let local_package_id = package_id.value; + let file_id = package_id.file_id; + + let (file, file_source_map) = db.hir_file_with_source_map(file_id); + let tree = db.parse(file_id); + + let mut package = + Package { name: file.packages.get(local_package_id).name.clone(), ..Default::default() }; + let mut package_source_map = PackageSourceMap::default(); + + let src = file_source_map.package_srcs.get(local_package_id); + if let Some(ast_package) = src.to_node(&tree) { + let mut lower_ctx = LowerPackageCtx { + db, + file_id, + package_id, + package: &mut package, + package_source_map: &mut package_source_map, + region_tree: crate::region_tree::RegionTreeBuilder::new(), + }; + lower_ctx.lower_package_decl(ast_package); + } + + package.shrink_to_fit(); + package_source_map.shrink_to_fit(); + (Arc::new(package), Arc::new(package_source_map)) +} + +pub(crate) fn packages_by_name_query(db: &dyn HirDb) -> Arc>> { + let mut map: FxHashMap> = FxHashMap::default(); + + for file_id in db.files().iter() { + let file_id = HirFileId(*file_id); + let hir_file = db.hir_file(file_id); + + for (local_package_id, package_info) in hir_file.packages.iter() { + if let Some(name) = &package_info.name { + map.entry(name.clone()).or_default().push(InFile::new(file_id, local_package_id)); + } + } + } + + for packages in map.values_mut() { + packages + .sort_by_key(|pkg_id| (pkg_id.file_id.file_id().0, pkg_id.value.into_raw().into_u32())); + packages.dedup(); + } + + Arc::new(map) +} diff --git a/crates/hir/src/hir_def/proc.rs b/crates/hir/src/hir_def/proc.rs index 0bb6ce67..322a2b45 100644 --- a/crates/hir/src/hir_def/proc.rs +++ b/crates/hir/src/hir_def/proc.rs @@ -50,7 +50,7 @@ pub type ProcId = Idx; define_src!(ProcSrc(ast::ProceduralBlock)); pub(crate) trait LowerProc: LowerStmt { - fn proc_ctx(&mut self) -> LowerProcCtx; + fn proc_ctx(&mut self) -> LowerProcCtx<'_>; } pub(crate) struct LowerProcCtx<'a> { diff --git a/crates/hir/src/hir_def/stmt.rs b/crates/hir/src/hir_def/stmt.rs index 40640acc..c20ab0b2 100644 --- a/crates/hir/src/hir_def/stmt.rs +++ b/crates/hir/src/hir_def/stmt.rs @@ -130,13 +130,13 @@ pub enum CaseItem { } pub(crate) trait LowerStmt: LowerExpr + LowerEventExpr + LowerDecl { - fn stmt_ctx(&mut self) -> LowerStmtCtx; + fn stmt_ctx(&mut self) -> LowerStmtCtx<'_>; } pub(in crate::hir_def) macro impl_lower_stmt { ($ctx:ty, $cont_id:ident $(,$data:ident, $src_map:ident)?) => { impl $crate::hir_def::stmt::LowerStmt for $ctx { - fn stmt_ctx(&mut self) -> $crate::hir_def::stmt::LowerStmtCtx { + fn stmt_ctx(&mut self) -> $crate::hir_def::stmt::LowerStmtCtx<'_> { $crate::hir_def::stmt::LowerStmtCtx { db: self.db, file_id: self.file_id, diff --git a/crates/hir/src/hir_def/subroutine.rs b/crates/hir/src/hir_def/subroutine.rs index d0fe85b3..fc3cbbba 100644 --- a/crates/hir/src/hir_def/subroutine.rs +++ b/crates/hir/src/hir_def/subroutine.rs @@ -1,42 +1,320 @@ -use la_arena::Arena; +use la_arena::{Arena, Idx}; +use proc_macro_utils::define_container; +use rustc_hash::FxHashMap; use smallvec::SmallVec; +use syntax::{ + TokenKind, + ast::{self, AstNode}, + match_ast, +}; +use triomphe::Arc; use super::{ Ident, + aggregate::{StructDef, StructId, StructSrc, lower_struct_def}, + alloc_idx_and_src, + block::{BlockInfo, BlockItem, BlockSrc, LocalBlockId}, + declaration::{ + Declaration, DeclarationId, DeclarationSrc, LowerDeclaration, impl_lower_declaration, + }, expr::{ - Expr, + Expr, ExprSrc, LowerExpr, data_ty::DataTy, - declarator::{DeclId, Declarator}, - timing_control::EventExpr, + declarator::{Declarator, DeclaratorSrc, impl_lower_decl}, + impl_lower_expr, + timing_control::{EventExpr, EventExprSrc}, + }, + lower_ident, lower_ident_opt, + stmt::{LowerStmt, Stmt, StmtId, StmtSrc, impl_lower_stmt}, + typedef::{Typedef, TypedefId, TypedefSrc, lower_typedef_data_ty}, +}; +use crate::{ + container::{ContainerId, InModule}, + db::{HirDb, InternDb}, + define_src, + file::HirFileId, + hir_def::{ + HirData, + declaration::DataDecl, + expr::{declarator::LowerDecl, timing_control::impl_lower_event_expr}, }, - module::port::PortDirection, + region_tree::{RegionTree, RegionTreeBuilder}, + source_map::SourceMap, }; -use crate::db::InternDb; -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Subroutine { - pub name: Ident, - pub sig: SubroutineSig, +define_container! { + #[derive(Debug, PartialEq, Eq, Clone)] + pub struct Subroutine { + name: Option, + kind: SubroutineKind, + ports: SmallVec<[SubroutinePort; 4]>, + has_body: bool, + declarations: [Declaration], + typedefs: [Typedef], + structs: [StructDef], + exprs: [Expr], + event_exprs: [EventExpr], + decls: [Declarator], + stmts: [Stmt] => { + [StmtId | Stmt], + [LocalBlockId | BlockInfo], + }, + source_map: SubroutineSourceMap + } +} - pub exprs: Arena, - pub event_exprs: Arena, - pub decls: Arena, +impl Default for Subroutine { + fn default() -> Self { + Subroutine { + name: None, + kind: SubroutineKind::Task, + ports: SmallVec::new(), + has_body: false, + declarations: Arena::new(), + typedefs: Arena::new(), + structs: Arena::new(), + exprs: Arena::new(), + event_exprs: Arena::new(), + decls: Arena::new(), + stmts: Arena::new(), + source_map: SubroutineSourceMap::default(), + } + } +} + +define_container! { + #[derive(Default, Debug, PartialEq, Eq, Clone)] + pub struct SubroutineSourceMap { + items: SmallVec<[BlockItem; 2]>, + region_tree: RegionTree, + + declaration_srcs: [Declaration | DeclarationSrc], + typedef_srcs: [Typedef | TypedefSrc], + struct_srcs: [StructDef | StructSrc], + expr_srcs: [Expr | ExprSrc], + event_expr_srcs: [EventExpr | EventExprSrc], + decl_srcs: [Declarator | DeclaratorSrc], + stmt_srcs: [Stmt | StmtSrc] => { + [StmtId | StmtSrc], + [LocalBlockId | BlockSrc], + }, + block_srcs: FxHashMap, + } } #[derive(Debug, PartialEq, Eq, Clone)] -pub enum SubroutineSig { - Task { ports: SmallVec<[SubroutinePort; 3]> }, - Fn { ports: SmallVec<[SubroutinePort; 3]>, ret_ty: DataTy }, +pub enum SubroutineKind { + Task, + Function { return_ty: Option }, } #[derive(Debug, PartialEq, Eq, Clone)] pub struct SubroutinePort { - dir: PortDirection, - var_kw: bool, - ty: DataTy, - decl: Option, + pub direction: SubroutinePortDir, + pub ty: Option, + pub name: Option, } -pub struct LowerSubroutineCtx<'a> { +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub enum SubroutinePortDir { + Input, + Output, + Inout, + Ref, + ConstRef, + #[default] + Unknown, +} + +pub type SubroutineId = Idx; + +define_src!(SubroutineSrc(ast::FunctionDeclaration)); + +pub fn lower_subroutine(func: &ast::FunctionDeclaration, mut lower_ty: F) -> Option +where + F: FnMut(ast::DataType) -> DataTy, +{ + let prototype = func.prototype(); + let name = lower_name(prototype.name())?; + + let is_task = func.as_task_declaration().is_some(); + + let mut ports = SmallVec::<[SubroutinePort; 4]>::new(); + if let Some(port_list) = prototype.port_list() { + for port_base in port_list.ports().children() { + if let Some(port) = port_base.as_function_port() { + let mut dir = map_direction(port.direction().map(|tok| tok.kind())); + if matches!(dir, SubroutinePortDir::Ref) && port.const_keyword().is_some() { + dir = SubroutinePortDir::ConstRef; + } + + let ty = port.data_type().map(&mut lower_ty); + let name = lower_ident_opt(port.declarator().name()); + ports.push(SubroutinePort { direction: dir, ty, name }); + } else if port_base.as_default_function_port().is_some() { + ports.push(SubroutinePort { + direction: SubroutinePortDir::Input, + ty: None, + name: None, + }); + } + } + } + + let kind = if is_task { + SubroutineKind::Task + } else { + let ret_ty = lower_ty(prototype.return_type()); + SubroutineKind::Function { return_ty: Some(ret_ty) } + }; + + Some(Subroutine { name: Some(name), kind, ports, ..Default::default() }) +} + +fn lower_name(name: ast::Name) -> Option { + if let Some(id) = name.as_identifier_name().and_then(|n| n.identifier()) { + return lower_ident(Some(id)); + } + if let Some(select) = name.as_identifier_select_name() { + return select.identifier().and_then(|tok| lower_ident(Some(tok))); + } + if let Some(scoped) = name.as_scoped_name() { + return lower_name(scoped.right()); + } + None +} + +fn map_direction(kind: Option) -> SubroutinePortDir { + match kind { + Some(TokenKind::OUTPUT_KEYWORD) => SubroutinePortDir::Output, + Some(TokenKind::IN_OUT_KEYWORD) => SubroutinePortDir::Inout, + Some(TokenKind::REF_KEYWORD) => SubroutinePortDir::Ref, + Some(TokenKind::INPUT_KEYWORD) | None => SubroutinePortDir::Input, + Some(_) => SubroutinePortDir::Unknown, + } +} + +pub struct LowerSubroutineBodyCtx<'a> { pub(crate) db: &'a dyn InternDb, + pub(crate) file_id: HirFileId, + pub(crate) subroutine_loc: InModule, + pub(crate) subroutine: &'a mut Subroutine, + pub(crate) subroutine_source_map: &'a mut SubroutineSourceMap, + pub(crate) region_tree: RegionTreeBuilder, +} + +impl_lower_expr!(LowerSubroutineBodyCtx<'_>, subroutine, subroutine_source_map); +impl_lower_decl!(LowerSubroutineBodyCtx<'_>, subroutine, subroutine_source_map); +impl_lower_event_expr!(LowerSubroutineBodyCtx<'_>, subroutine, subroutine_source_map); +impl_lower_stmt!(LowerSubroutineBodyCtx<'_>, subroutine_loc, subroutine, subroutine_source_map); +impl_lower_declaration!(LowerSubroutineBodyCtx<'_>, subroutine, subroutine_source_map); + +impl LowerSubroutineBodyCtx<'_> { + fn container_id(&self) -> ContainerId { + self.subroutine_loc.into() + } + + fn lower_struct_type(&mut self, struct_ty: ast::StructUnionType) -> StructId { + let container_id = self.container_id(); + let struct_def = lower_struct_def(struct_ty.clone(), container_id, |ty| { + self.expr_ctx().lower_data_ty(ty) + }); + + alloc_idx_and_src! { + struct_def => self.subroutine.structs, + struct_ty => self.subroutine_source_map.struct_srcs, + } + } + + fn lower_typedef(&mut self, typedef: ast::TypedefDeclaration) -> TypedefId { + let name = lower_ident_opt(typedef.name()); + + let typedef_id = alloc_idx_and_src! { + Typedef { name, ty: None } => self.subroutine.typedefs, + typedef => self.subroutine_source_map.typedef_srcs, + }; + + let data_ty = typedef.type_(); + let lowered_ty = lower_typedef_data_ty( + self, + data_ty, + self.container_id(), + |ctx, struct_ty| ctx.lower_struct_type(struct_ty), + |ctx, ty| ctx.expr_ctx().lower_data_ty(ty), + ); + + self.subroutine.typedefs[typedef_id].ty = Some(lowered_ty); + + typedef_id + } + + fn lower_local_variable_decl( + &mut self, + local_decl: ast::LocalVariableDeclaration, + ) -> DeclarationId { + let const_kw = false; + let var_kw = local_decl.var().is_some(); + let ty = self.expr_ctx().lower_data_ty(local_decl.type_()); + + let parent = self.subroutine.declarations.nxt_idx().into(); + let decls = self.decl_ctx().lower_declarators(local_decl.declarators(), parent); + + alloc_idx_and_src! { + DataDecl { ty, const_kw, var_kw, decls } => self.subroutine.declarations, + local_decl => self.subroutine_source_map.declaration_srcs, + } + } + + pub(crate) fn lower_items(&mut self, func: ast::FunctionDeclaration) { + self.subroutine.has_body = true; + + for item in func.items().children() { + self.region_tree.handle_node(item.syntax()); + + let syntax = item.syntax(); + match_ast! { syntax, + ast::Statement[it] => { + let stmt_id = self.stmt_ctx().lower_stmt(it); + if let Some(block_stmt) = it.as_block_statement() { + let block_src = BlockSrc::from(block_stmt); + let local_block_id = LocalBlockId(stmt_id); + self.subroutine_source_map.block_srcs.insert(block_src, local_block_id); + } + self.subroutine_source_map.items.push(BlockItem::StmtId(stmt_id)); + }, + ast::DataDeclaration[it] => { + let decl_id = self.declaration_ctx().lower_data_decl(it); + self.subroutine_source_map.items.push(BlockItem::DeclarationId(decl_id)); + }, + ast::LocalVariableDeclaration[it] => { + let decl_id = self.lower_local_variable_decl(it); + self.subroutine_source_map.items.push(BlockItem::DeclarationId(decl_id)); + }, + ast::TypedefDeclaration[it] => { + let typedef_id = self.lower_typedef(it); + self.subroutine_source_map.items.push(BlockItem::TypedefId(typedef_id)); + }, + _ => { + // TODO: handle other function items (assertions, cover, etc.) as needed + }, + } + } + + self.region_tree.stage(func.end()); + self.subroutine_source_map.region_tree = self.region_tree.finish(); + } +} + +pub fn lower_subroutine_body(ctx: &mut LowerSubroutineBodyCtx<'_>, func: ast::FunctionDeclaration) { + ctx.lower_items(func); +} + +pub(crate) fn subroutine_with_source_map_query( + db: &dyn HirDb, + loc: InModule, +) -> (Arc, Arc) { + let module = db.module(loc.module_id); + let subroutine = module.subroutines[loc.value].clone(); + let source_map = module.subroutine_source_maps.get(&loc.value).cloned().unwrap_or_default(); + (Arc::new(subroutine), Arc::new(source_map)) } diff --git a/crates/hir/src/hir_def/typedef.rs b/crates/hir/src/hir_def/typedef.rs new file mode 100644 index 00000000..3f6c9740 --- /dev/null +++ b/crates/hir/src/hir_def/typedef.rs @@ -0,0 +1,94 @@ +use la_arena::Idx; +use syntax::{ + SyntaxKind, TokenKind, + ast::{self, AstNode}, + ptr::{SyntaxNodePtr, SyntaxTokenPtr}, + slang_ext::AstNodeExt, +}; +use utils::text_edit::TextRange; + +use super::{Ident, aggregate::StructId, expr::data_ty::DataTy}; +use crate::{ + container::{ContainerId, InContainer}, + source_map::{IsNamedSrc, IsSrc, ToAstNode}, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Typedef { + pub name: Option, + pub ty: Option, +} + +pub type TypedefId = Idx; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct TypedefSrc { + pub node: SyntaxNodePtr, + pub name: Option, +} + +impl IsSrc for TypedefSrc { + #[inline] + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + #[inline] + fn range(&self) -> TextRange { + self.node.range() + } +} + +impl IsNamedSrc for TypedefSrc { + #[inline] + fn name_kind(&self) -> Option { + self.name.map(|name| name.kind()) + } + + #[inline] + fn name_range(&self) -> Option { + self.name.map(|name| name.range()) + } +} + +impl<'a> ToAstNode<'a, ast::TypedefDeclaration<'a>> for TypedefSrc { + fn to_node(&self, tree: &'a syntax::SyntaxTree) -> Option> { + let mut node = self.node.to_node(tree)?; + while !ast::TypedefDeclaration::can_cast(node.kind()) { + node = node.children().find_map(|elem| elem.as_node()).unwrap(); + } + ast::TypedefDeclaration::cast(node) + } +} + +impl From> for TypedefSrc { + fn from(node: ast::TypedefDeclaration<'_>) -> Self { + let name_token = node.name(); + TypedefSrc { + node: AstNodeExt::to_ptr(&node), + name: name_token.map(SyntaxTokenPtr::from_token), + } + } +} + +impl TypedefSrc { + pub fn ptr(&self) -> SyntaxNodePtr { + self.node + } +} + +pub(crate) fn lower_typedef_data_ty( + ctx: &mut Ctx, + data_ty: ast::DataType, + container_id: ContainerId, + mut lower_struct_type: impl FnMut(&mut Ctx, ast::StructUnionType) -> StructId, + mut lower_data_ty: impl FnMut(&mut Ctx, ast::DataType) -> DataTy, +) -> DataTy { + match data_ty { + ast::DataType::StructUnionType(struct_ty) => { + let struct_id = lower_struct_type(ctx, struct_ty); + DataTy::Struct(InContainer::new(container_id, struct_id)) + } + other => lower_data_ty(ctx, other), + } +} diff --git a/crates/hir/src/region_tree.rs b/crates/hir/src/region_tree.rs index 77273720..aaf2c2b7 100644 --- a/crates/hir/src/region_tree.rs +++ b/crates/hir/src/region_tree.rs @@ -16,13 +16,13 @@ pub enum RegionKind { PseudoRegion { description: Option }, } -#[derive(Default, Debug, PartialEq, Eq)] +#[derive(Default, Debug, PartialEq, Eq, Clone)] pub struct RegionTree { pub roots: Vec>, pub nodes: Arena, } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct RegionNode { pub range: TextRange, pub kind: RegionKind, From b9a4cea0273481695ab30febf9a617b4e35c7212 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:49:32 +0800 Subject: [PATCH 03/18] feat(hir): add class method support in HIR --- crates/hir/src/hir_def/aggregate.rs | 37 +++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/crates/hir/src/hir_def/aggregate.rs b/crates/hir/src/hir_def/aggregate.rs index e823c5f4..1802b9ad 100644 --- a/crates/hir/src/hir_def/aggregate.rs +++ b/crates/hir/src/hir_def/aggregate.rs @@ -8,7 +8,7 @@ use syntax::{ }; use utils::text_edit::TextRange; -use super::{Ident, expr::data_ty::DataTy, lower_ident_opt}; +use super::{Ident, expr::data_ty::DataTy, lower_ident, lower_ident_opt}; use crate::{ container::{ContainerId, InContainer}, source_map::{IsNamedSrc, IsSrc, ToAstNode}, @@ -81,18 +81,35 @@ pub(crate) fn lower_class_def( let mut members = SmallVec::<[ClassMember; 4]>::new(); for item in class_decl.items().children() { - if let ast::Member::ClassPropertyDeclaration(prop) = item - && let Some(data_decl) = prop.declaration().as_data_declaration() - { - let member_ty = lower_data_ty(data_decl.type_()); - for declarator in data_decl.declarators().children() { - let member_name = lower_ident_opt(declarator.name()); + match item { + ast::Member::ClassPropertyDeclaration(prop) => { + if let Some(data_decl) = prop.declaration().as_data_declaration() { + let member_ty = lower_data_ty(data_decl.type_()); + for declarator in data_decl.declarators().children() { + let member_name = lower_ident_opt(declarator.name()); + members.push(ClassMember { + name: member_name, + kind: ClassMemberKind::Property, + ty: Some(InContainer::new(container_id, member_ty)), + }); + } + } + } + ast::Member::ClassMethodDeclaration(method) => { + let func_decl = method.declaration(); + let prototype = func_decl.prototype(); + let method_name = if let Some(id) = prototype.name().as_identifier_name() { + lower_ident(id.identifier()) + } else { + None + }; members.push(ClassMember { - name: member_name, - kind: ClassMemberKind::Property, - ty: Some(InContainer::new(container_id, member_ty)), + name: method_name, + kind: ClassMemberKind::Method, + ty: None, // methods don't have a simple data type }); } + _ => {} } } From 1da6021c1c47974299cb7241f21851e264df685f Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 19:39:45 +0800 Subject: [PATCH 04/18] feat(hir): add file-level function declaration support --- crates/hir/src/container.rs | 19 +++++++ crates/hir/src/display.rs | 1 + crates/hir/src/hir_def/file.rs | 45 +++++++++++++++- crates/hir/src/hir_def/module.rs | 2 +- crates/hir/src/hir_def/subroutine.rs | 31 +++++++++++- crates/hir/src/semantics/hir_to_def.rs | 62 ++++++++++++++++++++++- crates/hir/src/semantics/source_to_def.rs | 30 ++++++++++- crates/ide/src/definitions.rs | 8 +++ crates/ide/src/document_symbols.rs | 11 ++++ crates/ide/src/references/search.rs | 4 ++ 10 files changed, 206 insertions(+), 7 deletions(-) diff --git a/crates/hir/src/container.rs b/crates/hir/src/container.rs index 142cb4e3..4138756e 100644 --- a/crates/hir/src/container.rs +++ b/crates/hir/src/container.rs @@ -35,6 +35,7 @@ define_enum_deriving_from! { PackageId(PackageId), BlockId(BlockId), SubroutineId(InModule), + FileSubroutineId(InFile), } } @@ -127,6 +128,7 @@ impl ContainerId { ContainerId::PackageId(package_id) => package_id.file_id(), ContainerId::BlockId(block_id) => block_id.file_id(db), ContainerId::SubroutineId(loc) => loc.module_id.file_id(), + ContainerId::FileSubroutineId(loc) => loc.file_id.file_id(), } } @@ -137,6 +139,7 @@ impl ContainerId { ContainerId::PackageId(package_id) => package_id.to_container(db).into(), ContainerId::BlockId(block_id) => block_id.to_container(db).into(), ContainerId::SubroutineId(loc) => loc.to_container(db).into(), + ContainerId::FileSubroutineId(loc) => loc.to_container(db).into(), } } @@ -147,6 +150,7 @@ impl ContainerId { ContainerId::PackageId(package_id) => package_id.to_container_src_map(db).into(), ContainerId::BlockId(block_id) => block_id.to_container_src_map(db).into(), ContainerId::SubroutineId(loc) => loc.to_container_src_map(db).into(), + ContainerId::FileSubroutineId(loc) => loc.to_container_src_map(db).into(), } } } @@ -227,6 +231,20 @@ impl InModule { } } +impl InFile { + #[inline] + pub fn to_container(&self, db: &dyn HirDb) -> Arc { + let file = db.hir_file(self.file_id); + Arc::new(file.subroutines[self.value].clone()) + } + + #[inline] + pub fn to_container_src_map(&self, db: &dyn HirDb) -> Arc { + let file = db.hir_file(self.file_id); + Arc::new(file.subroutine_source_maps[&self.value].clone()) + } +} + impl_container! { #[derive(Debug, PartialEq, Eq, Clone)] pub enum { @@ -308,6 +326,7 @@ impl Iterator for ContainerParent<'_> { ContainerId::PackageId(package_id) => Some(package_id.file_id.into()), ContainerId::BlockId(block_id) => Some(block_id.lookup(self.db).cont_id), ContainerId::SubroutineId(loc) => Some(loc.module_id.into()), + ContainerId::FileSubroutineId(loc) => Some(loc.file_id.into()), }; next } diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index c67c10cc..130b702d 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -165,6 +165,7 @@ impl HirDisplay for InContainer { } ContainerId::BlockId(_) => None, ContainerId::SubroutineId(_) => None, + ContainerId::FileSubroutineId(_) => None, }; if let Some(name) = class_name { f.write_str(" ")?; diff --git a/crates/hir/src/hir_def/file.rs b/crates/hir/src/hir_def/file.rs index 66196f1f..8ebb5b75 100644 --- a/crates/hir/src/hir_def/file.rs +++ b/crates/hir/src/hir_def/file.rs @@ -1,5 +1,6 @@ use la_arena::Arena; use proc_macro_utils::define_container; +use rustc_hash::FxHashMap; use smallvec::SmallVec; use syntax::{ ast::{self, AstNode}, @@ -32,10 +33,14 @@ use super::{ }, proc::{LowerProc, LowerProcCtx, Proc, ProcId, ProcSrc}, stmt::{Stmt, StmtId, StmtSrc, impl_lower_stmt}, + subroutine::{ + LowerSubroutineBodyCtx, Subroutine, SubroutineId, SubroutineSourceMap, SubroutineSrc, + lower_subroutine, lower_subroutine_body, + }, typedef::{Typedef, TypedefId, TypedefSrc, lower_typedef_data_ty}, }; use crate::{ - container::ContainerId, + container::{ContainerId, InFile}, db::{HirDb, InternDb}, file::HirFileId, hir_def::lower_ident_opt, @@ -54,6 +59,8 @@ define_container! { structs: [StructDef], classes: [ClassDef], package_imports: [PackageImport], + subroutines: [Subroutine], + subroutine_source_maps: FxHashMap, declarations: [Declaration], exprs: [Expr], @@ -81,6 +88,7 @@ define_container! { struct_srcs: [StructDef | StructSrc], class_srcs: [ClassDef | ClassSrc], package_import_srcs: [PackageImport | PackageImportSrc], + subroutine_srcs: [Subroutine | SubroutineSrc], expr_srcs: [Expr | ExprSrc], event_expr_srcs: [EventExpr | EventExprSrc], decl_srcs: [Declarator | DeclaratorSrc], @@ -102,6 +110,7 @@ define_enum_deriving_from! { StructId, ClassId, PackageImportId, + SubroutineId, } } @@ -116,6 +125,7 @@ impl FileSourceMap { FileItem::StructId(idx) => self.get(*idx).node, FileItem::ClassId(idx) => self.get(*idx).node, FileItem::PackageImportId(idx) => self.get(*idx).node, + FileItem::SubroutineId(idx) => self.get(*idx).0, } } } @@ -222,6 +232,35 @@ impl LowerFileCtx<'_> { typedef_id } + fn lower_subroutine_decl(&mut self, func: ast::FunctionDeclaration) -> Option { + let subroutine = lower_subroutine(&func, |ty| self.expr_ctx().lower_data_ty(ty))?; + + let subroutine_id = alloc_idx_and_src! { + subroutine => self.file.subroutines, + func => self.file_source_map.subroutine_srcs, + }; + + if func.end().is_some() { + let subroutine_loc = InFile::new(self.file_id, subroutine_id).into(); + let subroutine = &mut self.file.subroutines[subroutine_id]; + let mut subroutine_source_map = SubroutineSourceMap::default(); + let mut ctx = LowerSubroutineBodyCtx { + db: self.db, + file_id: self.file_id, + subroutine_loc, + subroutine, + subroutine_source_map: &mut subroutine_source_map, + region_tree: RegionTreeBuilder::new(), + }; + lower_subroutine_body(&mut ctx, func); + self.file.subroutine_source_maps.insert(subroutine_id, subroutine_source_map); + } + + self.file.subroutines[subroutine_id].shrink_to_fit(); + + Some(subroutine_id) + } + pub(crate) fn lower_file(&mut self, root: ast::CompilationUnit) { for member in root.members().children() { use ast::Member::*; @@ -250,6 +289,10 @@ impl LowerFileCtx<'_> { ClassDeclaration(class_decl) => self.lower_class_decl(class_decl).into(), EmptyMember(_x) => continue, TypedefDeclaration(typedef_decl) => self.lower_typedef(typedef_decl).into(), + FunctionDeclaration(fn_decl) => match self.lower_subroutine_decl(fn_decl) { + Some(id) => id.into(), + None => continue, + }, _ => unimplemented!("{:?}", member.syntax().kind()), }; self.file_source_map.items.push(idx); diff --git a/crates/hir/src/hir_def/module.rs b/crates/hir/src/hir_def/module.rs index 256a344b..06146436 100644 --- a/crates/hir/src/hir_def/module.rs +++ b/crates/hir/src/hir_def/module.rs @@ -322,7 +322,7 @@ impl LowerModuleCtx<'_> { }; if func.end().is_some() { - let subroutine_loc = InModule::new(self.module_id, subroutine_id); + let subroutine_loc = InModule::new(self.module_id, subroutine_id).into(); let subroutine = &mut self.module.subroutines[subroutine_id]; let mut subroutine_source_map = SubroutineSourceMap::default(); let mut ctx = LowerSubroutineBodyCtx { diff --git a/crates/hir/src/hir_def/subroutine.rs b/crates/hir/src/hir_def/subroutine.rs index fc3cbbba..e80eee32 100644 --- a/crates/hir/src/hir_def/subroutine.rs +++ b/crates/hir/src/hir_def/subroutine.rs @@ -29,7 +29,7 @@ use super::{ typedef::{Typedef, TypedefId, TypedefSrc, lower_typedef_data_ty}, }; use crate::{ - container::{ContainerId, InModule}, + container::{ContainerId, InFile, InModule}, db::{HirDb, InternDb}, define_src, file::HirFileId, @@ -194,10 +194,37 @@ fn map_direction(kind: Option) -> SubroutinePortDir { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SubroutineLocation { + InModule(InModule), + InFile(InFile), +} + +impl From> for SubroutineLocation { + fn from(loc: InModule) -> Self { + SubroutineLocation::InModule(loc) + } +} + +impl From> for SubroutineLocation { + fn from(loc: InFile) -> Self { + SubroutineLocation::InFile(loc) + } +} + +impl From for ContainerId { + fn from(loc: SubroutineLocation) -> ContainerId { + match loc { + SubroutineLocation::InModule(module_loc) => ContainerId::SubroutineId(module_loc), + SubroutineLocation::InFile(file_loc) => ContainerId::FileSubroutineId(file_loc), + } + } +} + pub struct LowerSubroutineBodyCtx<'a> { pub(crate) db: &'a dyn InternDb, pub(crate) file_id: HirFileId, - pub(crate) subroutine_loc: InModule, + pub(crate) subroutine_loc: SubroutineLocation, pub(crate) subroutine: &'a mut Subroutine, pub(crate) subroutine_source_map: &'a mut SubroutineSourceMap, pub(crate) region_tree: RegionTreeBuilder, diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index f898249c..a8a605a6 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -3,11 +3,14 @@ use utils::get::GetRef; use super::{Source2DefCtx, pathres::PathResolution}; use crate::{ - container::{ContainerId, ContainerParent, InBlock, InContainer, InModule}, + container::{ + ContainerId, ContainerParent, InBlock, InContainer, InModule, InPackage, InSubroutine, + }, hir_def::{ Ident, expr::{Expr, ExprId}, }, + scope::PackageEntry, }; #[derive(Default, Debug)] @@ -24,7 +27,36 @@ impl Source2DefCtx<'_, '_> { let db = self.db; let mut resolve = |expr: &Expr| match expr { - Expr::Field { .. } => unimplemented!(), + Expr::Field { receiver, field } => { + let field_ident = field.clone()?; + let receiver_res = self.expr_to_def(InContainer::new(cont_id, *receiver))?; + let res = match receiver_res { + PathResolution::Package(package_id) => { + let package_scope = db.package_scope(package_id); + let entry = package_scope.get(&field_ident)?; + match entry { + PackageEntry::DeclId(in_pkg_decl) => { + PathResolution::Decl(in_pkg_decl.into()) + } + PackageEntry::TypedefId(in_pkg_typedef) => { + PathResolution::Typedef(in_pkg_typedef.into()) + } + PackageEntry::ClassId(in_pkg_class) => { + PathResolution::Class(in_pkg_class.into()) + } + PackageEntry::StructId(_) + | PackageEntry::ProcId(_) + | PackageEntry::SubroutineId(_) => return None, + PackageEntry::Package(in_pkg_pkg) => { + PathResolution::Package(in_pkg_pkg.value) + } + } + } + _ => return None, + }; + self.hir_cache.expr_map.insert(InContainer::new(cont_id, expr_id), res); + Some(res) + } Expr::Ident(ident) => { let res = self.name_to_def(InContainer::new(cont_id, ident.clone()))?; self.hir_cache.expr_map.insert(InContainer::new(cont_id, expr_id), res); @@ -42,10 +74,22 @@ impl Source2DefCtx<'_, '_> { let module = db.module(in_file); resolve(module.get(expr_id)) } + ContainerId::PackageId(package_id) => { + let package = db.package(package_id); + resolve(package.get(expr_id)) + } ContainerId::BlockId(block_id) => { let block = db.block(block_id); resolve(block.get(expr_id)) } + ContainerId::SubroutineId(loc) => { + let subroutine = db.subroutine(loc); + resolve(&subroutine.exprs[expr_id]) + } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(db); + resolve(&subroutine.exprs[expr_id]) + } } } @@ -65,11 +109,25 @@ impl Source2DefCtx<'_, '_> { let entry = scope.get(&ident)?; Some(InModule::new(module_id, entry).into()) } + ContainerId::PackageId(package_id) => { + let scope = db.package_scope(package_id); + let entry = scope.get(&ident)?; + Some(InPackage::new(package_id, entry).into()) + } ContainerId::BlockId(block_id) => { let scope = db.block_scope(block_id); let entry = scope.get(&ident)?; Some(InBlock::new(block_id, entry).into()) } + ContainerId::SubroutineId(loc) => { + let scope = db.subroutine_scope(loc); + let entry = scope.get(&ident)?; + Some(InSubroutine::new(loc, entry).into()) + } + ContainerId::FileSubroutineId(_loc) => { + // TODO: implement file-level subroutine scope lookup + None + } })?; self.hir_cache.name_map.insert(InContainer::new(cont_id, ident), res); Some(res) diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 8f07a2d8..16c0e768 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -8,12 +8,13 @@ use utils::get::{Get, GetRef}; use super::hir_to_def::Hir2DefCache; use crate::{ - container::{ContainerId, InFile}, + container::{ContainerId, InFile, InModule}, db::HirDb, file::HirFileId, hir_def::{ block::{BlockId, BlockSrc}, module::{ModuleId, ModuleSrc}, + subroutine::SubroutineSrc, }, source_map::ToAstNode, }; @@ -68,11 +69,23 @@ impl Source2DefCtx<'_, '_> { let local_block_id = module_src_map.get(block_src); module.get(local_block_id).block_id } + ContainerId::PackageId(_) => return None, ContainerId::BlockId(block_id) => { let (block, block_src_map) = self.db.block_with_source_map(block_id); let local_block_id = block_src_map.get(block_src); block.get(local_block_id).block_id } + ContainerId::SubroutineId(loc) => { + let (subroutine, subroutine_src_map) = self.db.subroutine_with_source_map(loc); + let local_block_id = *subroutine_src_map.block_srcs.get(&block_src)?; + subroutine.stmts.get(local_block_id).block_id + } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(self.db); + let subroutine_src_map = loc.to_container_src_map(self.db); + let local_block_id = *subroutine_src_map.block_srcs.get(&block_src)?; + subroutine.stmts.get(local_block_id).block_id + } }; Some(block_id) @@ -88,6 +101,21 @@ impl Source2DefCtx<'_, '_> { let block_src = BlockSrc::from(block); self.block_to_def_inner(file_id, block, block_src)?.into() }, + ast::FunctionDeclaration[func] => { + let mut ancestors = SyntaxAncestors::start_from(node).skip(1); + let module_id = ancestors.find_map(|ancestor| { + match_ast! { ancestor, + ast::ModuleDeclaration[module] => { + let src = ModuleSrc::from(module); + self.module_to_def(InFile::new(file_id, src)) + }, + _ => None, + } + })?; + let (_, module_src_map) = self.db.module_with_source_map(module_id); + let subroutine_id = module_src_map.get(SubroutineSrc::from(func)); + InModule::new(module_id, subroutine_id).into() + }, ast::CompilationUnit => file_id.into(), _ => return None, }; diff --git a/crates/ide/src/definitions.rs b/crates/ide/src/definitions.rs index ac433601..4a97c502 100644 --- a/crates/ide/src/definitions.rs +++ b/crates/ide/src/definitions.rs @@ -244,6 +244,14 @@ impl Definition { ) } } + PathResolution::Typedef(_) | + PathResolution::Class(_) | + PathResolution::PackageImport(_) | + PathResolution::Package(_) | + PathResolution::Subroutine(_) => { + // TODO: implement definition for these items + unreachable!("Definition navigation not yet supported for this item type") + } } } } diff --git a/crates/ide/src/document_symbols.rs b/crates/ide/src/document_symbols.rs index e93075ca..a4e3725f 100644 --- a/crates/ide/src/document_symbols.rs +++ b/crates/ide/src/document_symbols.rs @@ -191,6 +191,10 @@ pub(crate) fn document_symbols(db: &RootDb, file_id: FileId) -> Vec { build_declaration(&mut collector, declaration_id, file, src_map); } + FileItem::LocalPackageId(_) | FileItem::TypedefId(_) | FileItem::StructId(_) | + FileItem::ClassId(_) | FileItem::PackageImportId(_) | FileItem::SubroutineId(_) => { + // TODO: implement document symbols for these items + } } } @@ -264,6 +268,10 @@ fn collect_module_items( build_decls(collector, &port_decl.decls, SymbolKind::PortDecl, module, src_map) } ModuleItem::ContAssignId(_) => {} + ModuleItem::StructId(_) | ModuleItem::ClassId(_) | ModuleItem::PackageImportId(_) | + ModuleItem::TypedefId(_) | ModuleItem::SubroutineId(_) => { + // TODO: implement document symbols for these items + } } } collector.pop(); @@ -293,6 +301,9 @@ fn collect_block_items( build_declaration(collector, declaration_id, block, src_map) } BlockItem::StmtId(stmt_id) => build_stmt(db, collector, stmt_id, block, src_map), + BlockItem::TypedefId(_) | BlockItem::StructId(_) => { + // TODO: implement document symbols for these items + } } } collector.pop(); diff --git a/crates/ide/src/references/search.rs b/crates/ide/src/references/search.rs index 84cf332d..c7556d40 100644 --- a/crates/ide/src/references/search.rs +++ b/crates/ide/src/references/search.rs @@ -87,6 +87,10 @@ impl SearchScope { let range = block_id.lookup(db).src.value.range(); Self::single_range(block_id.file_id(db), range) } + ContainerId::PackageId(_) | ContainerId::SubroutineId(_) | ContainerId::FileSubroutineId(_) => { + // TODO: implement search scope for these container types + Self::all(db) + } } } From a8cca1b6889efdf5f6934425605a704075684d11 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:59:53 +0800 Subject: [PATCH 05/18] fix(hir): add basic EnumType support in data type lowering --- crates/hir/src/hir_def/expr/data_ty.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/hir/src/hir_def/expr/data_ty.rs b/crates/hir/src/hir_def/expr/data_ty.rs index 0e78b14a..11f20758 100644 --- a/crates/hir/src/hir_def/expr/data_ty.rs +++ b/crates/hir/src/hir_def/expr/data_ty.rs @@ -86,6 +86,7 @@ impl LowerExprCtx<'_> { NamedType(named_type) => Either::Right(self.lower_named_ty(named_type)), IntegerType(ty) => Either::Left(self.lower_integer_type(ty)), ImplicitType(ty) => Either::Left(self.lower_implicit_type(ty)), + EnumType(enum_ty) => Either::Right(self.lower_enum_type(enum_ty)), _ => unimplemented!("{:?}", ty.syntax().kind()), }; match ty { @@ -117,6 +118,14 @@ impl LowerExprCtx<'_> { } } + fn lower_enum_type(&mut self, _enum_ty: ast::EnumType) -> NamedDataTy { + // For now, treat enum types as implicit types + // TODO: properly handle enum member completion + // We return a missing expression since enum types are anonymous + let expr_id = self.alloc_missing(); + NamedDataTy::Ident(expr_id) + } + fn lower_integer_type(&mut self, ty: ast::IntegerType) -> BuiltinDataTy { use ast::IntegerType::*; let kind = match ty { From 0e1d1b600c903f1d8fb61c8cdc6fac8cc6fb7f3f Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 19:42:13 +0800 Subject: [PATCH 06/18] feat(hir): record class base names --- crates/hir/src/hir_def/aggregate.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/hir/src/hir_def/aggregate.rs b/crates/hir/src/hir_def/aggregate.rs index 1802b9ad..246ea68e 100644 --- a/crates/hir/src/hir_def/aggregate.rs +++ b/crates/hir/src/hir_def/aggregate.rs @@ -80,6 +80,15 @@ pub(crate) fn lower_class_def( let name = lower_ident_opt(class_decl.name()); let mut members = SmallVec::<[ClassMember; 4]>::new(); + let base_class_name = class_decl.extends_clause().and_then(|extends| { + let base_name = extends.base_name(); + if let Some(id_name) = base_name.as_identifier_name() { + lower_ident_opt(id_name.identifier()) + } else { + None + } + }); + for item in class_decl.items().children() { match item { ast::Member::ClassPropertyDeclaration(prop) => { @@ -113,7 +122,7 @@ pub(crate) fn lower_class_def( } } - ClassDef { name, members } + ClassDef { name, base_class_name, members } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -167,6 +176,7 @@ pub struct ClassMember { #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClassDef { pub name: Option, + pub base_class_name: Option, pub members: SmallVec<[ClassMember; 4]>, } From 1a75fc2b9187c6ce68337436d8d82b785942573b Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 19:58:55 +0800 Subject: [PATCH 07/18] refactor(hir): remove unused container variants and simplify expression resolution --- crates/hir/src/semantics/hir_to_def.rs | 62 +++----------------------- crates/ide/src/definitions.rs | 10 +---- 2 files changed, 8 insertions(+), 64 deletions(-) diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index a8a605a6..8a293e32 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -3,14 +3,11 @@ use utils::get::GetRef; use super::{Source2DefCtx, pathres::PathResolution}; use crate::{ - container::{ - ContainerId, ContainerParent, InBlock, InContainer, InModule, InPackage, InSubroutine, - }, + container::{ContainerId, ContainerParent, InBlock, InContainer, InModule}, hir_def::{ Ident, expr::{Expr, ExprId}, }, - scope::PackageEntry, }; #[derive(Default, Debug)] @@ -27,36 +24,6 @@ impl Source2DefCtx<'_, '_> { let db = self.db; let mut resolve = |expr: &Expr| match expr { - Expr::Field { receiver, field } => { - let field_ident = field.clone()?; - let receiver_res = self.expr_to_def(InContainer::new(cont_id, *receiver))?; - let res = match receiver_res { - PathResolution::Package(package_id) => { - let package_scope = db.package_scope(package_id); - let entry = package_scope.get(&field_ident)?; - match entry { - PackageEntry::DeclId(in_pkg_decl) => { - PathResolution::Decl(in_pkg_decl.into()) - } - PackageEntry::TypedefId(in_pkg_typedef) => { - PathResolution::Typedef(in_pkg_typedef.into()) - } - PackageEntry::ClassId(in_pkg_class) => { - PathResolution::Class(in_pkg_class.into()) - } - PackageEntry::StructId(_) - | PackageEntry::ProcId(_) - | PackageEntry::SubroutineId(_) => return None, - PackageEntry::Package(in_pkg_pkg) => { - PathResolution::Package(in_pkg_pkg.value) - } - } - } - _ => return None, - }; - self.hir_cache.expr_map.insert(InContainer::new(cont_id, expr_id), res); - Some(res) - } Expr::Ident(ident) => { let res = self.name_to_def(InContainer::new(cont_id, ident.clone()))?; self.hir_cache.expr_map.insert(InContainer::new(cont_id, expr_id), res); @@ -82,14 +49,8 @@ impl Source2DefCtx<'_, '_> { let block = db.block(block_id); resolve(block.get(expr_id)) } - ContainerId::SubroutineId(loc) => { - let subroutine = db.subroutine(loc); - resolve(&subroutine.exprs[expr_id]) - } - ContainerId::FileSubroutineId(loc) => { - let subroutine = loc.to_container(db); - resolve(&subroutine.exprs[expr_id]) - } + ContainerId::SubroutineId(_) => None, + ContainerId::FileSubroutineId(_) => None, } } @@ -109,25 +70,14 @@ impl Source2DefCtx<'_, '_> { let entry = scope.get(&ident)?; Some(InModule::new(module_id, entry).into()) } - ContainerId::PackageId(package_id) => { - let scope = db.package_scope(package_id); - let entry = scope.get(&ident)?; - Some(InPackage::new(package_id, entry).into()) - } ContainerId::BlockId(block_id) => { let scope = db.block_scope(block_id); let entry = scope.get(&ident)?; Some(InBlock::new(block_id, entry).into()) } - ContainerId::SubroutineId(loc) => { - let scope = db.subroutine_scope(loc); - let entry = scope.get(&ident)?; - Some(InSubroutine::new(loc, entry).into()) - } - ContainerId::FileSubroutineId(_loc) => { - // TODO: implement file-level subroutine scope lookup - None - } + ContainerId::PackageId(_) => None, + ContainerId::SubroutineId(_) => None, + ContainerId::FileSubroutineId(_) => None, })?; self.hir_cache.name_map.insert(InContainer::new(cont_id, ident), res); Some(res) diff --git a/crates/ide/src/definitions.rs b/crates/ide/src/definitions.rs index 4a97c502..be90d6b3 100644 --- a/crates/ide/src/definitions.rs +++ b/crates/ide/src/definitions.rs @@ -244,14 +244,8 @@ impl Definition { ) } } - PathResolution::Typedef(_) | - PathResolution::Class(_) | - PathResolution::PackageImport(_) | - PathResolution::Package(_) | - PathResolution::Subroutine(_) => { - // TODO: implement definition for these items - unreachable!("Definition navigation not yet supported for this item type") - } + // Future PathResolution variants not yet handled in this branch. + _ => unreachable!("Definition navigation not yet supported for this item type"), } } } From 9a9fe6160e60b99193dc8fa7210e4038ce2e4084 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:46:31 +0800 Subject: [PATCH 08/18] feat(hir): add semantic completion infrastructure --- crates/hir/src/completion/mod.rs | 82 ++ crates/hir/src/db.rs | 8 +- crates/hir/src/scope.rs | 1601 ++++++++++++++++++++- crates/hir/src/semantics.rs | 34 +- crates/hir/src/semantics/completion.rs | 888 ++++++++++++ crates/hir/src/semantics/hir_to_def.rs | 14 +- crates/hir/src/semantics/pathres.rs | 60 +- crates/hir/src/semantics/source_to_def.rs | 6 - 8 files changed, 2666 insertions(+), 27 deletions(-) create mode 100644 crates/hir/src/completion/mod.rs create mode 100644 crates/hir/src/semantics/completion.rs diff --git a/crates/hir/src/completion/mod.rs b/crates/hir/src/completion/mod.rs new file mode 100644 index 00000000..973d924d --- /dev/null +++ b/crates/hir/src/completion/mod.rs @@ -0,0 +1,82 @@ +use crate::hir_def::Ident; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CompletionEntry { + pub name: Ident, + pub kind: CompletionEntryKind, + pub detail: Option, +} + +impl CompletionEntry { + pub fn new(name: Ident, kind: CompletionEntryKind) -> Self { + Self { name, kind, detail: None } + } + + pub fn with_detail(mut self, detail: impl Into) -> Self { + self.detail = Some(detail.into()); + self + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CompletionEntryKind { + Module, + Port, + Parameter, + Variable, + Net, + Instance, + Block, + Function, + Statement, + Type, + Import, +} + +impl CompletionEntryKind { + pub fn as_str(&self) -> &'static str { + match self { + CompletionEntryKind::Module => "module", + CompletionEntryKind::Port => "port", + CompletionEntryKind::Parameter => "parameter", + CompletionEntryKind::Variable => "variable", + CompletionEntryKind::Net => "net", + CompletionEntryKind::Instance => "instance", + CompletionEntryKind::Block => "block", + CompletionEntryKind::Function => "function", + CompletionEntryKind::Statement => "statement", + CompletionEntryKind::Type => "type", + CompletionEntryKind::Import => "import", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CompletionScope { + Local, + Subroutine, + Module, + Package, + File, + Unit, + Class, +} + +#[derive(Debug, Clone)] +pub struct ScopedCompletionEntry { + pub entry: CompletionEntry, + pub scope: CompletionScope, +} + +#[derive(Debug, Clone)] +pub struct DotField { + pub name: Ident, + pub detail: Option, + pub kind: DotFieldKind, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DotFieldKind { + Field, + Method, +} diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index 38c70b88..e2defdf9 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -15,7 +15,7 @@ use crate::{ package::{self, Package, PackageId, PackageSourceMap}, subroutine::{self, Subroutine, SubroutineId, SubroutineSourceMap}, }, - scope::{BlockScope, ModuleScope, UnitScope}, + scope::{BlockScope, ModuleScope, PackageScope, SubroutineScope, UnitScope}, }; pub(crate) macro impl_intern($id:ident, $loc:ident, $intern:ident, $lookup:ident) { @@ -83,8 +83,14 @@ pub trait HirDb: InternDb { #[salsa::invoke(ModuleScope::module_scope_query)] fn module_scope(&self, module_id: ModuleId) -> Arc; + #[salsa::invoke(PackageScope::package_scope_query)] + fn package_scope(&self, package_id: PackageId) -> Arc; + #[salsa::invoke(BlockScope::block_scope_query)] fn block_scope(&self, block_id: BlockId) -> Arc; + + #[salsa::invoke(SubroutineScope::subroutine_scope_query)] + fn subroutine_scope(&self, subroutine: InModule) -> Arc; } fn parse(db: &dyn HirDb, file_id: HirFileId) -> SyntaxTree { diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 7c7e6374..70c94e02 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -1,29 +1,45 @@ use rustc_hash::FxHashMap; +use smol_str::SmolStr; use triomphe::Arc; -use utils::define_enum_deriving_from; +use utils::{define_enum_deriving_from, get::GetRef}; use crate::{ - container::InFile, + completion::{CompletionEntry, CompletionEntryKind}, + container::{ContainerId, InContainer, InFile, InModule, InPackage}, db::HirDb, + display::HirDisplay, file::HirFileId, hir_def::{ Ident, - block::{BlockId, BlockInfo}, - expr::declarator::{DeclId, DeclaratorParent}, + aggregate::{ClassId, StructId}, + block::{Block, BlockId, BlockInfo}, + declaration::Declaration, + expr::{ + data_ty::DataTy, + declarator::{DeclId, DeclaratorParent}, + }, module::{ - ModuleId, + Module, ModuleId, instantiation::InstanceId, - port::{NonAnsiPortId, Ports}, + port::{NonAnsiPortId, PortDirection, PortHeader, Ports}, }, + package::{Package, PackageExport, PackageId, PackageImportId, PackageImportMember}, + proc::ProcId, stmt::{StmtId, StmtKind}, + subroutine::{Subroutine, SubroutineId, SubroutineKind}, + ty::NetKind, + typedef::{Typedef, TypedefId}, }, }; define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum UnitEntry { - ModuleId, - FiledDeclId, + ModuleId(ModuleId), + PackageId(PackageId), + FiledDeclId(FiledDeclId), + TypedefId(InFile), + ClassId(InFile), } } @@ -32,12 +48,17 @@ pub type FiledDeclId = InFile; define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum ModuleEntry { - DeclId, - NonAnsiPortEntry, - AnsiPortEntry, - InstanceId, - StmtId, - BlockId, + DeclId(DeclId), + NonAnsiPortEntry(NonAnsiPortEntry), + AnsiPortEntry(AnsiPortEntry), + InstanceId(InstanceId), + StmtId(StmtId), + BlockId(BlockId), + TypedefId(TypedefId), + ClassId(ClassId), + PackageImportEntry(PackageImportEntry), + PackageMember(PackageEntry), + SubroutineId(SubroutineId), } } @@ -52,12 +73,42 @@ pub struct NonAnsiPortEntry { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct AnsiPortEntry(pub DeclId); +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct PackageImportEntry { + pub import: PackageImportId, + pub item_idx: u32, +} + define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum BlockEntry { StmtId, DeclId, BlockId, + TypedefId, + } +} + +define_enum_deriving_from! { + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] + pub enum SubroutineEntry { + StmtId, + DeclId, + BlockId, + TypedefId, + } +} + +define_enum_deriving_from! { + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] + pub enum PackageEntry { + DeclId(InPackage), + TypedefId(InPackage), + ClassId(InPackage), + StructId(InPackage), + ProcId(InPackage), + SubroutineId(InPackage), + Package(InPackage), } } @@ -88,11 +139,17 @@ impl Scope { pub(crate) fn get_mut(&mut self, ident: &Ident) -> Option<&mut Entry> { self.entries.get_mut(ident) } + + pub fn iter(&self) -> impl Iterator { + self.entries.iter() + } } pub type UnitScope = Scope; pub type ModuleScope = Scope; +pub type PackageScope = Scope; pub type BlockScope = Scope; +pub type SubroutineScope = Scope; // TODO: diagnostics @@ -117,12 +174,68 @@ impl UnitScope { scope.insert_opt(&module_info.name, InFile::new(file_id, module_id).into()); } + for (package_id, package_info) in hir_file.packages.iter() { + scope.insert_opt(&package_info.name, InFile::new(file_id, package_id).into()); + } + for (decl_id, decl) in hir_file.decls.iter() { scope.insert_opt(&decl.name, InFile::new(file_id, decl_id).into()); } + for (typedef_id, typedef_) in hir_file.typedefs.iter() { + scope.insert_opt(&typedef_.name, InFile::new(file_id, typedef_id).into()); + } + + for (class_id, class_) in hir_file.classes.iter() { + scope.insert_opt(&class_.name, InFile::new(file_id, class_id).into()); + } + Arc::new(scope) } + + pub fn collect_completions(&self, db: &dyn HirDb) -> Vec { + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + match entry { + UnitEntry::ModuleId(_) => { + items.push(make_entry(ident, CompletionEntryKind::Module)); + } + UnitEntry::PackageId(_) => { + items.push(make_entry(ident, CompletionEntryKind::Module)); + } + UnitEntry::FiledDeclId(in_file) => { + let file = db.hir_file(in_file.file_id); + let declarator = file.decls.get(in_file.value); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match file.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = file_decl_detail(db, in_file.file_id, &file, in_file.value); + items.push(make_entry_with_detail(ident, kind, detail)); + } + UnitEntry::TypedefId(in_file) => { + let file = db.hir_file(in_file.file_id); + let typedef = file.typedefs.get(in_file.value); + let detail = + typedef_detail(db, ContainerId::HirFileId(in_file.file_id), typedef); + items.push(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)); + } + UnitEntry::ClassId(_) => { + items.push(make_entry(ident, CompletionEntryKind::Type)); + } + } + } + + items + } } impl ModuleScope { @@ -181,8 +294,205 @@ impl ModuleScope { } } + for (typedef_id, typedef_) in module.typedefs.iter() { + scope.insert_opt(&typedef_.name, typedef_id.into()); + } + + for (class_id, class_) in module.classes.iter() { + scope.insert_opt(&class_.name, class_id.into()); + } + + for (sub_id, subroutine) in module.subroutines.iter() { + scope.insert_opt(&subroutine.name, sub_id.into()); + } + + let packages_by_name = db.packages_by_name(); + let mut package_scope_cache: FxHashMap> = FxHashMap::default(); + let mut get_package_scope = |pkg_id: PackageId| { + package_scope_cache.entry(pkg_id).or_insert_with(|| db.package_scope(pkg_id)).clone() + }; + + for (import_id, import) in module.package_imports.iter() { + for (idx, item) in import.items.iter().enumerate() { + let entry = PackageImportEntry { import: import_id, item_idx: idx as u32 }; + let entry_name: Ident = match &item.member { + PackageImportMember::Named(name) => name.clone(), + PackageImportMember::All => { + SmolStr::from(format!("{}::*", item.package.as_str())) + } + }; + scope.insert(&entry_name, entry.into()); + + if let Some(target_packages) = packages_by_name.get(&item.package) { + for target_pkg in target_packages { + match &item.member { + PackageImportMember::All => { + let pkg_scope = get_package_scope(*target_pkg); + for (target_ident, &pkg_entry) in pkg_scope.iter() { + if scope.get(target_ident).is_none() { + scope.insert(target_ident, pkg_entry.into()); + } + } + } + PackageImportMember::Named(name) => { + if scope.get(name).is_none() { + let pkg_scope = get_package_scope(*target_pkg); + if let Some(pkg_entry) = pkg_scope.get(name) { + scope.insert(name, pkg_entry.into()); + break; + } + } + } + } + } + } + } + } + Arc::new(scope) } + + pub fn collect_completions(&self, db: &dyn HirDb, module_id: ModuleId) -> Vec { + let module = db.module(module_id); + let mut package_cache: FxHashMap> = FxHashMap::default(); + let mut get_package = |pkg_id: PackageId| { + package_cache.entry(pkg_id).or_insert_with(|| db.package(pkg_id)).clone() + }; + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + let completion = match entry { + ModuleEntry::DeclId(decl_id) => { + let declarator = module.decls.get(*decl_id); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match module.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = module_decl_detail(db, module_id, &module, *decl_id); + Some(make_entry_with_detail(ident, kind, detail)) + } + ModuleEntry::NonAnsiPortEntry(port_entry) => { + let detail = non_ansi_port_detail(db, module_id, &module, port_entry); + Some(make_entry_with_detail(ident, CompletionEntryKind::Port, detail)) + } + ModuleEntry::AnsiPortEntry(AnsiPortEntry(decl_id)) => { + let detail = module_port_detail(db, module_id, &module, *decl_id); + Some(make_entry_with_detail(ident, CompletionEntryKind::Port, detail)) + } + ModuleEntry::InstanceId(_) => { + Some(make_entry(ident, CompletionEntryKind::Instance)) + } + ModuleEntry::StmtId(_) => Some(make_entry(ident, CompletionEntryKind::Statement)), + ModuleEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), + ModuleEntry::TypedefId(typedef_id) => { + let typedef = module.typedefs.get(*typedef_id); + let detail = typedef_detail(db, ContainerId::ModuleId(module_id), typedef); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + ModuleEntry::ClassId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + ModuleEntry::PackageImportEntry(entry) => { + let import = module.package_imports.get(entry.import); + if let Some(item) = import.items.get(entry.item_idx as usize) { + let detail = match &item.member { + PackageImportMember::Named(name) => { + format!("import {}::{}", item.package, name) + } + PackageImportMember::All => { + format!("import {}::*", item.package) + } + }; + Some(make_entry_with_detail( + ident, + CompletionEntryKind::Import, + Some(detail), + )) + } else { + Some(make_entry(ident, CompletionEntryKind::Import)) + } + } + ModuleEntry::SubroutineId(sub_id) => { + let sub = module.subroutines.get(*sub_id); + let detail = match sub.kind { + SubroutineKind::Task => "task".to_string(), + SubroutineKind::Function { .. } => "function".to_string(), + }; + Some(make_entry_with_detail(ident, CompletionEntryKind::Function, Some(detail))) + } + ModuleEntry::PackageMember(pkg_entry) => match pkg_entry { + PackageEntry::DeclId(in_pkg_decl) => { + let package = get_package(in_pkg_decl.package_id); + let declarator = package.decls.get(in_pkg_decl.value); + let (kind, base_detail) = match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = package.declarations.get(declaration_id); + let kind = match declaration { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + }; + let detail = declaration_detail( + db, + ContainerId::PackageId(in_pkg_decl.package_id), + declaration, + ); + (kind, detail) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => { + (CompletionEntryKind::Variable, None) + } + }; + Some(make_entry_with_detail(ident, kind, base_detail)) + } + PackageEntry::TypedefId(in_pkg_typedef) => { + let package = get_package(in_pkg_typedef.package_id); + let typedef = package.typedefs.get(in_pkg_typedef.value); + let detail = typedef_detail( + db, + ContainerId::PackageId(in_pkg_typedef.package_id), + typedef, + ); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + PackageEntry::ClassId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + PackageEntry::StructId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + PackageEntry::ProcId(_) => { + Some(make_entry(ident, CompletionEntryKind::Function)) + } + PackageEntry::SubroutineId(in_pkg_sub) => { + let package = get_package(in_pkg_sub.package_id); + let sub = package.subroutines.get(in_pkg_sub.value); + let detail = match sub.kind { + SubroutineKind::Task => "task".to_string(), + SubroutineKind::Function { .. } => "function".to_string(), + }; + Some(make_entry_with_detail( + ident, + CompletionEntryKind::Function, + Some(detail), + )) + } + PackageEntry::Package(_) => Some(make_entry_with_detail( + ident, + CompletionEntryKind::Module, + Some(String::from("package")), + )), + }, + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } } impl BlockScope { @@ -202,6 +512,1269 @@ impl BlockScope { } } + for (typedef_id, typedef_) in block.typedefs.iter() { + scope.insert_opt(&typedef_.name, typedef_id.into()); + } + + Arc::new(scope) + } + + pub fn collect_completions(&self, db: &dyn HirDb, block_id: BlockId) -> Vec { + let block = db.block(block_id); + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + let completion = match entry { + BlockEntry::DeclId(decl_id) => { + let declarator = block.decls.get(*decl_id); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match block.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = block_decl_detail(db, block_id, &block, *decl_id); + Some(make_entry_with_detail(ident, kind, detail)) + } + BlockEntry::StmtId(_) => Some(make_entry(ident, CompletionEntryKind::Statement)), + BlockEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), + BlockEntry::TypedefId(typedef_id) => { + let typedef = block.typedefs.get(*typedef_id); + let detail = typedef_detail(db, ContainerId::BlockId(block_id), typedef); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } +} + +impl SubroutineScope { + pub fn subroutine_scope_query( + db: &dyn HirDb, + subroutine_loc: InModule, + ) -> Arc { + let mut scope = Scope::default(); + let subroutine = db.subroutine(subroutine_loc); + + if !subroutine.has_body { + return Arc::new(scope); + } + + for (decl_id, decl) in subroutine.decls.iter() { + scope.insert_opt(&decl.name, decl_id.into()); + } + + for (stmt_id, stmt) in subroutine.stmts.iter() { + scope.insert_opt(&stmt.label, stmt_id.into()); + + if let StmtKind::Block(BlockInfo { name, block_id }) = &stmt.kind { + scope.insert_opt(name, (*block_id).into()); + } + } + + for (typedef_id, typedef_) in subroutine.typedefs.iter() { + scope.insert_opt(&typedef_.name, typedef_id.into()); + } + + Arc::new(scope) + } + + pub fn collect_completions( + &self, + db: &dyn HirDb, + subroutine_loc: InModule, + ) -> Vec { + let subroutine = db.subroutine(subroutine_loc); + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + let completion = match entry { + SubroutineEntry::DeclId(decl_id) => { + let declarator = subroutine.decls.get(*decl_id); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match subroutine.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = subroutine_decl_detail(db, subroutine_loc, &subroutine, *decl_id); + Some(make_entry_with_detail(ident, kind, detail)) + } + SubroutineEntry::StmtId(_) => { + Some(make_entry(ident, CompletionEntryKind::Statement)) + } + SubroutineEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), + SubroutineEntry::TypedefId(typedef_id) => { + let typedef = subroutine.typedefs.get(*typedef_id); + let detail = + typedef_detail(db, ContainerId::SubroutineId(subroutine_loc), typedef); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } +} + +fn make_entry(ident: &Ident, kind: CompletionEntryKind) -> CompletionEntry { + make_entry_with_detail(ident, kind, None) +} + +fn make_entry_with_detail( + ident: &Ident, + kind: CompletionEntryKind, + detail: Option, +) -> CompletionEntry { + match detail { + Some(detail) => CompletionEntry::new(ident.clone(), kind).with_detail(detail), + None => CompletionEntry::new(ident.clone(), kind).with_detail(kind.as_str()), + } +} + +fn data_ty_signature(db: &dyn HirDb, container_id: ContainerId, ty: DataTy) -> Option { + InContainer::new(container_id, ty).display_signature(db).ok() +} + +fn typedef_detail(db: &dyn HirDb, container_id: ContainerId, typedef: &Typedef) -> Option { + typedef.ty.and_then(|ty| data_ty_signature(db, container_id, ty)) +} + +fn port_direction_str(dir: PortDirection) -> &'static str { + match dir { + PortDirection::Input => "input", + PortDirection::Output => "output", + PortDirection::Ref => "ref", + PortDirection::Inout => "inout", + } +} + +fn net_kind_str(kind: NetKind) -> &'static str { + match kind { + NetKind::Supply0 => "supply0", + NetKind::Supply1 => "supply1", + NetKind::Tri => "tri", + NetKind::Triand => "triand", + NetKind::Trior => "trior", + NetKind::Tri0 => "tri0", + NetKind::Tri1 => "tri1", + NetKind::Wire => "wire", + NetKind::Wand => "wand", + NetKind::Wor => "wor", + NetKind::Uwire => "uwire", + } +} + +fn port_header_detail(db: &dyn HirDb, module_id: ModuleId, header: &PortHeader) -> Option { + let mut parts: Vec = Vec::new(); + + if let Some(dir) = header.dir() { + parts.push(port_direction_str(dir).to_string()); + } + + match header { + PortHeader::Var { var_kw, ty, .. } => { + if *var_kw { + parts.push("var".to_string()); + } + if let Some(sig) = data_ty_signature(db, ContainerId::ModuleId(module_id), *ty) { + parts.push(sig); + } + } + PortHeader::Net { net_ty, .. } => { + parts.push(net_kind_str(net_ty.kind).to_string()); + if let Some(sig) = data_ty_signature(db, ContainerId::ModuleId(module_id), net_ty.ty) { + parts.push(sig); + } + } + } + + if parts.is_empty() { None } else { Some(parts.join(" ")) } +} + +fn module_port_detail( + db: &dyn HirDb, + module_id: ModuleId, + module: &Module, + decl_id: DeclId, +) -> Option { + let declarator = module.decls.get(decl_id); + let DeclaratorParent::PortDeclId(port_decl_id) = declarator.parent else { + return None; + }; + let port_decl = module.ports.get(port_decl_id); + port_header_detail(db, module_id, &port_decl.header) +} + +fn module_decl_detail( + db: &dyn HirDb, + module_id: ModuleId, + module: &Module, + decl_id: DeclId, +) -> Option { + let declarator = module.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::PortDeclId(_) => module_port_detail(db, module_id, module, decl_id), + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = module.declarations.get(declaration_id); + declaration_detail(db, ContainerId::ModuleId(module_id), declaration) + } + DeclaratorParent::StmtId(_) => None, + } +} + +fn non_ansi_port_detail( + db: &dyn HirDb, + module_id: ModuleId, + module: &Module, + entry: &NonAnsiPortEntry, +) -> Option { + if let Some(port_decl) = entry.port_decl + && let Some(detail) = module_port_detail(db, module_id, module, port_decl) + { + return Some(detail); + } + + entry.data_decl.and_then(|decl_id| module_decl_detail(db, module_id, module, decl_id)) +} + +fn declaration_detail( + db: &dyn HirDb, + container_id: ContainerId, + declaration: &Declaration, +) -> Option { + use Declaration::*; + match declaration { + DataDecl(data_decl) => { + let mut parts = Vec::new(); + if data_decl.const_kw { + parts.push("const".to_string()); + } + if data_decl.var_kw { + parts.push("var".to_string()); + } + if let Some(sig) = data_ty_signature(db, container_id, data_decl.ty) { + parts.push(sig); + } + if parts.is_empty() { None } else { Some(parts.join(" ")) } + } + NetDecl(net_decl) => { + let mut parts = Vec::new(); + if let Some(kind) = net_decl.net_kind { + parts.push(net_kind_str(kind).to_string()); + } + if let Some(sig) = data_ty_signature(db, container_id, net_decl.ty) { + parts.push(sig); + } + if parts.is_empty() { None } else { Some(parts.join(" ")) } + } + ParamDecl(param_decl) => data_ty_signature(db, container_id, param_decl.ty), + } +} + +fn block_decl_detail( + db: &dyn HirDb, + block_id: BlockId, + block: &Block, + decl_id: DeclId, +) -> Option { + let declarator = block.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = block.declarations.get(declaration_id); + declaration_detail(db, ContainerId::BlockId(block_id), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} + +fn subroutine_decl_detail( + db: &dyn HirDb, + loc: InModule, + subroutine: &Subroutine, + decl_id: DeclId, +) -> Option { + let declarator = subroutine.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = subroutine.declarations.get(declaration_id); + declaration_detail(db, ContainerId::SubroutineId(loc), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} + +fn package_decl_detail( + db: &dyn HirDb, + package_id: PackageId, + package: &Package, + decl_id: DeclId, +) -> Option { + let declarator = package.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = package.declarations.get(declaration_id); + declaration_detail(db, ContainerId::PackageId(package_id), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} + +fn file_decl_detail( + db: &dyn HirDb, + file_id: HirFileId, + file: &crate::hir_def::file::HirFile, + decl_id: DeclId, +) -> Option { + let declarator = file.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = file.declarations.get(declaration_id); + declaration_detail(db, ContainerId::HirFileId(file_id), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} + +impl PackageScope { + pub fn package_scope_query(db: &dyn HirDb, package_id: PackageId) -> Arc { + let mut scope = Scope::default(); + let package = db.package(package_id); + + for (decl_id, decl) in package.decls.iter() { + scope.insert_opt(&decl.name, InPackage::new(package_id, decl_id).into()); + } + + for (typedef_id, typedef_) in package.typedefs.iter() { + scope.insert_opt(&typedef_.name, InPackage::new(package_id, typedef_id).into()); + } + + for (class_id, class_) in package.classes.iter() { + scope.insert_opt(&class_.name, InPackage::new(package_id, class_id).into()); + } + + for (struct_id, struct_) in package.structs.iter() { + scope.insert_opt(&struct_.name, InPackage::new(package_id, struct_id).into()); + } + + for (sub_id, subroutine) in package.subroutines.iter() { + scope.insert_opt(&subroutine.name, InPackage::new(package_id, sub_id).into()); + } + + let file = db.hir_file(package_id.file_id); + for (local_pkg_id, pkg_info) in file.packages.iter() { + if pkg_info.parent == Some(package_id.value) { + scope.insert_opt( + &pkg_info.name, + InPackage::new(package_id, InFile::new(package_id.file_id, local_pkg_id)) + .into(), + ); + } + } + + let packages_by_name = db.packages_by_name(); + let mut package_cache: FxHashMap> = FxHashMap::default(); + + let mut get_package = |pkg_id: PackageId| { + package_cache.entry(pkg_id).or_insert_with(|| db.package(pkg_id)).clone() + }; + + for (_, export) in package.exports.iter() { + match export { + PackageExport::All => { + // Re-exporting the current package does not introduce new + // entries. + } + PackageExport::Items(items) => { + for item in items { + let Some(target_packages) = packages_by_name.get(&item.package) else { + continue; + }; + + match &item.member { + PackageImportMember::All => { + for target_pkg_id in target_packages { + let target_package = get_package(*target_pkg_id); + + for (decl_id, decl) in target_package.decls.iter() { + if let Some(name) = &decl.name + && scope.get(name).is_none() + { + scope.insert( + name, + InPackage::new(*target_pkg_id, decl_id).into(), + ); + } + } + + for (typedef_id, typedef_) in target_package.typedefs.iter() { + if let Some(name) = &typedef_.name + && scope.get(name).is_none() + { + scope.insert( + name, + InPackage::new(*target_pkg_id, typedef_id).into(), + ); + } + } + + for (class_id, class_) in target_package.classes.iter() { + if let Some(name) = &class_.name + && scope.get(name).is_none() + { + scope.insert( + name, + InPackage::new(*target_pkg_id, class_id).into(), + ); + } + } + + for (struct_id, struct_) in target_package.structs.iter() { + if let Some(name) = &struct_.name + && scope.get(name).is_none() + { + scope.insert( + name, + InPackage::new(*target_pkg_id, struct_id).into(), + ); + } + } + + for (sub_id, subroutine) in target_package.subroutines.iter() { + if let Some(name) = &subroutine.name + && scope.get(name).is_none() + { + scope.insert( + name, + InPackage::new(*target_pkg_id, sub_id).into(), + ); + } + } + } + } + PackageImportMember::Named(name) => { + if scope.get(name).is_some() { + continue; + } + + let mut found_entry = None; + + for target_pkg_id in target_packages { + let target_package = get_package(*target_pkg_id); + + found_entry = target_package + .decls + .iter() + .find_map(|(decl_id, decl)| { + (decl.name.as_ref() == Some(name)).then_some( + InPackage::new(*target_pkg_id, decl_id).into(), + ) + }) + .or_else(|| { + target_package.typedefs.iter().find_map( + |(typedef_id, ty)| { + ty.name + .as_ref() + .filter(|ident| *ident == name) + .map(|_| { + InPackage::new( + *target_pkg_id, + typedef_id, + ) + .into() + }) + }, + ) + }) + .or_else(|| { + target_package.classes.iter().find_map( + |(class_id, class_)| { + class_ + .name + .as_ref() + .filter(|ident| *ident == name) + .map(|_| { + InPackage::new(*target_pkg_id, class_id) + .into() + }) + }, + ) + }) + .or_else(|| { + target_package.structs.iter().find_map( + |(struct_id, struct_)| { + struct_ + .name + .as_ref() + .filter(|ident| *ident == name) + .map(|_| { + InPackage::new( + *target_pkg_id, + struct_id, + ) + .into() + }) + }, + ) + }) + .or_else(|| { + target_package.subroutines.iter().find_map( + |(sub_id, subroutine)| { + subroutine + .name + .as_ref() + .filter(|ident| *ident == name) + .map(|_| { + InPackage::new(*target_pkg_id, sub_id) + .into() + }) + }, + ) + }); + + if found_entry.is_some() { + break; + } + } + + if let Some(entry) = found_entry { + scope.insert(name, entry); + } + } + } + } + } + } + } + Arc::new(scope) } + + pub fn collect_completions( + &self, + db: &dyn HirDb, + package_id: PackageId, + ) -> Vec { + let mut items = Vec::new(); + let mut package_cache: FxHashMap> = FxHashMap::default(); + + let mut get_package = |pkg_id: PackageId| { + package_cache.entry(pkg_id).or_insert_with(|| db.package(pkg_id)).clone() + }; + + // Ensure the current package is cached for direct lookups. + let _ = get_package(package_id); + + for (ident, entry) in self.iter() { + let completion = match entry { + PackageEntry::DeclId(in_pkg_decl) => { + let pkg = get_package(in_pkg_decl.package_id); + let declarator = pkg.decls.get(in_pkg_decl.value); + let (kind, detail) = match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = pkg.declarations.get(declaration_id); + let kind = match declaration { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + }; + let detail = declaration_detail( + db, + ContainerId::PackageId(in_pkg_decl.package_id), + declaration, + ); + (kind, detail) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => { + (CompletionEntryKind::Variable, None) + } + }; + Some(make_entry_with_detail(ident, kind, detail)) + } + PackageEntry::TypedefId(in_pkg_typedef) => { + let pkg = get_package(in_pkg_typedef.package_id); + let typedef = pkg.typedefs.get(in_pkg_typedef.value); + let detail = typedef_detail( + db, + ContainerId::PackageId(in_pkg_typedef.package_id), + typedef, + ); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + PackageEntry::ClassId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + PackageEntry::StructId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + PackageEntry::ProcId(_) => Some(make_entry(ident, CompletionEntryKind::Function)), + PackageEntry::SubroutineId(in_pkg_sub) => { + let pkg = get_package(in_pkg_sub.package_id); + let sub = pkg.subroutines.get(in_pkg_sub.value); + let detail = match sub.kind { + SubroutineKind::Task => "task".to_string(), + SubroutineKind::Function { .. } => "function".to_string(), + }; + Some(make_entry_with_detail(ident, CompletionEntryKind::Function, Some(detail))) + } + PackageEntry::Package(_) => Some(make_entry_with_detail( + ident, + CompletionEntryKind::Module, + Some(String::from("package")), + )), + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } +} + +#[cfg(test)] +mod tests { + use std::fmt; + + use base_db::{ + salsa, + source_db::{FileLoader, SourceDb, SourceRootDb}, + source_root::{SourceRoot, SourceRootId}, + }; + use rustc_hash::FxHashSet; + use smol_str::SmolStr; + use triomphe::Arc; + use vfs::{FileId, FileSet, VfsPath, anchored_path::AnchoredPath}; + + use super::{BlockEntry, ModuleEntry, ModuleScope, PackageEntry, UnitEntry}; + use crate::{ + CompletionEntryKind, + container::{InFile, InPackage}, + db::HirDb, + file::HirFileId, + hir_def::module::ModuleId, + }; + + #[salsa::database( + base_db::source_db::SourceDbStorage, + base_db::source_db::SourceRootDbStorage, + crate::db::InternDbStorage, + crate::db::HirDbStorage + )] + struct TestDb { + storage: salsa::Storage, + } + + impl Default for TestDb { + fn default() -> Self { + TestDb { storage: salsa::Storage::default() } + } + } + + impl fmt::Debug for TestDb { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TestDb").finish() + } + } + + impl salsa::Database for TestDb {} + + impl FileLoader for TestDb { + fn resolve_path(&self, _path: AnchoredPath<'_>) -> Option { + None + } + } + + fn setup_db(text: &str) -> (TestDb, HirFileId) { + let mut db = TestDb::default(); + + let file_id = FileId(0); + + let mut files = FxHashSet::default(); + files.insert(file_id); + db.set_files(Box::new(files)); + + db.set_file_text(file_id, Arc::from(text.to_string())); + + let mut file_set = FileSet::default(); + file_set.insert(file_id, VfsPath::new_virtual_path("/test.sv".into())); + let source_root = SourceRoot::new_local(file_set); + let source_root_id = SourceRootId(0); + db.set_source_root_id(file_id, source_root_id); + db.set_source_root(source_root_id, Arc::new(source_root)); + + (db, HirFileId(file_id)) + } + + fn setup_multi_file_db(files: &[(&str, &str)]) -> (TestDb, Vec) { + let mut db = TestDb::default(); + + let mut file_ids = Vec::with_capacity(files.len()); + let mut files_set = FxHashSet::default(); + + for (idx, _) in files.iter().enumerate() { + let file_id = FileId(idx as u32); + file_ids.push(HirFileId(file_id)); + files_set.insert(file_id); + } + + db.set_files(Box::new(files_set)); + + let mut file_set = FileSet::default(); + let source_root_id = SourceRootId(0); + + for (idx, (path, text)) in files.iter().enumerate() { + let file_id = FileId(idx as u32); + db.set_file_text(file_id, Arc::from((*text).to_string())); + db.set_source_root_id(file_id, source_root_id); + file_set.insert(file_id, VfsPath::new_virtual_path((*path).into())); + } + + let source_root = SourceRoot::new_local(file_set); + db.set_source_root(source_root_id, Arc::new(source_root)); + + (db, file_ids) + } + + fn module_scope(db: &TestDb, file_id: HirFileId, name: &str) -> (ModuleId, Arc) { + let scope = HirDb::file_scope(db, file_id); + let entry = scope.get(&SmolStr::new(name)).expect("module is present in unit scope"); + let module_id = match entry { + UnitEntry::ModuleId(module_id) => module_id, + _ => panic!("expected module entry"), + }; + (module_id, HirDb::module_scope(db, module_id)) + } + + #[test] + fn unit_scope_contains_modules_and_file_declarations() { + let text = r#" +module leaf(); +endmodule + +module top(); +endmodule + +wire global_wire; +"#; + + let (db, file_id) = setup_db(text); + let scope = HirDb::file_scope(&db, file_id); + + let leaf_entry = scope.get(&SmolStr::new("leaf")).expect("leaf present"); + let leaf_module_id = match leaf_entry { + UnitEntry::ModuleId(module_id) => module_id, + _ => panic!("expected module"), + }; + assert_eq!(leaf_module_id.file_id(), file_id.file_id()); + + let global_entry = scope.get(&SmolStr::new("global_wire")).expect("global wire present"); + match global_entry { + UnitEntry::FiledDeclId(InFile { file_id: decl_file, .. }) => { + assert_eq!(decl_file, file_id); + } + _ => panic!("expected file-level declaration"), + } + + let completions = scope.collect_completions(&db); + let labels: Vec<_> = completions.iter().map(|entry| entry.name.clone()).collect(); + assert!(labels.contains(&SmolStr::new("leaf"))); + assert!(labels.contains(&SmolStr::new("top"))); + assert!(labels.contains(&SmolStr::new("global_wire"))); + } + + #[test] + fn unit_scope_includes_top_level_classes() { + let text = r#" +class pkt; + int data; +endclass + +module top; +endmodule +"#; + + let (db, file_id) = setup_db(text); + let scope = HirDb::file_scope(&db, file_id); + + let class_entry = scope.get(&SmolStr::new("pkt")).expect("class present"); + assert!(matches!(class_entry, UnitEntry::ClassId(_))); + + let completions = scope.collect_completions(&db); + let mut seen_class = false; + for entry in completions { + if entry.name.as_str() == "pkt" { + assert_eq!(entry.kind, CompletionEntryKind::Type); + seen_class = true; + } + } + assert!(seen_class, "expected class completion"); + } + + #[test] + fn module_scope_classifies_members() { + let text = r#" +module leaf(); +endmodule + +module non_ansi(clk); + input logic clk; + logic clk; +endmodule + +module top( + input logic a, + output logic b +); + non_ansi u0(.clk(a)); + logic data; + + import pkg::foo; + + class my_class; + int value; + endclass + + initial begin : init_blk + logic inner; + nested_stmt: inner = 1'b0; + begin : nested_blk + end + end +endmodule +"#; + + let (db, file_id) = setup_db(text); + + let (_, non_ansi_scope) = module_scope(&db, file_id, "non_ansi"); + + let clk_entry = non_ansi_scope.get(&SmolStr::new("clk")).expect("clk in scope"); + match clk_entry { + ModuleEntry::NonAnsiPortEntry(entry) => { + assert!(entry.label.is_some(), "non-ANSI port keeps label"); + assert!(entry.port_decl.is_some(), "non-ANSI port tracks port decl"); + assert!(entry.data_decl.is_some(), "non-ANSI port tracks data decl"); + } + _ => panic!("expected non-ANSI port entry"), + } + + let (top_module_id, top_scope) = module_scope(&db, file_id, "top"); + + for name in ["a", "b"] { + let entry = top_scope.get(&SmolStr::new(name)).expect("ANSI port present"); + assert!(matches!(entry, ModuleEntry::AnsiPortEntry(_)), "expected ANSI port entry"); + } + + let data_entry = top_scope.get(&SmolStr::new("data")).expect("module decl"); + assert!(matches!(data_entry, ModuleEntry::DeclId(_))); + + let instance_entry = top_scope.get(&SmolStr::new("u0")).expect("instance present"); + assert!(matches!(instance_entry, ModuleEntry::InstanceId(_))); + + let init_entry = top_scope.get(&SmolStr::new("init_blk")).expect("block in module scope"); + let init_block_id = match init_entry { + ModuleEntry::BlockId(block_id) => block_id, + _ => panic!("expected block id"), + }; + + let class_entry = top_scope.get(&SmolStr::new("my_class")).expect("class present"); + assert!(matches!(class_entry, ModuleEntry::ClassId(_))); + + let import_entry = top_scope.get(&SmolStr::new("foo")).expect("import present"); + match import_entry { + ModuleEntry::PackageImportEntry(entry) => { + assert_eq!(entry.item_idx, 0); + } + _ => panic!("expected package import entry"), + } + + assert!( + top_scope.get(&SmolStr::new("nested_stmt")).is_none(), + "statement labels stay within block scope" + ); + + let block_scope = HirDb::block_scope(&db, init_block_id); + + let inner_decl = block_scope.get(&SmolStr::new("inner")).expect("block declaration"); + assert!(matches!(inner_decl, BlockEntry::DeclId(_))); + + let nested_stmt = block_scope.get(&SmolStr::new("nested_stmt")).expect("stmt label"); + assert!(matches!(nested_stmt, BlockEntry::StmtId(_))); + + let nested_block = block_scope.get(&SmolStr::new("nested_blk")).expect("nested block"); + assert!(matches!(nested_block, BlockEntry::BlockId(_))); + + let module_items = top_scope.collect_completions(&db, top_module_id); + let labels: Vec<_> = module_items.iter().map(|entry| entry.name.clone()).collect(); + assert!(labels.contains(&SmolStr::new("data"))); + assert!(labels.contains(&SmolStr::new("u0"))); + assert!(labels.contains(&SmolStr::new("init_blk"))); + assert!(labels.contains(&SmolStr::new("my_class"))); + assert!(labels.contains(&SmolStr::new("foo"))); + + let import_completion = module_items + .iter() + .find(|entry| entry.name == SmolStr::new("foo")) + .expect("import completion"); + assert_eq!(import_completion.kind, CompletionEntryKind::Import); + + let block_items = block_scope.collect_completions(&db, init_block_id); + let block_labels: Vec<_> = block_items.iter().map(|entry| entry.name.clone()).collect(); + assert!(block_labels.contains(&SmolStr::new("inner"))); + assert!(block_labels.contains(&SmolStr::new("nested_stmt"))); + assert!(block_labels.contains(&SmolStr::new("nested_blk"))); + } + + #[test] + fn package_scope_collects_members() { + let text = r#" +package my_pkg; + typedef struct { + int value; + } pkt_t; + + int data; + + class my_class; + int field; + endclass + + function automatic int compute(); + endfunction +endpackage +"#; + + let (db, file_id) = setup_db(text); + let unit_scope = HirDb::file_scope(&db, file_id); + let pkg_entry = unit_scope.get(&SmolStr::new("my_pkg")).expect("package present"); + let package_id = match pkg_entry { + UnitEntry::PackageId(package_id) => package_id, + _ => panic!("expected package entry"), + }; + + let package_scope = HirDb::package_scope(&db, package_id); + + let data_entry = package_scope.get(&SmolStr::new("data")).expect("data declaration"); + match data_entry { + PackageEntry::DeclId(InPackage { package_id: owner, .. }) => { + assert_eq!(owner, package_id); + } + _ => panic!("expected decl entry"), + } + + let typedef_entry = package_scope.get(&SmolStr::new("pkt_t")).expect("typedef entry"); + match typedef_entry { + PackageEntry::TypedefId(InPackage { package_id: owner, .. }) => { + assert_eq!(owner, package_id); + } + _ => panic!("expected typedef entry"), + } + + let class_entry = package_scope.get(&SmolStr::new("my_class")).expect("class entry"); + match class_entry { + PackageEntry::ClassId(InPackage { package_id: owner, .. }) => { + assert_eq!(owner, package_id); + } + _ => panic!("expected class entry"), + } + + let func_entry = package_scope.get(&SmolStr::new("compute")).expect("function entry"); + match func_entry { + PackageEntry::SubroutineId(InPackage { package_id: owner, .. }) => { + assert_eq!(owner, package_id); + } + _ => panic!("expected subroutine entry"), + } + + let completions = package_scope.collect_completions(&db, package_id); + let mut seen = + completions.into_iter().map(|entry| (entry.name, entry.kind)).collect::>(); + seen.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!( + seen, + vec![ + (SmolStr::new("compute"), CompletionEntryKind::Function), + (SmolStr::new("data"), CompletionEntryKind::Variable), + (SmolStr::new("my_class"), CompletionEntryKind::Type), + (SmolStr::new("pkt_t"), CompletionEntryKind::Type), + ] + ); + } + + #[test] + fn package_scope_includes_package_exports() { + let text = r#" +package inner_pkg; + typedef int inner_t; + int inner_var; + function automatic void inner_fn(); + endfunction +endpackage + +package outer_pkg; + import inner_pkg::*; + export inner_pkg::inner_var; + export inner_pkg::*; +endpackage +"#; + + let (db, file_id) = setup_db(text); + let unit_scope = HirDb::file_scope(&db, file_id); + + let inner_pkg_entry = unit_scope.get(&SmolStr::new("inner_pkg")).expect("inner package"); + let inner_pkg_id = match inner_pkg_entry { + UnitEntry::PackageId(package_id) => package_id, + _ => panic!("expected package entry"), + }; + + let outer_pkg_entry = unit_scope.get(&SmolStr::new("outer_pkg")).expect("outer package"); + let outer_pkg_id = match outer_pkg_entry { + UnitEntry::PackageId(package_id) => package_id, + _ => panic!("expected package entry"), + }; + + let outer_scope = HirDb::package_scope(&db, outer_pkg_id); + + let inner_var_entry = + outer_scope.get(&SmolStr::new("inner_var")).expect("inner var re-exported"); + match inner_var_entry { + PackageEntry::DeclId(InPackage { package_id, .. }) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected declaration entry"), + } + + let inner_t_entry = outer_scope.get(&SmolStr::new("inner_t")).expect("typedef export"); + match inner_t_entry { + PackageEntry::TypedefId(InPackage { package_id, .. }) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected typedef entry"), + } + + let inner_fn_entry = outer_scope.get(&SmolStr::new("inner_fn")).expect("function export"); + match inner_fn_entry { + PackageEntry::SubroutineId(InPackage { package_id, .. }) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected subroutine entry"), + } + + let completions = outer_scope.collect_completions(&db, outer_pkg_id); + let mut seen = + completions.into_iter().map(|entry| (entry.name, entry.kind)).collect::>(); + seen.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!( + seen, + vec![ + (SmolStr::new("inner_fn"), CompletionEntryKind::Function), + (SmolStr::new("inner_t"), CompletionEntryKind::Type), + (SmolStr::new("inner_var"), CompletionEntryKind::Variable), + ] + ); + } + + #[test] + fn module_scope_resolves_package_imports() { + let text = r#" +package inner_pkg; + typedef int inner_t; + int inner_var; + function automatic void inner_fn(); + endfunction +endpackage + +module top; + import inner_pkg::*; +endmodule +"#; + + let (db, file_id) = setup_db(text); + let unit_scope = HirDb::file_scope(&db, file_id); + + let inner_pkg_entry = unit_scope.get(&SmolStr::new("inner_pkg")).expect("inner package"); + let inner_pkg_id = match inner_pkg_entry { + UnitEntry::PackageId(package_id) => package_id, + _ => panic!("expected package entry"), + }; + + let (module_id, module_scope) = module_scope(&db, file_id, "top"); + + let import_typedef = + module_scope.get(&SmolStr::new("inner_t")).expect("typedef import present"); + match import_typedef { + ModuleEntry::PackageMember(PackageEntry::TypedefId(InPackage { + package_id, .. + })) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected typedef import entry"), + } + + let import_var = + module_scope.get(&SmolStr::new("inner_var")).expect("variable import present"); + match import_var { + ModuleEntry::PackageMember(PackageEntry::DeclId(InPackage { package_id, .. })) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected variable import entry"), + } + + let import_fn = + module_scope.get(&SmolStr::new("inner_fn")).expect("function import present"); + match import_fn { + ModuleEntry::PackageMember(PackageEntry::SubroutineId(InPackage { + package_id, + .. + })) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected subroutine import entry"), + } + + let completions = module_scope.collect_completions(&db, module_id); + let mut labels = completions.iter().map(|entry| entry.name.clone()).collect::>(); + labels.sort(); + assert!(labels.contains(&SmolStr::new("inner_t"))); + assert!(labels.contains(&SmolStr::new("inner_var"))); + assert!(labels.contains(&SmolStr::new("inner_fn"))); + } + + #[test] + fn package_exports_across_files() { + let files = [ + ( + "/inner.sv", + r#" +package inner_pkg; + typedef int inner_t; + int inner_var; + function automatic int inner_fn(); + endfunction +endpackage +"#, + ), + ( + "/outer.sv", + r#" +package outer_pkg; + import inner_pkg::*; + export inner_pkg::inner_var; + export inner_pkg::*; +endpackage +"#, + ), + ( + "/consumer.sv", + r#" +module consumer; + import outer_pkg::*; +endmodule +"#, + ), + ]; + + let (db, file_ids) = setup_multi_file_db(&files); + let unit_scope = HirDb::unit_scope(&db); + + let inner_pkg_entry = unit_scope.get(&SmolStr::new("inner_pkg")).expect("inner pkg"); + let inner_pkg_id = match inner_pkg_entry { + UnitEntry::PackageId(package_id) => package_id, + _ => panic!("expected package entry"), + }; + + let outer_pkg_entry = unit_scope.get(&SmolStr::new("outer_pkg")).expect("outer pkg"); + let outer_pkg_id = match outer_pkg_entry { + UnitEntry::PackageId(package_id) => package_id, + _ => panic!("expected package entry"), + }; + + let outer_scope = HirDb::package_scope(&db, outer_pkg_id); + + let reexported_var = + outer_scope.get(&SmolStr::new("inner_var")).expect("re-exported variable present"); + match reexported_var { + PackageEntry::DeclId(InPackage { package_id, .. }) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected declaration entry"), + } + + let reexported_typedef = + outer_scope.get(&SmolStr::new("inner_t")).expect("re-exported typedef present"); + match reexported_typedef { + PackageEntry::TypedefId(InPackage { package_id, .. }) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected typedef entry"), + } + + let reexported_fn = + outer_scope.get(&SmolStr::new("inner_fn")).expect("re-exported function present"); + match reexported_fn { + PackageEntry::SubroutineId(InPackage { package_id, .. }) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected subroutine entry"), + } + + let (_module_id, consumer_scope) = module_scope(&db, file_ids[2], "consumer"); + + let imported_var = consumer_scope + .get(&SmolStr::new("inner_var")) + .expect("module sees re-exported variable"); + match imported_var { + ModuleEntry::PackageMember(entry) => match entry { + PackageEntry::DeclId(InPackage { package_id, .. }) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected declaration package member"), + }, + _ => panic!("expected package member"), + } + + let imported_type = + consumer_scope.get(&SmolStr::new("inner_t")).expect("module sees re-exported typedef"); + match imported_type { + ModuleEntry::PackageMember(entry) => match entry { + PackageEntry::TypedefId(InPackage { package_id, .. }) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected typedef package member"), + }, + _ => panic!("expected package member"), + } + + let imported_fn = consumer_scope + .get(&SmolStr::new("inner_fn")) + .expect("module sees re-exported function"); + match imported_fn { + ModuleEntry::PackageMember(entry) => match entry { + PackageEntry::SubroutineId(InPackage { package_id, .. }) => { + assert_eq!(package_id, inner_pkg_id); + } + _ => panic!("expected subroutine package member"), + }, + _ => panic!("expected package member"), + } + } } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 48b2c9c6..6d737334 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -4,6 +4,7 @@ use hir_to_def::Hir2DefCache; use itertools::{Either, Itertools}; use pathres::PathResolution; use rustc_hash::FxHashMap; +use smol_str::SmolStr; use source_to_def::{Source2DefCache, Source2DefCtx}; use syntax::{ SyntaxAncestors, SyntaxNode, SyntaxNodeExt, @@ -13,12 +14,14 @@ use utils::text_edit::TextSize; use vfs::FileId; use crate::{ + completion::{DotField, ScopedCompletionEntry}, container::InContainer, db::HirDb, file::HirFileId, hir_def::{Ident, expr::ExprId}, }; +mod completion; mod hir_to_def; pub mod pathres; pub mod resolver; @@ -62,6 +65,33 @@ impl Semantics<'_, DB> { Either::Right(node) => SyntaxAncestors::start_from(node).find_map(N::cast), } } + + pub fn scope_completions( + &self, + file_id: FileId, + offset: TextSize, + ) -> Vec { + self.impl_.scope_completions(file_id, offset) + } + + pub fn scope_resolution_completions( + &self, + file_id: FileId, + chain: &[SmolStr], + prefix: &str, + ) -> Vec { + self.impl_.scope_resolution_completions(file_id, chain, prefix) + } + + pub fn dot_completions( + &self, + file_id: FileId, + offset: TextSize, + chain: &[SmolStr], + prefix: &str, + ) -> Vec { + self.impl_.dot_completions(file_id, offset, chain, prefix) + } } pub struct SemanticsImpl<'db> { @@ -84,7 +114,7 @@ impl<'db> SemanticsImpl<'db> { } } - pub fn parse(&self, file_id: FileId) -> ast::CompilationUnit { + pub fn parse(&'_ self, file_id: FileId) -> ast::CompilationUnit<'_> { let tree = self.db.parse_src(file_id); // Unsafe: we garentee that the root node is valid for the lifetime of the db @@ -128,7 +158,7 @@ impl<'db> SemanticsImpl<'db> { } } -impl SemanticsImpl<'_> { +impl<'db> SemanticsImpl<'db> { pub fn expr_to_def(&self, in_cont: InContainer) -> Option { self.with_ctx(|ctx| ctx.expr_to_def(in_cont)) } diff --git a/crates/hir/src/semantics/completion.rs b/crates/hir/src/semantics/completion.rs new file mode 100644 index 00000000..a93329d8 --- /dev/null +++ b/crates/hir/src/semantics/completion.rs @@ -0,0 +1,888 @@ +use itertools::Either; +use rustc_hash::FxHashSet; +use smol_str::SmolStr; +use syntax::{SyntaxNodeExt, ast::AstNode}; +use utils::{get::GetRef, text_edit::TextSize}; +use vfs::FileId; + +use super::SemanticsImpl; +use crate::{ + completion::{ + CompletionEntry, CompletionEntryKind, CompletionScope, DotField, DotFieldKind, + ScopedCompletionEntry, + }, + container::{ContainerId, ContainerParent, InContainer, InFile, InModule}, + display::HirDisplay, + file::HirFileId, + hir_def::{ + Ident, + aggregate::{ClassDef, ClassId, ClassMemberKind, StructDef, StructId}, + expr::{ + data_ty::{DataTy, NamedDataTy}, + declarator::{DeclId, DeclaratorParent}, + }, + module::ModuleId, + package::{PackageId, PackageImportMember}, + stmt::{ForInit, Stmt, StmtId, StmtKind}, + typedef::TypedefId, + }, + scope::{ModuleEntry, PackageEntry, PackageImportEntry, UnitEntry}, + semantics::PathResolution, +}; + +#[derive(Debug, Clone)] +enum ScopeResolvedType { + Struct(InContainer), + Class(InContainer), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum ScopeResolutionTarget { + Package(PackageId), + Module(ModuleId), + Class(InContainer), +} + +impl<'db> SemanticsImpl<'db> { + pub fn scope_completions( + &self, + file_id: FileId, + offset: TextSize, + ) -> Vec { + let hir_file_id = HirFileId::from(file_id); + let root = self.parse(file_id); + let syntax = root.syntax(); + + let node = match syntax.token_or_node_at_offset(offset) { + Either::Left(tok_at_offset) => { + tok_at_offset.left_biased().map(|tok| tok.parent).unwrap_or_else(|| syntax) + } + Either::Right(node) => node, + }; + + let container_id = self.with_ctx(|ctx| ctx.find_container(InFile::new(hir_file_id, node))); + let mut seen = FxHashSet::default(); + let mut items = Vec::new(); + + for cont_id in ContainerParent::start_from(self.db, container_id) { + match cont_id { + ContainerId::BlockId(block_id) => { + let scope = self.db.block_scope(block_id); + for entry in scope.collect_completions(self.db, block_id) { + if seen.insert(entry.name.clone()) { + items.push(ScopedCompletionEntry { + entry, + scope: CompletionScope::Local, + }); + } + } + } + ContainerId::ModuleId(module_id) => { + let scope = self.db.module_scope(module_id); + for entry in scope.collect_completions(self.db, module_id) { + if seen.insert(entry.name.clone()) { + items.push(ScopedCompletionEntry { + entry, + scope: CompletionScope::Module, + }); + } + } + } + ContainerId::PackageId(package_id) => { + let scope = self.db.package_scope(package_id); + for entry in scope.collect_completions(self.db, package_id) { + if seen.insert(entry.name.clone()) { + items.push(ScopedCompletionEntry { + entry, + scope: CompletionScope::Package, + }); + } + } + } + ContainerId::SubroutineId(loc) => { + let scope = self.db.subroutine_scope(loc); + for entry in scope.collect_completions(self.db, loc) { + if seen.insert(entry.name.clone()) { + items.push(ScopedCompletionEntry { + entry, + scope: CompletionScope::Subroutine, + }); + } + } + } + ContainerId::HirFileId(file_id) => { + let scope = self.db.file_scope(file_id); + for entry in scope.collect_completions(self.db) { + if seen.insert(entry.name.clone()) { + items.push(ScopedCompletionEntry { + entry, + scope: CompletionScope::File, + }); + } + } + } + } + } + + let unit_scope = self.db.unit_scope(); + for entry in unit_scope.collect_completions(self.db) { + if seen.insert(entry.name.clone()) { + items.push(ScopedCompletionEntry { entry, scope: CompletionScope::Unit }); + } + } + + items + } + + pub fn scope_resolution_completions( + &self, + _file_id: FileId, + chain: &[SmolStr], + prefix: &str, + ) -> Vec { + if chain.is_empty() { + return Vec::new(); + } + + let unit_scope = self.db.unit_scope(); + let packages_by_name = self.db.packages_by_name(); + + let first_ident = Ident::from(chain[0].clone()); + let mut targets_set = FxHashSet::default(); + + if let Some(entry) = unit_scope.get(&first_ident) + && let Some(target) = self.unit_entry_to_scope_target(entry) + { + targets_set.insert(target); + } + + if let Some(pkg_ids) = packages_by_name.get(&first_ident) { + for pkg_id in pkg_ids.iter().copied() { + targets_set.insert(ScopeResolutionTarget::Package(pkg_id)); + } + } + + let mut targets: Vec<_> = targets_set.into_iter().collect(); + if targets.is_empty() { + return Vec::new(); + } + + for segment in chain.iter().skip(1) { + let ident = Ident::from(segment.clone()); + let mut next_targets_set = FxHashSet::default(); + + for target in targets.iter().copied() { + match target { + ScopeResolutionTarget::Package(package_id) => { + let package_scope = self.db.package_scope(package_id); + let mut matched = false; + + if let Some(entry) = package_scope.get(&ident) { + matched = true; + if let Some(next) = self.package_entry_to_scope_target(entry) { + next_targets_set.insert(next); + } + } + + if !matched && let Some(pkg_ids) = packages_by_name.get(&ident) { + for pkg_id in pkg_ids.iter().copied() { + next_targets_set.insert(ScopeResolutionTarget::Package(pkg_id)); + } + } + } + ScopeResolutionTarget::Module(module_id) => { + let module_scope = self.db.module_scope(module_id); + if let Some(entry) = module_scope.get(&ident) + && let Some(next) = self.module_entry_to_scope_target(module_id, entry) + { + next_targets_set.insert(next); + } + } + ScopeResolutionTarget::Class(class_ref) => { + let mut handle_class = |def: &ClassDef| { + for member in &def.members { + if member.kind == ClassMemberKind::Typedef + && member.name.as_ref().map_or(false, |n| n == &ident) + { + if let Some(ty) = member.ty { + if let Some(resolved) = self.data_ty_to_scope_type(ty) { + if let ScopeResolvedType::Class(nested_class) = resolved + { + next_targets_set.insert( + ScopeResolutionTarget::Class(nested_class), + ); + } + } + } + } + } + }; + + match class_ref.cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + handle_class(file.classes.get(class_ref.value)); + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + handle_class(module.classes.get(class_ref.value)); + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + handle_class(package.classes.get(class_ref.value)); + } + _ => {} + } + } + } + } + + if next_targets_set.is_empty() { + return Vec::new(); + } + + targets = next_targets_set.into_iter().collect(); + } + + let mut seen = FxHashSet::default(); + let mut results = Vec::new(); + + for target in targets { + let (entries, scope) = match target { + ScopeResolutionTarget::Package(package_id) => { + let scope = self.db.package_scope(package_id); + let entries = scope.collect_completions(self.db, package_id); + (entries, CompletionScope::Package) + } + ScopeResolutionTarget::Module(module_id) => { + let scope = self.db.module_scope(module_id); + let entries = scope.collect_completions(self.db, module_id); + (entries, CompletionScope::Module) + } + ScopeResolutionTarget::Class(class_ref) => { + let entries = self.class_scope_completions(class_ref, prefix); + (entries, CompletionScope::Class) + } + }; + + for entry in entries { + if seen.insert(entry.name.clone()) { + results.push(ScopedCompletionEntry { entry, scope }); + } + } + } + + results + } + + fn unit_entry_to_scope_target(&self, entry: UnitEntry) -> Option { + match entry { + UnitEntry::PackageId(package_id) => Some(ScopeResolutionTarget::Package(package_id)), + UnitEntry::ModuleId(module_id) => Some(ScopeResolutionTarget::Module(module_id)), + UnitEntry::ClassId(class_id) => Some(ScopeResolutionTarget::Class(class_id.into())), + UnitEntry::FiledDeclId(_) => None, + UnitEntry::TypedefId(_) => None, + } + } + + fn package_entry_to_scope_target(&self, entry: PackageEntry) -> Option { + match entry { + PackageEntry::ClassId(in_pkg_class) => { + Some(ScopeResolutionTarget::Class(in_pkg_class.into())) + } + PackageEntry::StructId(_) => None, + PackageEntry::DeclId(_) => None, + PackageEntry::TypedefId(_) => None, + PackageEntry::ProcId(_) => None, + PackageEntry::SubroutineId(_) => None, + PackageEntry::Package(in_pkg_pkg) => { + Some(ScopeResolutionTarget::Package(in_pkg_pkg.value)) + } + } + } + + fn module_entry_to_scope_target( + &self, + module_id: ModuleId, + entry: ModuleEntry, + ) -> Option { + match entry { + ModuleEntry::ClassId(class_id) => Some(ScopeResolutionTarget::Class(InContainer::new( + ContainerId::ModuleId(module_id), + class_id, + ))), + ModuleEntry::PackageMember(pkg_entry) => self.package_entry_to_scope_target(pkg_entry), + ModuleEntry::DeclId(_) + | ModuleEntry::NonAnsiPortEntry(_) + | ModuleEntry::AnsiPortEntry(_) + | ModuleEntry::InstanceId(_) + | ModuleEntry::StmtId(_) + | ModuleEntry::BlockId(_) + | ModuleEntry::TypedefId(_) + | ModuleEntry::PackageImportEntry(_) + | ModuleEntry::SubroutineId(_) => None, + } + } + + pub fn dot_completions( + &self, + file_id: FileId, + offset: TextSize, + chain: &[SmolStr], + prefix: &str, + ) -> Vec { + if chain.is_empty() { + return Vec::new(); + } + + let container_id = self.container_at_offset(file_id, offset); + let mut current_type: Option = None; + + for (idx, ident) in chain.iter().enumerate() { + if idx == 0 { + current_type = self.resolve_identifier_type(container_id, ident); + } else if let Some(ref ty) = current_type { + current_type = self.resolve_field_scope_type(ty, ident); + } else { + return Vec::new(); + } + + if current_type.is_none() { + return Vec::new(); + } + } + + let Some(ty) = current_type else { + return Vec::new(); + }; + self.collect_fields(&ty, prefix) + } + + fn container_at_offset(&self, file_id: FileId, offset: TextSize) -> ContainerId { + let hir_file_id = HirFileId::from(file_id); + let root = self.parse(file_id); + let syntax = root.syntax(); + + let node = match syntax.token_or_node_at_offset(offset) { + Either::Left(tok_at_offset) => { + tok_at_offset.left_biased().map(|tok| tok.parent).unwrap_or_else(|| syntax) + } + Either::Right(node) => node, + }; + + self.with_ctx(|ctx| ctx.find_container(InFile::new(hir_file_id, node))) + } + + fn resolve_identifier_type( + &self, + container_id: ContainerId, + ident: &SmolStr, + ) -> Option { + let resolution = + self.with_ctx(|ctx| ctx.name_to_def(InContainer::new(container_id, ident.clone())))?; + self.path_resolution_to_scope_type(resolution) + } + + fn resolve_field_scope_type( + &self, + ty: &ScopeResolvedType, + field_name: &SmolStr, + ) -> Option { + match ty { + ScopeResolvedType::Struct(struct_ref) => { + self.struct_field_type(*struct_ref, field_name) + } + ScopeResolvedType::Class(class_ref) => self.class_field_type(*class_ref, field_name), + } + } + + fn struct_field_type( + &self, + struct_ref: InContainer, + field_name: &SmolStr, + ) -> Option { + match struct_ref.cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + let def = file.structs.get(struct_ref.value); + self.struct_member_type(def, field_name) + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + let def = module.structs.get(struct_ref.value); + self.struct_member_type(def, field_name) + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + let def = package.structs.get(struct_ref.value); + self.struct_member_type(def, field_name) + } + ContainerId::BlockId(block_id) => { + let block = self.db.block(block_id); + let def = block.structs.get(struct_ref.value); + self.struct_member_type(def, field_name) + } + ContainerId::SubroutineId(loc) => { + let subroutine = self.db.subroutine(loc); + let def = subroutine.structs.get(struct_ref.value); + self.struct_member_type(def, field_name) + } + } + } + + fn class_field_type( + &self, + class_ref: InContainer, + field_name: &SmolStr, + ) -> Option { + match class_ref.cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + let def = file.classes.get(class_ref.value); + self.class_member_type(def, field_name) + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + let def = module.classes.get(class_ref.value); + self.class_member_type(def, field_name) + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + let def = package.classes.get(class_ref.value); + self.class_member_type(def, field_name) + } + ContainerId::BlockId(_) => None, + ContainerId::SubroutineId(_) => None, + } + } + + fn struct_member_type( + &self, + def: &StructDef, + field_name: &SmolStr, + ) -> Option { + let member = def.members.iter().find(|member| { + member.name.as_ref().map(|name| name.as_str() == field_name.as_str()).unwrap_or(false) + })?; + let ty = member.ty?; + self.data_ty_to_scope_type(ty) + } + + fn class_member_type(&self, def: &ClassDef, field_name: &SmolStr) -> Option { + let member = def.members.iter().find(|member| { + member.name.as_ref().map(|name| name.as_str() == field_name.as_str()).unwrap_or(false) + })?; + match member.kind { + ClassMemberKind::Property => { + let ty = member.ty?; + self.data_ty_to_scope_type(ty) + } + _ => None, + } + } + + fn collect_fields(&self, ty: &ScopeResolvedType, prefix: &str) -> Vec { + let mut items = match ty { + ScopeResolvedType::Struct(struct_ref) => { + self.collect_struct_fields(*struct_ref, prefix) + } + ScopeResolvedType::Class(class_ref) => self.collect_class_fields(*class_ref, prefix), + }; + items.sort_by(|lhs, rhs| lhs.name.as_str().cmp(rhs.name.as_str())); + items + } + + fn collect_struct_fields( + &self, + struct_ref: InContainer, + prefix: &str, + ) -> Vec { + match struct_ref.cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + self.collect_struct_def_fields(file.structs.get(struct_ref.value), prefix) + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + self.collect_struct_def_fields(module.structs.get(struct_ref.value), prefix) + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + self.collect_struct_def_fields(package.structs.get(struct_ref.value), prefix) + } + ContainerId::BlockId(block_id) => { + let block = self.db.block(block_id); + self.collect_struct_def_fields(block.structs.get(struct_ref.value), prefix) + } + ContainerId::SubroutineId(loc) => { + let subroutine = self.db.subroutine(loc); + self.collect_struct_def_fields(subroutine.structs.get(struct_ref.value), prefix) + } + } + } + + fn collect_class_fields(&self, class_ref: InContainer, prefix: &str) -> Vec { + match class_ref.cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + self.collect_class_def_fields(file.classes.get(class_ref.value), prefix) + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + self.collect_class_def_fields(module.classes.get(class_ref.value), prefix) + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + self.collect_class_def_fields(package.classes.get(class_ref.value), prefix) + } + ContainerId::BlockId(_) | ContainerId::SubroutineId(_) => Vec::new(), + } + } + + fn collect_struct_def_fields(&self, def: &StructDef, prefix: &str) -> Vec { + let mut items = Vec::new(); + + for member in &def.members { + let Some(name) = &member.name else { continue }; + if !prefix.is_empty() && !name.as_str().starts_with(prefix) { + continue; + } + + let detail = member.ty.as_ref().and_then(|ty| ty.display_signature(self.db).ok()); + items.push(DotField { name: name.clone(), detail, kind: DotFieldKind::Field }); + } + + items + } + + fn collect_class_def_fields(&self, def: &ClassDef, prefix: &str) -> Vec { + let mut items = Vec::new(); + + for member in &def.members { + let Some(name) = &member.name else { continue }; + if !prefix.is_empty() && !name.as_str().starts_with(prefix) { + continue; + } + + match member.kind { + ClassMemberKind::Property => { + let detail = + member.ty.as_ref().and_then(|ty| ty.display_signature(self.db).ok()); + items.push(DotField { name: name.clone(), detail, kind: DotFieldKind::Field }); + } + ClassMemberKind::Method => { + items.push(DotField { + name: name.clone(), + detail: Some(String::from("method")), + kind: DotFieldKind::Method, + }); + } + ClassMemberKind::Typedef | ClassMemberKind::Unknown => {} + } + } + + items + } + + fn class_scope_completions( + &self, + class_ref: InContainer, + prefix: &str, + ) -> Vec { + match class_ref.cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + self.collect_class_scope_entries(file.classes.get(class_ref.value), prefix) + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + self.collect_class_scope_entries(module.classes.get(class_ref.value), prefix) + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + self.collect_class_scope_entries(package.classes.get(class_ref.value), prefix) + } + ContainerId::BlockId(_) | ContainerId::SubroutineId(_) => Vec::new(), + } + } + + fn collect_class_scope_entries(&self, def: &ClassDef, prefix: &str) -> Vec { + let mut items = Vec::new(); + + for member in &def.members { + let Some(name) = &member.name else { continue }; + if !prefix.is_empty() && !name.as_str().starts_with(prefix) { + continue; + } + + let completion = match member.kind { + ClassMemberKind::Property => { + let detail = member + .ty + .as_ref() + .and_then(|ty| ty.display_signature(self.db).ok()) + .unwrap_or_else(|| CompletionEntryKind::Variable.as_str().to_string()); + CompletionEntry::new(name.clone(), CompletionEntryKind::Variable) + .with_detail(detail) + } + ClassMemberKind::Method => { + CompletionEntry::new(name.clone(), CompletionEntryKind::Function) + .with_detail(CompletionEntryKind::Function.as_str()) + } + ClassMemberKind::Typedef => { + CompletionEntry::new(name.clone(), CompletionEntryKind::Type) + .with_detail(CompletionEntryKind::Type.as_str()) + } + ClassMemberKind::Unknown => continue, + }; + + items.push(completion); + } + + items.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name)); + items + } + + fn path_resolution_to_scope_type(&self, res: PathResolution) -> Option { + let ty = self.path_resolution_to_data_ty(res)?; + self.data_ty_to_scope_type(ty) + } + + fn path_resolution_to_data_ty(&self, res: PathResolution) -> Option> { + match res { + PathResolution::Decl(decl) => self.decl_type(decl), + PathResolution::ParamDecl(param_decl) => self.decl_type(param_decl.into()), + PathResolution::Typedef(typedef) => self.typedef_type(typedef), + PathResolution::Class(class_ref) => { + Some(InContainer::new(class_ref.cont_id, DataTy::Class(class_ref))) + } + PathResolution::PackageImport(import) => self.package_import_type(import), + _ => None, + } + } + + fn package_entry_to_data_ty(&self, entry: PackageEntry) -> Option> { + match entry { + PackageEntry::DeclId(in_pkg_decl) => self.decl_type(in_pkg_decl.into()), + PackageEntry::TypedefId(in_pkg_typedef) => self.typedef_type(in_pkg_typedef.into()), + PackageEntry::ClassId(in_pkg_class) => { + let in_container: InContainer = in_pkg_class.into(); + Some(InContainer::new(in_container.cont_id, DataTy::Class(in_container))) + } + PackageEntry::StructId(_) + | PackageEntry::ProcId(_) + | PackageEntry::SubroutineId(_) + | PackageEntry::Package(_) => None, + } + } + + fn package_import_type( + &self, + import: InModule, + ) -> Option> { + let module_id = import.module_id; + let module = self.db.module(module_id); + let import_entry = module.package_imports.get(import.value.import); + let item = import_entry.items.get(import.value.item_idx as usize)?; + + match &item.member { + PackageImportMember::Named(name) => { + let unit_scope = self.db.unit_scope(); + let package_id = match unit_scope.get(&item.package)? { + UnitEntry::PackageId(package_id) => package_id, + _ => return None, + }; + + let package_scope = self.db.package_scope(package_id); + let entry = package_scope.get(name)?; + self.package_entry_to_data_ty(entry) + } + PackageImportMember::All => None, + } + } + + fn decl_type(&self, decl: InContainer) -> Option> { + match decl.cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + let declarator = file.decls.get(decl.value); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = file.declarations.get(declaration_id); + Some(InContainer::new(decl.cont_id, declaration.ty())) + } + DeclaratorParent::PortDeclId(_) => None, + DeclaratorParent::StmtId(stmt_id) => { + self.stmt_decl_ty(decl.cont_id, stmt_id, decl.value) + } + } + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + let declarator = module.decls.get(decl.value); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = module.declarations.get(declaration_id); + Some(InContainer::new(decl.cont_id, declaration.ty())) + } + DeclaratorParent::PortDeclId(_) => None, + DeclaratorParent::StmtId(stmt_id) => { + self.stmt_decl_ty(decl.cont_id, stmt_id, decl.value) + } + } + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + let declarator = package.decls.get(decl.value); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = package.declarations.get(declaration_id); + Some(InContainer::new(decl.cont_id, declaration.ty())) + } + DeclaratorParent::PortDeclId(_) => None, + DeclaratorParent::StmtId(stmt_id) => { + self.stmt_decl_ty(decl.cont_id, stmt_id, decl.value) + } + } + } + ContainerId::BlockId(block_id) => { + let block = self.db.block(block_id); + let declarator = block.decls.get(decl.value); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = block.declarations.get(declaration_id); + Some(InContainer::new(decl.cont_id, declaration.ty())) + } + DeclaratorParent::PortDeclId(_) => None, + DeclaratorParent::StmtId(stmt_id) => { + self.stmt_decl_ty(decl.cont_id, stmt_id, decl.value) + } + } + } + ContainerId::SubroutineId(loc) => { + let subroutine = self.db.subroutine(loc); + let declarator = subroutine.decls.get(decl.value); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = subroutine.declarations.get(declaration_id); + Some(InContainer::new(decl.cont_id, declaration.ty())) + } + DeclaratorParent::PortDeclId(_) => None, + DeclaratorParent::StmtId(stmt_id) => { + self.stmt_decl_ty(decl.cont_id, stmt_id, decl.value) + } + } + } + } + } + + fn typedef_type(&self, typedef: InContainer) -> Option> { + match typedef.cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + file.typedefs.get(typedef.value).ty.map(|ty| InContainer::new(typedef.cont_id, ty)) + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + module + .typedefs + .get(typedef.value) + .ty + .map(|ty| InContainer::new(typedef.cont_id, ty)) + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + package + .typedefs + .get(typedef.value) + .ty + .map(|ty| InContainer::new(typedef.cont_id, ty)) + } + ContainerId::BlockId(block_id) => { + let block = self.db.block(block_id); + block.typedefs.get(typedef.value).ty.map(|ty| InContainer::new(typedef.cont_id, ty)) + } + ContainerId::SubroutineId(loc) => { + let subroutine = self.db.subroutine(loc); + subroutine + .typedefs + .get(typedef.value) + .ty + .map(|ty| InContainer::new(typedef.cont_id, ty)) + } + } + } + + fn data_ty_to_scope_type(&self, ty: InContainer) -> Option { + match ty.value { + DataTy::Struct(struct_ref) => Some(ScopeResolvedType::Struct(struct_ref)), + DataTy::Class(class_ref) => Some(ScopeResolvedType::Class(class_ref)), + DataTy::Named(named) => { + let resolved = self.resolve_named_data_ty(ty.with_value(named))?; + self.data_ty_to_scope_type(resolved) + } + DataTy::Builtin(_) => None, + } + } + + fn resolve_named_data_ty( + &self, + named: InContainer, + ) -> Option> { + let expr_id = match named.value { + NamedDataTy::Ident(expr_id) | NamedDataTy::Field(expr_id) => expr_id, + }; + let resolution = self.expr_to_def(named.with_value(expr_id))?; + self.path_resolution_to_data_ty(resolution) + } + + fn stmt_decl_ty( + &self, + cont_id: ContainerId, + stmt_id: StmtId, + decl_id: DeclId, + ) -> Option> { + match cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + let stmt = file.stmts.get(stmt_id); + self.stmt_decl_ty_from_stmt(cont_id, stmt, decl_id) + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + let stmt = module.stmts.get(stmt_id); + self.stmt_decl_ty_from_stmt(cont_id, stmt, decl_id) + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + let stmt = package.stmts.get(stmt_id); + self.stmt_decl_ty_from_stmt(cont_id, stmt, decl_id) + } + ContainerId::BlockId(block_id) => { + let block = self.db.block(block_id); + let stmt = block.stmts.get(stmt_id); + self.stmt_decl_ty_from_stmt(cont_id, stmt, decl_id) + } + ContainerId::SubroutineId(loc) => { + let subroutine = self.db.subroutine(loc); + let stmt = subroutine.stmts.get(stmt_id); + self.stmt_decl_ty_from_stmt(cont_id, stmt, decl_id) + } + } + } + + fn stmt_decl_ty_from_stmt( + &self, + cont_id: ContainerId, + stmt: &Stmt, + decl_id: DeclId, + ) -> Option> { + match &stmt.kind { + StmtKind::For { inits: ForInit::Init(inits), .. } => inits + .iter() + .find(|(_, id)| *id == decl_id) + .map(|(ty, _)| InContainer::new(cont_id, *ty)), + _ => None, + } + } +} diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index 8a293e32..a667feac 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -49,8 +49,10 @@ impl Source2DefCtx<'_, '_> { let block = db.block(block_id); resolve(block.get(expr_id)) } - ContainerId::SubroutineId(_) => None, - ContainerId::FileSubroutineId(_) => None, + ContainerId::SubroutineId(loc) => { + let subroutine = db.subroutine(loc); + resolve(&subroutine.exprs[expr_id]) + } } } @@ -75,9 +77,17 @@ impl Source2DefCtx<'_, '_> { let entry = scope.get(&ident)?; Some(InBlock::new(block_id, entry).into()) } +<<<<<<< HEAD ContainerId::PackageId(_) => None, ContainerId::SubroutineId(_) => None, ContainerId::FileSubroutineId(_) => None, +======= + ContainerId::SubroutineId(loc) => { + let scope = db.subroutine_scope(loc); + let entry = scope.get(&ident)?; + Some(InSubroutine::new(loc, entry).into()) + } +>>>>>>> 32145766 (feat(hir): add semantic completion infrastructure) })?; self.hir_cache.name_map.insert(InContainer::new(cont_id, ident), res); Some(res) diff --git a/crates/hir/src/semantics/pathres.rs b/crates/hir/src/semantics/pathres.rs index 80925183..3686d813 100644 --- a/crates/hir/src/semantics/pathres.rs +++ b/crates/hir/src/semantics/pathres.rs @@ -6,16 +6,22 @@ use utils::get::GetRef; use super::SemanticsImpl; use crate::{ - container::{ContainerId, InBlock, InContainer, InFile, InModule}, + container::{ContainerId, InBlock, InContainer, InFile, InModule, InPackage, InSubroutine}, hir_def::{ + aggregate::ClassId, block::BlockId, declaration::Declaration, expr::declarator::{DeclId, DeclaratorParent}, lower_ident_opt, module::{ModuleId, instantiation::InstanceId, port::NonAnsiPortId}, + package::PackageId, stmt::StmtId, + subroutine::SubroutineId, + typedef::TypedefId, + }, + scope::{ + self, BlockEntry, ModuleEntry, PackageEntry, PackageImportEntry, SubroutineEntry, UnitEntry, }, - scope::{self, BlockEntry, ModuleEntry, UnitEntry}, }; impl SemanticsImpl<'_> { @@ -85,6 +91,9 @@ impl SemanticsImpl<'_> { match self.db.unit_scope().get(&module_name)? { UnitEntry::ModuleId(module_id) => Some(module_id), UnitEntry::FiledDeclId(_) => None, + UnitEntry::TypedefId(_) => None, + UnitEntry::ClassId(_) => None, + UnitEntry::PackageId(_) => None, } } @@ -98,6 +107,9 @@ pub enum PathResolution { Module(ModuleId), Decl(InContainer), ParamDecl(InModule), + Typedef(InContainer), + Class(InContainer), + PackageImport(InModule), NonAnsiPort { // There won't be a situation where all fields are None. label: Option, @@ -109,6 +121,8 @@ pub enum PathResolution { Instance(InModule), Stmt(InContainer), Block(BlockId), + Package(PackageId), + Subroutine(InContainer), } impl From for PathResolution { @@ -117,6 +131,9 @@ impl From for PathResolution { match entry { ModuleId(idx) => Self::Module(idx), FiledDeclId(idx) => Self::Decl(idx.into()), + TypedefId(idx) => Self::Typedef(idx.into()), + ClassId(idx) => Self::Class(idx.into()), + PackageId(idx) => Self::Package(idx), } } } @@ -133,6 +150,18 @@ impl From> for PathResolution { } AnsiPortEntry(scope::AnsiPortEntry(idx)) => Self::AnsiPort(entry.with_value(idx)), BlockId(block_id) => Self::Block(block_id), + TypedefId(typedef_id) => Self::Typedef(entry.with_value(typedef_id).into()), + ClassId(class_id) => Self::Class(entry.with_value(class_id).into()), + PackageImportEntry(import_entry) => Self::PackageImport(entry.with_value(import_entry)), + PackageMember(pkg_entry) => match pkg_entry { + PackageEntry::DeclId(in_pkg_decl) => Self::Decl(in_pkg_decl.into()), + PackageEntry::TypedefId(in_pkg_typedef) => Self::Typedef(in_pkg_typedef.into()), + PackageEntry::ClassId(in_pkg_class) => Self::Class(in_pkg_class.into()), + PackageEntry::StructId(_) | PackageEntry::ProcId(_) => unreachable!(), + PackageEntry::SubroutineId(in_pkg_sub) => Self::Subroutine(in_pkg_sub.into()), + PackageEntry::Package(in_pkg_pkg) => Self::Package(in_pkg_pkg.value), + }, + SubroutineId(sub_id) => Self::Subroutine(entry.with_value(sub_id).into()), } } } @@ -144,6 +173,33 @@ impl From> for PathResolution { DeclId(idx) => Self::Decl(entry.with_value(idx).into()), StmtId(idx) => Self::Stmt(entry.with_value(idx).into()), BlockId(block_id) => Self::Block(block_id), + TypedefId(typedef_id) => Self::Typedef(entry.with_value(typedef_id).into()), + } + } +} + +impl From> for PathResolution { + fn from(entry: InSubroutine) -> Self { + use SubroutineEntry::*; + match entry.value { + DeclId(idx) => Self::Decl(entry.with_value(idx).into()), + StmtId(idx) => Self::Stmt(entry.with_value(idx).into()), + BlockId(block_id) => Self::Block(block_id), + TypedefId(typedef_id) => Self::Typedef(entry.with_value(typedef_id).into()), + } + } +} + +impl From> for PathResolution { + fn from(entry: InPackage) -> Self { + use PackageEntry::*; + match entry.value { + DeclId(in_pkg_decl) => Self::Decl(in_pkg_decl.into()), + TypedefId(in_pkg_typedef) => Self::Typedef(in_pkg_typedef.into()), + ClassId(in_pkg_class) => Self::Class(in_pkg_class.into()), + StructId(_) | ProcId(_) => unreachable!(), + SubroutineId(in_pkg_sub) => Self::Subroutine(in_pkg_sub.into()), + Package(in_pkg_pkg) => Self::Package(in_pkg_pkg.value), } } } diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 16c0e768..b042c31e 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -80,12 +80,6 @@ impl Source2DefCtx<'_, '_> { let local_block_id = *subroutine_src_map.block_srcs.get(&block_src)?; subroutine.stmts.get(local_block_id).block_id } - ContainerId::FileSubroutineId(loc) => { - let subroutine = loc.to_container(self.db); - let subroutine_src_map = loc.to_container_src_map(self.db); - let local_block_id = *subroutine_src_map.block_srcs.get(&block_src)?; - subroutine.stmts.get(local_block_id).block_id - } }; Some(block_id) From bd0e2159dec0595059491c67d6b2c91917e664e8 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:54:34 +0800 Subject: [PATCH 09/18] feat(hir): add file-level function declaration support --- crates/hir/src/semantics/completion.rs | 44 +++++++++++++++++++++-- crates/hir/src/semantics/hir_to_def.rs | 4 +++ crates/hir/src/semantics/source_to_def.rs | 6 ++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/crates/hir/src/semantics/completion.rs b/crates/hir/src/semantics/completion.rs index a93329d8..4bd116df 100644 --- a/crates/hir/src/semantics/completion.rs +++ b/crates/hir/src/semantics/completion.rs @@ -110,6 +110,9 @@ impl<'db> SemanticsImpl<'db> { } } } + ContainerId::FileSubroutineId(_loc) => { + // TODO: implement file-level subroutine scope + } ContainerId::HirFileId(file_id) => { let scope = self.db.file_scope(file_id); for entry in scope.collect_completions(self.db) { @@ -427,6 +430,11 @@ impl<'db> SemanticsImpl<'db> { let def = subroutine.structs.get(struct_ref.value); self.struct_member_type(def, field_name) } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(self.db); + let def = subroutine.structs.get(struct_ref.value); + self.struct_member_type(def, field_name) + } } } @@ -453,6 +461,7 @@ impl<'db> SemanticsImpl<'db> { } ContainerId::BlockId(_) => None, ContainerId::SubroutineId(_) => None, + ContainerId::FileSubroutineId(_) => None, } } @@ -518,6 +527,10 @@ impl<'db> SemanticsImpl<'db> { let subroutine = self.db.subroutine(loc); self.collect_struct_def_fields(subroutine.structs.get(struct_ref.value), prefix) } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(self.db); + self.collect_struct_def_fields(subroutine.structs.get(struct_ref.value), prefix) + } } } @@ -535,7 +548,7 @@ impl<'db> SemanticsImpl<'db> { let package = self.db.package(package_id); self.collect_class_def_fields(package.classes.get(class_ref.value), prefix) } - ContainerId::BlockId(_) | ContainerId::SubroutineId(_) => Vec::new(), + ContainerId::BlockId(_) | ContainerId::SubroutineId(_) | ContainerId::FileSubroutineId(_) => Vec::new(), } } @@ -602,7 +615,7 @@ impl<'db> SemanticsImpl<'db> { let package = self.db.package(package_id); self.collect_class_scope_entries(package.classes.get(class_ref.value), prefix) } - ContainerId::BlockId(_) | ContainerId::SubroutineId(_) => Vec::new(), + ContainerId::BlockId(_) | ContainerId::SubroutineId(_) | ContainerId::FileSubroutineId(_) => Vec::new(), } } @@ -773,6 +786,20 @@ impl<'db> SemanticsImpl<'db> { } } } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(self.db); + let declarator = subroutine.decls.get(decl.value); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = subroutine.declarations.get(declaration_id); + Some(InContainer::new(decl.cont_id, declaration.ty())) + } + DeclaratorParent::PortDeclId(_) => None, + DeclaratorParent::StmtId(stmt_id) => { + self.stmt_decl_ty(decl.cont_id, stmt_id, decl.value) + } + } + } } } @@ -810,6 +837,14 @@ impl<'db> SemanticsImpl<'db> { .ty .map(|ty| InContainer::new(typedef.cont_id, ty)) } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(self.db); + subroutine + .typedefs + .get(typedef.value) + .ty + .map(|ty| InContainer::new(typedef.cont_id, ty)) + } } } @@ -868,6 +903,11 @@ impl<'db> SemanticsImpl<'db> { let stmt = subroutine.stmts.get(stmt_id); self.stmt_decl_ty_from_stmt(cont_id, stmt, decl_id) } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(self.db); + let stmt = subroutine.stmts.get(stmt_id); + self.stmt_decl_ty_from_stmt(cont_id, stmt, decl_id) + } } } diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index a667feac..6874de54 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -53,6 +53,10 @@ impl Source2DefCtx<'_, '_> { let subroutine = db.subroutine(loc); resolve(&subroutine.exprs[expr_id]) } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(db); + resolve(&subroutine.exprs[expr_id]) + } } } diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index b042c31e..16c0e768 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -80,6 +80,12 @@ impl Source2DefCtx<'_, '_> { let local_block_id = *subroutine_src_map.block_srcs.get(&block_src)?; subroutine.stmts.get(local_block_id).block_id } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(self.db); + let subroutine_src_map = loc.to_container_src_map(self.db); + let local_block_id = *subroutine_src_map.block_srcs.get(&block_src)?; + subroutine.stmts.get(local_block_id).block_id + } }; Some(block_id) From 9da8d0f78df8534f00a6a4587868386e1d7c4cda Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:56:27 +0800 Subject: [PATCH 10/18] feat(scope): update import for CompletionEntryKind in tests --- crates/hir/src/lib.rs | 1 + crates/hir/src/scope.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 4238de32..2a65a6ee 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2,6 +2,7 @@ #![feature(trait_alias)] #![feature(decl_macro)] +pub mod completion; pub mod container; pub mod db; pub mod display; diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 70c94e02..9d4570d6 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -1165,11 +1165,11 @@ mod tests { use super::{BlockEntry, ModuleEntry, ModuleScope, PackageEntry, UnitEntry}; use crate::{ - CompletionEntryKind, container::{InFile, InPackage}, db::HirDb, file::HirFileId, hir_def::module::ModuleId, + scope::CompletionEntryKind, }; #[salsa::database( From 001ab00addcebc0dbc0ca0ee48a793e259712b0b Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:58:48 +0800 Subject: [PATCH 11/18] feat(hir): implement file-level subroutine scope completions --- crates/hir/src/db.rs | 5 +- crates/hir/src/scope.rs | 93 ++++++++++++++++++++++++++ crates/hir/src/semantics/completion.rs | 12 +++- 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index e2defdf9..101908b1 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -4,7 +4,7 @@ use syntax::SyntaxTree; use triomphe::Arc; use crate::{ - container::InModule, + container::{InFile, InModule}, file::HirFileId, hir_def::{ Ident, @@ -91,6 +91,9 @@ pub trait HirDb: InternDb { #[salsa::invoke(SubroutineScope::subroutine_scope_query)] fn subroutine_scope(&self, subroutine: InModule) -> Arc; + + #[salsa::invoke(SubroutineScope::file_subroutine_scope_query)] + fn file_subroutine_scope(&self, subroutine: InFile) -> Arc; } fn parse(db: &dyn HirDb, file_id: HirFileId) -> SyntaxTree { diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 9d4570d6..bf020a55 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -635,6 +635,83 @@ impl SubroutineScope { items } + + pub fn file_subroutine_scope_query( + db: &dyn HirDb, + subroutine_loc: InFile, + ) -> Arc { + let mut scope = Scope::default(); + let subroutine = subroutine_loc.to_container(db); + + if !subroutine.has_body { + return Arc::new(scope); + } + + for (decl_id, decl) in subroutine.decls.iter() { + scope.insert_opt(&decl.name, decl_id.into()); + } + + for (stmt_id, stmt) in subroutine.stmts.iter() { + scope.insert_opt(&stmt.label, stmt_id.into()); + + if let StmtKind::Block(BlockInfo { name, block_id }) = &stmt.kind { + scope.insert_opt(name, (*block_id).into()); + } + } + + for (typedef_id, typedef_) in subroutine.typedefs.iter() { + scope.insert_opt(&typedef_.name, typedef_id.into()); + } + + Arc::new(scope) + } + + pub fn collect_file_subroutine_completions( + &self, + db: &dyn HirDb, + subroutine_loc: InFile, + ) -> Vec { + let subroutine = subroutine_loc.to_container(db); + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + let completion = match entry { + SubroutineEntry::DeclId(decl_id) => { + let declarator = subroutine.decls.get(*decl_id); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match subroutine.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = + file_subroutine_decl_detail(db, subroutine_loc, &subroutine, *decl_id); + Some(make_entry_with_detail(ident, kind, detail)) + } + SubroutineEntry::StmtId(_) => { + Some(make_entry(ident, CompletionEntryKind::Statement)) + } + SubroutineEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), + SubroutineEntry::TypedefId(typedef_id) => { + let typedef = subroutine.typedefs.get(*typedef_id); + let detail = + typedef_detail(db, ContainerId::FileSubroutineId(subroutine_loc), typedef); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } } fn make_entry(ident: &Ident, kind: CompletionEntryKind) -> CompletionEntry { @@ -824,6 +901,22 @@ fn subroutine_decl_detail( } } +fn file_subroutine_decl_detail( + db: &dyn HirDb, + loc: InFile, + subroutine: &Subroutine, + decl_id: DeclId, +) -> Option { + let declarator = subroutine.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = subroutine.declarations.get(declaration_id); + declaration_detail(db, ContainerId::FileSubroutineId(loc), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} + fn package_decl_detail( db: &dyn HirDb, package_id: PackageId, diff --git a/crates/hir/src/semantics/completion.rs b/crates/hir/src/semantics/completion.rs index 4bd116df..894c0dad 100644 --- a/crates/hir/src/semantics/completion.rs +++ b/crates/hir/src/semantics/completion.rs @@ -110,8 +110,16 @@ impl<'db> SemanticsImpl<'db> { } } } - ContainerId::FileSubroutineId(_loc) => { - // TODO: implement file-level subroutine scope + ContainerId::FileSubroutineId(loc) => { + let scope = self.db.file_subroutine_scope(loc); + for entry in scope.collect_file_subroutine_completions(self.db, loc) { + if seen.insert(entry.name.clone()) { + items.push(ScopedCompletionEntry { + entry, + scope: CompletionScope::Subroutine, + }); + } + } } ContainerId::HirFileId(file_id) => { let scope = self.db.file_scope(file_id); From 805db0f970c5b2d796ea82b9e0c01e140cfecba7 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 17:01:33 +0800 Subject: [PATCH 12/18] feat(hir): add class inheritance support for completion --- crates/hir/src/semantics/completion.rs | 38 ++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/crates/hir/src/semantics/completion.rs b/crates/hir/src/semantics/completion.rs index 894c0dad..c9ac1b06 100644 --- a/crates/hir/src/semantics/completion.rs +++ b/crates/hir/src/semantics/completion.rs @@ -546,17 +546,31 @@ impl<'db> SemanticsImpl<'db> { match class_ref.cont_id { ContainerId::HirFileId(file_id) => { let file = self.db.hir_file(file_id); - self.collect_class_def_fields(file.classes.get(class_ref.value), prefix) + self.collect_class_def_fields( + file.classes.get(class_ref.value), + prefix, + class_ref.cont_id, + ) } ContainerId::ModuleId(module_id) => { let module = self.db.module(module_id); - self.collect_class_def_fields(module.classes.get(class_ref.value), prefix) + self.collect_class_def_fields( + module.classes.get(class_ref.value), + prefix, + class_ref.cont_id, + ) } ContainerId::PackageId(package_id) => { let package = self.db.package(package_id); - self.collect_class_def_fields(package.classes.get(class_ref.value), prefix) - } - ContainerId::BlockId(_) | ContainerId::SubroutineId(_) | ContainerId::FileSubroutineId(_) => Vec::new(), + self.collect_class_def_fields( + package.classes.get(class_ref.value), + prefix, + class_ref.cont_id, + ) + } + ContainerId::BlockId(_) + | ContainerId::SubroutineId(_) + | ContainerId::FileSubroutineId(_) => Vec::new(), } } @@ -576,9 +590,21 @@ impl<'db> SemanticsImpl<'db> { items } - fn collect_class_def_fields(&self, def: &ClassDef, prefix: &str) -> Vec { + fn collect_class_def_fields( + &self, + def: &ClassDef, + prefix: &str, + container_id: ContainerId, + ) -> Vec { let mut items = Vec::new(); + if let Some(base_class_name) = &def.base_class_name { + if let Some(base_class_ref) = self.find_class_in_scope(base_class_name, container_id) { + let base_items = self.collect_class_fields(base_class_ref, prefix); + items.extend(base_items); + } + } + for member in &def.members { let Some(name) = &member.name else { continue }; if !prefix.is_empty() && !name.as_str().starts_with(prefix) { From 87801578fca5637a490a5930b1e0bf9b0c996893 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 19:01:10 +0800 Subject: [PATCH 13/18] fix(hir): add base class name to ClassDef and implement class lookup in scope --- crates/hir/src/semantics/completion.rs | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/crates/hir/src/semantics/completion.rs b/crates/hir/src/semantics/completion.rs index c9ac1b06..ab9f8efe 100644 --- a/crates/hir/src/semantics/completion.rs +++ b/crates/hir/src/semantics/completion.rs @@ -631,6 +631,48 @@ impl<'db> SemanticsImpl<'db> { items } + fn find_class_in_scope( + &self, + class_name: &str, + container_id: ContainerId, + ) -> Option> { + match container_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + file.classes.iter().find_map(|(idx, class_def)| { + class_def + .name + .as_ref() + .filter(|name| name.as_str() == class_name) + .map(|_| InContainer::new(container_id, idx)) + }) + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + module.classes.iter().find_map(|(idx, class_def)| { + class_def + .name + .as_ref() + .filter(|name| name.as_str() == class_name) + .map(|_| InContainer::new(container_id, idx)) + }) + } + ContainerId::PackageId(package_id) => { + let package = self.db.package(package_id); + package.classes.iter().find_map(|(idx, class_def)| { + class_def + .name + .as_ref() + .filter(|name| name.as_str() == class_name) + .map(|_| InContainer::new(container_id, idx)) + }) + } + ContainerId::BlockId(_) + | ContainerId::SubroutineId(_) + | ContainerId::FileSubroutineId(_) => None, + } + } + fn class_scope_completions( &self, class_ref: InContainer, From ad3f51a383d3083809ea90e04dc7a61d252d716b Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 20:01:06 +0800 Subject: [PATCH 14/18] fix(hir): remove unused subroutine ID handling in name resolution --- crates/hir/src/semantics/hir_to_def.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index 6874de54..00cc819e 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -81,17 +81,9 @@ impl Source2DefCtx<'_, '_> { let entry = scope.get(&ident)?; Some(InBlock::new(block_id, entry).into()) } -<<<<<<< HEAD ContainerId::PackageId(_) => None, ContainerId::SubroutineId(_) => None, ContainerId::FileSubroutineId(_) => None, -======= - ContainerId::SubroutineId(loc) => { - let scope = db.subroutine_scope(loc); - let entry = scope.get(&ident)?; - Some(InSubroutine::new(loc, entry).into()) - } ->>>>>>> 32145766 (feat(hir): add semantic completion infrastructure) })?; self.hir_cache.name_map.insert(InContainer::new(cont_id, ident), res); Some(res) From 166d4ecbf137e1ad86efe31c278b034d9e3183b5 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:46:31 +0800 Subject: [PATCH 15/18] feat(hir): add semantic completion infrastructure # Conflicts: # crates/hir/src/db.rs # crates/hir/src/scope.rs # crates/hir/src/semantics/completion.rs # crates/hir/src/semantics/hir_to_def.rs # crates/hir/src/semantics/source_to_def.rs --- crates/hir/src/semantics/hir_to_def.rs | 52 ++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index 00cc819e..7bbb68a8 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -3,11 +3,14 @@ use utils::get::GetRef; use super::{Source2DefCtx, pathres::PathResolution}; use crate::{ - container::{ContainerId, ContainerParent, InBlock, InContainer, InModule}, + container::{ + ContainerId, ContainerParent, InBlock, InContainer, InModule, InPackage, InSubroutine, + }, hir_def::{ Ident, expr::{Expr, ExprId}, }, + scope::PackageEntry, }; #[derive(Default, Debug)] @@ -24,6 +27,36 @@ impl Source2DefCtx<'_, '_> { let db = self.db; let mut resolve = |expr: &Expr| match expr { + Expr::Field { receiver, field } => { + let field_ident = field.clone()?; + let receiver_res = self.expr_to_def(InContainer::new(cont_id, *receiver))?; + let res = match receiver_res { + PathResolution::Package(package_id) => { + let package_scope = db.package_scope(package_id); + let entry = package_scope.get(&field_ident)?; + match entry { + PackageEntry::DeclId(in_pkg_decl) => { + PathResolution::Decl(in_pkg_decl.into()) + } + PackageEntry::TypedefId(in_pkg_typedef) => { + PathResolution::Typedef(in_pkg_typedef.into()) + } + PackageEntry::ClassId(in_pkg_class) => { + PathResolution::Class(in_pkg_class.into()) + } + PackageEntry::StructId(_) + | PackageEntry::ProcId(_) + | PackageEntry::SubroutineId(_) => return None, + PackageEntry::Package(in_pkg_pkg) => { + PathResolution::Package(in_pkg_pkg.value) + } + } + } + _ => return None, + }; + self.hir_cache.expr_map.insert(InContainer::new(cont_id, expr_id), res); + Some(res) + } Expr::Ident(ident) => { let res = self.name_to_def(InContainer::new(cont_id, ident.clone()))?; self.hir_cache.expr_map.insert(InContainer::new(cont_id, expr_id), res); @@ -53,10 +86,6 @@ impl Source2DefCtx<'_, '_> { let subroutine = db.subroutine(loc); resolve(&subroutine.exprs[expr_id]) } - ContainerId::FileSubroutineId(loc) => { - let subroutine = loc.to_container(db); - resolve(&subroutine.exprs[expr_id]) - } } } @@ -76,14 +105,21 @@ impl Source2DefCtx<'_, '_> { let entry = scope.get(&ident)?; Some(InModule::new(module_id, entry).into()) } + ContainerId::PackageId(package_id) => { + let scope = db.package_scope(package_id); + let entry = scope.get(&ident)?; + Some(InPackage::new(package_id, entry).into()) + } ContainerId::BlockId(block_id) => { let scope = db.block_scope(block_id); let entry = scope.get(&ident)?; Some(InBlock::new(block_id, entry).into()) } - ContainerId::PackageId(_) => None, - ContainerId::SubroutineId(_) => None, - ContainerId::FileSubroutineId(_) => None, + ContainerId::SubroutineId(loc) => { + let scope = db.subroutine_scope(loc); + let entry = scope.get(&ident)?; + Some(InSubroutine::new(loc, entry).into()) + } })?; self.hir_cache.name_map.insert(InContainer::new(cont_id, ident), res); Some(res) From 57f7184246b40543b302e52bd3ff3c846fac28ba Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 16:54:34 +0800 Subject: [PATCH 16/18] feat(hir): add file-level function declaration support --- crates/hir/src/semantics/hir_to_def.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index 7bbb68a8..a8a605a6 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -86,6 +86,10 @@ impl Source2DefCtx<'_, '_> { let subroutine = db.subroutine(loc); resolve(&subroutine.exprs[expr_id]) } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(db); + resolve(&subroutine.exprs[expr_id]) + } } } @@ -120,6 +124,10 @@ impl Source2DefCtx<'_, '_> { let entry = scope.get(&ident)?; Some(InSubroutine::new(loc, entry).into()) } + ContainerId::FileSubroutineId(_loc) => { + // TODO: implement file-level subroutine scope lookup + None + } })?; self.hir_cache.name_map.insert(InContainer::new(cont_id, ident), res); Some(res) From 39ad0641489108e6e5060ad50ad74783d73b4020 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 20:23:24 +0800 Subject: [PATCH 17/18] feat(hir): collect subroutines --- crates/hir/src/scope.rs | 145 +++++++++++++++++++++++++ crates/hir/src/semantics/completion.rs | 1 + crates/hir/src/semantics/pathres.rs | 2 + 3 files changed, 148 insertions(+) diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index bf020a55..e89c59b7 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -40,6 +40,7 @@ define_enum_deriving_from! { FiledDeclId(FiledDeclId), TypedefId(InFile), ClassId(InFile), + SubroutineId(InFile), } } @@ -190,6 +191,10 @@ impl UnitScope { scope.insert_opt(&class_.name, InFile::new(file_id, class_id).into()); } + for (subroutine_id, subroutine_info) in hir_file.subroutines.iter() { + scope.insert_opt(&subroutine_info.name, InFile::new(file_id, subroutine_id).into()); + } + Arc::new(scope) } @@ -231,6 +236,15 @@ impl UnitScope { UnitEntry::ClassId(_) => { items.push(make_entry(ident, CompletionEntryKind::Type)); } + UnitEntry::SubroutineId(in_file) => { + let file = db.hir_file(in_file.file_id); + let subroutine = file.subroutines.get(in_file.value); + let detail = match subroutine.kind { + SubroutineKind::Task => "task".to_string(), + SubroutineKind::Function { .. } => "function".to_string(), + }; + items.push(make_entry_with_detail(ident, CompletionEntryKind::Function, Some(detail))); + } } } @@ -1870,4 +1884,135 @@ endmodule _ => panic!("expected package member"), } } + + #[test] + fn debug_package_imports() { + use crate::hir_def::{module::ModuleId, package::PackageId}; + + let source = r#" +package my_pkg; + typedef logic [7:0] byte_t; + parameter int MAX_SIZE = 256; + + function int get_size(); + return MAX_SIZE; + endfunction +endpackage + +module test; + import my_pkg::*; +endmodule +"#; + let (db, hir_file_id) = setup_db(source); + let file = db.hir_file(hir_file_id); + + eprintln!("\n=== File Contents ==="); + eprintln!("Packages: {}", file.packages.len()); + eprintln!("Modules: {}", file.modules.len()); + + // Find the package + let mut pkg_id = None; + for (idx, pkg_info) in file.packages.iter() { + eprintln!("Package {} => {:?}", idx.into_raw(), pkg_info.name); + if pkg_info.name.as_ref().map(|n| n.as_str()) == Some("my_pkg") { + pkg_id = Some(PackageId { file_id: hir_file_id, value: idx }); + break; + } + } + let pkg_id = pkg_id.expect("package my_pkg should exist"); + + // Check package HIR + let package = db.package(pkg_id); + eprintln!("\n=== Package HIR ==="); + eprintln!("Decls: {}", package.decls.len()); + eprintln!("Typedefs: {}", package.typedefs.len()); + eprintln!("Subroutines: {}", package.subroutines.len()); + + // Check package scope + let pkg_scope = db.package_scope(pkg_id); + eprintln!("\n=== Package Scope Contents ==="); + for (name, entry) in pkg_scope.iter() { + eprintln!(" {} => {:?}", name, entry); + } + + // Check module scope + let mut mod_id = None; + for (idx, mod_info) in file.modules.iter() { + if mod_info.name.as_ref().map(|n| n.as_str()) == Some("test") { + mod_id = Some(ModuleId { file_id: hir_file_id, value: idx }); + break; + } + } + let mod_id = mod_id.expect("module test should exist"); + + let mod_scope = db.module_scope(mod_id); + eprintln!("\n=== Module Scope Contents ==="); + for (name, entry) in mod_scope.iter() { + eprintln!(" {} => {:?}", name, entry); + } + + // Check if items are imported + assert!(pkg_scope.get(&SmolStr::new("byte_t")).is_some(), "byte_t should be in package scope"); + assert!(pkg_scope.get(&SmolStr::new("MAX_SIZE")).is_some(), "MAX_SIZE should be in package scope"); + assert!(pkg_scope.get(&SmolStr::new("get_size")).is_some(), "get_size should be in package scope"); + + assert!(mod_scope.get(&SmolStr::new("byte_t")).is_some(), "byte_t should be imported into module"); + assert!(mod_scope.get(&SmolStr::new("MAX_SIZE")).is_some(), "MAX_SIZE should be imported into module"); + assert!(mod_scope.get(&SmolStr::new("get_size")).is_some(), "get_size should be imported into module"); + } + + #[test] + fn debug_completion_in_block() { + use crate::semantics::Semantics; + use utils::text_edit::TextSize; + + let source = r#" +package my_pkg; + typedef logic [7:0] byte_t; + parameter int MAX_SIZE = 256; + + function int fetch_size(); + return MAX_SIZE; + endfunction +endpackage + +module test; + import my_pkg::*; + + initial begin + byte_t data; + int size = fetch_ + end +endmodule +"#; + + let (db, hir_file_id) = setup_db(source); + + // Find the offset of "= fetch_" in the module (not the function definition) + let pattern_pos = source.find("= fetch_").unwrap(); + let fetch_pos = pattern_pos + "= ".len(); + let offset = TextSize::from((fetch_pos + "fetch_".len()) as u32); + + eprintln!("\n=== Completion Debug ==="); + eprintln!("Source around offset:"); + let start = fetch_pos.saturating_sub(20); + let end = (fetch_pos + 30).min(source.len()); + eprintln!(" '{}'", &source[start..end]); + eprintln!("Offset: {:?} (bytes from start of source string)", offset); + + let sema = Semantics::new(&db); + let completions = sema.scope_completions(hir_file_id.file_id(), offset); + + eprintln!("\n=== Completion Results ==="); + eprintln!("Total items: {}", completions.len()); + for item in &completions { + eprintln!(" {} => {:?} (scope: {:?})", item.entry.name, item.entry.kind, item.scope); + } + + // Check if fetch_size is present + let has_fetch_size = completions.iter().any(|item| item.entry.name == "fetch_size"); + eprintln!("\n=== Has fetch_size: {} ===", has_fetch_size); + + assert!(has_fetch_size, "fetch_size should be present in completions"); + } } diff --git a/crates/hir/src/semantics/completion.rs b/crates/hir/src/semantics/completion.rs index ab9f8efe..e73fcd8f 100644 --- a/crates/hir/src/semantics/completion.rs +++ b/crates/hir/src/semantics/completion.rs @@ -293,6 +293,7 @@ impl<'db> SemanticsImpl<'db> { UnitEntry::ClassId(class_id) => Some(ScopeResolutionTarget::Class(class_id.into())), UnitEntry::FiledDeclId(_) => None, UnitEntry::TypedefId(_) => None, + UnitEntry::SubroutineId(_) => None, } } diff --git a/crates/hir/src/semantics/pathres.rs b/crates/hir/src/semantics/pathres.rs index 3686d813..479e24c6 100644 --- a/crates/hir/src/semantics/pathres.rs +++ b/crates/hir/src/semantics/pathres.rs @@ -94,6 +94,7 @@ impl SemanticsImpl<'_> { UnitEntry::TypedefId(_) => None, UnitEntry::ClassId(_) => None, UnitEntry::PackageId(_) => None, + UnitEntry::SubroutineId(_) => None, } } @@ -134,6 +135,7 @@ impl From for PathResolution { TypedefId(idx) => Self::Typedef(idx.into()), ClassId(idx) => Self::Class(idx.into()), PackageId(idx) => Self::Package(idx), + SubroutineId(idx) => Self::Subroutine(idx.into()), } } } From e34c7cd389e52ae320e9b87853725dea80e08baf Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Tue, 4 Nov 2025 20:20:10 +0800 Subject: [PATCH 18/18] refactor(hir): scope --- crates/hir/src/completion/mod.rs | 7 + crates/hir/src/completion/scope.rs | 715 ++++++++++++ crates/hir/src/hir_def/file.rs | 8 +- crates/hir/src/hir_def/module.rs | 8 +- crates/hir/src/hir_def/package.rs | 8 +- crates/hir/src/scope.rs | 1424 +----------------------- crates/hir/src/semantics/completion.rs | 5 +- 7 files changed, 742 insertions(+), 1433 deletions(-) create mode 100644 crates/hir/src/completion/scope.rs diff --git a/crates/hir/src/completion/mod.rs b/crates/hir/src/completion/mod.rs index 973d924d..74acb136 100644 --- a/crates/hir/src/completion/mod.rs +++ b/crates/hir/src/completion/mod.rs @@ -1,3 +1,10 @@ +pub mod scope; + +pub use scope::{ + BlockScopeCompletionExt, ModuleScopeCompletionExt, PackageScopeCompletionExt, + SubroutineScopeCompletionExt, UnitScopeCompletionExt, +}; + use crate::hir_def::Ident; #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/hir/src/completion/scope.rs b/crates/hir/src/completion/scope.rs new file mode 100644 index 00000000..559920a6 --- /dev/null +++ b/crates/hir/src/completion/scope.rs @@ -0,0 +1,715 @@ +use rustc_hash::FxHashMap; +use triomphe::Arc; +use utils::get::GetRef; + +use crate::{ + completion::{CompletionEntry, CompletionEntryKind}, + container::{ContainerId, InContainer, InFile, InModule, InPackage}, + db::HirDb, + display::HirDisplay, + file::HirFileId, + hir_def::{ + Ident, + aggregate::{ClassId, StructId}, + block::{Block, BlockId, BlockInfo}, + declaration::Declaration, + expr::{ + data_ty::DataTy, + declarator::{DeclId, DeclaratorParent}, + }, + module::{ + Module, ModuleId, + instantiation::InstanceId, + port::{PortDirection, PortHeader}, + }, + package::{Package, PackageId, PackageImportMember}, + proc::ProcId, + stmt::{StmtId, StmtKind}, + subroutine::{Subroutine, SubroutineId, SubroutineKind}, + ty::NetKind, + typedef::{Typedef, TypedefId}, + }, + scope::{ + AnsiPortEntry, BlockEntry, ModuleEntry, NonAnsiPortEntry, PackageEntry, PackageImportEntry, + Scope as RawScope, SubroutineEntry, UnitEntry, + }, +}; + +pub trait UnitScopeCompletionExt { + fn collect_completions(&self, db: &dyn HirDb) -> Vec; +} + +impl UnitScopeCompletionExt for RawScope { + fn collect_completions(&self, db: &dyn HirDb) -> Vec { + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + match entry { + UnitEntry::ModuleId(_) => { + items.push(make_entry(ident, CompletionEntryKind::Module)); + } + UnitEntry::PackageId(_) => { + items.push(make_entry(ident, CompletionEntryKind::Module)); + } + UnitEntry::FiledDeclId(in_file) => { + let file = db.hir_file(in_file.file_id); + let declarator = file.decls.get(in_file.value); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match file.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = file_decl_detail(db, in_file.file_id, &file, in_file.value); + items.push(make_entry_with_detail(ident, kind, detail)); + } + UnitEntry::TypedefId(in_file) => { + let file = db.hir_file(in_file.file_id); + let typedef = file.typedefs.get(in_file.value); + let detail = + typedef_detail(db, ContainerId::HirFileId(in_file.file_id), typedef); + items.push(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)); + } + UnitEntry::ClassId(_) => { + items.push(make_entry(ident, CompletionEntryKind::Type)); + } + UnitEntry::SubroutineId(in_file) => { + let file = db.hir_file(in_file.file_id); + let subroutine = file.subroutines.get(in_file.value); + let detail = match subroutine.kind { + SubroutineKind::Task => "task".to_string(), + SubroutineKind::Function { .. } => "function".to_string(), + }; + items.push(make_entry_with_detail( + ident, + CompletionEntryKind::Function, + Some(detail), + )); + } + } + } + + items + } +} + +pub trait ModuleScopeCompletionExt { + fn collect_completions(&self, db: &dyn HirDb, module_id: ModuleId) -> Vec; +} + +impl ModuleScopeCompletionExt for RawScope { + fn collect_completions(&self, db: &dyn HirDb, module_id: ModuleId) -> Vec { + let module = db.module(module_id); + let mut package_cache: FxHashMap> = FxHashMap::default(); + let mut get_package = |pkg_id: PackageId| { + package_cache.entry(pkg_id).or_insert_with(|| db.package(pkg_id)).clone() + }; + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + let completion = match entry { + ModuleEntry::DeclId(decl_id) => { + let declarator = module.decls.get(*decl_id); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match module.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = module_decl_detail(db, module_id, &module, *decl_id); + Some(make_entry_with_detail(ident, kind, detail)) + } + ModuleEntry::NonAnsiPortEntry(port_entry) => { + let detail = non_ansi_port_detail(db, module_id, &module, port_entry); + Some(make_entry_with_detail(ident, CompletionEntryKind::Port, detail)) + } + ModuleEntry::AnsiPortEntry(AnsiPortEntry(decl_id)) => { + let detail = module_port_detail(db, module_id, &module, *decl_id); + Some(make_entry_with_detail(ident, CompletionEntryKind::Port, detail)) + } + ModuleEntry::InstanceId(_) => { + Some(make_entry(ident, CompletionEntryKind::Instance)) + } + ModuleEntry::StmtId(_) => Some(make_entry(ident, CompletionEntryKind::Statement)), + ModuleEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), + ModuleEntry::TypedefId(typedef_id) => { + let typedef = module.typedefs.get(*typedef_id); + let detail = typedef_detail(db, ContainerId::ModuleId(module_id), typedef); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + ModuleEntry::ClassId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + ModuleEntry::PackageImportEntry(entry) => { + let import = module.package_imports.get(entry.import); + if let Some(item) = import.items.get(entry.item_idx as usize) { + let detail = match &item.member { + PackageImportMember::Named(name) => { + format!("import {}::{}", item.package, name) + } + PackageImportMember::All => { + format!("import {}::*", item.package) + } + }; + Some(make_entry_with_detail( + ident, + CompletionEntryKind::Import, + Some(detail), + )) + } else { + Some(make_entry(ident, CompletionEntryKind::Import)) + } + } + ModuleEntry::SubroutineId(sub_id) => { + let sub = module.subroutines.get(*sub_id); + let detail = match sub.kind { + SubroutineKind::Task => "task".to_string(), + SubroutineKind::Function { .. } => "function".to_string(), + }; + Some(make_entry_with_detail(ident, CompletionEntryKind::Function, Some(detail))) + } + ModuleEntry::PackageMember(pkg_entry) => match pkg_entry { + PackageEntry::DeclId(in_pkg_decl) => { + let package = get_package(in_pkg_decl.package_id); + let declarator = package.decls.get(in_pkg_decl.value); + let (kind, base_detail) = match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = package.declarations.get(declaration_id); + let kind = match declaration { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + }; + let detail = declaration_detail( + db, + ContainerId::PackageId(in_pkg_decl.package_id), + declaration, + ); + (kind, detail) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => { + (CompletionEntryKind::Variable, None) + } + }; + Some(make_entry_with_detail(ident, kind, base_detail)) + } + PackageEntry::TypedefId(in_pkg_typedef) => { + let package = get_package(in_pkg_typedef.package_id); + let typedef = package.typedefs.get(in_pkg_typedef.value); + let detail = typedef_detail( + db, + ContainerId::PackageId(in_pkg_typedef.package_id), + typedef, + ); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + PackageEntry::ClassId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + PackageEntry::StructId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + PackageEntry::ProcId(_) => { + Some(make_entry(ident, CompletionEntryKind::Function)) + } + PackageEntry::SubroutineId(in_pkg_sub) => { + let package = get_package(in_pkg_sub.package_id); + let sub = package.subroutines.get(in_pkg_sub.value); + let detail = match sub.kind { + SubroutineKind::Task => "task".to_string(), + SubroutineKind::Function { .. } => "function".to_string(), + }; + Some(make_entry_with_detail( + ident, + CompletionEntryKind::Function, + Some(detail), + )) + } + PackageEntry::Package(_) => Some(make_entry_with_detail( + ident, + CompletionEntryKind::Module, + Some(String::from("package")), + )), + }, + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } +} + +pub trait BlockScopeCompletionExt { + fn collect_completions(&self, db: &dyn HirDb, block_id: BlockId) -> Vec; +} + +impl BlockScopeCompletionExt for RawScope { + fn collect_completions(&self, db: &dyn HirDb, block_id: BlockId) -> Vec { + let block = db.block(block_id); + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + let completion = match entry { + BlockEntry::DeclId(decl_id) => { + let declarator = block.decls.get(*decl_id); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match block.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = block_decl_detail(db, block_id, &block, *decl_id); + Some(make_entry_with_detail(ident, kind, detail)) + } + BlockEntry::StmtId(_) => Some(make_entry(ident, CompletionEntryKind::Statement)), + BlockEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), + BlockEntry::TypedefId(typedef_id) => { + let typedef = block.typedefs.get(*typedef_id); + let detail = typedef_detail(db, ContainerId::BlockId(block_id), typedef); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } +} + +pub trait SubroutineScopeCompletionExt { + fn collect_completions( + &self, + db: &dyn HirDb, + subroutine_loc: InModule, + ) -> Vec; + + fn collect_file_subroutine_completions( + &self, + db: &dyn HirDb, + subroutine_loc: InFile, + ) -> Vec; +} + +impl SubroutineScopeCompletionExt for RawScope { + fn collect_completions( + &self, + db: &dyn HirDb, + subroutine_loc: InModule, + ) -> Vec { + let subroutine = db.subroutine(subroutine_loc); + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + let completion = match entry { + SubroutineEntry::DeclId(decl_id) => { + let declarator = subroutine.decls.get(*decl_id); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match subroutine.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = subroutine_decl_detail(db, subroutine_loc, &subroutine, *decl_id); + Some(make_entry_with_detail(ident, kind, detail)) + } + SubroutineEntry::StmtId(_) => { + Some(make_entry(ident, CompletionEntryKind::Statement)) + } + SubroutineEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), + SubroutineEntry::TypedefId(typedef_id) => { + let typedef = subroutine.typedefs.get(*typedef_id); + let detail = + typedef_detail(db, ContainerId::SubroutineId(subroutine_loc), typedef); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } + + fn collect_file_subroutine_completions( + &self, + db: &dyn HirDb, + subroutine_loc: InFile, + ) -> Vec { + let subroutine = subroutine_loc.to_container(db); + let mut items = Vec::new(); + + for (ident, entry) in self.iter() { + let completion = match entry { + SubroutineEntry::DeclId(decl_id) => { + let declarator = subroutine.decls.get(*decl_id); + let kind = match declarator.parent { + DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, + DeclaratorParent::DeclarationId(declaration_id) => { + match subroutine.declarations.get(declaration_id) { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + } + } + DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, + }; + let detail = + file_subroutine_decl_detail(db, subroutine_loc, &subroutine, *decl_id); + Some(make_entry_with_detail(ident, kind, detail)) + } + SubroutineEntry::StmtId(_) => { + Some(make_entry(ident, CompletionEntryKind::Statement)) + } + SubroutineEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), + SubroutineEntry::TypedefId(typedef_id) => { + let typedef = subroutine.typedefs.get(*typedef_id); + let detail = + typedef_detail(db, ContainerId::FileSubroutineId(subroutine_loc), typedef); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } +} + +pub trait PackageScopeCompletionExt { + fn collect_completions(&self, db: &dyn HirDb, package_id: PackageId) -> Vec; +} + +impl PackageScopeCompletionExt for RawScope { + fn collect_completions(&self, db: &dyn HirDb, package_id: PackageId) -> Vec { + let mut items = Vec::new(); + let mut package_cache: FxHashMap> = FxHashMap::default(); + + let mut get_package = |pkg_id: PackageId| { + package_cache.entry(pkg_id).or_insert_with(|| db.package(pkg_id)).clone() + }; + + // Ensure the current package is cached for direct lookups. + let _ = get_package(package_id); + + for (ident, entry) in self.iter() { + let completion = match entry { + PackageEntry::DeclId(in_pkg_decl) => { + let pkg = get_package(in_pkg_decl.package_id); + let declarator = pkg.decls.get(in_pkg_decl.value); + let (kind, detail) = match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = pkg.declarations.get(declaration_id); + let kind = match declaration { + Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, + Declaration::NetDecl(_) => CompletionEntryKind::Net, + Declaration::DataDecl(_) => CompletionEntryKind::Variable, + }; + let detail = declaration_detail( + db, + ContainerId::PackageId(in_pkg_decl.package_id), + declaration, + ); + (kind, detail) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => { + (CompletionEntryKind::Variable, None) + } + }; + Some(make_entry_with_detail(ident, kind, detail)) + } + PackageEntry::TypedefId(in_pkg_typedef) => { + let pkg = get_package(in_pkg_typedef.package_id); + let typedef = pkg.typedefs.get(in_pkg_typedef.value); + let detail = typedef_detail( + db, + ContainerId::PackageId(in_pkg_typedef.package_id), + typedef, + ); + Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) + } + PackageEntry::ClassId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + PackageEntry::StructId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), + PackageEntry::ProcId(_) => Some(make_entry(ident, CompletionEntryKind::Function)), + PackageEntry::SubroutineId(in_pkg_sub) => { + let pkg = get_package(in_pkg_sub.package_id); + let sub = pkg.subroutines.get(in_pkg_sub.value); + let detail = match sub.kind { + SubroutineKind::Task => "task".to_string(), + SubroutineKind::Function { .. } => "function".to_string(), + }; + Some(make_entry_with_detail(ident, CompletionEntryKind::Function, Some(detail))) + } + PackageEntry::Package(_) => Some(make_entry_with_detail( + ident, + CompletionEntryKind::Module, + Some(String::from("package")), + )), + }; + + if let Some(entry) = completion { + items.push(entry); + } + } + + items + } +} +fn make_entry(ident: &Ident, kind: CompletionEntryKind) -> CompletionEntry { + make_entry_with_detail(ident, kind, None) +} + +fn make_entry_with_detail( + ident: &Ident, + kind: CompletionEntryKind, + detail: Option, +) -> CompletionEntry { + match detail { + Some(detail) => CompletionEntry::new(ident.clone(), kind).with_detail(detail), + None => CompletionEntry::new(ident.clone(), kind).with_detail(kind.as_str()), + } +} + +fn data_ty_signature(db: &dyn HirDb, container_id: ContainerId, ty: DataTy) -> Option { + InContainer::new(container_id, ty).display_signature(db).ok() +} + +fn typedef_detail(db: &dyn HirDb, container_id: ContainerId, typedef: &Typedef) -> Option { + typedef.ty.and_then(|ty| data_ty_signature(db, container_id, ty)) +} + +fn port_direction_str(dir: PortDirection) -> &'static str { + match dir { + PortDirection::Input => "input", + PortDirection::Output => "output", + PortDirection::Ref => "ref", + PortDirection::Inout => "inout", + } +} + +fn net_kind_str(kind: NetKind) -> &'static str { + match kind { + NetKind::Supply0 => "supply0", + NetKind::Supply1 => "supply1", + NetKind::Tri => "tri", + NetKind::Triand => "triand", + NetKind::Trior => "trior", + NetKind::Tri0 => "tri0", + NetKind::Tri1 => "tri1", + NetKind::Wire => "wire", + NetKind::Wand => "wand", + NetKind::Wor => "wor", + NetKind::Uwire => "uwire", + } +} + +fn port_header_detail(db: &dyn HirDb, module_id: ModuleId, header: &PortHeader) -> Option { + let mut parts: Vec = Vec::new(); + + if let Some(dir) = header.dir() { + parts.push(port_direction_str(dir).to_string()); + } + + match header { + PortHeader::Var { var_kw, ty, .. } => { + if *var_kw { + parts.push("var".to_string()); + } + if let Some(sig) = data_ty_signature(db, ContainerId::ModuleId(module_id), *ty) { + parts.push(sig); + } + } + PortHeader::Net { net_ty, .. } => { + parts.push(net_kind_str(net_ty.kind).to_string()); + if let Some(sig) = data_ty_signature(db, ContainerId::ModuleId(module_id), net_ty.ty) { + parts.push(sig); + } + } + } + + if parts.is_empty() { None } else { Some(parts.join(" ")) } +} + +fn module_port_detail( + db: &dyn HirDb, + module_id: ModuleId, + module: &Module, + decl_id: DeclId, +) -> Option { + let declarator = module.decls.get(decl_id); + let DeclaratorParent::PortDeclId(port_decl_id) = declarator.parent else { + return None; + }; + let port_decl = module.ports.get(port_decl_id); + port_header_detail(db, module_id, &port_decl.header) +} + +fn module_decl_detail( + db: &dyn HirDb, + module_id: ModuleId, + module: &Module, + decl_id: DeclId, +) -> Option { + let declarator = module.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::PortDeclId(_) => module_port_detail(db, module_id, module, decl_id), + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = module.declarations.get(declaration_id); + declaration_detail(db, ContainerId::ModuleId(module_id), declaration) + } + DeclaratorParent::StmtId(_) => None, + } +} + +fn non_ansi_port_detail( + db: &dyn HirDb, + module_id: ModuleId, + module: &Module, + entry: &NonAnsiPortEntry, +) -> Option { + if let Some(port_decl) = entry.port_decl + && let Some(detail) = module_port_detail(db, module_id, module, port_decl) + { + return Some(detail); + } + + entry.data_decl.and_then(|decl_id| module_decl_detail(db, module_id, module, decl_id)) +} + +fn declaration_detail( + db: &dyn HirDb, + container_id: ContainerId, + declaration: &Declaration, +) -> Option { + use Declaration::*; + match declaration { + DataDecl(data_decl) => { + let mut parts = Vec::new(); + if data_decl.const_kw { + parts.push("const".to_string()); + } + if data_decl.var_kw { + parts.push("var".to_string()); + } + if let Some(sig) = data_ty_signature(db, container_id, data_decl.ty) { + parts.push(sig); + } + if parts.is_empty() { None } else { Some(parts.join(" ")) } + } + NetDecl(net_decl) => { + let mut parts = Vec::new(); + if let Some(kind) = net_decl.net_kind { + parts.push(net_kind_str(kind).to_string()); + } + if let Some(sig) = data_ty_signature(db, container_id, net_decl.ty) { + parts.push(sig); + } + if parts.is_empty() { None } else { Some(parts.join(" ")) } + } + ParamDecl(param_decl) => data_ty_signature(db, container_id, param_decl.ty), + } +} + +fn block_decl_detail( + db: &dyn HirDb, + block_id: BlockId, + block: &Block, + decl_id: DeclId, +) -> Option { + let declarator = block.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = block.declarations.get(declaration_id); + declaration_detail(db, ContainerId::BlockId(block_id), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} + +fn subroutine_decl_detail( + db: &dyn HirDb, + loc: InModule, + subroutine: &Subroutine, + decl_id: DeclId, +) -> Option { + let declarator = subroutine.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = subroutine.declarations.get(declaration_id); + declaration_detail(db, ContainerId::SubroutineId(loc), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} + +fn file_subroutine_decl_detail( + db: &dyn HirDb, + loc: InFile, + subroutine: &Subroutine, + decl_id: DeclId, +) -> Option { + let declarator = subroutine.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = subroutine.declarations.get(declaration_id); + declaration_detail(db, ContainerId::FileSubroutineId(loc), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} + +fn package_decl_detail( + db: &dyn HirDb, + package_id: PackageId, + package: &Package, + decl_id: DeclId, +) -> Option { + let declarator = package.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = package.declarations.get(declaration_id); + declaration_detail(db, ContainerId::PackageId(package_id), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} + +fn file_decl_detail( + db: &dyn HirDb, + file_id: HirFileId, + file: &crate::hir_def::file::HirFile, + decl_id: DeclId, +) -> Option { + let declarator = file.decls.get(decl_id); + match declarator.parent { + DeclaratorParent::DeclarationId(declaration_id) => { + let declaration = file.declarations.get(declaration_id); + declaration_detail(db, ContainerId::HirFileId(file_id), declaration) + } + DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, + } +} diff --git a/crates/hir/src/hir_def/file.rs b/crates/hir/src/hir_def/file.rs index 8ebb5b75..c6f416b3 100644 --- a/crates/hir/src/hir_def/file.rs +++ b/crates/hir/src/hir_def/file.rs @@ -185,11 +185,9 @@ impl LowerFileCtx<'_> { fn lower_class_decl(&mut self, class_decl: ast::ClassDeclaration) -> ClassId { let container_id = ContainerId::HirFileId(self.file_id); - let class_def = lower_class_def( - class_decl.clone(), - container_id, - |ty| self.expr_ctx().lower_data_ty(ty), - ); + let class_def = lower_class_def(class_decl.clone(), container_id, |ty| { + self.expr_ctx().lower_data_ty(ty) + }); alloc_idx_and_src! { class_def => self.file.classes, diff --git a/crates/hir/src/hir_def/module.rs b/crates/hir/src/hir_def/module.rs index 06146436..a9185fd6 100644 --- a/crates/hir/src/hir_def/module.rs +++ b/crates/hir/src/hir_def/module.rs @@ -265,11 +265,9 @@ impl LowerModuleCtx<'_> { fn lower_class_decl(&mut self, class_decl: ast::ClassDeclaration) -> ClassId { let container_id = ContainerId::ModuleId(self.module_id); - let class_def = lower_class_def( - class_decl.clone(), - container_id, - |ty| self.expr_ctx().lower_data_ty(ty), - ); + let class_def = lower_class_def(class_decl.clone(), container_id, |ty| { + self.expr_ctx().lower_data_ty(ty) + }); alloc_idx_and_src! { class_def => self.module.classes, diff --git a/crates/hir/src/hir_def/package.rs b/crates/hir/src/hir_def/package.rs index aeb8b2e8..dcb0941a 100644 --- a/crates/hir/src/hir_def/package.rs +++ b/crates/hir/src/hir_def/package.rs @@ -431,11 +431,9 @@ impl LowerPackageCtx<'_> { fn lower_class_decl(&mut self, class_decl: ast::ClassDeclaration) -> ClassId { let container_id = ContainerId::PackageId(self.package_id); - let class_def = lower_class_def( - class_decl.clone(), - container_id, - |ty| self.expr_ctx().lower_data_ty(ty), - ); + let class_def = lower_class_def(class_decl.clone(), container_id, |ty| { + self.expr_ctx().lower_data_ty(ty) + }); alloc_idx_and_src! { class_def => self.package.classes, diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index e89c59b7..813cc85e 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -1,34 +1,27 @@ use rustc_hash::FxHashMap; use smol_str::SmolStr; use triomphe::Arc; -use utils::{define_enum_deriving_from, get::GetRef}; +use utils::define_enum_deriving_from; use crate::{ - completion::{CompletionEntry, CompletionEntryKind}, - container::{ContainerId, InContainer, InFile, InModule, InPackage}, + container::{InFile, InModule, InPackage}, db::HirDb, - display::HirDisplay, file::HirFileId, hir_def::{ Ident, aggregate::{ClassId, StructId}, - block::{Block, BlockId, BlockInfo}, - declaration::Declaration, - expr::{ - data_ty::DataTy, - declarator::{DeclId, DeclaratorParent}, - }, + block::{BlockId, BlockInfo}, + expr::declarator::{DeclId, DeclaratorParent}, module::{ - Module, ModuleId, + ModuleId, instantiation::InstanceId, - port::{NonAnsiPortId, PortDirection, PortHeader, Ports}, + port::{NonAnsiPortId, Ports}, }, package::{Package, PackageExport, PackageId, PackageImportId, PackageImportMember}, proc::ProcId, stmt::{StmtId, StmtKind}, - subroutine::{Subroutine, SubroutineId, SubroutineKind}, - ty::NetKind, - typedef::{Typedef, TypedefId}, + subroutine::SubroutineId, + typedef::TypedefId, }, }; @@ -197,59 +190,6 @@ impl UnitScope { Arc::new(scope) } - - pub fn collect_completions(&self, db: &dyn HirDb) -> Vec { - let mut items = Vec::new(); - - for (ident, entry) in self.iter() { - match entry { - UnitEntry::ModuleId(_) => { - items.push(make_entry(ident, CompletionEntryKind::Module)); - } - UnitEntry::PackageId(_) => { - items.push(make_entry(ident, CompletionEntryKind::Module)); - } - UnitEntry::FiledDeclId(in_file) => { - let file = db.hir_file(in_file.file_id); - let declarator = file.decls.get(in_file.value); - let kind = match declarator.parent { - DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, - DeclaratorParent::DeclarationId(declaration_id) => { - match file.declarations.get(declaration_id) { - Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, - Declaration::NetDecl(_) => CompletionEntryKind::Net, - Declaration::DataDecl(_) => CompletionEntryKind::Variable, - } - } - DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, - }; - let detail = file_decl_detail(db, in_file.file_id, &file, in_file.value); - items.push(make_entry_with_detail(ident, kind, detail)); - } - UnitEntry::TypedefId(in_file) => { - let file = db.hir_file(in_file.file_id); - let typedef = file.typedefs.get(in_file.value); - let detail = - typedef_detail(db, ContainerId::HirFileId(in_file.file_id), typedef); - items.push(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)); - } - UnitEntry::ClassId(_) => { - items.push(make_entry(ident, CompletionEntryKind::Type)); - } - UnitEntry::SubroutineId(in_file) => { - let file = db.hir_file(in_file.file_id); - let subroutine = file.subroutines.get(in_file.value); - let detail = match subroutine.kind { - SubroutineKind::Task => "task".to_string(), - SubroutineKind::Function { .. } => "function".to_string(), - }; - items.push(make_entry_with_detail(ident, CompletionEntryKind::Function, Some(detail))); - } - } - } - - items - } } impl ModuleScope { @@ -365,148 +305,6 @@ impl ModuleScope { Arc::new(scope) } - - pub fn collect_completions(&self, db: &dyn HirDb, module_id: ModuleId) -> Vec { - let module = db.module(module_id); - let mut package_cache: FxHashMap> = FxHashMap::default(); - let mut get_package = |pkg_id: PackageId| { - package_cache.entry(pkg_id).or_insert_with(|| db.package(pkg_id)).clone() - }; - let mut items = Vec::new(); - - for (ident, entry) in self.iter() { - let completion = match entry { - ModuleEntry::DeclId(decl_id) => { - let declarator = module.decls.get(*decl_id); - let kind = match declarator.parent { - DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, - DeclaratorParent::DeclarationId(declaration_id) => { - match module.declarations.get(declaration_id) { - Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, - Declaration::NetDecl(_) => CompletionEntryKind::Net, - Declaration::DataDecl(_) => CompletionEntryKind::Variable, - } - } - DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, - }; - let detail = module_decl_detail(db, module_id, &module, *decl_id); - Some(make_entry_with_detail(ident, kind, detail)) - } - ModuleEntry::NonAnsiPortEntry(port_entry) => { - let detail = non_ansi_port_detail(db, module_id, &module, port_entry); - Some(make_entry_with_detail(ident, CompletionEntryKind::Port, detail)) - } - ModuleEntry::AnsiPortEntry(AnsiPortEntry(decl_id)) => { - let detail = module_port_detail(db, module_id, &module, *decl_id); - Some(make_entry_with_detail(ident, CompletionEntryKind::Port, detail)) - } - ModuleEntry::InstanceId(_) => { - Some(make_entry(ident, CompletionEntryKind::Instance)) - } - ModuleEntry::StmtId(_) => Some(make_entry(ident, CompletionEntryKind::Statement)), - ModuleEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), - ModuleEntry::TypedefId(typedef_id) => { - let typedef = module.typedefs.get(*typedef_id); - let detail = typedef_detail(db, ContainerId::ModuleId(module_id), typedef); - Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) - } - ModuleEntry::ClassId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), - ModuleEntry::PackageImportEntry(entry) => { - let import = module.package_imports.get(entry.import); - if let Some(item) = import.items.get(entry.item_idx as usize) { - let detail = match &item.member { - PackageImportMember::Named(name) => { - format!("import {}::{}", item.package, name) - } - PackageImportMember::All => { - format!("import {}::*", item.package) - } - }; - Some(make_entry_with_detail( - ident, - CompletionEntryKind::Import, - Some(detail), - )) - } else { - Some(make_entry(ident, CompletionEntryKind::Import)) - } - } - ModuleEntry::SubroutineId(sub_id) => { - let sub = module.subroutines.get(*sub_id); - let detail = match sub.kind { - SubroutineKind::Task => "task".to_string(), - SubroutineKind::Function { .. } => "function".to_string(), - }; - Some(make_entry_with_detail(ident, CompletionEntryKind::Function, Some(detail))) - } - ModuleEntry::PackageMember(pkg_entry) => match pkg_entry { - PackageEntry::DeclId(in_pkg_decl) => { - let package = get_package(in_pkg_decl.package_id); - let declarator = package.decls.get(in_pkg_decl.value); - let (kind, base_detail) = match declarator.parent { - DeclaratorParent::DeclarationId(declaration_id) => { - let declaration = package.declarations.get(declaration_id); - let kind = match declaration { - Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, - Declaration::NetDecl(_) => CompletionEntryKind::Net, - Declaration::DataDecl(_) => CompletionEntryKind::Variable, - }; - let detail = declaration_detail( - db, - ContainerId::PackageId(in_pkg_decl.package_id), - declaration, - ); - (kind, detail) - } - DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => { - (CompletionEntryKind::Variable, None) - } - }; - Some(make_entry_with_detail(ident, kind, base_detail)) - } - PackageEntry::TypedefId(in_pkg_typedef) => { - let package = get_package(in_pkg_typedef.package_id); - let typedef = package.typedefs.get(in_pkg_typedef.value); - let detail = typedef_detail( - db, - ContainerId::PackageId(in_pkg_typedef.package_id), - typedef, - ); - Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) - } - PackageEntry::ClassId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), - PackageEntry::StructId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), - PackageEntry::ProcId(_) => { - Some(make_entry(ident, CompletionEntryKind::Function)) - } - PackageEntry::SubroutineId(in_pkg_sub) => { - let package = get_package(in_pkg_sub.package_id); - let sub = package.subroutines.get(in_pkg_sub.value); - let detail = match sub.kind { - SubroutineKind::Task => "task".to_string(), - SubroutineKind::Function { .. } => "function".to_string(), - }; - Some(make_entry_with_detail( - ident, - CompletionEntryKind::Function, - Some(detail), - )) - } - PackageEntry::Package(_) => Some(make_entry_with_detail( - ident, - CompletionEntryKind::Module, - Some(String::from("package")), - )), - }, - }; - - if let Some(entry) = completion { - items.push(entry); - } - } - - items - } } impl BlockScope { @@ -532,45 +330,6 @@ impl BlockScope { Arc::new(scope) } - - pub fn collect_completions(&self, db: &dyn HirDb, block_id: BlockId) -> Vec { - let block = db.block(block_id); - let mut items = Vec::new(); - - for (ident, entry) in self.iter() { - let completion = match entry { - BlockEntry::DeclId(decl_id) => { - let declarator = block.decls.get(*decl_id); - let kind = match declarator.parent { - DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, - DeclaratorParent::DeclarationId(declaration_id) => { - match block.declarations.get(declaration_id) { - Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, - Declaration::NetDecl(_) => CompletionEntryKind::Net, - Declaration::DataDecl(_) => CompletionEntryKind::Variable, - } - } - DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, - }; - let detail = block_decl_detail(db, block_id, &block, *decl_id); - Some(make_entry_with_detail(ident, kind, detail)) - } - BlockEntry::StmtId(_) => Some(make_entry(ident, CompletionEntryKind::Statement)), - BlockEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), - BlockEntry::TypedefId(typedef_id) => { - let typedef = block.typedefs.get(*typedef_id); - let detail = typedef_detail(db, ContainerId::BlockId(block_id), typedef); - Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) - } - }; - - if let Some(entry) = completion { - items.push(entry); - } - } - - items - } } impl SubroutineScope { @@ -604,52 +363,6 @@ impl SubroutineScope { Arc::new(scope) } - pub fn collect_completions( - &self, - db: &dyn HirDb, - subroutine_loc: InModule, - ) -> Vec { - let subroutine = db.subroutine(subroutine_loc); - let mut items = Vec::new(); - - for (ident, entry) in self.iter() { - let completion = match entry { - SubroutineEntry::DeclId(decl_id) => { - let declarator = subroutine.decls.get(*decl_id); - let kind = match declarator.parent { - DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, - DeclaratorParent::DeclarationId(declaration_id) => { - match subroutine.declarations.get(declaration_id) { - Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, - Declaration::NetDecl(_) => CompletionEntryKind::Net, - Declaration::DataDecl(_) => CompletionEntryKind::Variable, - } - } - DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, - }; - let detail = subroutine_decl_detail(db, subroutine_loc, &subroutine, *decl_id); - Some(make_entry_with_detail(ident, kind, detail)) - } - SubroutineEntry::StmtId(_) => { - Some(make_entry(ident, CompletionEntryKind::Statement)) - } - SubroutineEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), - SubroutineEntry::TypedefId(typedef_id) => { - let typedef = subroutine.typedefs.get(*typedef_id); - let detail = - typedef_detail(db, ContainerId::SubroutineId(subroutine_loc), typedef); - Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) - } - }; - - if let Some(entry) = completion { - items.push(entry); - } - } - - items - } - pub fn file_subroutine_scope_query( db: &dyn HirDb, subroutine_loc: InFile, @@ -679,288 +392,6 @@ impl SubroutineScope { Arc::new(scope) } - - pub fn collect_file_subroutine_completions( - &self, - db: &dyn HirDb, - subroutine_loc: InFile, - ) -> Vec { - let subroutine = subroutine_loc.to_container(db); - let mut items = Vec::new(); - - for (ident, entry) in self.iter() { - let completion = match entry { - SubroutineEntry::DeclId(decl_id) => { - let declarator = subroutine.decls.get(*decl_id); - let kind = match declarator.parent { - DeclaratorParent::PortDeclId(_) => CompletionEntryKind::Port, - DeclaratorParent::DeclarationId(declaration_id) => { - match subroutine.declarations.get(declaration_id) { - Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, - Declaration::NetDecl(_) => CompletionEntryKind::Net, - Declaration::DataDecl(_) => CompletionEntryKind::Variable, - } - } - DeclaratorParent::StmtId(_) => CompletionEntryKind::Variable, - }; - let detail = - file_subroutine_decl_detail(db, subroutine_loc, &subroutine, *decl_id); - Some(make_entry_with_detail(ident, kind, detail)) - } - SubroutineEntry::StmtId(_) => { - Some(make_entry(ident, CompletionEntryKind::Statement)) - } - SubroutineEntry::BlockId(_) => Some(make_entry(ident, CompletionEntryKind::Block)), - SubroutineEntry::TypedefId(typedef_id) => { - let typedef = subroutine.typedefs.get(*typedef_id); - let detail = - typedef_detail(db, ContainerId::FileSubroutineId(subroutine_loc), typedef); - Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) - } - }; - - if let Some(entry) = completion { - items.push(entry); - } - } - - items - } -} - -fn make_entry(ident: &Ident, kind: CompletionEntryKind) -> CompletionEntry { - make_entry_with_detail(ident, kind, None) -} - -fn make_entry_with_detail( - ident: &Ident, - kind: CompletionEntryKind, - detail: Option, -) -> CompletionEntry { - match detail { - Some(detail) => CompletionEntry::new(ident.clone(), kind).with_detail(detail), - None => CompletionEntry::new(ident.clone(), kind).with_detail(kind.as_str()), - } -} - -fn data_ty_signature(db: &dyn HirDb, container_id: ContainerId, ty: DataTy) -> Option { - InContainer::new(container_id, ty).display_signature(db).ok() -} - -fn typedef_detail(db: &dyn HirDb, container_id: ContainerId, typedef: &Typedef) -> Option { - typedef.ty.and_then(|ty| data_ty_signature(db, container_id, ty)) -} - -fn port_direction_str(dir: PortDirection) -> &'static str { - match dir { - PortDirection::Input => "input", - PortDirection::Output => "output", - PortDirection::Ref => "ref", - PortDirection::Inout => "inout", - } -} - -fn net_kind_str(kind: NetKind) -> &'static str { - match kind { - NetKind::Supply0 => "supply0", - NetKind::Supply1 => "supply1", - NetKind::Tri => "tri", - NetKind::Triand => "triand", - NetKind::Trior => "trior", - NetKind::Tri0 => "tri0", - NetKind::Tri1 => "tri1", - NetKind::Wire => "wire", - NetKind::Wand => "wand", - NetKind::Wor => "wor", - NetKind::Uwire => "uwire", - } -} - -fn port_header_detail(db: &dyn HirDb, module_id: ModuleId, header: &PortHeader) -> Option { - let mut parts: Vec = Vec::new(); - - if let Some(dir) = header.dir() { - parts.push(port_direction_str(dir).to_string()); - } - - match header { - PortHeader::Var { var_kw, ty, .. } => { - if *var_kw { - parts.push("var".to_string()); - } - if let Some(sig) = data_ty_signature(db, ContainerId::ModuleId(module_id), *ty) { - parts.push(sig); - } - } - PortHeader::Net { net_ty, .. } => { - parts.push(net_kind_str(net_ty.kind).to_string()); - if let Some(sig) = data_ty_signature(db, ContainerId::ModuleId(module_id), net_ty.ty) { - parts.push(sig); - } - } - } - - if parts.is_empty() { None } else { Some(parts.join(" ")) } -} - -fn module_port_detail( - db: &dyn HirDb, - module_id: ModuleId, - module: &Module, - decl_id: DeclId, -) -> Option { - let declarator = module.decls.get(decl_id); - let DeclaratorParent::PortDeclId(port_decl_id) = declarator.parent else { - return None; - }; - let port_decl = module.ports.get(port_decl_id); - port_header_detail(db, module_id, &port_decl.header) -} - -fn module_decl_detail( - db: &dyn HirDb, - module_id: ModuleId, - module: &Module, - decl_id: DeclId, -) -> Option { - let declarator = module.decls.get(decl_id); - match declarator.parent { - DeclaratorParent::PortDeclId(_) => module_port_detail(db, module_id, module, decl_id), - DeclaratorParent::DeclarationId(declaration_id) => { - let declaration = module.declarations.get(declaration_id); - declaration_detail(db, ContainerId::ModuleId(module_id), declaration) - } - DeclaratorParent::StmtId(_) => None, - } -} - -fn non_ansi_port_detail( - db: &dyn HirDb, - module_id: ModuleId, - module: &Module, - entry: &NonAnsiPortEntry, -) -> Option { - if let Some(port_decl) = entry.port_decl - && let Some(detail) = module_port_detail(db, module_id, module, port_decl) - { - return Some(detail); - } - - entry.data_decl.and_then(|decl_id| module_decl_detail(db, module_id, module, decl_id)) -} - -fn declaration_detail( - db: &dyn HirDb, - container_id: ContainerId, - declaration: &Declaration, -) -> Option { - use Declaration::*; - match declaration { - DataDecl(data_decl) => { - let mut parts = Vec::new(); - if data_decl.const_kw { - parts.push("const".to_string()); - } - if data_decl.var_kw { - parts.push("var".to_string()); - } - if let Some(sig) = data_ty_signature(db, container_id, data_decl.ty) { - parts.push(sig); - } - if parts.is_empty() { None } else { Some(parts.join(" ")) } - } - NetDecl(net_decl) => { - let mut parts = Vec::new(); - if let Some(kind) = net_decl.net_kind { - parts.push(net_kind_str(kind).to_string()); - } - if let Some(sig) = data_ty_signature(db, container_id, net_decl.ty) { - parts.push(sig); - } - if parts.is_empty() { None } else { Some(parts.join(" ")) } - } - ParamDecl(param_decl) => data_ty_signature(db, container_id, param_decl.ty), - } -} - -fn block_decl_detail( - db: &dyn HirDb, - block_id: BlockId, - block: &Block, - decl_id: DeclId, -) -> Option { - let declarator = block.decls.get(decl_id); - match declarator.parent { - DeclaratorParent::DeclarationId(declaration_id) => { - let declaration = block.declarations.get(declaration_id); - declaration_detail(db, ContainerId::BlockId(block_id), declaration) - } - DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, - } -} - -fn subroutine_decl_detail( - db: &dyn HirDb, - loc: InModule, - subroutine: &Subroutine, - decl_id: DeclId, -) -> Option { - let declarator = subroutine.decls.get(decl_id); - match declarator.parent { - DeclaratorParent::DeclarationId(declaration_id) => { - let declaration = subroutine.declarations.get(declaration_id); - declaration_detail(db, ContainerId::SubroutineId(loc), declaration) - } - DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, - } -} - -fn file_subroutine_decl_detail( - db: &dyn HirDb, - loc: InFile, - subroutine: &Subroutine, - decl_id: DeclId, -) -> Option { - let declarator = subroutine.decls.get(decl_id); - match declarator.parent { - DeclaratorParent::DeclarationId(declaration_id) => { - let declaration = subroutine.declarations.get(declaration_id); - declaration_detail(db, ContainerId::FileSubroutineId(loc), declaration) - } - DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, - } -} - -fn package_decl_detail( - db: &dyn HirDb, - package_id: PackageId, - package: &Package, - decl_id: DeclId, -) -> Option { - let declarator = package.decls.get(decl_id); - match declarator.parent { - DeclaratorParent::DeclarationId(declaration_id) => { - let declaration = package.declarations.get(declaration_id); - declaration_detail(db, ContainerId::PackageId(package_id), declaration) - } - DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, - } -} - -fn file_decl_detail( - db: &dyn HirDb, - file_id: HirFileId, - file: &crate::hir_def::file::HirFile, - decl_id: DeclId, -) -> Option { - let declarator = file.decls.get(decl_id); - match declarator.parent { - DeclaratorParent::DeclarationId(declaration_id) => { - let declaration = file.declarations.get(declaration_id); - declaration_detail(db, ContainerId::HirFileId(file_id), declaration) - } - DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => None, - } } impl PackageScope { @@ -1176,843 +607,4 @@ impl PackageScope { Arc::new(scope) } - - pub fn collect_completions( - &self, - db: &dyn HirDb, - package_id: PackageId, - ) -> Vec { - let mut items = Vec::new(); - let mut package_cache: FxHashMap> = FxHashMap::default(); - - let mut get_package = |pkg_id: PackageId| { - package_cache.entry(pkg_id).or_insert_with(|| db.package(pkg_id)).clone() - }; - - // Ensure the current package is cached for direct lookups. - let _ = get_package(package_id); - - for (ident, entry) in self.iter() { - let completion = match entry { - PackageEntry::DeclId(in_pkg_decl) => { - let pkg = get_package(in_pkg_decl.package_id); - let declarator = pkg.decls.get(in_pkg_decl.value); - let (kind, detail) = match declarator.parent { - DeclaratorParent::DeclarationId(declaration_id) => { - let declaration = pkg.declarations.get(declaration_id); - let kind = match declaration { - Declaration::ParamDecl(_) => CompletionEntryKind::Parameter, - Declaration::NetDecl(_) => CompletionEntryKind::Net, - Declaration::DataDecl(_) => CompletionEntryKind::Variable, - }; - let detail = declaration_detail( - db, - ContainerId::PackageId(in_pkg_decl.package_id), - declaration, - ); - (kind, detail) - } - DeclaratorParent::StmtId(_) | DeclaratorParent::PortDeclId(_) => { - (CompletionEntryKind::Variable, None) - } - }; - Some(make_entry_with_detail(ident, kind, detail)) - } - PackageEntry::TypedefId(in_pkg_typedef) => { - let pkg = get_package(in_pkg_typedef.package_id); - let typedef = pkg.typedefs.get(in_pkg_typedef.value); - let detail = typedef_detail( - db, - ContainerId::PackageId(in_pkg_typedef.package_id), - typedef, - ); - Some(make_entry_with_detail(ident, CompletionEntryKind::Type, detail)) - } - PackageEntry::ClassId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), - PackageEntry::StructId(_) => Some(make_entry(ident, CompletionEntryKind::Type)), - PackageEntry::ProcId(_) => Some(make_entry(ident, CompletionEntryKind::Function)), - PackageEntry::SubroutineId(in_pkg_sub) => { - let pkg = get_package(in_pkg_sub.package_id); - let sub = pkg.subroutines.get(in_pkg_sub.value); - let detail = match sub.kind { - SubroutineKind::Task => "task".to_string(), - SubroutineKind::Function { .. } => "function".to_string(), - }; - Some(make_entry_with_detail(ident, CompletionEntryKind::Function, Some(detail))) - } - PackageEntry::Package(_) => Some(make_entry_with_detail( - ident, - CompletionEntryKind::Module, - Some(String::from("package")), - )), - }; - - if let Some(entry) = completion { - items.push(entry); - } - } - - items - } -} - -#[cfg(test)] -mod tests { - use std::fmt; - - use base_db::{ - salsa, - source_db::{FileLoader, SourceDb, SourceRootDb}, - source_root::{SourceRoot, SourceRootId}, - }; - use rustc_hash::FxHashSet; - use smol_str::SmolStr; - use triomphe::Arc; - use vfs::{FileId, FileSet, VfsPath, anchored_path::AnchoredPath}; - - use super::{BlockEntry, ModuleEntry, ModuleScope, PackageEntry, UnitEntry}; - use crate::{ - container::{InFile, InPackage}, - db::HirDb, - file::HirFileId, - hir_def::module::ModuleId, - scope::CompletionEntryKind, - }; - - #[salsa::database( - base_db::source_db::SourceDbStorage, - base_db::source_db::SourceRootDbStorage, - crate::db::InternDbStorage, - crate::db::HirDbStorage - )] - struct TestDb { - storage: salsa::Storage, - } - - impl Default for TestDb { - fn default() -> Self { - TestDb { storage: salsa::Storage::default() } - } - } - - impl fmt::Debug for TestDb { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TestDb").finish() - } - } - - impl salsa::Database for TestDb {} - - impl FileLoader for TestDb { - fn resolve_path(&self, _path: AnchoredPath<'_>) -> Option { - None - } - } - - fn setup_db(text: &str) -> (TestDb, HirFileId) { - let mut db = TestDb::default(); - - let file_id = FileId(0); - - let mut files = FxHashSet::default(); - files.insert(file_id); - db.set_files(Box::new(files)); - - db.set_file_text(file_id, Arc::from(text.to_string())); - - let mut file_set = FileSet::default(); - file_set.insert(file_id, VfsPath::new_virtual_path("/test.sv".into())); - let source_root = SourceRoot::new_local(file_set); - let source_root_id = SourceRootId(0); - db.set_source_root_id(file_id, source_root_id); - db.set_source_root(source_root_id, Arc::new(source_root)); - - (db, HirFileId(file_id)) - } - - fn setup_multi_file_db(files: &[(&str, &str)]) -> (TestDb, Vec) { - let mut db = TestDb::default(); - - let mut file_ids = Vec::with_capacity(files.len()); - let mut files_set = FxHashSet::default(); - - for (idx, _) in files.iter().enumerate() { - let file_id = FileId(idx as u32); - file_ids.push(HirFileId(file_id)); - files_set.insert(file_id); - } - - db.set_files(Box::new(files_set)); - - let mut file_set = FileSet::default(); - let source_root_id = SourceRootId(0); - - for (idx, (path, text)) in files.iter().enumerate() { - let file_id = FileId(idx as u32); - db.set_file_text(file_id, Arc::from((*text).to_string())); - db.set_source_root_id(file_id, source_root_id); - file_set.insert(file_id, VfsPath::new_virtual_path((*path).into())); - } - - let source_root = SourceRoot::new_local(file_set); - db.set_source_root(source_root_id, Arc::new(source_root)); - - (db, file_ids) - } - - fn module_scope(db: &TestDb, file_id: HirFileId, name: &str) -> (ModuleId, Arc) { - let scope = HirDb::file_scope(db, file_id); - let entry = scope.get(&SmolStr::new(name)).expect("module is present in unit scope"); - let module_id = match entry { - UnitEntry::ModuleId(module_id) => module_id, - _ => panic!("expected module entry"), - }; - (module_id, HirDb::module_scope(db, module_id)) - } - - #[test] - fn unit_scope_contains_modules_and_file_declarations() { - let text = r#" -module leaf(); -endmodule - -module top(); -endmodule - -wire global_wire; -"#; - - let (db, file_id) = setup_db(text); - let scope = HirDb::file_scope(&db, file_id); - - let leaf_entry = scope.get(&SmolStr::new("leaf")).expect("leaf present"); - let leaf_module_id = match leaf_entry { - UnitEntry::ModuleId(module_id) => module_id, - _ => panic!("expected module"), - }; - assert_eq!(leaf_module_id.file_id(), file_id.file_id()); - - let global_entry = scope.get(&SmolStr::new("global_wire")).expect("global wire present"); - match global_entry { - UnitEntry::FiledDeclId(InFile { file_id: decl_file, .. }) => { - assert_eq!(decl_file, file_id); - } - _ => panic!("expected file-level declaration"), - } - - let completions = scope.collect_completions(&db); - let labels: Vec<_> = completions.iter().map(|entry| entry.name.clone()).collect(); - assert!(labels.contains(&SmolStr::new("leaf"))); - assert!(labels.contains(&SmolStr::new("top"))); - assert!(labels.contains(&SmolStr::new("global_wire"))); - } - - #[test] - fn unit_scope_includes_top_level_classes() { - let text = r#" -class pkt; - int data; -endclass - -module top; -endmodule -"#; - - let (db, file_id) = setup_db(text); - let scope = HirDb::file_scope(&db, file_id); - - let class_entry = scope.get(&SmolStr::new("pkt")).expect("class present"); - assert!(matches!(class_entry, UnitEntry::ClassId(_))); - - let completions = scope.collect_completions(&db); - let mut seen_class = false; - for entry in completions { - if entry.name.as_str() == "pkt" { - assert_eq!(entry.kind, CompletionEntryKind::Type); - seen_class = true; - } - } - assert!(seen_class, "expected class completion"); - } - - #[test] - fn module_scope_classifies_members() { - let text = r#" -module leaf(); -endmodule - -module non_ansi(clk); - input logic clk; - logic clk; -endmodule - -module top( - input logic a, - output logic b -); - non_ansi u0(.clk(a)); - logic data; - - import pkg::foo; - - class my_class; - int value; - endclass - - initial begin : init_blk - logic inner; - nested_stmt: inner = 1'b0; - begin : nested_blk - end - end -endmodule -"#; - - let (db, file_id) = setup_db(text); - - let (_, non_ansi_scope) = module_scope(&db, file_id, "non_ansi"); - - let clk_entry = non_ansi_scope.get(&SmolStr::new("clk")).expect("clk in scope"); - match clk_entry { - ModuleEntry::NonAnsiPortEntry(entry) => { - assert!(entry.label.is_some(), "non-ANSI port keeps label"); - assert!(entry.port_decl.is_some(), "non-ANSI port tracks port decl"); - assert!(entry.data_decl.is_some(), "non-ANSI port tracks data decl"); - } - _ => panic!("expected non-ANSI port entry"), - } - - let (top_module_id, top_scope) = module_scope(&db, file_id, "top"); - - for name in ["a", "b"] { - let entry = top_scope.get(&SmolStr::new(name)).expect("ANSI port present"); - assert!(matches!(entry, ModuleEntry::AnsiPortEntry(_)), "expected ANSI port entry"); - } - - let data_entry = top_scope.get(&SmolStr::new("data")).expect("module decl"); - assert!(matches!(data_entry, ModuleEntry::DeclId(_))); - - let instance_entry = top_scope.get(&SmolStr::new("u0")).expect("instance present"); - assert!(matches!(instance_entry, ModuleEntry::InstanceId(_))); - - let init_entry = top_scope.get(&SmolStr::new("init_blk")).expect("block in module scope"); - let init_block_id = match init_entry { - ModuleEntry::BlockId(block_id) => block_id, - _ => panic!("expected block id"), - }; - - let class_entry = top_scope.get(&SmolStr::new("my_class")).expect("class present"); - assert!(matches!(class_entry, ModuleEntry::ClassId(_))); - - let import_entry = top_scope.get(&SmolStr::new("foo")).expect("import present"); - match import_entry { - ModuleEntry::PackageImportEntry(entry) => { - assert_eq!(entry.item_idx, 0); - } - _ => panic!("expected package import entry"), - } - - assert!( - top_scope.get(&SmolStr::new("nested_stmt")).is_none(), - "statement labels stay within block scope" - ); - - let block_scope = HirDb::block_scope(&db, init_block_id); - - let inner_decl = block_scope.get(&SmolStr::new("inner")).expect("block declaration"); - assert!(matches!(inner_decl, BlockEntry::DeclId(_))); - - let nested_stmt = block_scope.get(&SmolStr::new("nested_stmt")).expect("stmt label"); - assert!(matches!(nested_stmt, BlockEntry::StmtId(_))); - - let nested_block = block_scope.get(&SmolStr::new("nested_blk")).expect("nested block"); - assert!(matches!(nested_block, BlockEntry::BlockId(_))); - - let module_items = top_scope.collect_completions(&db, top_module_id); - let labels: Vec<_> = module_items.iter().map(|entry| entry.name.clone()).collect(); - assert!(labels.contains(&SmolStr::new("data"))); - assert!(labels.contains(&SmolStr::new("u0"))); - assert!(labels.contains(&SmolStr::new("init_blk"))); - assert!(labels.contains(&SmolStr::new("my_class"))); - assert!(labels.contains(&SmolStr::new("foo"))); - - let import_completion = module_items - .iter() - .find(|entry| entry.name == SmolStr::new("foo")) - .expect("import completion"); - assert_eq!(import_completion.kind, CompletionEntryKind::Import); - - let block_items = block_scope.collect_completions(&db, init_block_id); - let block_labels: Vec<_> = block_items.iter().map(|entry| entry.name.clone()).collect(); - assert!(block_labels.contains(&SmolStr::new("inner"))); - assert!(block_labels.contains(&SmolStr::new("nested_stmt"))); - assert!(block_labels.contains(&SmolStr::new("nested_blk"))); - } - - #[test] - fn package_scope_collects_members() { - let text = r#" -package my_pkg; - typedef struct { - int value; - } pkt_t; - - int data; - - class my_class; - int field; - endclass - - function automatic int compute(); - endfunction -endpackage -"#; - - let (db, file_id) = setup_db(text); - let unit_scope = HirDb::file_scope(&db, file_id); - let pkg_entry = unit_scope.get(&SmolStr::new("my_pkg")).expect("package present"); - let package_id = match pkg_entry { - UnitEntry::PackageId(package_id) => package_id, - _ => panic!("expected package entry"), - }; - - let package_scope = HirDb::package_scope(&db, package_id); - - let data_entry = package_scope.get(&SmolStr::new("data")).expect("data declaration"); - match data_entry { - PackageEntry::DeclId(InPackage { package_id: owner, .. }) => { - assert_eq!(owner, package_id); - } - _ => panic!("expected decl entry"), - } - - let typedef_entry = package_scope.get(&SmolStr::new("pkt_t")).expect("typedef entry"); - match typedef_entry { - PackageEntry::TypedefId(InPackage { package_id: owner, .. }) => { - assert_eq!(owner, package_id); - } - _ => panic!("expected typedef entry"), - } - - let class_entry = package_scope.get(&SmolStr::new("my_class")).expect("class entry"); - match class_entry { - PackageEntry::ClassId(InPackage { package_id: owner, .. }) => { - assert_eq!(owner, package_id); - } - _ => panic!("expected class entry"), - } - - let func_entry = package_scope.get(&SmolStr::new("compute")).expect("function entry"); - match func_entry { - PackageEntry::SubroutineId(InPackage { package_id: owner, .. }) => { - assert_eq!(owner, package_id); - } - _ => panic!("expected subroutine entry"), - } - - let completions = package_scope.collect_completions(&db, package_id); - let mut seen = - completions.into_iter().map(|entry| (entry.name, entry.kind)).collect::>(); - seen.sort_by(|a, b| a.0.cmp(&b.0)); - - assert_eq!( - seen, - vec![ - (SmolStr::new("compute"), CompletionEntryKind::Function), - (SmolStr::new("data"), CompletionEntryKind::Variable), - (SmolStr::new("my_class"), CompletionEntryKind::Type), - (SmolStr::new("pkt_t"), CompletionEntryKind::Type), - ] - ); - } - - #[test] - fn package_scope_includes_package_exports() { - let text = r#" -package inner_pkg; - typedef int inner_t; - int inner_var; - function automatic void inner_fn(); - endfunction -endpackage - -package outer_pkg; - import inner_pkg::*; - export inner_pkg::inner_var; - export inner_pkg::*; -endpackage -"#; - - let (db, file_id) = setup_db(text); - let unit_scope = HirDb::file_scope(&db, file_id); - - let inner_pkg_entry = unit_scope.get(&SmolStr::new("inner_pkg")).expect("inner package"); - let inner_pkg_id = match inner_pkg_entry { - UnitEntry::PackageId(package_id) => package_id, - _ => panic!("expected package entry"), - }; - - let outer_pkg_entry = unit_scope.get(&SmolStr::new("outer_pkg")).expect("outer package"); - let outer_pkg_id = match outer_pkg_entry { - UnitEntry::PackageId(package_id) => package_id, - _ => panic!("expected package entry"), - }; - - let outer_scope = HirDb::package_scope(&db, outer_pkg_id); - - let inner_var_entry = - outer_scope.get(&SmolStr::new("inner_var")).expect("inner var re-exported"); - match inner_var_entry { - PackageEntry::DeclId(InPackage { package_id, .. }) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected declaration entry"), - } - - let inner_t_entry = outer_scope.get(&SmolStr::new("inner_t")).expect("typedef export"); - match inner_t_entry { - PackageEntry::TypedefId(InPackage { package_id, .. }) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected typedef entry"), - } - - let inner_fn_entry = outer_scope.get(&SmolStr::new("inner_fn")).expect("function export"); - match inner_fn_entry { - PackageEntry::SubroutineId(InPackage { package_id, .. }) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected subroutine entry"), - } - - let completions = outer_scope.collect_completions(&db, outer_pkg_id); - let mut seen = - completions.into_iter().map(|entry| (entry.name, entry.kind)).collect::>(); - seen.sort_by(|a, b| a.0.cmp(&b.0)); - - assert_eq!( - seen, - vec![ - (SmolStr::new("inner_fn"), CompletionEntryKind::Function), - (SmolStr::new("inner_t"), CompletionEntryKind::Type), - (SmolStr::new("inner_var"), CompletionEntryKind::Variable), - ] - ); - } - - #[test] - fn module_scope_resolves_package_imports() { - let text = r#" -package inner_pkg; - typedef int inner_t; - int inner_var; - function automatic void inner_fn(); - endfunction -endpackage - -module top; - import inner_pkg::*; -endmodule -"#; - - let (db, file_id) = setup_db(text); - let unit_scope = HirDb::file_scope(&db, file_id); - - let inner_pkg_entry = unit_scope.get(&SmolStr::new("inner_pkg")).expect("inner package"); - let inner_pkg_id = match inner_pkg_entry { - UnitEntry::PackageId(package_id) => package_id, - _ => panic!("expected package entry"), - }; - - let (module_id, module_scope) = module_scope(&db, file_id, "top"); - - let import_typedef = - module_scope.get(&SmolStr::new("inner_t")).expect("typedef import present"); - match import_typedef { - ModuleEntry::PackageMember(PackageEntry::TypedefId(InPackage { - package_id, .. - })) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected typedef import entry"), - } - - let import_var = - module_scope.get(&SmolStr::new("inner_var")).expect("variable import present"); - match import_var { - ModuleEntry::PackageMember(PackageEntry::DeclId(InPackage { package_id, .. })) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected variable import entry"), - } - - let import_fn = - module_scope.get(&SmolStr::new("inner_fn")).expect("function import present"); - match import_fn { - ModuleEntry::PackageMember(PackageEntry::SubroutineId(InPackage { - package_id, - .. - })) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected subroutine import entry"), - } - - let completions = module_scope.collect_completions(&db, module_id); - let mut labels = completions.iter().map(|entry| entry.name.clone()).collect::>(); - labels.sort(); - assert!(labels.contains(&SmolStr::new("inner_t"))); - assert!(labels.contains(&SmolStr::new("inner_var"))); - assert!(labels.contains(&SmolStr::new("inner_fn"))); - } - - #[test] - fn package_exports_across_files() { - let files = [ - ( - "/inner.sv", - r#" -package inner_pkg; - typedef int inner_t; - int inner_var; - function automatic int inner_fn(); - endfunction -endpackage -"#, - ), - ( - "/outer.sv", - r#" -package outer_pkg; - import inner_pkg::*; - export inner_pkg::inner_var; - export inner_pkg::*; -endpackage -"#, - ), - ( - "/consumer.sv", - r#" -module consumer; - import outer_pkg::*; -endmodule -"#, - ), - ]; - - let (db, file_ids) = setup_multi_file_db(&files); - let unit_scope = HirDb::unit_scope(&db); - - let inner_pkg_entry = unit_scope.get(&SmolStr::new("inner_pkg")).expect("inner pkg"); - let inner_pkg_id = match inner_pkg_entry { - UnitEntry::PackageId(package_id) => package_id, - _ => panic!("expected package entry"), - }; - - let outer_pkg_entry = unit_scope.get(&SmolStr::new("outer_pkg")).expect("outer pkg"); - let outer_pkg_id = match outer_pkg_entry { - UnitEntry::PackageId(package_id) => package_id, - _ => panic!("expected package entry"), - }; - - let outer_scope = HirDb::package_scope(&db, outer_pkg_id); - - let reexported_var = - outer_scope.get(&SmolStr::new("inner_var")).expect("re-exported variable present"); - match reexported_var { - PackageEntry::DeclId(InPackage { package_id, .. }) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected declaration entry"), - } - - let reexported_typedef = - outer_scope.get(&SmolStr::new("inner_t")).expect("re-exported typedef present"); - match reexported_typedef { - PackageEntry::TypedefId(InPackage { package_id, .. }) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected typedef entry"), - } - - let reexported_fn = - outer_scope.get(&SmolStr::new("inner_fn")).expect("re-exported function present"); - match reexported_fn { - PackageEntry::SubroutineId(InPackage { package_id, .. }) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected subroutine entry"), - } - - let (_module_id, consumer_scope) = module_scope(&db, file_ids[2], "consumer"); - - let imported_var = consumer_scope - .get(&SmolStr::new("inner_var")) - .expect("module sees re-exported variable"); - match imported_var { - ModuleEntry::PackageMember(entry) => match entry { - PackageEntry::DeclId(InPackage { package_id, .. }) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected declaration package member"), - }, - _ => panic!("expected package member"), - } - - let imported_type = - consumer_scope.get(&SmolStr::new("inner_t")).expect("module sees re-exported typedef"); - match imported_type { - ModuleEntry::PackageMember(entry) => match entry { - PackageEntry::TypedefId(InPackage { package_id, .. }) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected typedef package member"), - }, - _ => panic!("expected package member"), - } - - let imported_fn = consumer_scope - .get(&SmolStr::new("inner_fn")) - .expect("module sees re-exported function"); - match imported_fn { - ModuleEntry::PackageMember(entry) => match entry { - PackageEntry::SubroutineId(InPackage { package_id, .. }) => { - assert_eq!(package_id, inner_pkg_id); - } - _ => panic!("expected subroutine package member"), - }, - _ => panic!("expected package member"), - } - } - - #[test] - fn debug_package_imports() { - use crate::hir_def::{module::ModuleId, package::PackageId}; - - let source = r#" -package my_pkg; - typedef logic [7:0] byte_t; - parameter int MAX_SIZE = 256; - - function int get_size(); - return MAX_SIZE; - endfunction -endpackage - -module test; - import my_pkg::*; -endmodule -"#; - let (db, hir_file_id) = setup_db(source); - let file = db.hir_file(hir_file_id); - - eprintln!("\n=== File Contents ==="); - eprintln!("Packages: {}", file.packages.len()); - eprintln!("Modules: {}", file.modules.len()); - - // Find the package - let mut pkg_id = None; - for (idx, pkg_info) in file.packages.iter() { - eprintln!("Package {} => {:?}", idx.into_raw(), pkg_info.name); - if pkg_info.name.as_ref().map(|n| n.as_str()) == Some("my_pkg") { - pkg_id = Some(PackageId { file_id: hir_file_id, value: idx }); - break; - } - } - let pkg_id = pkg_id.expect("package my_pkg should exist"); - - // Check package HIR - let package = db.package(pkg_id); - eprintln!("\n=== Package HIR ==="); - eprintln!("Decls: {}", package.decls.len()); - eprintln!("Typedefs: {}", package.typedefs.len()); - eprintln!("Subroutines: {}", package.subroutines.len()); - - // Check package scope - let pkg_scope = db.package_scope(pkg_id); - eprintln!("\n=== Package Scope Contents ==="); - for (name, entry) in pkg_scope.iter() { - eprintln!(" {} => {:?}", name, entry); - } - - // Check module scope - let mut mod_id = None; - for (idx, mod_info) in file.modules.iter() { - if mod_info.name.as_ref().map(|n| n.as_str()) == Some("test") { - mod_id = Some(ModuleId { file_id: hir_file_id, value: idx }); - break; - } - } - let mod_id = mod_id.expect("module test should exist"); - - let mod_scope = db.module_scope(mod_id); - eprintln!("\n=== Module Scope Contents ==="); - for (name, entry) in mod_scope.iter() { - eprintln!(" {} => {:?}", name, entry); - } - - // Check if items are imported - assert!(pkg_scope.get(&SmolStr::new("byte_t")).is_some(), "byte_t should be in package scope"); - assert!(pkg_scope.get(&SmolStr::new("MAX_SIZE")).is_some(), "MAX_SIZE should be in package scope"); - assert!(pkg_scope.get(&SmolStr::new("get_size")).is_some(), "get_size should be in package scope"); - - assert!(mod_scope.get(&SmolStr::new("byte_t")).is_some(), "byte_t should be imported into module"); - assert!(mod_scope.get(&SmolStr::new("MAX_SIZE")).is_some(), "MAX_SIZE should be imported into module"); - assert!(mod_scope.get(&SmolStr::new("get_size")).is_some(), "get_size should be imported into module"); - } - - #[test] - fn debug_completion_in_block() { - use crate::semantics::Semantics; - use utils::text_edit::TextSize; - - let source = r#" -package my_pkg; - typedef logic [7:0] byte_t; - parameter int MAX_SIZE = 256; - - function int fetch_size(); - return MAX_SIZE; - endfunction -endpackage - -module test; - import my_pkg::*; - - initial begin - byte_t data; - int size = fetch_ - end -endmodule -"#; - - let (db, hir_file_id) = setup_db(source); - - // Find the offset of "= fetch_" in the module (not the function definition) - let pattern_pos = source.find("= fetch_").unwrap(); - let fetch_pos = pattern_pos + "= ".len(); - let offset = TextSize::from((fetch_pos + "fetch_".len()) as u32); - - eprintln!("\n=== Completion Debug ==="); - eprintln!("Source around offset:"); - let start = fetch_pos.saturating_sub(20); - let end = (fetch_pos + 30).min(source.len()); - eprintln!(" '{}'", &source[start..end]); - eprintln!("Offset: {:?} (bytes from start of source string)", offset); - - let sema = Semantics::new(&db); - let completions = sema.scope_completions(hir_file_id.file_id(), offset); - - eprintln!("\n=== Completion Results ==="); - eprintln!("Total items: {}", completions.len()); - for item in &completions { - eprintln!(" {} => {:?} (scope: {:?})", item.entry.name, item.entry.kind, item.scope); - } - - // Check if fetch_size is present - let has_fetch_size = completions.iter().any(|item| item.entry.name == "fetch_size"); - eprintln!("\n=== Has fetch_size: {} ===", has_fetch_size); - - assert!(has_fetch_size, "fetch_size should be present in completions"); - } } diff --git a/crates/hir/src/semantics/completion.rs b/crates/hir/src/semantics/completion.rs index e73fcd8f..6945ead2 100644 --- a/crates/hir/src/semantics/completion.rs +++ b/crates/hir/src/semantics/completion.rs @@ -8,8 +8,9 @@ use vfs::FileId; use super::SemanticsImpl; use crate::{ completion::{ - CompletionEntry, CompletionEntryKind, CompletionScope, DotField, DotFieldKind, - ScopedCompletionEntry, + BlockScopeCompletionExt, CompletionEntry, CompletionEntryKind, CompletionScope, DotField, + DotFieldKind, ModuleScopeCompletionExt, PackageScopeCompletionExt, ScopedCompletionEntry, + SubroutineScopeCompletionExt, UnitScopeCompletionExt, }, container::{ContainerId, ContainerParent, InContainer, InFile, InModule}, display::HirDisplay,