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/completion/mod.rs b/crates/hir/src/completion/mod.rs new file mode 100644 index 00000000..74acb136 --- /dev/null +++ b/crates/hir/src/completion/mod.rs @@ -0,0 +1,89 @@ +pub mod scope; + +pub use scope::{ + BlockScopeCompletionExt, ModuleScopeCompletionExt, PackageScopeCompletionExt, + SubroutineScopeCompletionExt, UnitScopeCompletionExt, +}; + +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/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/container.rs b/crates/hir/src/container.rs index 7f2dd959..4138756e 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,12 @@ 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), + FileSubroutineId(InFile), } } @@ -52,6 +59,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 +116,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 +125,10 @@ 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(), + ContainerId::FileSubroutineId(loc) => loc.file_id.file_id(), } } @@ -103,7 +136,10 @@ 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(), + ContainerId::FileSubroutineId(loc) => loc.to_container(db).into(), } } @@ -111,7 +147,10 @@ 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(), + ContainerId::FileSubroutineId(loc) => loc.to_container_src_map(db).into(), } } } @@ -148,6 +187,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 +219,44 @@ 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 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 { 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 +271,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 +290,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 +310,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 +323,10 @@ 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()), + ContainerId::FileSubroutineId(loc) => Some(loc.file_id.into()), }; next } diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index f1aa2a1c..101908b1 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -1,16 +1,21 @@ 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::{InFile, 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}, + scope::{BlockScope, ModuleScope, PackageScope, SubroutineScope, UnitScope}, }; pub(crate) macro impl_intern($id:ident, $loc:ident, $intern:ident, $lookup:ident) { @@ -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; @@ -59,8 +83,17 @@ 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; + + #[salsa::invoke(SubroutineScope::file_subroutine_scope_query)] + fn file_subroutine_scope(&self, subroutine: InFile) -> Arc; } fn parse(db: &dyn HirDb, file_id: HirFileId) -> SyntaxTree { @@ -75,6 +108,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..130b702d 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,48 @@ 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, + ContainerId::FileSubroutineId(_) => 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..246ea68e --- /dev/null +++ b/crates/hir/src/hir_def/aggregate.rs @@ -0,0 +1,230 @@ +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, 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(); + + 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) => { + 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: method_name, + kind: ClassMemberKind::Method, + ty: None, // methods don't have a simple data type + }); + } + _ => {} + } + } + + ClassDef { name, base_class_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 base_class_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..11f20758 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 { @@ -79,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 { @@ -94,6 +102,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()), } } @@ -109,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 { 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..c6f416b3 100644 --- a/crates/hir/src/hir_def/file.rs +++ b/crates/hir/src/hir_def/file.rs @@ -1,30 +1,46 @@ use la_arena::Arena; use proc_macro_utils::define_container; +use rustc_hash::FxHashMap; 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}, + subroutine::{ + LowerSubroutineBodyCtx, Subroutine, SubroutineId, SubroutineSourceMap, SubroutineSrc, + lower_subroutine, lower_subroutine_body, + }, + typedef::{Typedef, TypedefId, TypedefSrc, lower_typedef_data_ty}, }; use crate::{ + container::{ContainerId, InFile}, db::{HirDb, InternDb}, file::HirFileId, hir_def::lower_ident_opt, @@ -36,8 +52,16 @@ 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], + subroutines: [Subroutine], + subroutine_source_maps: FxHashMap, + declarations: [Declaration], exprs: [Expr], event_exprs: [EventExpr], @@ -56,9 +80,15 @@ 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], + subroutine_srcs: [Subroutine | SubroutineSrc], expr_srcs: [Expr | ExprSrc], event_expr_srcs: [EventExpr | EventExprSrc], decl_srcs: [Declarator | DeclaratorSrc], @@ -73,8 +103,14 @@ define_enum_deriving_from! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum FileItem { LocalModuleId, + LocalPackageId, ProcId, DeclarationId, + TypedefId, + StructId, + ClassId, + PackageImportId, + SubroutineId, } } @@ -82,8 +118,14 @@ 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, + FileItem::SubroutineId(idx) => self.get(*idx).0, } } } @@ -129,25 +171,126 @@ 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 + } + + 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::*; 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(), + 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); @@ -159,6 +302,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..a9185fd6 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,95 @@ 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).into(); + 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 +367,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 +742,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..dcb0941a --- /dev/null +++ b/crates/hir/src/hir_def/package.rs @@ -0,0 +1,546 @@ +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..e80eee32 100644 --- a/crates/hir/src/hir_def/subroutine.rs +++ b/crates/hir/src/hir_def/subroutine.rs @@ -1,42 +1,347 @@ -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, InFile, 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 + } +} + +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, - pub exprs: Arena, - pub event_exprs: Arena, - pub decls: Arena, + 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, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub enum SubroutinePortDir { + Input, + Output, + Inout, + Ref, + ConstRef, + #[default] + Unknown, } -pub struct LowerSubroutineCtx<'a> { +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, + } +} + +#[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: SubroutineLocation, + 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/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/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, diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 7c7e6374..813cc85e 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -1,13 +1,15 @@ use rustc_hash::FxHashMap; +use smol_str::SmolStr; use triomphe::Arc; use utils::define_enum_deriving_from; use crate::{ - container::InFile, + container::{InFile, InModule, InPackage}, db::HirDb, file::HirFileId, hir_def::{ Ident, + aggregate::{ClassId, StructId}, block::{BlockId, BlockInfo}, expr::declarator::{DeclId, DeclaratorParent}, module::{ @@ -15,15 +17,23 @@ use crate::{ instantiation::InstanceId, port::{NonAnsiPortId, Ports}, }, + package::{Package, PackageExport, PackageId, PackageImportId, PackageImportMember}, + proc::ProcId, stmt::{StmtId, StmtKind}, + subroutine::SubroutineId, + 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), + SubroutineId(InFile), } } @@ -32,12 +42,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 +67,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 +133,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,10 +168,26 @@ 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()); + } + + 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) } } @@ -181,6 +248,61 @@ 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) } } @@ -202,6 +324,287 @@ impl BlockScope { } } + for (typedef_id, typedef_) in block.typedefs.iter() { + scope.insert_opt(&typedef_.name, typedef_id.into()); + } + + Arc::new(scope) + } +} + +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 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) + } +} + +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) } } 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..6945ead2 --- /dev/null +++ b/crates/hir/src/semantics/completion.rs @@ -0,0 +1,1006 @@ +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::{ + BlockScopeCompletionExt, CompletionEntry, CompletionEntryKind, CompletionScope, DotField, + DotFieldKind, ModuleScopeCompletionExt, PackageScopeCompletionExt, ScopedCompletionEntry, + SubroutineScopeCompletionExt, UnitScopeCompletionExt, + }, + 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::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); + 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, + UnitEntry::SubroutineId(_) => 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) + } + 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) + } + } + } + + 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, + ContainerId::FileSubroutineId(_) => 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) + } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(self.db); + 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, + 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, + 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, + class_ref.cont_id, + ) + } + ContainerId::BlockId(_) + | ContainerId::SubroutineId(_) + | ContainerId::FileSubroutineId(_) => 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, + 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) { + 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 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, + 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(_) | ContainerId::FileSubroutineId(_) => 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) + } + } + } + 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) + } + } + } + } + } + + 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)) + } + ContainerId::FileSubroutineId(loc) => { + let subroutine = loc.to_container(self.db); + 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) + } + 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) + } + } + } + + 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 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/pathres.rs b/crates/hir/src/semantics/pathres.rs index 80925183..479e24c6 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,10 @@ 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, + UnitEntry::SubroutineId(_) => None, } } @@ -98,6 +108,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 +122,8 @@ pub enum PathResolution { Instance(InModule), Stmt(InContainer), Block(BlockId), + Package(PackageId), + Subroutine(InContainer), } impl From for PathResolution { @@ -117,6 +132,10 @@ 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), + SubroutineId(idx) => Self::Subroutine(idx.into()), } } } @@ -133,6 +152,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 +175,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 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..be90d6b3 100644 --- a/crates/ide/src/definitions.rs +++ b/crates/ide/src/definitions.rs @@ -244,6 +244,8 @@ impl Definition { ) } } + // Future PathResolution variants not yet handled in this branch. + _ => 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) + } } } 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() + } +}