From fa5672b5d11025b1d82fc8c3d673499d9106b272 Mon Sep 17 00:00:00 2001 From: roife Date: Mon, 1 Jun 2026 02:44:12 +0800 Subject: [PATCH] Add interface and modport support --- crates/hir/src/display.rs | 12 + crates/hir/src/hir_def.rs | 1 + crates/hir/src/hir_def/block.rs | 6 + crates/hir/src/hir_def/file.rs | 7 + crates/hir/src/hir_def/module.rs | 55 ++++- crates/hir/src/hir_def/module/generate.rs | 9 + crates/hir/src/hir_def/module/modport.rs | 75 ++++++ crates/hir/src/hir_def/module/port.rs | 26 ++- crates/hir/src/hir_def/package_import.rs | 82 +++++++ crates/hir/src/hir_def/subroutine.rs | 6 + crates/hir/src/scope.rs | 6 + crates/hir/src/semantics/hir_to_def.rs | 204 +++++++++++++++- crates/hir/src/semantics/pathres.rs | 5 +- crates/hir/src/type_infer.rs | 271 +++++++++++++++++++++- crates/ide/src/completion/engine/expr.rs | 144 ++++++++++-- crates/ide/src/completion/engine/tests.rs | 140 +++++++++++ crates/ide/src/definitions.rs | 48 +++- crates/ide/src/document_symbols.rs | 7 + crates/ide/src/lib.rs | 1 + crates/ide/src/navigation_target.rs | 20 +- crates/ide/src/render.rs | 15 +- crates/ide/src/signature_help.rs | 2 +- crates/ide/src/verilog_2005.rs | 146 ++++++++++++ 23 files changed, 1231 insertions(+), 57 deletions(-) create mode 100644 crates/hir/src/hir_def/module/modport.rs create mode 100644 crates/hir/src/hir_def/package_import.rs diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index df0d0c43..42c745ba 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -212,6 +212,18 @@ impl HirDisplay for InModule { } InContainer::new((*module_id).into(), *ty).hir_fmt(f) } + PortHeader::Interface { interface, modport } => { + if let Some(interface) = interface { + f.write_str(interface.as_str())?; + } else { + f.write_str("interface")?; + } + if let Some(modport) = modport { + f.write_str(".")?; + f.write_str(modport.as_str())?; + } + Ok(()) + } } } } diff --git a/crates/hir/src/hir_def.rs b/crates/hir/src/hir_def.rs index 045dd283..61d4d731 100644 --- a/crates/hir/src/hir_def.rs +++ b/crates/hir/src/hir_def.rs @@ -5,6 +5,7 @@ pub mod expr; pub mod file; pub mod literal; pub mod module; +pub mod package_import; pub mod proc; pub mod stmt; pub mod subroutine; diff --git a/crates/hir/src/hir_def/block.rs b/crates/hir/src/hir_def/block.rs index 6ebf8cf3..feb6f24a 100644 --- a/crates/hir/src/hir_def/block.rs +++ b/crates/hir/src/hir_def/block.rs @@ -28,6 +28,7 @@ use super::{ timing_control::{EventExpr, EventExprSrc, impl_lower_event_expr}, }, lower_ident_opt, + package_import::{PackageImport, lower_package_imports}, stmt::{LowerStmt, Stmt, StmtId, StmtKind, StmtSrc, impl_lower_stmt}, typedef::{Typedef, TypedefId, TypedefSrc, lower_typedef_data_ty}, }; @@ -50,6 +51,7 @@ define_container! { declarations: [Declaration], typedefs: [Typedef], structs: [StructDef], + package_imports: [PackageImport], exprs: [Expr], event_exprs: [EventExpr], decls: [Declarator], @@ -286,6 +288,10 @@ impl LowerBlockCtx<'_> { self.declaration_ctx().lower_param_decl_base(it.parameter()).into() }, ast::TypedefDeclaration[it] => self.lower_typedef(it).into(), + ast::PackageImportDeclaration[it] => { + lower_package_imports(it, &mut self.block.package_imports); + continue; + }, _ => continue, }; self.block_source_map.items.push(idx); diff --git a/crates/hir/src/hir_def/file.rs b/crates/hir/src/hir_def/file.rs index 746b58c7..aebe4d73 100644 --- a/crates/hir/src/hir_def/file.rs +++ b/crates/hir/src/hir_def/file.rs @@ -27,6 +27,7 @@ use super::{ timing_control::{EventExpr, EventExprSrc, impl_lower_event_expr}, }, module::{LocalModuleId, ModuleInfo, ModuleSrc}, + package_import::{PackageImport, lower_package_imports}, proc::{LowerProc, LowerProcCtx, Proc, ProcId, ProcSrc}, stmt::{Stmt, StmtId, StmtSrc, impl_lower_stmt}, subroutine::{ @@ -60,6 +61,7 @@ define_container! { udp_decls: [UdpDecl], library_decls: [LibraryDecl], library_includes: [LibraryInclude], + package_imports: [PackageImport], subroutines: [Subroutine], declarations: [Declaration], @@ -306,6 +308,11 @@ impl LowerFileCtx<'_> { Some(id) => id.into(), None => continue, }, + PackageImportDeclaration(import) => { + lower_package_imports(import, &mut self.file.package_imports); + self.region_tree.handle_node(member.syntax()); + continue; + } UdpDeclaration(udp_decl) => self.lower_udp_decl(udp_decl).into(), ConfigDeclaration(config_decl) => self.lower_config_decl(config_decl).into(), _ => continue, diff --git a/crates/hir/src/hir_def/module.rs b/crates/hir/src/hir_def/module.rs index c2856f46..0f51db6a 100644 --- a/crates/hir/src/hir_def/module.rs +++ b/crates/hir/src/hir_def/module.rs @@ -8,6 +8,7 @@ use instantiation::{ ParamAssign, ParamAssignSrc, PortConn, PortConnSrc, impl_lower_instantiation, }; use la_arena::{Arena, Idx, IdxRange, RawIdx}; +use modport::{Modport, ModportId, ModportSrc, lower_modport_item}; use port::{ NonAnsiPort, NonAnsiPortId, NonAnsiPortSrc, PortDecl, PortDeclId, PortDeclSrc, PortRef, PortRefId, PortRefSrc, PortSrcs, Ports, @@ -41,6 +42,10 @@ use super::{ timing_control::{EventExpr, EventExprSrc, impl_lower_event_expr}, }, lower_ident_opt, + package_import::{ + PackageExport, PackageImport, lower_package_export_all, lower_package_exports, + lower_package_imports, + }, proc::{LowerProc, LowerProcCtx, Proc, ProcId, ProcSrc}, stmt::{Stmt, StmtId, StmtSrc, impl_lower_stmt}, subroutine::{ @@ -63,6 +68,7 @@ pub mod continuous_assgin; pub mod defparam; pub mod generate; pub mod instantiation; +pub mod modport; pub mod port; pub mod specify; @@ -83,9 +89,12 @@ define_container! { generate_regions: [GenerateRegion], specify_blocks: [SpecifyBlock], specify_items: [SpecifyItem], + modports: [Modport], declarations: [Declaration], typedefs: [Typedef], structs: [StructDef], + package_imports: [PackageImport], + package_exports: [PackageExport], subroutines: [Subroutine], instantiations: [Instantiation], @@ -122,6 +131,7 @@ define_container! { generate_region_srcs: [GenerateRegion | GenerateRegionSrc], specify_block_srcs: [SpecifyBlock | SpecifyBlockSrc], specify_item_srcs: [SpecifyItem | SpecifyItemSrc], + modport_srcs: [Modport | ModportSrc], declaration_srcs: [Declaration | DeclarationSrc], typedef_srcs: [Typedef | TypedefSrc], struct_srcs: [StructDef | StructSrc], @@ -174,6 +184,7 @@ impl ModuleSourceMap { ModuleItem::GenerateRegionId(idx) => self.get(*idx)?.into(), ModuleItem::SpecifyBlockId(idx) => self.get(*idx)?.0, ModuleItem::SpecifyItemId(idx) => self.get(*idx)?.into(), + ModuleItem::ModportId(idx) => self.get(*idx)?.node, ModuleItem::DeclarationId(idx) => self.get(*idx)?.ptr(), ModuleItem::StructId(idx) => self.get(*idx)?.node, ModuleItem::InstantiationId(idx) => self.get(*idx)?.into(), @@ -193,6 +204,7 @@ define_enum_deriving_from! { GenerateRegionId(GenerateRegionId), SpecifyBlockId(SpecifyBlockId), SpecifyItemId(SpecifyItemId), + ModportId(ModportId), DeclarationId(DeclarationId), StructId(StructId), InstantiationId(InstantiationId), @@ -257,6 +269,18 @@ impl LowerProc for LowerModuleCtx<'_> { } impl LowerModuleCtx<'_> { + fn lower_modport_decl(&mut self, decl: ast::ModportDeclaration) -> Vec { + decl.items() + .children() + .map(|item| { + alloc_idx_and_src! { + lower_modport_item(item) => self.module.modports, + item => self.module_source_map.modport_srcs, + } + }) + .collect() + } + fn lower_struct_type(&mut self, struct_ty: ast::StructUnionType) -> StructId { let container_id = ContainerId::ModuleId(self.module_id); let struct_def = @@ -403,7 +427,11 @@ impl LowerModuleCtx<'_> { ExplicitAnsiPort(_) | ImplicitAnsiPort(_) => continue, // Imports - PackageImportDeclaration(_) => continue, + PackageImportDeclaration(import) => { + lower_package_imports(import, &mut self.module.package_imports); + self.region_tree.handle_node(member.syntax()); + continue; + } // Aggregates ClassDeclaration(_) => continue, @@ -469,10 +497,18 @@ impl LowerModuleCtx<'_> { NetAlias(_) => continue, // Modport - ModportDeclaration(_) - | ModportClockingPort(_) + ModportDeclaration(decl) => { + for modport_id in self.lower_modport_decl(decl) { + self.module_source_map.items.push(modport_id.into()); + } + self.region_tree.handle_node(member.syntax()); + continue; + } + ModportClockingPort(_) | ModportSimplePortList(_) - | ModportSubroutinePortList(_) => continue, + | ModportSubroutinePortList(_) => { + continue; + } // Class members (shouldn't appear in module but handle anyway) ClassPropertyDeclaration(_) @@ -492,7 +528,16 @@ impl LowerModuleCtx<'_> { BindDirective(_) => continue, // Package exports - PackageExportDeclaration(_) | PackageExportAllDeclaration(_) => continue, + PackageExportDeclaration(export) => { + lower_package_exports(export, &mut self.module.package_exports); + self.region_tree.handle_node(member.syntax()); + continue; + } + PackageExportAllDeclaration(export) => { + lower_package_export_all(export, &mut self.module.package_exports); + self.region_tree.handle_node(member.syntax()); + continue; + } // Library LibraryDeclaration(_) | LibraryIncludeStatement(_) => continue, diff --git a/crates/hir/src/hir_def/module/generate.rs b/crates/hir/src/hir_def/module/generate.rs index 05b6398d..00ce75fd 100644 --- a/crates/hir/src/hir_def/module/generate.rs +++ b/crates/hir/src/hir_def/module/generate.rs @@ -40,6 +40,7 @@ use crate::{ timing_control::{EventExpr, EventExprSrc, impl_lower_event_expr}, }, lower_ident_opt, + package_import::{PackageImport, lower_package_imports}, proc::{LowerProc, LowerProcCtx, Proc, ProcId, ProcSrc}, stmt::{Stmt, StmtId, StmtSrc, impl_lower_stmt}, subroutine::{ @@ -299,6 +300,7 @@ define_container! { declarations: [Declaration], typedefs: [Typedef], structs: [StructDef], + package_imports: [PackageImport], subroutines: [Subroutine], instantiations: [Instantiation], @@ -618,6 +620,10 @@ impl LowerGenerateBlockCtx<'_> { self.instantiation_ctx().lower_primitive_instantiation(instantiation).into() } FunctionDeclaration(fn_decl) => self.lower_subroutine_decl(fn_decl)?.into(), + PackageImportDeclaration(import) => { + lower_package_imports(import, &mut self.generate_block.package_imports); + return None; + } ProceduralBlock(proc) => self.proc_ctx().lower_proc(proc).into(), GenerateBlock(block) => { self.intern_generate_block(GenerateBlockSrc::from_generate_block(block)).into() @@ -821,6 +827,9 @@ impl LowerModuleCtx<'_> { items.push(sub_id.into()); } } + PackageImportDeclaration(import) => { + lower_package_imports(import, &mut self.module.package_imports); + } ProceduralBlock(proc) => { items.push(self.proc_ctx().lower_proc(proc).into()); } diff --git a/crates/hir/src/hir_def/module/modport.rs b/crates/hir/src/hir_def/module/modport.rs new file mode 100644 index 00000000..24f4fc5b --- /dev/null +++ b/crates/hir/src/hir_def/module/modport.rs @@ -0,0 +1,75 @@ +use la_arena::Idx; +use syntax::{ + SyntaxKind, TokenKind, + ast::{self, AstNode}, + ptr::{SyntaxNodePtr, SyntaxTokenPtr}, + slang_ext::AstNodeExt, +}; +use utils::text_edit::TextRange; + +use crate::{ + hir_def::{Ident, lower_ident_opt}, + source_map::{FromSourceAst, IsNamedSrc, IsSrc, SourceAst, ToAstNode, root_token_in}, +}; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct Modport { + pub name: Option, +} + +pub type ModportId = Idx; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct ModportSrc { + pub node: SyntaxNodePtr, + pub name: Option, +} + +impl IsSrc for ModportSrc { + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + fn range(&self) -> TextRange { + self.node.range() + } +} + +impl IsNamedSrc for ModportSrc { + fn name_kind(&self) -> Option { + self.name.map(|name| name.kind()) + } + + fn name_range(&self) -> Option { + self.name.map(|name| name.range()) + } +} + +impl<'a> ToAstNode<'a, ast::ModportItem<'a>> for ModportSrc { + fn to_node(&self, tree: &'a syntax::SyntaxTree) -> Option> { + ast::ModportItem::cast(self.node.to_node(tree)?) + } +} + +impl From> for ModportSrc { + fn from(node: ast::ModportItem<'_>) -> Self { + let syntax = node.syntax(); + let name = node.name().map(|name| SyntaxTokenPtr::from_token_in(syntax, name)); + ModportSrc { node: AstNodeExt::to_ptr(&node), name } + } +} + +impl<'a> FromSourceAst<'a, ast::ModportItem<'a>> for ModportSrc { + fn from_source_ast(node: SourceAst>) -> Self { + let node = node.into_inner(); + let syntax = node.syntax(); + let name = node + .name() + .and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token)); + ModportSrc { node: AstNodeExt::to_ptr(&node), name } + } +} + +pub(crate) fn lower_modport_item(item: ast::ModportItem) -> Modport { + Modport { name: lower_ident_opt(item.name()) } +} diff --git a/crates/hir/src/hir_def/module/port.rs b/crates/hir/src/hir_def/module/port.rs index 33f24604..2205f5ff 100644 --- a/crates/hir/src/hir_def/module/port.rs +++ b/crates/hir/src/hir_def/module/port.rs @@ -66,16 +66,18 @@ pub enum PortDirection { Inout, } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum PortHeader { Var { dir: PortDirection, var_kw: bool, ty: DataTy }, Net { dir: PortDirection, net_ty: NetType }, + Interface { interface: Option, modport: Option }, } impl PortHeader { pub fn dir(&self) -> PortDirection { match self { PortHeader::Var { dir, .. } | PortHeader::Net { dir, .. } => *dir, + PortHeader::Interface { .. } => PortDirection::Inout, } } @@ -83,6 +85,7 @@ impl PortHeader { match self { PortHeader::Var { ty, .. } => *ty, PortHeader::Net { net_ty: NetType { ty, .. }, .. } => *ty, + PortHeader::Interface { .. } => panic!("interface ports do not have a data type"), } } } @@ -295,7 +298,7 @@ impl LowerModuleCtx<'_> { let end = self.module.decls.nxt_idx(); let current_header = header.unwrap_or_else(|| self.default_port_header()); - header = Some(current_header); + header = Some(current_header.clone()); alloc_idx_and_src! { PortDecl { header: current_header, @@ -312,7 +315,7 @@ impl LowerModuleCtx<'_> { } let current_header = header.unwrap_or_else(|| self.default_port_header()); - header = Some(current_header); + header = Some(current_header.clone()); let idx = ports.alloc(PortDecl { header: current_header, decls: IdxRange::new( @@ -475,7 +478,14 @@ impl LowerModuleCtx<'_> { lower_net_kind(header.net_type()).map(Either::Right), header.data_type(), ), - InterfacePortHeader(_header) => return prev_header, + InterfacePortHeader(header) => { + let interface = match header.name_or_keyword() { + Some(tok) if tok.kind() == TokenKind::INTERFACE_KEYWORD => None, + other => lower_ident_opt(other), + }; + let modport = header.modport().and_then(|it| lower_ident_opt(it.member())); + return PortHeader::Interface { interface, modport }; + } }; let ty_omitted = DataTy::is_ast_missing(ast_ty); @@ -485,7 +495,10 @@ impl LowerModuleCtx<'_> { let ty = if !ty_omitted { self.expr_ctx().lower_data_ty(ast_ty) } else if all_omitted { - prev_header.ty() + match prev_header { + PortHeader::Interface { .. } => default_data_ty, + _ => prev_header.ty(), + } } else { default_data_ty }; @@ -520,6 +533,9 @@ impl LowerModuleCtx<'_> { match prev_header { PortHeader::Var { var_kw, ty, .. } => PortHeader::Var { dir, var_kw, ty }, PortHeader::Net { net_ty, .. } => PortHeader::Net { dir, net_ty }, + PortHeader::Interface { interface, modport } => { + PortHeader::Interface { interface, modport } + } } } diff --git a/crates/hir/src/hir_def/package_import.rs b/crates/hir/src/hir_def/package_import.rs new file mode 100644 index 00000000..edca47c7 --- /dev/null +++ b/crates/hir/src/hir_def/package_import.rs @@ -0,0 +1,82 @@ +use la_arena::{Arena, Idx}; +use syntax::{TokenKind, ast}; + +use super::{Ident, lower_ident_opt}; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct PackageImport { + pub package: Option, + pub item: PackageImportName, +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum PackageImportName { + Wildcard, + Name(Ident), +} + +pub type PackageImportId = Idx; + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct PackageExport { + pub package: Option, + pub item: PackageExportName, +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum PackageExportName { + Wildcard, + Name(Ident), + AllImports, +} + +pub type PackageExportId = Idx; + +pub(crate) fn lower_package_imports( + import: ast::PackageImportDeclaration, + imports: &mut Arena, +) { + for item in import.items().children() { + let package = lower_ident_opt(item.package()); + let Some(item_token) = item.item() else { + continue; + }; + let item = if item_token.kind() == TokenKind::STAR { + PackageImportName::Wildcard + } else { + let Some(name) = lower_ident_opt(Some(item_token)) else { + continue; + }; + PackageImportName::Name(name) + }; + imports.alloc(PackageImport { package, item }); + } +} + +pub(crate) fn lower_package_exports( + export: ast::PackageExportDeclaration, + exports: &mut Arena, +) { + for item in export.items().children() { + let package = lower_ident_opt(item.package()); + let Some(item_token) = item.item() else { + continue; + }; + let item = if item_token.kind() == TokenKind::STAR { + PackageExportName::Wildcard + } else { + let Some(name) = lower_ident_opt(Some(item_token)) else { + continue; + }; + PackageExportName::Name(name) + }; + exports.alloc(PackageExport { package, item }); + } +} + +pub(crate) fn lower_package_export_all( + _export: ast::PackageExportAllDeclaration, + exports: &mut Arena, +) { + exports.alloc(PackageExport { package: None, item: PackageExportName::AllImports }); +} diff --git a/crates/hir/src/hir_def/subroutine.rs b/crates/hir/src/hir_def/subroutine.rs index e833b25c..89c7d28f 100644 --- a/crates/hir/src/hir_def/subroutine.rs +++ b/crates/hir/src/hir_def/subroutine.rs @@ -26,6 +26,7 @@ use super::{ }, lower_ident, lower_ident_opt, module::{ModuleId, generate::GenerateBlockId}, + package_import::{PackageImport, lower_package_imports}, stmt::{LowerStmt, Stmt, StmtId, StmtSrc, impl_lower_stmt}, typedef::{Typedef, TypedefId, TypedefSrc, lower_typedef_data_ty}, }; @@ -54,6 +55,7 @@ define_container! { declarations: [Declaration], typedefs: [Typedef], structs: [StructDef], + package_imports: [PackageImport], exprs: [Expr], event_exprs: [EventExpr], decls: [Declarator], @@ -75,6 +77,7 @@ impl Default for Subroutine { declarations: Arena::new(), typedefs: Arena::new(), structs: Arena::new(), + package_imports: Arena::new(), exprs: Arena::new(), event_exprs: Arena::new(), decls: Arena::new(), @@ -367,6 +370,9 @@ impl LowerSubroutineBodyCtx<'_> { let typedef_id = self.lower_typedef(it); self.subroutine_source_map.items.push(BlockItem::TypedefId(typedef_id)); }, + ast::PackageImportDeclaration[it] => { + lower_package_imports(it, &mut self.subroutine.package_imports); + }, _ => {}, } } diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index faf3e0f5..6c6271d2 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -20,6 +20,7 @@ use crate::{ Module, ModuleId, generate::GenerateBlockId, instantiation::InstanceId, + modport::ModportId, port::{NonAnsiPortId, PortDeclId, Ports}, }, stmt::{StmtId, StmtKind}, @@ -55,6 +56,7 @@ pub enum ModuleEntry { NonAnsiPortEntry, AnsiPortEntry, InstanceId, + ModportId, StmtId, BlockId, SubroutineId, @@ -331,6 +333,10 @@ impl ModuleScope { scope.insert_opt(&instance.name, instance_id.into()); } + for (modport_id, modport) in module.modports.iter() { + scope.insert_opt(&modport.name, modport_id.into()); + } + for item in &module_src_map.items { if let crate::hir_def::module::ModuleItem::GenerateRegionId(generate_region_id) = item { let generate_region = module.get(*generate_region_id); diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index 93f1ab60..30f4fbbe 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -1,4 +1,4 @@ -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use utils::get::GetRef; use super::{Source2DefCtx, pathres::PathResolution}; @@ -11,7 +11,9 @@ use crate::{ block::BlockId, expr::{Expr, ExprId}, module::{ModuleId, generate::GenerateBlockId, instantiation::InstanceId}, + package_import::{PackageExportName, PackageImport, PackageImportName}, }, + type_infer::{Ty, type_of_path_resolution}, }; #[derive(Default, Debug)] @@ -75,37 +77,187 @@ impl Source2DefCtx<'_, '_> { pub(super) fn name_to_def( &mut self, InContainer { cont_id, value: ident }: InContainer, + ) -> Option { + let res = ContainerParent::start_from(self.db, cont_id).find_map(|id| { + self.resolve_local_name(id, &ident).or_else(|| self.resolve_imported_name(id, &ident)) + })?; + self.hir_cache.name_map.insert(InContainer::new(cont_id, ident), res); + Some(res) + } + + fn resolve_local_name( + &mut self, + cont_id: ContainerId, + ident: &Ident, ) -> Option { let db = self.db; - let res = ContainerParent::start_from(db, cont_id).find_map(|id| match id { + match cont_id { ContainerId::HirFileId(_) => { let scope = db.unit_scope(); - let entry = scope.get(&ident)?; + let entry = scope.get(ident)?; Some(entry.into()) } ContainerId::ModuleId(module_id) => { let scope = db.module_scope(module_id); - let entry = scope.get(&ident)?; + let entry = scope.get(ident)?; Some(InModule::new(module_id, entry).into()) } ContainerId::BlockId(block_id) => { let scope = db.block_scope(block_id); - let entry = scope.get(&ident)?; + let entry = scope.get(ident)?; Some(InBlock::new(block_id, entry).into()) } ContainerId::GenerateBlockId(generate_block_id) => { let scope = db.generate_block_scope(generate_block_id); - let entry = scope.get(&ident)?; + let entry = scope.get(ident)?; Some(InGenerateBlock::new(generate_block_id, entry).into()) } ContainerId::SubroutineId(subroutine_id) => { let scope = db.subroutine_scope(subroutine_id); - let entry = scope.get(&ident)?; + let entry = scope.get(ident)?; Some(InSubroutine::new(subroutine_id, entry).into()) } - })?; - self.hir_cache.name_map.insert(InContainer::new(cont_id, ident), res); - Some(res) + } + } + + fn resolve_imported_name( + &mut self, + cont_id: ContainerId, + ident: &Ident, + ) -> Option { + match cont_id { + ContainerId::HirFileId(file_id) => { + let file = self.db.hir_file(file_id); + self.resolve_imports(file.package_imports.iter().map(|(_, import)| import), ident) + } + ContainerId::ModuleId(module_id) => { + let module = self.db.module(module_id); + self.resolve_imports(module.package_imports.iter().map(|(_, import)| import), ident) + } + ContainerId::GenerateBlockId(generate_block_id) => { + let generate_block = self.db.generate_block(generate_block_id); + self.resolve_imports( + generate_block.package_imports.iter().map(|(_, import)| import), + ident, + ) + } + ContainerId::BlockId(block_id) => { + let block = self.db.block(block_id); + self.resolve_imports(block.package_imports.iter().map(|(_, import)| import), ident) + } + ContainerId::SubroutineId(subroutine_id) => { + let subroutine = self.db.subroutine(subroutine_id); + self.resolve_imports( + subroutine.package_imports.iter().map(|(_, import)| import), + ident, + ) + } + } + } + + fn resolve_imports<'a>( + &mut self, + imports: impl Iterator, + ident: &Ident, + ) -> Option { + let mut explicit = Vec::new(); + let mut wildcard = Vec::new(); + + for import in imports { + match &import.item { + PackageImportName::Name(name) if name == ident => { + if let Some(res) = self.resolve_package_member(import.package.as_ref()?, ident) + { + push_unique_resolution(&mut explicit, res); + } + } + PackageImportName::Wildcard => { + if let Some(res) = self.resolve_package_member(import.package.as_ref()?, ident) + { + push_unique_resolution(&mut wildcard, res); + } + } + PackageImportName::Name(_) => {} + } + } + + if explicit.is_empty() { single_resolution(wildcard) } else { single_resolution(explicit) } + } + + fn resolve_package_member(&mut self, package: &Ident, ident: &Ident) -> Option { + let package_id = self.db.unit_scope().resolve_module(package).unique()?; + self.resolve_package_member_by_id(package_id, ident, &mut FxHashSet::default()) + } + + fn resolve_package_member_by_id( + &mut self, + package_id: ModuleId, + ident: &Ident, + seen: &mut FxHashSet<(ModuleId, Ident)>, + ) -> Option { + if let Some(res) = self.resolve_local_member_in_module(package_id, ident) { + return Some(res); + } + + if !seen.insert((package_id, ident.clone())) { + return None; + } + + let module = self.db.module(package_id); + let mut explicit = Vec::new(); + let mut wildcard = Vec::new(); + + for (_, export) in module.package_exports.iter() { + match &export.item { + PackageExportName::Name(name) if name == ident => { + if let Some(package) = export.package.as_ref() + && let Some(target) = self.db.unit_scope().resolve_module(package).unique() + && let Some(res) = self.resolve_package_member_by_id(target, ident, seen) + { + push_unique_resolution(&mut explicit, res); + } + } + PackageExportName::Wildcard => { + if let Some(package) = export.package.as_ref() + && let Some(target) = self.db.unit_scope().resolve_module(package).unique() + && let Some(res) = self.resolve_package_member_by_id(target, ident, seen) + { + push_unique_resolution(&mut wildcard, res); + } + } + PackageExportName::AllImports => { + for (_, import) in module.package_imports.iter() { + match &import.item { + PackageImportName::Name(name) if name == ident => { + if let Some(package) = import.package.as_ref() + && let Some(target) = + self.db.unit_scope().resolve_module(package).unique() + && let Some(res) = + self.resolve_package_member_by_id(target, ident, seen) + { + push_unique_resolution(&mut explicit, res); + } + } + PackageImportName::Wildcard => { + if let Some(package) = import.package.as_ref() + && let Some(target) = + self.db.unit_scope().resolve_module(package).unique() + && let Some(res) = + self.resolve_package_member_by_id(target, ident, seen) + { + push_unique_resolution(&mut wildcard, res); + } + } + PackageImportName::Name(_) => {} + } + } + } + PackageExportName::Name(_) => {} + } + } + + seen.remove(&(package_id, ident.clone())); + if explicit.is_empty() { single_resolution(wildcard) } else { single_resolution(explicit) } } fn resolve_member_from_resolution( @@ -124,7 +276,19 @@ impl Source2DefCtx<'_, '_> { PathResolution::GenerateBlock(generate_block_id) => { self.resolve_member_in_generate_block(generate_block_id, field) } - _ => None, + _ => self.resolve_member_from_ty(&type_of_path_resolution(self.db, res).ty, field), + } + } + + fn resolve_member_from_ty(&mut self, ty: &Ty, field: &Ident) -> Option { + match ty { + Ty::Module(module_id) => self.resolve_member_in_module(*module_id, field), + Ty::GenerateBlock(generate_block_id) => { + self.resolve_member_in_generate_block(*generate_block_id, field) + } + Ty::Block(block_id) => self.resolve_member_in_block(*block_id, field), + Ty::Alias { target, .. } => self.resolve_member_from_ty(target, field), + Ty::Unknown | Ty::Error | Ty::Void | Ty::Builtin(_) | Ty::Struct(_) => None, } } @@ -132,6 +296,14 @@ impl Source2DefCtx<'_, '_> { &mut self, module_id: ModuleId, field: &Ident, + ) -> Option { + self.resolve_package_member_by_id(module_id, field, &mut FxHashSet::default()) + } + + fn resolve_local_member_in_module( + &mut self, + module_id: ModuleId, + field: &Ident, ) -> Option { let scope = self.db.module_scope(module_id); let entry = scope.get(field)?; @@ -170,3 +342,13 @@ impl Source2DefCtx<'_, '_> { self.db.unit_scope().resolve_module(module_name).unique() } } + +fn push_unique_resolution(resolutions: &mut Vec, res: PathResolution) { + if !resolutions.contains(&res) { + resolutions.push(res); + } +} + +fn single_resolution(mut resolutions: Vec) -> Option { + if resolutions.len() == 1 { resolutions.pop() } else { None } +} diff --git a/crates/hir/src/semantics/pathres.rs b/crates/hir/src/semantics/pathres.rs index acdb344a..8401e209 100644 --- a/crates/hir/src/semantics/pathres.rs +++ b/crates/hir/src/semantics/pathres.rs @@ -12,7 +12,8 @@ use crate::{ file::{config::ConfigDeclId, library::LibraryDeclId, udp::UdpDeclId}, lower_ident_opt, module::{ - ModuleId, generate::GenerateBlockId, instantiation::InstanceId, port::NonAnsiPortId, + ModuleId, generate::GenerateBlockId, instantiation::InstanceId, modport::ModportId, + port::NonAnsiPortId, }, stmt::StmtId, subroutine::{SubroutineId, SubroutinePortId}, @@ -59,6 +60,7 @@ pub enum PathResolution { }, AnsiPort(InModule), Instance(InModule), + Modport(InModule), Stmt(InContainer), Block(BlockId), GenerateBlock(GenerateBlockId), @@ -85,6 +87,7 @@ impl From> for PathResolution { DeclId(decl_id) => Self::Decl(entry.with_value(decl_id).into()), TypedefId(typedef_id) => Self::Typedef(entry.with_value(typedef_id).into()), InstanceId(idx) => Self::Instance(entry.with_value(idx)), + ModportId(idx) => Self::Modport(entry.with_value(idx)), GenerateBlockId(generate_block_id) => Self::GenerateBlock(generate_block_id), StmtId(idx) => Self::Stmt(entry.with_value(idx).into()), SubroutineId(subroutine_id) => Self::Subroutine(subroutine_id), diff --git a/crates/hir/src/type_infer.rs b/crates/hir/src/type_infer.rs index 28a8a6c1..b9e37bef 100644 --- a/crates/hir/src/type_infer.rs +++ b/crates/hir/src/type_infer.rs @@ -16,7 +16,12 @@ use crate::{ declarator::{DeclId, DeclaratorParent}, }, literal::Literal, - module::{ModuleId, generate::GenerateBlockId, port::PortDeclId}, + module::{ + ModuleId, + generate::GenerateBlockId, + port::{PortDeclId, PortHeader}, + }, + package_import::{PackageExportName, PackageImport, PackageImportName}, stmt::{ForInit, StmtKind}, subroutine::SubroutinePortId, typedef::TypedefId, @@ -82,6 +87,10 @@ pub fn type_of_typedef(db: &dyn HirDb, typedef: InContainer) -> TyRes } pub fn type_of_decl(db: &dyn HirDb, decl: InContainer) -> TyResult { + if let Some(ty) = type_of_interface_port_decl(db, decl) { + return ty; + } + let Some(data_ty) = data_ty_of_decl(db, decl) else { return TyResult::new(Ty::Unknown); }; @@ -109,6 +118,7 @@ pub fn type_of_path_resolution(db: &dyn HirDb, res: PathResolution) -> TyResult PathResolution::GenerateBlock(generate_block_id) => { TyResult::new(Ty::GenerateBlock(generate_block_id)) } + PathResolution::Modport(_) => TyResult::new(Ty::Unknown), PathResolution::Block(block_id) => TyResult::new(Ty::Block(block_id)), PathResolution::Config(_) | PathResolution::Library(_) @@ -342,10 +352,77 @@ fn module_members(db: &dyn HirDb, module_id: ModuleId) -> Vec { TyMember { name: name.clone(), ty, origin: Some(origin) } }) .collect(); + for name in exported_member_names(db, module_id, &mut FxHashSet::default()) { + let Some(origin) = + resolve_package_member_by_id(db, module_id, &name, &mut FxHashSet::default()) + else { + continue; + }; + let ty = type_of_path_resolution(db, origin).ty; + members.push(TyMember { name, ty, origin: Some(origin) }); + } sort_members(&mut members); members } +fn exported_member_names( + db: &dyn HirDb, + module_id: ModuleId, + seen: &mut FxHashSet, +) -> Vec { + if !seen.insert(module_id) { + return Vec::new(); + } + + let module = db.module(module_id); + let mut names = Vec::new(); + + for (_, export) in module.package_exports.iter() { + match &export.item { + PackageExportName::Name(name) => { + names.push(name.clone()); + } + PackageExportName::Wildcard => { + if let Some(package) = export.package.as_ref() + && let Some(target) = db.unit_scope().resolve_module(package).unique() + { + names.extend(visible_module_member_names(db, target, seen)); + } + } + PackageExportName::AllImports => { + for (_, import) in module.package_imports.iter() { + match &import.item { + PackageImportName::Name(name) => names.push(name.clone()), + PackageImportName::Wildcard => { + if let Some(package) = import.package.as_ref() + && let Some(target) = + db.unit_scope().resolve_module(package).unique() + { + names.extend(visible_module_member_names(db, target, seen)); + } + } + } + } + } + } + } + + seen.remove(&module_id); + names +} + +fn visible_module_member_names( + db: &dyn HirDb, + module_id: ModuleId, + seen: &mut FxHashSet, +) -> Vec { + db.module_scope(module_id) + .iter() + .map(|(name, _)| name.clone()) + .chain(exported_member_names(db, module_id, seen)) + .collect() +} + fn generate_block_members(db: &dyn HirDb, generate_block_id: GenerateBlockId) -> Vec { let mut members: Vec<_> = db .generate_block_scope(generate_block_id) @@ -379,6 +456,24 @@ fn sort_members(members: &mut Vec) { members.dedup_by(|left, right| left.name == right.name); } +fn type_of_interface_port_decl(db: &dyn HirDb, decl: InContainer) -> Option { + let declarator = decl_of(db, decl)?; + let DeclaratorParent::PortDeclId(port_decl_id) = declarator.parent else { + return None; + }; + let ContainerId::ModuleId(module_id) = decl.cont_id else { + return None; + }; + let module = db.module(module_id); + let PortHeader::Interface { interface: Some(interface), .. } = + module.ports.get(port_decl_id).header.clone() + else { + return None; + }; + let interface_id = db.unit_scope().resolve_module(&interface).unique()?; + Some(TyResult::new(Ty::Module(interface_id))) +} + fn data_ty_of_decl(db: &dyn HirDb, decl: InContainer) -> Option { let declarator = decl_of(db, decl)?; match declarator.parent { @@ -425,7 +520,17 @@ fn type_of_subroutine_port(db: &dyn HirDb, port: InSubroutine) } fn resolve_name(db: &dyn HirDb, cont_id: ContainerId, ident: &Ident) -> Option { - ContainerParent::start_from(db, cont_id).find_map(|id| match id { + ContainerParent::start_from(db, cont_id).find_map(|id| { + resolve_local_name(db, id, ident).or_else(|| resolve_imported_name(db, id, ident)) + }) +} + +fn resolve_local_name( + db: &dyn HirDb, + cont_id: ContainerId, + ident: &Ident, +) -> Option { + match cont_id { ContainerId::HirFileId(_) => db.unit_scope().get(ident).map(PathResolution::from), ContainerId::ModuleId(module_id) => db .module_scope(module_id) @@ -443,7 +548,167 @@ fn resolve_name(db: &dyn HirDb, cont_id: ContainerId, ident: &Ident) -> Option

Option { + match cont_id { + ContainerId::HirFileId(file_id) => { + let file = db.hir_file(file_id); + resolve_imports(db, file.package_imports.iter().map(|(_, import)| import), ident) + } + ContainerId::ModuleId(module_id) => { + let module = db.module(module_id); + resolve_imports(db, module.package_imports.iter().map(|(_, import)| import), ident) + } + ContainerId::GenerateBlockId(generate_block_id) => { + let generate_block = db.generate_block(generate_block_id); + resolve_imports( + db, + generate_block.package_imports.iter().map(|(_, import)| import), + ident, + ) + } + ContainerId::BlockId(block_id) => { + let block = db.block(block_id); + resolve_imports(db, block.package_imports.iter().map(|(_, import)| import), ident) + } + ContainerId::SubroutineId(subroutine_id) => { + let subroutine = db.subroutine(subroutine_id); + resolve_imports(db, subroutine.package_imports.iter().map(|(_, import)| import), ident) + } + } +} + +fn resolve_imports<'a>( + db: &dyn HirDb, + imports: impl Iterator, + ident: &Ident, +) -> Option { + let mut explicit = Vec::new(); + let mut wildcard = Vec::new(); + + for import in imports { + match &import.item { + PackageImportName::Name(name) if name == ident => { + if let Some(res) = resolve_package_member(db, import.package.as_ref()?, ident) { + push_unique_resolution(&mut explicit, res); + } + } + PackageImportName::Wildcard => { + if let Some(res) = resolve_package_member(db, import.package.as_ref()?, ident) { + push_unique_resolution(&mut wildcard, res); + } + } + PackageImportName::Name(_) => {} + } + } + + if explicit.is_empty() { single_resolution(wildcard) } else { single_resolution(explicit) } +} + +fn resolve_package_member( + db: &dyn HirDb, + package: &Ident, + ident: &Ident, +) -> Option { + let module_id = db.unit_scope().resolve_module(package).unique()?; + resolve_package_member_by_id(db, module_id, ident, &mut FxHashSet::default()) +} + +fn resolve_package_member_by_id( + db: &dyn HirDb, + package_id: ModuleId, + ident: &Ident, + seen: &mut FxHashSet<(ModuleId, Ident)>, +) -> Option { + if let Some(res) = resolve_local_member_in_module(db, package_id, ident) { + return Some(res); + } + + if !seen.insert((package_id, ident.clone())) { + return None; + } + + let module = db.module(package_id); + let mut explicit = Vec::new(); + let mut wildcard = Vec::new(); + + for (_, export) in module.package_exports.iter() { + match &export.item { + PackageExportName::Name(name) if name == ident => { + if let Some(package) = export.package.as_ref() + && let Some(target) = db.unit_scope().resolve_module(package).unique() + && let Some(res) = resolve_package_member_by_id(db, target, ident, seen) + { + push_unique_resolution(&mut explicit, res); + } + } + PackageExportName::Wildcard => { + if let Some(package) = export.package.as_ref() + && let Some(target) = db.unit_scope().resolve_module(package).unique() + && let Some(res) = resolve_package_member_by_id(db, target, ident, seen) + { + push_unique_resolution(&mut wildcard, res); + } + } + PackageExportName::AllImports => { + for (_, import) in module.package_imports.iter() { + match &import.item { + PackageImportName::Name(name) if name == ident => { + if let Some(package) = import.package.as_ref() + && let Some(target) = + db.unit_scope().resolve_module(package).unique() + && let Some(res) = + resolve_package_member_by_id(db, target, ident, seen) + { + push_unique_resolution(&mut explicit, res); + } + } + PackageImportName::Wildcard => { + if let Some(package) = import.package.as_ref() + && let Some(target) = + db.unit_scope().resolve_module(package).unique() + && let Some(res) = + resolve_package_member_by_id(db, target, ident, seen) + { + push_unique_resolution(&mut wildcard, res); + } + } + PackageImportName::Name(_) => {} + } + } + } + PackageExportName::Name(_) => {} + } + } + + seen.remove(&(package_id, ident.clone())); + if explicit.is_empty() { single_resolution(wildcard) } else { single_resolution(explicit) } +} + +fn resolve_local_member_in_module( + db: &dyn HirDb, + module_id: ModuleId, + ident: &Ident, +) -> Option { + db.module_scope(module_id) + .get(ident) + .map(|entry| PathResolution::from(InModule::new(module_id, entry))) +} + +fn push_unique_resolution(resolutions: &mut Vec, res: PathResolution) { + if !resolutions.contains(&res) { + resolutions.push(res); + } +} + +fn single_resolution(mut resolutions: Vec) -> Option { + if resolutions.len() == 1 { resolutions.pop() } else { None } } fn instance_target_module_id( diff --git a/crates/ide/src/completion/engine/expr.rs b/crates/ide/src/completion/engine/expr.rs index 4eedd681..3f7ac60e 100644 --- a/crates/ide/src/completion/engine/expr.rs +++ b/crates/ide/src/completion/engine/expr.rs @@ -7,6 +7,7 @@ use hir::{ hir_def::{ lower_ident_opt, module::ModuleId, + package_import::{PackageImport, PackageImportName}, subroutine::{SubroutineId, SubroutineKind}, }, scope::{ @@ -14,7 +15,10 @@ use hir::{ SubroutineEntry, UnitEntry, }, semantics::{Semantics, pathres::PathResolution}, - type_infer::{Ty, normalize_data_ty, type_class, type_of_decl, type_of_path_resolution}, + type_infer::{ + Ty, TyMember, members_of_ty, normalize_data_ty, type_class, type_of_decl, + type_of_path_resolution, + }, }; use syntax::{ SyntaxKind, SyntaxNode, SyntaxNodeExt, @@ -169,6 +173,7 @@ fn collect_container_names( } } } + collect_imported_names(db, container_id, names); } fn collect_file_names(db: &RootDb, file_id: HirFileId, names: &mut BTreeMap) { @@ -189,34 +194,125 @@ fn collect_file_names(db: &RootDb, file_id: HirFileId, names: &mut BTreeMap) { let scope = db.module_scope(module_id); for (ident, entry) in scope.iter() { - match entry { - ModuleEntry::DeclId(decl_id) => { - names.entry(ident.to_string()).or_insert(NameKind::Value { - ty: type_of_decl(db, InContainer::new(module_id.into(), decl_id)).ty, - }); - } - ModuleEntry::AnsiPortEntry(AnsiPortEntry(decl_id)) => { - names.entry(ident.to_string()).or_insert(NameKind::Value { - ty: type_of_decl(db, InContainer::new(module_id.into(), decl_id)).ty, - }); - } - ModuleEntry::NonAnsiPortEntry(NonAnsiPortEntry { port_decl, data_decl, .. }) => { - let ty = data_decl - .or(port_decl) - .map(|decl_id| type_of_decl(db, InContainer::new(module_id.into(), decl_id)).ty) - .unwrap_or(Ty::Unknown); - names.entry(ident.to_string()).or_insert(NameKind::Value { ty }); - } - ModuleEntry::SubroutineId(subroutine_id) => { - names.entry(ident.to_string()).or_insert(NameKind::SubroutineCall { - return_ty: subroutine_return_ty(db, subroutine_id), - }); + collect_module_entry_name(db, module_id, ident, entry, names); + } + for member in members_of_ty(db, &Ty::Module(module_id)) { + collect_ty_member_name(db, member, names); + } +} + +fn collect_module_entry_name( + db: &RootDb, + module_id: ModuleId, + ident: &hir::hir_def::Ident, + entry: ModuleEntry, + names: &mut BTreeMap, +) { + match entry { + ModuleEntry::DeclId(decl_id) => { + names.entry(ident.to_string()).or_insert(NameKind::Value { + ty: type_of_decl(db, InContainer::new(module_id.into(), decl_id)).ty, + }); + } + ModuleEntry::AnsiPortEntry(AnsiPortEntry(decl_id)) => { + names.entry(ident.to_string()).or_insert(NameKind::Value { + ty: type_of_decl(db, InContainer::new(module_id.into(), decl_id)).ty, + }); + } + ModuleEntry::NonAnsiPortEntry(NonAnsiPortEntry { port_decl, data_decl, .. }) => { + let ty = data_decl + .or(port_decl) + .map(|decl_id| type_of_decl(db, InContainer::new(module_id.into(), decl_id)).ty) + .unwrap_or(Ty::Unknown); + names.entry(ident.to_string()).or_insert(NameKind::Value { ty }); + } + ModuleEntry::SubroutineId(subroutine_id) => { + names.entry(ident.to_string()).or_insert(NameKind::SubroutineCall { + return_ty: subroutine_return_ty(db, subroutine_id), + }); + } + _ => {} + } +} + +fn collect_imported_names( + db: &RootDb, + container_id: ContainerId, + names: &mut BTreeMap, +) { + match container_id { + ContainerId::HirFileId(file_id) => { + let file = db.hir_file(file_id); + collect_imports(db, file.package_imports.iter().map(|(_, import)| import), names); + } + ContainerId::ModuleId(module_id) => { + let module = db.module(module_id); + collect_imports(db, module.package_imports.iter().map(|(_, import)| import), names); + } + ContainerId::GenerateBlockId(generate_block_id) => { + let generate_block = db.generate_block(generate_block_id); + collect_imports( + db, + generate_block.package_imports.iter().map(|(_, import)| import), + names, + ); + } + ContainerId::BlockId(block_id) => { + let block = db.block(block_id); + collect_imports(db, block.package_imports.iter().map(|(_, import)| import), names); + } + ContainerId::SubroutineId(subroutine_id) => { + let subroutine = db.subroutine(subroutine_id); + collect_imports(db, subroutine.package_imports.iter().map(|(_, import)| import), names); + } + } +} + +fn collect_imports<'a>( + db: &RootDb, + imports: impl Iterator, + names: &mut BTreeMap, +) { + for import in imports { + let Some(package) = import + .package + .as_ref() + .and_then(|package| db.unit_scope().resolve_module(package).unique()) + else { + continue; + }; + + match &import.item { + PackageImportName::Wildcard => collect_module_names(db, package, names), + PackageImportName::Name(name) => { + let Some(member) = + members_of_ty(db, &Ty::Module(package)).into_iter().find(|it| &it.name == name) + else { + continue; + }; + collect_ty_member_name(db, member, names); } - _ => {} } } } +fn collect_ty_member_name(db: &RootDb, member: TyMember, names: &mut BTreeMap) { + match member.origin { + Some(PathResolution::Decl(_)) + | Some(PathResolution::ParamDecl(_)) + | Some(PathResolution::AnsiPort(_)) + | Some(PathResolution::NonAnsiPort { .. }) => { + names.entry(member.name.to_string()).or_insert(NameKind::Value { ty: member.ty }); + } + Some(PathResolution::Subroutine(subroutine_id)) => { + names.entry(member.name.to_string()).or_insert(NameKind::SubroutineCall { + return_ty: subroutine_return_ty(db, subroutine_id), + }); + } + _ => {} + } +} + fn subroutine_return_ty(db: &RootDb, subroutine_id: SubroutineId) -> Ty { match db.subroutine(subroutine_id).kind { SubroutineKind::Function { return_ty: Some(return_ty) } => { diff --git a/crates/ide/src/completion/engine/tests.rs b/crates/ide/src/completion/engine/tests.rs index 0d1e9488..3d4e2e77 100644 --- a/crates/ide/src/completion/engine/tests.rs +++ b/crates/ide/src/completion/engine/tests.rs @@ -886,6 +886,123 @@ endmodule assert!(!labels.contains(&"other_value"), "non-matching package member leaked: {items:?}"); } +#[test] +fn expression_completion_uses_package_wildcard_imports() { + let items = completions_in_text( + r#" +package pkg; + localparam int pkg_value = 1; + function int pkg_func; + endfunction +endpackage + +module top; + import pkg::*; + localparam int value = pkg_/*caret*/; +endmodule +"#, + None, + ); + let labels = labels(&items); + + assert!(labels.contains(&"pkg_value"), "imported package value expected: {items:?}"); + assert!(labels.contains(&"pkg_func"), "imported package function expected: {items:?}"); +} + +#[test] +fn expression_completion_uses_package_explicit_imports() { + let items = completions_in_text( + r#" +package pkg; + localparam int exported_value = 1; + localparam int hidden_value = 2; +endpackage + +module top; + import pkg::exported_value; + localparam int value = /*caret*/; +endmodule +"#, + None, + ); + let labels = labels(&items); + + assert!(labels.contains(&"exported_value"), "explicit package import expected: {items:?}"); + assert!(!labels.contains(&"hidden_value"), "non-imported package value leaked: {items:?}"); +} + +#[test] +fn scoped_name_completion_uses_package_exports() { + let items = completions_in_text( + r#" +package leaf_pkg; + localparam int visible_value = 1; +endpackage + +package mid_pkg; + export leaf_pkg::visible_value; +endpackage + +module top; + localparam int value = mid_pkg::visible/*caret*/; +endmodule +"#, + None, + ); + let labels = labels(&items); + + assert!(labels.contains(&"visible_value"), "exported package member expected: {items:?}"); +} + +#[test] +fn expression_completion_uses_package_exports() { + let items = completions_in_text( + r#" +package leaf_pkg; + localparam int exported_value = 1; +endpackage + +package mid_pkg; + export leaf_pkg::*; +endpackage + +module top; + import mid_pkg::*; + localparam int value = export/*caret*/; +endmodule +"#, + None, + ); + let labels = labels(&items); + + assert!(labels.contains(&"exported_value"), "exported package import expected: {items:?}"); +} + +#[test] +fn expression_completion_uses_package_export_all_imports() { + let items = completions_in_text( + r#" +package leaf_pkg; + localparam int exported_value = 1; +endpackage + +package mid_pkg; + import leaf_pkg::exported_value; + export *::*; +endpackage + +module top; + import mid_pkg::*; + localparam int value = export/*caret*/; +endmodule +"#, + None, + ); + let labels = labels(&items); + + assert!(labels.contains(&"exported_value"), "export *::* member expected: {items:?}"); +} + #[test] fn member_access_completion_uses_struct_fields() { let items = completions_in_text( @@ -907,6 +1024,29 @@ endmodule assert!(labels.contains(&"second_field"), "struct field expected: {items:?}"); } +#[test] +fn member_access_completion_uses_interface_port_type() { + let items = completions_in_text( + r#" +interface bus_if; + logic req; + logic gnt; + modport master(input gnt, output req); +endinterface + +module top(bus_if.master bus); + initial bus./*caret*/ +endmodule +"#, + None, + ); + let labels = labels(&items); + + assert!(labels.contains(&"req"), "interface member expected: {items:?}"); + assert!(labels.contains(&"gnt"), "interface member expected: {items:?}"); + assert!(labels.contains(&"master"), "modport member expected: {items:?}"); +} + #[test] fn manual_and_triggered_at_use_same_sensitivity_expectation_behavior() { let text = "module m; wire clk; always @/*caret*/(posedge clk) begin end endmodule\n"; diff --git a/crates/ide/src/definitions.rs b/crates/ide/src/definitions.rs index 7c4b6458..b0184927 100644 --- a/crates/ide/src/definitions.rs +++ b/crates/ide/src/definitions.rs @@ -7,23 +7,26 @@ use hir::{ block::{BlockId, BlockLoc}, expr::declarator::DeclId, file::{config::ConfigDeclId, library::LibraryDeclId, udp::UdpDeclId}, + lower_ident_opt, module::{ ModuleId, generate::{GenerateBlockId, GenerateBlockLoc}, instantiation::InstanceId, + modport::ModportId, port::NonAnsiPortId, }, stmt::StmtId, subroutine::{SubroutineId, SubroutinePortId}, typedef::TypedefId, }, + scope::ModuleEntry, semantics::{Semantics, pathres::PathResolution}, source_map::{IsNamedSrc, IsSrc, ToAstNode}, }; use smallvec::{SmallVec, smallvec}; use smol_str::SmolStr; use syntax::{ - SyntaxAncestors, SyntaxToken, SyntaxTokenWithParent, + SyntaxAncestors, SyntaxToken, SyntaxTokenWithParent, TokenKind, ast::{self, AstNode}, has_name::HasName, has_text_range::{HasTextRange, HasTextRangeIn}, @@ -59,6 +62,7 @@ pub enum DefinitionOrigin { Decl(InContainer), Typedef(InContainer), Instance(InModule), + Modport(InModule), Stmt(InContainer), } @@ -75,6 +79,7 @@ impl_from! { DefinitionOrigin => Decl(InContainer), Typedef(InContainer), Instance(InModule), + Modport(InModule), Stmt(InContainer), } @@ -100,6 +105,7 @@ impl DefinitionOrigin { DefinitionOrigin::Decl(InContainer { cont_id, .. }) => cont_id, DefinitionOrigin::Typedef(InContainer { cont_id, .. }) => cont_id, DefinitionOrigin::Instance(InModule { module_id, .. }) => module_id.into(), + DefinitionOrigin::Modport(InModule { module_id, .. }) => module_id.into(), DefinitionOrigin::Stmt(InContainer { cont_id, .. }) => cont_id, } } @@ -144,6 +150,9 @@ impl DefinitionOrigin { DefinitionOrigin::Instance(InModule { value, module_id }) => { module_id.to_container(db).get(value).name.clone() } + DefinitionOrigin::Modport(InModule { value, module_id }) => { + module_id.to_container(db).get(value).name.clone() + } DefinitionOrigin::Stmt(InContainer { value, cont_id }) => { cont_id.to_container(db).get(value).label.clone() } @@ -216,6 +225,10 @@ impl DefinitionOrigin { let range = module_id.to_container_src_map(db).get(value)?.name_range()?; Some(InFile::new(module_id.file_id, range)) } + DefinitionOrigin::Modport(InModule { value, module_id }) => { + let range = module_id.to_container_src_map(db).get(value)?.name_range()?; + Some(InFile::new(module_id.file_id, range)) + } DefinitionOrigin::Stmt(InContainer { value, cont_id }) => { let range = cont_id.to_container_src_map(db).get(value)?.name_range()?; Some(InFile::new(cont_id.file_id(db).into(), range)) @@ -286,6 +299,10 @@ impl DefinitionOrigin { let range = module_id.to_container_src_map(db).get(value)?.range(); InFile::new(module_id.file_id, range) } + DefinitionOrigin::Modport(InModule { value, module_id }) => { + let range = module_id.to_container_src_map(db).get(value)?.range(); + InFile::new(module_id.file_id, range) + } DefinitionOrigin::Stmt(InContainer { value, cont_id }) => { let range = cont_id.to_container_src_map(db).get(value)?.range(); InFile::new(cont_id.file_id(db).into(), range) @@ -395,6 +412,7 @@ impl Definition { PathResolution::Decl(decl_id) => Some(decl_id.into()), PathResolution::Typedef(typedef_id) => Some(typedef_id.into()), PathResolution::Instance(instance_id) => Some(instance_id.into()), + PathResolution::Modport(modport_id) => Some(modport_id.into()), PathResolution::Stmt(stmt_id) => Some(stmt_id.into()), PathResolution::Block(blk_id) => Some(blk_id.into()), PathResolution::GenerateBlock(generate_block_id) => Some(generate_block_id.into()), @@ -442,6 +460,10 @@ impl DefinitionClass { return Some(def); } + if let Some(def) = resolve_interface_port_modport(sema, file_id, tp) { + return Some(def); + } + if let Some(def) = resolve_declaration_name(sema, file_id, tp) { return Some(def); } @@ -530,6 +552,30 @@ fn resolve_member_or_scoped_name( Some(Definition::from(res).into()) } +fn resolve_interface_port_modport( + sema: &Semantics<'_, RootDb>, + _file_id: HirFileId, + SyntaxTokenWithParent { parent, tok }: SyntaxTokenWithParent, +) -> Option { + let header = SyntaxAncestors::start_from(parent).find_map(ast::InterfacePortHeader::cast)?; + if header.modport()?.member()? != tok { + return None; + } + + let interface = match header.name_or_keyword()? { + name if name.kind() == TokenKind::INTERFACE_KEYWORD => return None, + name => lower_ident_opt(Some(name))?, + }; + let modport = lower_ident_opt(Some(tok))?; + let module_id = sema.db.unit_scope().resolve_module(&interface).unique()?; + let entry = sema.db.module_scope(module_id).get(&modport)?; + if !matches!(entry, ModuleEntry::ModportId(_)) { + return None; + } + + Some(Definition::from(PathResolution::from(InModule::new(module_id, entry))).into()) +} + fn resolve_instantiation_type_name( sema: &Semantics<'_, RootDb>, file_id: HirFileId, diff --git a/crates/ide/src/document_symbols.rs b/crates/ide/src/document_symbols.rs index 367aa93b..55cd4d01 100644 --- a/crates/ide/src/document_symbols.rs +++ b/crates/ide/src/document_symbols.rs @@ -322,6 +322,13 @@ fn collect_module_items( build_specify_block(collector, specify_block_id, module, src_map) } ModuleItem::SpecifyItemId(_) => {} + ModuleItem::ModportId(modport_id) => { + let hir = module.get(modport_id); + if let Some(src) = src_map.get(modport_id) { + collector.push_symbol_with_kind(&hir.name, src, SymbolKind::Interface); + collector.pop(); + } + } ModuleItem::TypedefId(typedef_id) => { build_typedef(collector, typedef_id, module, src_map) } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 0e4603ad..55f0c547 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -73,6 +73,7 @@ impl SymbolKind { pub fn from_syntax_kind(kind: SyntaxKind) -> Self { match_ast_kind! { kind, ast::ModuleDeclaration where kind == SyntaxKind::MODULE_DECLARATION => SymbolKind::Module, + ast::ModuleDeclaration where kind == SyntaxKind::INTERFACE_DECLARATION => SymbolKind::Interface, ast::ConfigDeclaration => SymbolKind::Config, ast::UdpDeclaration => SymbolKind::Primitive, ast::NonAnsiPort => SymbolKind::NonAnsiPortLabel, diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 7dda6ab8..b2b6e637 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -11,6 +11,7 @@ use hir::{ ModuleId, generate::{GenerateBlockId, GenerateBlockLoc}, instantiation::InstanceId, + modport::ModportId, port::NonAnsiPortId, }, stmt::StmtId, @@ -71,6 +72,7 @@ impl ToNav for DefinitionOrigin { DefinitionOrigin::Decl(decl_id) => decl_id.to_nav(db), DefinitionOrigin::Typedef(typedef_id) => typedef_id.to_nav(db), DefinitionOrigin::Instance(instance_id) => instance_id.to_nav(db), + DefinitionOrigin::Modport(modport_id) => modport_id.to_nav(db), DefinitionOrigin::Stmt(stmt_id) => stmt_id.to_nav(db), } } @@ -83,7 +85,8 @@ impl ToNav for ModuleId { let name = self.to_container(db).name.clone(); let file_id = file_id.file_id(); - Some(build(file_id, src.name_range(), src.range(), name, SymbolKind::Module, None)) + let kind = SymbolKind::from_syntax_kind(src.kind()); + Some(build(file_id, src.name_range(), src.range(), name, kind, None)) } } @@ -303,6 +306,21 @@ impl ToNav for InModule { } } +impl ToNav for InModule { + fn to_nav(&self, db: &RootDb) -> Option { + let InModule { value: modport_id, module_id } = *self; + + let file_id = module_id.file_id(); + let src = module_id.to_container_src_map(db).get(modport_id)?; + + let module = module_id.to_container(db); + let name = module.get(modport_id).name.clone(); + let cont_name = module.name.clone(); + + Some(build(file_id, src.name_range(), src.range(), name, SymbolKind::Interface, cont_name)) + } +} + impl ToNav for InContainer { fn to_nav(&self, db: &RootDb) -> Option { let InContainer { value: stmt_id, cont_id } = *self; diff --git a/crates/ide/src/render.rs b/crates/ide/src/render.rs index 0c935ee3..48db67ad 100644 --- a/crates/ide/src/render.rs +++ b/crates/ide/src/render.rs @@ -22,15 +22,16 @@ use hir::{ }, region_tree::RegionParent, semantics::Semantics, + source_map::IsSrc, }; use itertools::Itertools; use syntax::{ - SVInt, SyntaxCursorExt, SyntaxNodeExt, + SVInt, SyntaxCursorExt, SyntaxKind, SyntaxNodeExt, has_text_range::HasTextRange, token::SyntaxTokenWithParentExt, trivia::{TriviaExt, TriviaKindExt}, }; -use utils::get::GetRef; +use utils::get::{Get, GetRef}; use crate::{ db::{line_index_db::LineIndexDb, root_db::RootDb}, @@ -224,7 +225,9 @@ fn render_signature(sema: &Semantics, origin: &DefinitionOrigin) -> Opti fn render_module_signature(db: &RootDb, module_id: ModuleId) -> Option { let module = db.module(module_id); let name = module.name.as_ref()?; - let mut signature = format!("module {name}"); + let src = module_id.file_id.to_container_src_map(db).get(module_id.value)?; + let kind = if src.kind() == SyntaxKind::INTERFACE_DECLARATION { "interface" } else { "module" }; + let mut signature = format!("{kind} {name}"); let params = render_module_param_ports(db, module_id); if !params.is_empty() { @@ -342,7 +345,8 @@ fn render_module_port_list(db: &RootDb, module_id: ModuleId) -> Vec { Ports::Ansi(port_decls) => { let mut ports = Vec::new(); for port_decl in port_decls.values() { - let header = InModule::new(module_id, port_decl.header).display_source(db).ok(); + let header = + InModule::new(module_id, port_decl.header.clone()).display_source(db).ok(); for decl_id in port_decl.decls.clone() { let name = InContainer::new(module_id.into(), decl_id).display_signature(db).ok(); @@ -390,7 +394,7 @@ fn render_decl_signature(db: &RootDb, decl_id: InContainer) -> Option Option "block", DefinitionOrigin::GenerateBlockId(_) => "generate", DefinitionOrigin::Instance(_) => "instance", + DefinitionOrigin::Modport(_) => "modport", DefinitionOrigin::Stmt(_) => "statement", DefinitionOrigin::Typedef(_) => "typedef", DefinitionOrigin::ModuleId(_) diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index ce801814..8b9dfd4d 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -192,7 +192,7 @@ fn sig_help_for_instance( for port_decl in port_decls.values() { let mut buf = String::new(); if !res.config.params_only { - let header = InModule::new(target_module_id, port_decl.header) + let header = InModule::new(target_module_id, port_decl.header.clone()) .display_signature(db) .unwrap_or_else(|_| "".to_string()); let header = header.trim_end(); diff --git a/crates/ide/src/verilog_2005.rs b/crates/ide/src/verilog_2005.rs index 16c21722..258c05e8 100644 --- a/crates/ide/src/verilog_2005.rs +++ b/crates/ide/src/verilog_2005.rs @@ -555,6 +555,152 @@ endconfig } } +#[test] +fn package_imports_resolve_unqualified_names() { + let text = r#" +package pkg; + typedef enum logic [1:0] { + IDLE + } /*marker:type_def*/state_e; + localparam int /*marker:param_def*/pkg_value = 1; +endpackage + +module top; + import pkg::*; + /*marker:type_ref*/state_e state; + initial state = /*marker:param_ref*/pkg_value; +endmodule +"#; + let (host, file_id, _clean_text, markers) = setup_marked(text); + let analysis = host.make_analysis(); + + let type_nav = analysis + .goto_definition(position(file_id, &markers, "type_ref")) + .unwrap() + .expect("imported typedef definition expected"); + assert!( + type_nav.info.iter().any(|nav| nav.name.as_deref() == Some("state_e")), + "imported typedef should resolve: {type_nav:?}" + ); + + let param_nav = analysis + .goto_definition(position(file_id, &markers, "param_ref")) + .unwrap() + .expect("imported parameter definition expected"); + assert!( + param_nav.info.iter().any(|nav| nav.name.as_deref() == Some("pkg_value")), + "imported parameter should resolve: {param_nav:?}" + ); +} + +#[test] +fn package_exports_resolve_reexported_names() { + let text = r#" +package leaf_pkg; + typedef enum logic [1:0] { + IDLE + } /*marker:type_def*/state_e; + localparam int /*marker:param_def*/exported_value = 1; +endpackage + +package mid_pkg; + export leaf_pkg::state_e; + export leaf_pkg::exported_value; +endpackage + +package all_pkg; + import leaf_pkg::exported_value; + export *::*; +endpackage + +module top; + import mid_pkg::*; + /*marker:type_ref*/state_e state; + initial state = /*marker:param_ref*/exported_value; + initial state = mid_pkg::/*marker:scoped_ref*/exported_value; + import all_pkg::exported_value; + initial state = /*marker:export_all_ref*/exported_value; +endmodule +"#; + let (host, file_id, _clean_text, markers) = setup_marked(text); + let analysis = host.make_analysis(); + + for marker in ["type_ref"] { + let nav = analysis + .goto_definition(position(file_id, &markers, marker)) + .unwrap() + .unwrap_or_else(|| panic!("{marker} definition expected")); + assert!( + nav.info.iter().any(|nav| nav.name.as_deref() == Some("state_e")), + "{marker} should resolve to exported typedef: {nav:?}" + ); + } + + for marker in ["param_ref", "scoped_ref", "export_all_ref"] { + let nav = analysis + .goto_definition(position(file_id, &markers, marker)) + .unwrap() + .unwrap_or_else(|| panic!("{marker} definition expected")); + assert!( + nav.info.iter().any(|nav| nav.name.as_deref() == Some("exported_value")), + "{marker} should resolve to exported parameter: {nav:?}" + ); + } +} + +#[test] +fn interface_ports_resolve_members_and_modports() { + let text = r#" +interface /*marker:if_def*/bus_if; + logic /*marker:req_def*/req; + logic /*marker:gnt_def*/gnt; + modport /*marker:master_def*/master(input gnt, output req); +endinterface + +module top(/*marker:if_ref*/bus_if./*marker:modport_ref*/master bus); + assign bus./*marker:req_ref*/req = bus./*marker:gnt_ref*/gnt; +endmodule +"#; + let (host, file_id, _clean_text, markers) = setup_marked(text); + let analysis = host.make_analysis(); + + for (marker, expected) in + [("if_ref", "bus_if"), ("modport_ref", "master"), ("req_ref", "req"), ("gnt_ref", "gnt")] + { + let nav = analysis + .goto_definition(position(file_id, &markers, marker)) + .unwrap() + .unwrap_or_else(|| panic!("{marker} definition expected")); + assert!( + nav.info.iter().any(|nav| nav.name.as_deref() == Some(expected)), + "{marker} should resolve to {expected:?}: {nav:?}" + ); + } + + let symbols = analysis.document_symbol(file_id).unwrap(); + let mut names = Vec::new(); + flatten_symbols(&symbols, &mut names); + for expected in ["bus_if", "req", "gnt", "master", "top"] { + assert!( + names.iter().any(|name| name == expected), + "missing document symbol {expected:?}; got {names:?}" + ); + } + + let hover = analysis + .hover( + position(file_id, &markers, "if_ref"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("interface hover expected"); + assert!( + hover.info.as_str().contains("interface bus_if"), + "interface hover should use interface signature: {}", + hover.info.as_str() + ); +} + #[test] fn verilog_2005_completion_keywords_cover_core_contexts() { let text = r#"