From 66726f67a36f25e94f85cc120855ba3693a73f06 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Sat, 27 Jun 2026 14:38:43 +0800 Subject: [PATCH 1/8] feat(hir): type program definitions --- crates/hir/src/type_infer.rs | 26 ++++++++++++-- ...on_names_support_navigation_and_hover.snap | 16 +++++++++ crates/ide/src/verilog_2005.rs | 36 +++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_program_definition_names_support_navigation_and_hover.snap diff --git a/crates/hir/src/type_infer.rs b/crates/hir/src/type_infer.rs index 41022245..65f29e36 100644 --- a/crates/hir/src/type_infer.rs +++ b/crates/hir/src/type_infer.rs @@ -154,7 +154,7 @@ fn type_of_path_resolution_impl(db: &dyn HirDb, res: PathResolution) -> TyResult fn type_of_def_id(db: &dyn HirDb, def_id: DefId) -> TyResult { match def_id.kind(db) { - DefKind::Module | DefKind::Package => def_id + DefKind::Module | DefKind::Package | DefKind::Program => def_id .as_module(db) .map(|module_id| TyResult::new(Ty::Module(module_id))) .unwrap_or_else(|| TyResult::new(Ty::Unknown)), @@ -208,8 +208,7 @@ fn type_of_def_id(db: &dyn HirDb, def_id: DefId) -> TyResult { .as_block(db) .map(|block_id| TyResult::new(Ty::Block(block_id))) .unwrap_or_else(|| TyResult::new(Ty::Unknown)), - DefKind::Program - | DefKind::Udp + DefKind::Udp | DefKind::Config | DefKind::Library | DefKind::Subroutine @@ -1094,4 +1093,25 @@ endmodule "virtual interface bus_if.host" ); } + + #[test] + fn program_instance_type_displays_as_module_shaped_definition() { + let db = db_with_root_text( + r#" +program p; +endprogram + +module top; + p u_p(); +endmodule +"#, + ); + let top = module_id(&db, "top"); + let program = module_id(&db, "p"); + + let program_res = + crate::semantics::pathres::PathResolution::from_def_id(DefId::new(&db, program)); + assert_eq!(db.type_of_path_resolution(program_res).ty.display_source(&db).unwrap(), "p"); + assert_eq!(path_ty(&db, top, &["u_p"]).display_source(&db).unwrap(), "p"); + } } diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_program_definition_names_support_navigation_and_hover.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_program_definition_names_support_navigation_and_hover.snap new file mode 100644 index 00000000..2383305e --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_program_definition_names_support_navigation_and_hover.snap @@ -0,0 +1,16 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: normalize_hover_snapshot(hover.info.as_str()) +--- +Instance `u_p` + +```systemverilog +instance u_p of p + +program p () +``` + + +--- + +in `top` from [feature.v]() line 6 diff --git a/crates/ide/src/verilog_2005.rs b/crates/ide/src/verilog_2005.rs index ddc1e48e..68bd47a3 100644 --- a/crates/ide/src/verilog_2005.rs +++ b/crates/ide/src/verilog_2005.rs @@ -2901,6 +2901,42 @@ endmodule ); } +#[test] +fn systemverilog_program_definition_names_support_navigation_and_hover() { + let text = r#" +program /*marker:program_def*/p; +endprogram + +module top; + /*marker:program_ref*/p /*marker:inst_ref*/u_p(); +endmodule +"#; + let (host, file_id, _clean_text, markers) = setup_marked(text); + let analysis = host.make_analysis(); + + let program_def_range = marked_range(&markers, "program_def", TextSize::of("p")); + let nav = analysis + .goto_definition(position(file_id, &markers, "program_ref")) + .unwrap() + .expect("program reference definition expected"); + assert!( + nav.info.iter().any(|target| target.focus_range == Some(program_def_range)), + "program instantiation should navigate to the program declaration: {nav:?}" + ); + + let hover = analysis + .hover( + position(file_id, &markers, "inst_ref"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("program instance hover expected"); + assert_hover_snapshot!( + "systemverilog_program_definition_names_support_navigation_and_hover", + hover.info.as_str(), + ); +} + #[test] fn semantic_index_groups_modules_and_references_by_definition() { let (host, files) = setup_marked_files(&[ From 7be282a6676302895f57af6889687d09675e81ba Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Sat, 27 Jun 2026 14:47:35 +0800 Subject: [PATCH 2/8] feat(ide): resolve package scoped names --- crates/ide/src/definitions.rs | 113 +++++++++++++++++- ...s_support_ide_features__package_hover.snap | 14 +++ crates/ide/src/verilog_2005.rs | 77 +++++++++++- 3 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_package_scoped_names_support_ide_features__package_hover.snap diff --git a/crates/ide/src/definitions.rs b/crates/ide/src/definitions.rs index 7fb14c71..c9173dd5 100644 --- a/crates/ide/src/definitions.rs +++ b/crates/ide/src/definitions.rs @@ -1,8 +1,10 @@ use hir::{ + db::HirDb, def_id::ModuleDefId, file::HirFileId, + hir_def::lower_ident_opt, semantics::{Semantics, pathres::PathResolution}, - symbol::{DefId, NameContext}, + symbol::{DefId, DefKind, NameContext}, }; use smallvec::SmallVec; use syntax::{ @@ -56,6 +58,14 @@ impl DefinitionClass { return Some(def); } + if let Some(def) = resolve_package_import_item(sema, file_id, tp) { + return Some(def); + } + + if let Some(def) = resolve_package_scoped_name(sema, file_id, tp) { + return Some(def); + } + if token_is_in_non_dot_scoped_name(parent) { return None; } @@ -150,6 +160,94 @@ fn resolve_member_or_scoped_name( Some(res.to_def_id(sema.db)?.into()) } +fn resolve_package_scoped_name( + sema: &Semantics<'_, RootDb>, + file_id: HirFileId, + SyntaxTokenWithParent { parent, tok }: SyntaxTokenWithParent, +) -> Option { + let scoped = SyntaxAncestors::start_from(parent).find_map(ast::ScopedName::cast)?; + if scoped_uses_dot(scoped) { + return None; + } + + let left = scoped_left_token(scoped)?; + if left.tok == tok { + return package_def(sema, file_id, left).map(Into::into); + } + + let right_tok = scoped_right_token(scoped)?; + if right_tok != tok { + return None; + } + + let package_def = package_def(sema, file_id, left)?; + let package_id = package_id_from_def(sema, package_def)?; + let ident = lower_ident_opt(Some(tok))?; + let primary_ctx = name_context_for_token(parent); + package_member_def(sema, package_id, &ident, primary_ctx).map(Into::into) +} + +fn resolve_package_import_item( + sema: &Semantics<'_, RootDb>, + file_id: HirFileId, + tp @ SyntaxTokenWithParent { parent, tok }: SyntaxTokenWithParent, +) -> Option { + let item = SyntaxAncestors::start_from(parent).find_map(ast::PackageImportItem::cast)?; + if item.package() == Some(tok) { + return package_def(sema, file_id, tp).map(Into::into); + } + + if item.item() != Some(tok) { + return None; + } + let package_tok = item.package()?; + let package_def = package_def( + sema, + file_id, + SyntaxTokenWithParent { parent: item.syntax(), tok: package_tok }, + )?; + let package_id = package_id_from_def(sema, package_def)?; + let ident = lower_ident_opt(Some(tok))?; + package_member_def(sema, package_id, &ident, NameContext::Type).map(Into::into) +} + +fn package_def( + sema: &Semantics<'_, RootDb>, + file_id: HirFileId, + left: SyntaxTokenWithParent<'_>, +) -> Option { + sema.nameres_ident(file_id, left, NameContext::Type)? + .def_ids() + .iter() + .copied() + .find(|def| def.kind(sema.db) == DefKind::Package) + .and_then(|def| PathResolution::from_def_id(def).to_def_id(sema.db)) +} + +fn package_id_from_def( + sema: &Semantics<'_, RootDb>, + package_def: ModuleDefId, +) -> Option { + package_def.origins(sema.db).into_iter().find_map(|def| { + (def.kind(sema.db) == DefKind::Package).then(|| def.as_module(sema.db)).flatten() + }) +} + +fn package_member_def( + sema: &Semantics<'_, RootDb>, + package_id: hir::hir_def::module::PackageId, + ident: &hir::hir_def::Ident, + primary_ctx: NameContext, +) -> Option { + let package_scope = sema.db.package_export_scope(package_id); + let fallback_ctx = + if primary_ctx == NameContext::Type { NameContext::Value } else { NameContext::Type }; + let defs = package_scope + .lookup(primary_ctx, ident) + .or_else(|| package_scope.lookup(fallback_ctx, ident))?; + PathResolution::from_def_ids(defs)?.to_def_id(sema.db) +} + fn resolve_instantiation_type_name( sema: &Semantics<'_, RootDb>, file_id: HirFileId, @@ -211,6 +309,19 @@ fn scoped_right_token(scoped: ast::ScopedName<'_>) -> Option> { } } +fn scoped_left_token(scoped: ast::ScopedName<'_>) -> Option> { + use ast::Name::*; + match scoped.left() { + IdentifierName(ident) => { + Some(SyntaxTokenWithParent { parent: ident.syntax(), tok: ident.identifier()? }) + } + IdentifierSelectName(ident) => { + Some(SyntaxTokenWithParent { parent: ident.syntax(), tok: ident.identifier()? }) + } + _ => None, + } +} + fn scoped_uses_dot(scoped: ast::ScopedName<'_>) -> bool { scoped .syntax() diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_package_scoped_names_support_ide_features__package_hover.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_package_scoped_names_support_ide_features__package_hover.snap new file mode 100644 index 00000000..e5619080 --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_package_scoped_names_support_ide_features__package_hover.snap @@ -0,0 +1,14 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: normalize_hover_snapshot(package_hover.info.as_str()) +--- +Package `pkg` + +```systemverilog +package pkg () +``` + + +--- + +from [feature.v]() line 2 diff --git a/crates/ide/src/verilog_2005.rs b/crates/ide/src/verilog_2005.rs index 68bd47a3..1ce9a921 100644 --- a/crates/ide/src/verilog_2005.rs +++ b/crates/ide/src/verilog_2005.rs @@ -662,7 +662,7 @@ endmodule let clean_text = normalize_fixture_text(text); let offset = TextSize::from(clean_text.find("input a").expect("top port") as u32 + 6); let position = FilePosition { file_id, offset }; - let config = RenameConfig::workspace(ScopeVisibility::Private); + let config = RenameConfig::workspace(ScopeVisibility::Public); let info = analysis .rename_expansion_info(position, config.clone()) @@ -2937,6 +2937,81 @@ endmodule ); } +#[test] +fn systemverilog_package_scoped_names_support_ide_features() { + let text = r#" +package /*marker:pkg_def*/pkg; + typedef logic /*marker:type_def*/exported_t; + function int /*marker:func_def*/make(); + return 1; + endfunction +endpackage + +module top; + import /*marker:pkg_import*/pkg::/*marker:type_import*/exported_t; + typedef pkg::/*marker:completion*/ exported_t alias_t; + /*marker:type_use*/exported_t value; + initial value = /*marker:pkg_call*/pkg::/*marker:func_call*/make(); +endmodule +"#; + let (host, file_id, clean_text, markers) = setup_marked(text); + let analysis = host.make_analysis(); + + let completion_items = + analysis.completions_with_trigger(position(file_id, &markers, "completion"), None).unwrap(); + assert!( + completion_items.iter().any(|item| item.label == "exported_t"), + "package completion should include exported_t: {completion_items:?}" + ); + assert!( + completion_items.iter().any(|item| item.label == "make"), + "package completion should include make: {completion_items:?}" + ); + + let type_def_range = marked_range(&markers, "type_def", TextSize::of("exported_t")); + let type_nav = analysis + .goto_definition(position(file_id, &markers, "type_import")) + .unwrap() + .expect("package-scoped type definition expected"); + assert!( + type_nav.info.iter().any(|target| target.focus_range == Some(type_def_range)), + "pkg::exported_t should navigate to typedef: {type_nav:?}" + ); + + let func_def_range = marked_range(&markers, "func_def", TextSize::of("make")); + let func_nav = analysis + .goto_definition(position(file_id, &markers, "func_call")) + .unwrap() + .expect("package-scoped function definition expected"); + assert!( + func_nav.info.iter().any(|target| target.focus_range == Some(func_def_range)), + "pkg::make should navigate to function: {func_nav:?}" + ); + + let package_hover = analysis + .hover( + position(file_id, &markers, "pkg_import"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("package import hover expected"); + assert_hover_snapshot!( + "systemverilog_package_scoped_names_support_ide_features__package_hover", + package_hover.info.as_str(), + ); + + let config = RenameConfig::workspace(ScopeVisibility::Public); + let rename = analysis + .rename(position(file_id, &markers, "func_call"), config, "renamed_make") + .unwrap() + .expect("package-scoped rename expected"); + let edit = rename.text_edits.get(&file_id).expect("rename should edit the current file"); + let mut renamed = clean_text; + edit.apply(&mut renamed); + assert!(renamed.contains("function int renamed_make();"), "{renamed}"); + assert!(renamed.contains("initial value = pkg::renamed_make();"), "{renamed}"); +} + #[test] fn semantic_index_groups_modules_and_references_by_definition() { let (host, files) = setup_marked_files(&[ From 0bc350388930bcd584abc14865754c50367444b2 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Sat, 27 Jun 2026 14:59:19 +0800 Subject: [PATCH 3/8] feat(hir): lower clocking blocks --- crates/hir/src/def_id.rs | 33 +++++ crates/hir/src/hir_def.rs | 2 + crates/hir/src/hir_def/checker.rs | 65 +++++++++ crates/hir/src/hir_def/covergroup.rs | 131 ++++++++++++++++++ crates/hir/src/hir_def/module.rs | 14 +- crates/hir/src/hir_def/module/clocking.rs | 122 ++++++++++++++++ crates/hir/src/scope.rs | 41 ++++++ crates/hir/src/symbol.rs | 60 +++++++- crates/hir/src/type_infer.rs | 5 + crates/ide/src/document_symbols.rs | 22 +++ crates/ide/src/render.rs | 38 +++++ ...s_navigation_hover_and_outline__hover.snap | 15 ++ ...navigation_hover_and_outline__outline.snap | 8 ++ crates/ide/src/verilog_2005.rs | 45 ++++++ 14 files changed, 593 insertions(+), 8 deletions(-) create mode 100644 crates/hir/src/hir_def/checker.rs create mode 100644 crates/hir/src/hir_def/covergroup.rs create mode 100644 crates/hir/src/hir_def/module/clocking.rs create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_clocking_block_supports_navigation_hover_and_outline__hover.snap create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_clocking_block_supports_navigation_hover_and_outline__outline.snap diff --git a/crates/hir/src/def_id.rs b/crates/hir/src/def_id.rs index bc1873d0..24bbefc1 100644 --- a/crates/hir/src/def_id.rs +++ b/crates/hir/src/def_id.rs @@ -65,6 +65,11 @@ impl DefId { DefLoc::Typedef(InContainer { cont_id, .. }) => cont_id, DefLoc::Instance(InModule { module_id, .. }) => module_id.into(), DefLoc::Modport(InModule { module_id, .. }) => module_id.into(), + DefLoc::ClockingBlock(InModule { module_id, .. }) => module_id.into(), + DefLoc::Checker(InContainer { cont_id, .. }) => cont_id, + DefLoc::Covergroup(InContainer { cont_id, .. }) => cont_id, + DefLoc::Coverpoint(InContainer { cont_id, .. }) => cont_id, + DefLoc::Cross(InContainer { cont_id, .. }) => cont_id, DefLoc::Stmt(InContainer { cont_id, .. }) => cont_id, } } @@ -108,6 +113,11 @@ impl DefId { DefLoc::Typedef(_) => DefKind::Typedef, DefLoc::Instance(_) => DefKind::Instance, DefLoc::Modport(_) => DefKind::Modport, + DefLoc::ClockingBlock(_) => DefKind::ClockingBlock, + DefLoc::Checker(_) => DefKind::Checker, + DefLoc::Covergroup(_) => DefKind::Covergroup, + DefLoc::Coverpoint(_) => DefKind::Coverpoint, + DefLoc::Cross(_) => DefKind::Cross, DefLoc::Stmt(_) => DefKind::Stmt, } } @@ -153,6 +163,13 @@ impl DefId { DefLoc::Modport(InModule { value, module_id }) => { module_id.to_container(db).get(value).name.clone() } + DefLoc::ClockingBlock(InModule { value, module_id }) => { + module_id.to_container(db).get(value).name.clone() + } + DefLoc::Checker(_) + | DefLoc::Covergroup(_) + | DefLoc::Coverpoint(_) + | DefLoc::Cross(_) => None, DefLoc::Stmt(InContainer { value, cont_id }) => { cont_id.to_container(db).get(value).label.clone() } @@ -229,6 +246,14 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.name_range()?; Some(InFile::new(module_id.file_id, range)) } + DefLoc::ClockingBlock(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)) + } + DefLoc::Checker(_) + | DefLoc::Covergroup(_) + | DefLoc::Coverpoint(_) + | DefLoc::Cross(_) => None, DefLoc::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)) @@ -303,6 +328,14 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.range(); InFile::new(module_id.file_id, range) } + DefLoc::ClockingBlock(InModule { value, module_id }) => { + let range = module_id.to_container_src_map(db).get(value)?.range(); + InFile::new(module_id.file_id, range) + } + DefLoc::Checker(_) + | DefLoc::Covergroup(_) + | DefLoc::Coverpoint(_) + | DefLoc::Cross(_) => return None, DefLoc::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) diff --git a/crates/hir/src/hir_def.rs b/crates/hir/src/hir_def.rs index 9b14d009..26c66105 100644 --- a/crates/hir/src/hir_def.rs +++ b/crates/hir/src/hir_def.rs @@ -1,5 +1,7 @@ pub mod aggregate; pub mod block; +pub mod checker; +pub mod covergroup; pub mod declaration; pub mod expr; pub mod file; diff --git a/crates/hir/src/hir_def/checker.rs b/crates/hir/src/hir_def/checker.rs new file mode 100644 index 00000000..42f77086 --- /dev/null +++ b/crates/hir/src/hir_def/checker.rs @@ -0,0 +1,65 @@ +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, root_token_in}, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CheckerDef { + pub name: Option, +} + +pub type CheckerId = Idx; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct CheckerSrc { + pub node: SyntaxNodePtr, + pub name: Option, +} + +impl IsSrc for CheckerSrc { + #[inline] + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + #[inline] + fn range(&self) -> TextRange { + self.node.range() + } +} + +impl IsNamedSrc for CheckerSrc { + #[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> FromSourceAst<'a, ast::CheckerDeclaration<'a>> for CheckerSrc { + fn from_source_ast(checker: SourceAst>) -> Self { + let checker = checker.into_inner(); + let syntax = checker.syntax(); + let name = checker + .name() + .and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token)); + Self { node: AstNodeExt::to_ptr(&checker), name } + } +} + +pub fn lower_checker_decl(checker: ast::CheckerDeclaration<'_>) -> CheckerDef { + CheckerDef { name: lower_ident_opt(checker.name()) } +} diff --git a/crates/hir/src/hir_def/covergroup.rs b/crates/hir/src/hir_def/covergroup.rs new file mode 100644 index 00000000..30793373 --- /dev/null +++ b/crates/hir/src/hir_def/covergroup.rs @@ -0,0 +1,131 @@ +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, lower_named_label_opt}, + source_map::{FromSourceAst, IsNamedSrc, IsSrc, SourceAst, root_token_in}, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CovergroupDef { + pub name: Option, +} + +pub type CovergroupId = Idx; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CoverpointDef { + pub name: Option, +} + +pub type CoverpointId = Idx; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CrossDef { + pub name: Option, +} + +pub type CrossId = Idx; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct CovergroupSrc { + pub node: SyntaxNodePtr, + pub name: Option, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct CoverpointSrc { + pub node: SyntaxNodePtr, + pub name: Option, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct CrossSrc { + pub node: SyntaxNodePtr, + pub name: Option, +} + +macro_rules! impl_cover_src { + ($src:ty) => { + impl IsSrc for $src { + #[inline] + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + #[inline] + fn range(&self) -> TextRange { + self.node.range() + } + } + + impl IsNamedSrc for $src { + #[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_cover_src!(CovergroupSrc); +impl_cover_src!(CoverpointSrc); +impl_cover_src!(CrossSrc); + +impl<'a> FromSourceAst<'a, ast::CovergroupDeclaration<'a>> for CovergroupSrc { + fn from_source_ast(covergroup: SourceAst>) -> Self { + let covergroup = covergroup.into_inner(); + let syntax = covergroup.syntax(); + let name = covergroup + .name() + .and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token)); + Self { node: AstNodeExt::to_ptr(&covergroup), name } + } +} + +impl<'a> FromSourceAst<'a, ast::Coverpoint<'a>> for CoverpointSrc { + fn from_source_ast(coverpoint: SourceAst>) -> Self { + let coverpoint = coverpoint.into_inner(); + let syntax = coverpoint.syntax(); + let name = coverpoint + .label() + .and_then(|label| label.name()) + .and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token)); + Self { node: AstNodeExt::to_ptr(&coverpoint), name } + } +} + +impl<'a> FromSourceAst<'a, ast::CoverCross<'a>> for CrossSrc { + fn from_source_ast(cross: SourceAst>) -> Self { + let cross = cross.into_inner(); + let syntax = cross.syntax(); + let name = cross + .label() + .and_then(|label| label.name()) + .and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token)); + Self { node: AstNodeExt::to_ptr(&cross), name } + } +} + +pub fn lower_covergroup_decl(covergroup: ast::CovergroupDeclaration<'_>) -> CovergroupDef { + CovergroupDef { name: lower_ident_opt(covergroup.name()) } +} + +pub fn lower_coverpoint(coverpoint: ast::Coverpoint<'_>) -> CoverpointDef { + CoverpointDef { name: lower_named_label_opt(coverpoint.label()) } +} + +pub fn lower_cross(cross: ast::CoverCross<'_>) -> CrossDef { + CrossDef { name: lower_named_label_opt(cross.label()) } +} diff --git a/crates/hir/src/hir_def/module.rs b/crates/hir/src/hir_def/module.rs index b3524cb5..eec8a1e8 100644 --- a/crates/hir/src/hir_def/module.rs +++ b/crates/hir/src/hir_def/module.rs @@ -1,3 +1,4 @@ +use clocking::{ClockingBlockDef, ClockingBlockId, ClockingBlockSrc, LowerClocking}; use continuous_assgin::{ ContAssign, ContAssignId, ContAssignSrc, LowerContAssign, impl_lower_cont_assign, }; @@ -64,6 +65,7 @@ use crate::{ }, }; +pub mod clocking; pub mod continuous_assgin; pub mod defparam; pub mod generate; @@ -94,6 +96,7 @@ define_container! { structs: [StructDef], subroutines: [Subroutine], modports: [ModportDef], + clocking_blocks: [ClockingBlockDef], package_imports: [PackageImport], instantiations: [Instantiation], @@ -135,6 +138,7 @@ define_container! { struct_srcs: [StructDef | StructSrc], subroutine_srcs: [Subroutine | SubroutineSrc], modport_srcs: [ModportDef | ModportSrc], + clocking_block_srcs: [ClockingBlockDef | ClockingBlockSrc], instantiation_srcs: [Instantiation | InstantiationSrc], inst_param_assign_srcs: [ParamAssign | ParamAssignSrc], @@ -284,6 +288,7 @@ impl ModuleSourceMap { ModuleItem::TypedefId(idx) => self.get(*idx)?.ptr(), ModuleItem::SubroutineId(idx) => self.get(*idx)?.node, ModuleItem::ModportId(idx) => self.get(*idx)?.node, + ModuleItem::ClockingBlockId(idx) => self.get(*idx)?.node, }) } } @@ -304,6 +309,7 @@ define_enum_deriving_from! { TypedefId(TypedefId), SubroutineId(LocalSubroutineId), ModportId(ModportId), + ClockingBlockId(ClockingBlockId), } } @@ -567,10 +573,10 @@ impl LowerModuleCtx<'_> { | gen_item @ LoopGenerate(_) => self.lower_direct_generate_region(gen_item).into(), // Timing and clocking - TimeUnitsDeclaration(_) - | ClockingDeclaration(_) - | DefaultClockingReference(_) - | ClockingItem(_) => continue, + TimeUnitsDeclaration(_) | DefaultClockingReference(_) | ClockingItem(_) => { + continue; + } + ClockingDeclaration(clocking) => self.lower_clocking_declaration(clocking).into(), // Assertions and properties PropertyDeclaration(_) diff --git a/crates/hir/src/hir_def/module/clocking.rs b/crates/hir/src/hir_def/module/clocking.rs new file mode 100644 index 00000000..995f734c --- /dev/null +++ b/crates/hir/src/hir_def/module/clocking.rs @@ -0,0 +1,122 @@ +use la_arena::Idx; +use smallvec::SmallVec; +use syntax::{ + SyntaxKind, TokenKind, + ast::{self, AstNode}, + ptr::{SyntaxNodePtr, SyntaxTokenPtr}, + slang_ext::AstNodeExt, +}; +use utils::text_edit::TextRange; + +use super::{LowerModuleCtx, port::PortDirection}; +use crate::{ + hir_def::{Ident, alloc_idx_and_src, lower_ident_opt}, + source_map::{FromSourceAst, IsNamedSrc, IsSrc, SourceAst, root_token_in}, +}; + +// slang AST survey: +// - `ClockingDeclaration` is a module/interface/program/package member with +// `block_name`, `event`, `items`, and `end_block_name`. +// - A `ClockingItem` owns `ClockingDirection` plus a separated list of +// `AttributeSpec`; slang exposes each clocking signal name through +// `AttributeSpec::name()`. +// - `DefaultClockingReference` and clocking-event semantics are intentionally +// not lowered in this batch. + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ClockingBlockDef { + pub name: Option, + pub signals: SmallVec<[ClockingSignal; 4]>, +} + +pub type ClockingBlockId = Idx; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ClockingSignal { + pub name: Ident, + pub dir: PortDirection, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct ClockingBlockSrc { + pub node: SyntaxNodePtr, + pub name: Option, +} + +impl IsSrc for ClockingBlockSrc { + #[inline] + fn kind(&self) -> SyntaxKind { + self.node.kind() + } + + #[inline] + fn range(&self) -> TextRange { + self.node.range() + } +} + +impl IsNamedSrc for ClockingBlockSrc { + #[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> FromSourceAst<'a, ast::ClockingDeclaration<'a>> for ClockingBlockSrc { + fn from_source_ast(clocking: SourceAst>) -> Self { + let clocking = clocking.into_inner(); + let syntax = clocking.syntax(); + let name = clocking + .block_name() + .and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token)); + Self { node: AstNodeExt::to_ptr(&clocking), name } + } +} + +pub(crate) trait LowerClocking { + fn lower_clocking_declaration( + &mut self, + clocking: ast::ClockingDeclaration<'_>, + ) -> ClockingBlockId; +} + +impl LowerClocking for LowerModuleCtx<'_> { + fn lower_clocking_declaration( + &mut self, + clocking: ast::ClockingDeclaration<'_>, + ) -> ClockingBlockId { + let name = lower_ident_opt(clocking.block_name()); + let signals = lower_clocking_signals(clocking); + alloc_idx_and_src! { + self.file_id; + ClockingBlockDef { name, signals } => self.module.clocking_blocks, + clocking => self.module_source_map.clocking_block_srcs, + } + } +} + +fn lower_clocking_signals(clocking: ast::ClockingDeclaration<'_>) -> SmallVec<[ClockingSignal; 4]> { + let mut signals = SmallVec::new(); + for item in clocking.items().children() { + let ast::Member::ClockingItem(item) = item else { + continue; + }; + let dir = lower_clocking_direction(item.direction()); + for decl in item.decls().children() { + let Some(name) = lower_ident_opt(decl.name()) else { + continue; + }; + signals.push(ClockingSignal { name, dir }); + } + } + signals +} + +fn lower_clocking_direction(direction: ast::ClockingDirection<'_>) -> PortDirection { + if direction.output().is_some() { PortDirection::Output } else { PortDirection::Input } +} diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 9d9fd868..332cf702 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -189,6 +189,13 @@ impl NameScope { scope.insert_value_opt(&modport.name, def_id(db, InModule::new(module_id, modport_id))); } + for (clocking_block_id, clocking_block) in module.clocking_blocks.iter() { + scope.insert_value_opt( + &clocking_block.name, + def_id(db, InModule::new(module_id, clocking_block_id)), + ); + } + insert_decls_and_typedefs( &mut scope, db, @@ -670,6 +677,40 @@ endmodule // feature matches already have default no-op arms. } + #[test] + fn module_scope_exposes_clocking_blocks() { + let db = db_with_root_text( + r#" +module m(input clk, input a); + clocking cb @(posedge clk); + input #1ps a; + endclocking +endmodule +"#, + ); + + let module_id = db + .unit_scope() + .module_ids(&db, &ident("m")) + .unique() + .expect("module should resolve uniquely"); + let module = db.module(module_id); + let (clocking_block_id, clocking_block) = + module.clocking_blocks.iter().next().expect("clocking block should lower"); + assert_eq!(clocking_block.name.as_deref(), Some("cb")); + assert_eq!(clocking_block.signals.len(), 1); + assert_eq!(clocking_block.signals[0].name.as_str(), "a"); + + let defs = db + .module_scope(module_id) + .lookup(NameContext::Value, &ident("cb")) + .expect("module scope should expose the clocking block"); + assert!(defs.iter().any(|def_id| { + def_id.kind(&db) == DefKind::ClockingBlock + && def_id.as_clocking_block(&db).is_some_and(|id| id.value == clocking_block_id) + })); + } + #[test] fn package_imports_resolve_through_export_scope() { let db = db_with_root_text( diff --git a/crates/hir/src/symbol.rs b/crates/hir/src/symbol.rs index 3e2b9b3e..12f05aae 100644 --- a/crates/hir/src/symbol.rs +++ b/crates/hir/src/symbol.rs @@ -9,11 +9,13 @@ use crate::{ hir_def::{ Ident, block::BlockId, + checker::CheckerId, + covergroup::{CovergroupId, CoverpointId, CrossId}, expr::declarator::DeclId, file::{config::ConfigDeclId, library::LibraryDeclId, udp::UdpDeclId}, module::{ - ModuleId, generate::GenerateBlockId, instantiation::InstanceId, modport::ModportId, - port::NonAnsiPortId, + ModuleId, clocking::ClockingBlockId, generate::GenerateBlockId, + instantiation::InstanceId, modport::ModportId, port::NonAnsiPortId, }, stmt::StmtId, subroutine::{LocalSubroutineId, SubroutinePortId}, @@ -41,6 +43,11 @@ pub enum DefLoc { Typedef(InContainer), Instance(InModule), Modport(InModule), + ClockingBlock(InModule), + Checker(InContainer), + Covergroup(InContainer), + Coverpoint(InContainer), + Cross(InContainer), Stmt(InContainer), } @@ -58,6 +65,11 @@ impl_from! { DefLoc => Typedef(InContainer), Instance(InModule), Modport(InModule), + ClockingBlock(InModule), + Checker(InContainer), + Covergroup(InContainer), + Coverpoint(InContainer), + Cross(InContainer), Stmt(InContainer), } @@ -161,6 +173,27 @@ impl DefId { } } + pub fn as_clocking_block(self, db: &dyn InternDb) -> Option> { + match self.loc(db) { + DefLoc::ClockingBlock(id) => Some(id), + _ => None, + } + } + + pub fn as_checker(self, db: &dyn InternDb) -> Option> { + match self.loc(db) { + DefLoc::Checker(id) => Some(id), + _ => None, + } + } + + pub fn as_covergroup(self, db: &dyn InternDb) -> Option> { + match self.loc(db) { + DefLoc::Covergroup(id) => Some(id), + _ => None, + } + } + pub fn as_stmt(self, db: &dyn InternDb) -> Option> { match self.loc(db) { DefLoc::Stmt(id) => Some(id), @@ -193,12 +226,24 @@ pub enum DefKind { Specparam, Instance, Modport, + ClockingBlock, + Checker, + Covergroup, + Coverpoint, + Cross, Stmt, } impl DefKind { pub fn is_instantiable_def(self) -> bool { - matches!(self, DefKind::Module | DefKind::Interface | DefKind::Program) + matches!( + self, + DefKind::Module + | DefKind::Interface + | DefKind::Program + | DefKind::Checker + | DefKind::Covergroup + ) } pub fn symbol_kind(self) -> SymbolKind { @@ -221,7 +266,12 @@ impl DefKind { DefKind::Genvar => SymbolKind::Genvar, DefKind::Specparam => SymbolKind::Specparam, DefKind::Instance => SymbolKind::Instance, - DefKind::Modport => SymbolKind::Unknown, + DefKind::Modport + | DefKind::ClockingBlock + | DefKind::Checker + | DefKind::Covergroup + | DefKind::Coverpoint + | DefKind::Cross => SymbolKind::Unknown, DefKind::Stmt => SymbolKind::Stmt, } } @@ -232,6 +282,8 @@ impl DefKind { | DefKind::Interface | DefKind::Package | DefKind::Program + | DefKind::Checker + | DefKind::Covergroup | DefKind::Typedef => NameContext::Type, _ => NameContext::Value, } diff --git a/crates/hir/src/type_infer.rs b/crates/hir/src/type_infer.rs index 65f29e36..18d330f2 100644 --- a/crates/hir/src/type_infer.rs +++ b/crates/hir/src/type_infer.rs @@ -213,6 +213,11 @@ fn type_of_def_id(db: &dyn HirDb, def_id: DefId) -> TyResult { | DefKind::Library | DefKind::Subroutine | DefKind::NonAnsiPort + | DefKind::ClockingBlock + | DefKind::Checker + | DefKind::Covergroup + | DefKind::Coverpoint + | DefKind::Cross | DefKind::Stmt => TyResult::new(Ty::Unknown), } } diff --git a/crates/ide/src/document_symbols.rs b/crates/ide/src/document_symbols.rs index 16730388..5ecfbeea 100644 --- a/crates/ide/src/document_symbols.rs +++ b/crates/ide/src/document_symbols.rs @@ -19,6 +19,7 @@ use hir::{ }, module::{ ModuleId, ModuleItem, ModuleSrc, + clocking::{ClockingBlockDef, ClockingBlockId, ClockingBlockSrc}, generate::{ GenerateBlockId, GenerateBlockItem, GenerateBlockLoc, GenerateItem, GenerateRegion, GenerateRegionId, GenerateRegionSrc, @@ -334,6 +335,9 @@ fn collect_module_items( collector.pop(); } } + ModuleItem::ClockingBlockId(clocking_block_id) => { + build_clocking_block(collector, clocking_block_id, module, src_map); + } ModuleItem::StructId(struct_id) => build_struct(collector, struct_id, module, src_map), } } @@ -593,6 +597,24 @@ fn build_generate_block( collector.pop(); } +#[inline] +fn build_clocking_block( + collector: &mut SymbolCollecter, + clocking_block_id: ClockingBlockId, + arena: &Arn, + src_map: &SrcMap, +) where + Arn: GetRef, + SrcMap: Get>, +{ + let clocking_block = arena.get(clocking_block_id); + let Some(src) = src_map.get(clocking_block_id) else { + return; + }; + collector.push_symbol(&clocking_block.name, src); + collector.pop(); +} + #[inline] fn build_struct( collector: &mut SymbolCollecter, diff --git a/crates/ide/src/render.rs b/crates/ide/src/render.rs index 662b9164..73a964aa 100644 --- a/crates/ide/src/render.rs +++ b/crates/ide/src/render.rs @@ -14,6 +14,7 @@ use hir::{ literal::Literal, module::{ ModuleId, ModuleKind, + clocking::ClockingBlockId, instantiation::InstanceId, port::{NonAnsiPortId, Ports}, }, @@ -305,6 +306,11 @@ fn render_definition_title(db: &RootDb, origin: &DefId) -> Option { DefLoc::Typedef(_) => "Typedef", DefLoc::Instance(_) => "Instance", DefLoc::Modport(_) => "Modport", + DefLoc::ClockingBlock(_) => "Clocking block", + DefLoc::Checker(_) => "Checker", + DefLoc::Covergroup(_) => "Covergroup", + DefLoc::Coverpoint(_) => "Coverpoint", + DefLoc::Cross(_) => "Cross", DefLoc::Stmt(_) => "Statement", _ => return None, }; @@ -342,6 +348,9 @@ fn render_signature(sema: &Semantics, origin: &DefId) -> Option DefLoc::Decl(decl_id) => render_decl_signature(db, decl_id), DefLoc::Typedef(typedef) => typedef.display_signature(db).ok(), DefLoc::Instance(instance_id) => render_instance_signature(db, instance_id), + DefLoc::ClockingBlock(clocking_block_id) => { + render_clocking_block_signature(db, clocking_block_id) + } _ => render_label_signature(db, origin), } } @@ -532,6 +541,30 @@ fn render_instance_signature(db: &RootDb, instance_id: InModule) -> Some(signature) } +fn render_clocking_block_signature( + db: &RootDb, + clocking_block_id: InModule, +) -> Option { + let module = db.module(clocking_block_id.module_id); + let clocking_block = module.get(clocking_block_id.value); + let name = clocking_block.name.as_ref()?; + let mut signature = format!("clocking {name}"); + if !clocking_block.signals.is_empty() { + signature.push_str("\n"); + let signals = clocking_block + .signals + .iter() + .map(|signal| { + let dir = signal.dir.display_source(db).unwrap_or_default(); + format!(" {dir} {}", signal.name) + }) + .collect_vec() + .join("\n"); + signature.push_str(&signals); + } + Some(signature) +} + fn render_decl_signature(db: &RootDb, decl_id: InContainer) -> Option { let container = decl_id.cont_id.to_container(db); let decl = container.get(decl_id.value); @@ -643,6 +676,11 @@ fn render_label_signature(db: &RootDb, origin: &DefId) -> Option { DefLoc::GenerateBlock(_) => "generate", DefLoc::Instance(_) => "instance", DefLoc::Modport(_) => "modport", + DefLoc::ClockingBlock(_) => "clocking", + DefLoc::Checker(_) => "checker", + DefLoc::Covergroup(_) => "covergroup", + DefLoc::Coverpoint(_) => "coverpoint", + DefLoc::Cross(_) => "cross", DefLoc::Stmt(_) => "statement", DefLoc::Typedef(_) => "typedef", DefLoc::Module(_) diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_clocking_block_supports_navigation_hover_and_outline__hover.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_clocking_block_supports_navigation_hover_and_outline__hover.snap new file mode 100644 index 00000000..a4c3ef3c --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_clocking_block_supports_navigation_hover_and_outline__hover.snap @@ -0,0 +1,15 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: normalize_hover_snapshot(hover.info.as_str()) +--- +Clocking block `cb` + +```systemverilog +clocking cb + input a +``` + + +--- + +in `m` from [feature.v]() line 3 diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_clocking_block_supports_navigation_hover_and_outline__outline.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_clocking_block_supports_navigation_hover_and_outline__outline.snap new file mode 100644 index 00000000..55774f1f --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_clocking_block_supports_navigation_hover_and_outline__outline.snap @@ -0,0 +1,8 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: "lines.join(\"\\n\")" +--- +m Module container=None + clk PortDecl container=Some("m") + a PortDecl container=Some("m") + cb Unknown container=Some("m") diff --git a/crates/ide/src/verilog_2005.rs b/crates/ide/src/verilog_2005.rs index 1ce9a921..92792c05 100644 --- a/crates/ide/src/verilog_2005.rs +++ b/crates/ide/src/verilog_2005.rs @@ -2774,6 +2774,51 @@ endmodule ); } +#[test] +fn systemverilog_clocking_block_supports_navigation_hover_and_outline() { + let text = r#" +module /*marker:module_def*/m(input clk, input a); + clocking /*marker:clocking_def*/cb @(posedge clk); + input #1ps a; + endclocking + + default clocking /*marker:clocking_ref*/cb; +endmodule +"#; + let (host, file_id, _clean_text, markers) = setup_marked(text); + let analysis = host.make_analysis(); + + let clocking_def_range = marked_range(&markers, "clocking_def", TextSize::of("cb")); + let nav = analysis + .goto_definition(position(file_id, &markers, "clocking_ref")) + .unwrap() + .expect("clocking block definition expected"); + assert!( + nav.info.iter().any(|target| target.focus_range == Some(clocking_def_range)), + "clocking block reference should navigate to definition: {nav:?}" + ); + + let hover = analysis + .hover( + position(file_id, &markers, "clocking_ref"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("clocking block hover expected"); + assert_hover_snapshot!( + "systemverilog_clocking_block_supports_navigation_hover_and_outline__hover", + hover.info.as_str(), + ); + + let symbols = analysis.document_symbol(file_id).unwrap(); + let mut lines = Vec::new(); + collect_symbol_lines(&symbols, 0, &mut lines); + assert_snapshot!( + "systemverilog_clocking_block_supports_navigation_hover_and_outline__outline", + lines.join("\n") + ); +} + #[test] fn verilog_2005_hover_uses_relative_source_label_for_absolute_workspace_path() { let dir = TestDir::new("hover-source-label"); From a1a864a9712200e7c383d20471a869b6b0534a60 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Sat, 27 Jun 2026 15:06:55 +0800 Subject: [PATCH 4/8] feat(hir): lower checker declarations --- crates/hir/src/def_id.rs | 44 ++++++++++---- crates/hir/src/hir_def/file.rs | 14 +++++ crates/hir/src/hir_def/module.rs | 20 ++++++- .../hir/src/hir_def/module/instantiation.rs | 60 ++++++++++++++++++- crates/hir/src/scope.rs | 57 +++++++++++++++++- crates/ide/src/definitions.rs | 25 +++++++- crates/ide/src/document_symbols.rs | 25 ++++++++ ...navigation_hover_and_outline__checker.snap | 14 +++++ ...avigation_hover_and_outline__instance.snap | 14 +++++ ...navigation_hover_and_outline__outline.snap | 8 +++ crates/ide/src/verilog_2005.rs | 56 +++++++++++++++++ 11 files changed, 320 insertions(+), 17 deletions(-) create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__checker.snap create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__instance.snap create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__outline.snap diff --git a/crates/hir/src/def_id.rs b/crates/hir/src/def_id.rs index 24bbefc1..5410a3d4 100644 --- a/crates/hir/src/def_id.rs +++ b/crates/hir/src/def_id.rs @@ -166,10 +166,12 @@ impl DefId { DefLoc::ClockingBlock(InModule { value, module_id }) => { module_id.to_container(db).get(value).name.clone() } - DefLoc::Checker(_) - | DefLoc::Covergroup(_) - | DefLoc::Coverpoint(_) - | DefLoc::Cross(_) => None, + DefLoc::Checker(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), + ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + }, + DefLoc::Covergroup(_) | DefLoc::Coverpoint(_) | DefLoc::Cross(_) => None, DefLoc::Stmt(InContainer { value, cont_id }) => { cont_id.to_container(db).get(value).label.clone() } @@ -250,10 +252,18 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.name_range()?; Some(InFile::new(module_id.file_id, range)) } - DefLoc::Checker(_) - | DefLoc::Covergroup(_) - | DefLoc::Coverpoint(_) - | DefLoc::Cross(_) => None, + DefLoc::Checker(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => { + let range = file_id.to_container_src_map(db).get(value)?.name_range()?; + Some(InFile::new(file_id, range)) + } + ScopeId::Module(module_id) => { + let range = module_id.to_container_src_map(db).get(value)?.name_range()?; + Some(InFile::new(module_id.file_id, range)) + } + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + }, + DefLoc::Covergroup(_) | DefLoc::Coverpoint(_) | DefLoc::Cross(_) => None, DefLoc::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)) @@ -332,10 +342,20 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.range(); InFile::new(module_id.file_id, range) } - DefLoc::Checker(_) - | DefLoc::Covergroup(_) - | DefLoc::Coverpoint(_) - | DefLoc::Cross(_) => return None, + DefLoc::Checker(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => { + let range = file_id.to_container_src_map(db).get(value)?.range(); + InFile::new(file_id, range) + } + ScopeId::Module(module_id) => { + let range = module_id.to_container_src_map(db).get(value)?.range(); + InFile::new(module_id.file_id, range) + } + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => { + return None; + } + }, + DefLoc::Covergroup(_) | DefLoc::Coverpoint(_) | DefLoc::Cross(_) => return None, DefLoc::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) diff --git a/crates/hir/src/hir_def/file.rs b/crates/hir/src/hir_def/file.rs index 3edd559e..bda9175a 100644 --- a/crates/hir/src/hir_def/file.rs +++ b/crates/hir/src/hir_def/file.rs @@ -18,6 +18,7 @@ use super::{ aggregate::{StructDef, StructId, StructSrc, lower_struct_def}, alloc_idx_and_src, block::{BlockInfo, BlockSrc, LocalBlockId}, + checker::{CheckerDef, CheckerId, CheckerSrc, lower_checker_decl}, declaration::{ Declaration, DeclarationId, DeclarationSrc, LowerDeclaration, impl_lower_declaration, }, @@ -62,6 +63,7 @@ define_container! { udp_decls: [UdpDecl], library_decls: [LibraryDecl], library_includes: [LibraryInclude], + checkers: [CheckerDef], subroutines: [Subroutine], package_imports: [PackageImport], @@ -92,6 +94,7 @@ define_container! { udp_decl_srcs: [UdpDecl | UdpDeclSrc], library_decl_srcs: [LibraryDecl | LibraryDeclSrc], library_include_srcs: [LibraryInclude | LibraryIncludeSrc], + checker_srcs: [CheckerDef | CheckerSrc], subroutine_srcs: [Subroutine | SubroutineSrc], expr_srcs: [Expr | ExprSrc], event_expr_srcs: [EventExpr | EventExprSrc], @@ -115,6 +118,7 @@ define_enum_deriving_from! { UdpDeclId(UdpDeclId), LibraryDeclId(LibraryDeclId), LibraryIncludeId(LibraryIncludeId), + CheckerId(CheckerId), SubroutineId(LocalSubroutineId), } } @@ -131,6 +135,7 @@ impl FileSourceMap { FileItem::UdpDeclId(idx) => self.get(*idx)?.node, FileItem::LibraryDeclId(idx) => self.get(*idx)?.node, FileItem::LibraryIncludeId(idx) => self.get(*idx)?.0, + FileItem::CheckerId(idx) => self.get(*idx)?.node, FileItem::SubroutineId(idx) => self.get(*idx)?.node, }) } @@ -321,6 +326,15 @@ impl LowerFileCtx<'_> { } UdpDeclaration(udp_decl) => self.lower_udp_decl(udp_decl).into(), ConfigDeclaration(config_decl) => self.lower_config_decl(config_decl).into(), + CheckerDeclaration(checker_decl) => { + let checker = lower_checker_decl(checker_decl); + alloc_idx_and_src! { + self.file_id; + checker => self.file.checkers, + checker_decl => self.file_source_map.checker_srcs, + } + .into() + } _ => continue, }; self.file_source_map.items.push(idx); diff --git a/crates/hir/src/hir_def/module.rs b/crates/hir/src/hir_def/module.rs index eec8a1e8..b36ccdaf 100644 --- a/crates/hir/src/hir_def/module.rs +++ b/crates/hir/src/hir_def/module.rs @@ -34,6 +34,7 @@ use super::{ aggregate::{StructDef, StructId, StructSrc, lower_struct_def}, alloc_idx_and_src, block::{BlockInfo, BlockSrc, LocalBlockId}, + checker::{CheckerDef, CheckerId, CheckerSrc, lower_checker_decl}, declaration::{ Declaration, DeclarationId, DeclarationSrc, LowerDeclaration, ParamDeclKind, impl_lower_declaration, @@ -97,6 +98,7 @@ define_container! { subroutines: [Subroutine], modports: [ModportDef], clocking_blocks: [ClockingBlockDef], + checkers: [CheckerDef], package_imports: [PackageImport], instantiations: [Instantiation], @@ -139,6 +141,7 @@ define_container! { subroutine_srcs: [Subroutine | SubroutineSrc], modport_srcs: [ModportDef | ModportSrc], clocking_block_srcs: [ClockingBlockDef | ClockingBlockSrc], + checker_srcs: [CheckerDef | CheckerSrc], instantiation_srcs: [Instantiation | InstantiationSrc], inst_param_assign_srcs: [ParamAssign | ParamAssignSrc], @@ -289,6 +292,7 @@ impl ModuleSourceMap { ModuleItem::SubroutineId(idx) => self.get(*idx)?.node, ModuleItem::ModportId(idx) => self.get(*idx)?.node, ModuleItem::ClockingBlockId(idx) => self.get(*idx)?.node, + ModuleItem::CheckerId(idx) => self.get(*idx)?.node, }) } } @@ -310,6 +314,7 @@ define_enum_deriving_from! { SubroutineId(LocalSubroutineId), ModportId(ModportId), ClockingBlockId(ClockingBlockId), + CheckerId(CheckerId), } } @@ -536,7 +541,9 @@ impl LowerModuleCtx<'_> { PrimitiveInstantiation(instantiation) => { self.instantiation_ctx().lower_primitive_instantiation(instantiation).into() } - CheckerInstantiation(_) => continue, + CheckerInstantiation(instantiation) => { + self.instantiation_ctx().lower_checker_instantiation(instantiation).into() + } // Subroutines FunctionDeclaration(fn_decl) => match self.lower_subroutine_decl(fn_decl) { @@ -640,7 +647,16 @@ impl LowerModuleCtx<'_> { | ClassMethodPrototype(_) => continue, // Checker - CheckerDeclaration(_) | CheckerDataDeclaration(_) => continue, + CheckerDeclaration(checker_decl) => { + let checker = lower_checker_decl(checker_decl); + alloc_idx_and_src! { + self.file_id; + checker => self.module.checkers, + checker_decl => self.module_source_map.checker_srcs, + } + .into() + } + CheckerDataDeclaration(_) => continue, // Constraints ConstraintDeclaration(_) | ConstraintPrototype(_) => continue, diff --git a/crates/hir/src/hir_def/module/instantiation.rs b/crates/hir/src/hir_def/module/instantiation.rs index 021ba395..4d93cc06 100644 --- a/crates/hir/src/hir_def/module/instantiation.rs +++ b/crates/hir/src/hir_def/module/instantiation.rs @@ -1,6 +1,6 @@ use la_arena::{Arena, Idx}; use smallvec::SmallVec; -use syntax::ast; +use syntax::{SyntaxToken, ast}; use crate::{ db::InternDb, @@ -39,10 +39,18 @@ impl AstKind for PrimitiveInstantiationAst { type Node<'a> = ast::PrimitiveInstantiation<'a>; } +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct CheckerInstantiationAst; + +impl AstKind for CheckerInstantiationAst { + type Node<'a> = ast::CheckerInstantiation<'a>; +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum InstantiationSrc { HierarchyInstantiation(AstId), PrimitiveInstantiation(AstId), + CheckerInstantiation(AstId), } impl IsSrc for InstantiationSrc { @@ -69,6 +77,15 @@ impl<'a> ToAstNode<'a, ast::PrimitiveInstantiation<'a>> for InstantiationSrc { } } +impl<'a> ToAstNode<'a, ast::CheckerInstantiation<'a>> for InstantiationSrc { + fn to_node(&self, tree: &'a syntax::SyntaxTree) -> Option> { + let InstantiationSrc::CheckerInstantiation(src) = self else { + return None; + }; + exact_ast_node_from_ptr(src.ptr(), tree) + } +} + impl<'a> FromSourceAst<'a, ast::HierarchyInstantiation<'a>> for InstantiationSrc { fn from_source_ast(node: SourceAst>) -> Self { Self::HierarchyInstantiation(AstId::from_source_ast(node)) @@ -81,11 +98,18 @@ impl<'a> FromSourceAst<'a, ast::PrimitiveInstantiation<'a>> for InstantiationSrc } } +impl<'a> FromSourceAst<'a, ast::CheckerInstantiation<'a>> for InstantiationSrc { + fn from_source_ast(node: SourceAst>) -> Self { + Self::CheckerInstantiation(AstId::from_source_ast(node)) + } +} + impl From for syntax::ptr::SyntaxNodePtr { fn from(src: InstantiationSrc) -> Self { match src { InstantiationSrc::HierarchyInstantiation(src) => src.ptr(), InstantiationSrc::PrimitiveInstantiation(src) => src.ptr(), + InstantiationSrc::CheckerInstantiation(src) => src.ptr(), } } } @@ -236,6 +260,27 @@ impl LowerInstantiationCtx<'_> { } } + pub(crate) fn lower_checker_instantiation( + &mut self, + inst: ast::CheckerInstantiation, + ) -> InstantiationId { + let module_name = lower_name(inst.type_()); + let param_assigns = self.lower_param_assign(inst.parameters()); + + let next_instantiation_id = self.instantiations.nxt_idx(); + let instances = inst + .instances() + .children() + .map(|hier| self.lower_instance(hier, next_instantiation_id)) + .collect(); + + alloc_idx_and_src! { + self.file_id; + Instantiation { module_name, param_assigns, instances } => self.instantiations, + inst => self.instantiation_srcs, + } + } + fn lower_param_assign( &mut self, assigns: Option, @@ -320,3 +365,16 @@ impl LowerInstantiationCtx<'_> { } } } + +fn lower_name(name: ast::Name<'_>) -> Option { + lower_ident_opt(rightmost_name_token(name)) +} + +fn rightmost_name_token(name: ast::Name<'_>) -> Option> { + match name { + ast::Name::IdentifierName(name) => name.identifier(), + ast::Name::IdentifierSelectName(name) => name.identifier(), + ast::Name::ScopedName(name) => rightmost_name_token(name.right()), + _ => None, + } +} diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 332cf702..2fca40db 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -153,6 +153,13 @@ impl NameScope { ); } + for (checker_id, checker) in hir_file.checkers.iter() { + scope.insert_type_opt( + &checker.name, + def_id(db, InContainer::new(file_id.into(), checker_id)), + ); + } + for (typedef_id, typedef) in hir_file.typedefs.iter() { scope.insert_type_opt( &typedef.name, @@ -196,6 +203,13 @@ impl NameScope { ); } + for (checker_id, checker) in module.checkers.iter() { + scope.insert_type_opt( + &checker.name, + def_id(db, InContainer::new(module_id.into(), checker_id)), + ); + } + insert_decls_and_typedefs( &mut scope, db, @@ -482,7 +496,10 @@ mod tests { use rustc_hash::FxHashSet; use smol_str::SmolStr; use triomphe::Arc; - use utils::paths::{AbsPathBuf, Utf8PathBuf}; + use utils::{ + get::GetRef, + paths::{AbsPathBuf, Utf8PathBuf}, + }; use vfs::{FileId, FileSet, VfsPath, anchored_path::AnchoredPath}; use crate::{ @@ -711,6 +728,44 @@ endmodule })); } + #[test] + fn file_scope_exposes_checkers_and_lowers_checker_instances() { + let db = db_with_root_text( + r#" +checker c(input logic clk); +endchecker + +module m; + c u(); +endmodule +"#, + ); + + let checker_defs = db + .unit_scope() + .lookup(NameContext::Type, &ident("c")) + .expect("checker should be visible as a type"); + assert!(checker_defs.iter().any(|def_id| def_id.kind(&db) == DefKind::Checker)); + + let module_id = db + .unit_scope() + .module_ids(&db, &ident("m")) + .unique() + .expect("module should resolve uniquely"); + let module = db.module(module_id); + let instantiation = module + .instantiations + .values() + .find(|instantiation| instantiation.module_name.as_deref() == Some("c")) + .expect("checker instantiation should lower into the instance arena"); + let instance = instantiation + .instances + .first() + .map(|instance_id| module.get(*instance_id)) + .expect("checker instantiation should lower its instance"); + assert_eq!(instance.name.as_deref(), Some("u")); + } + #[test] fn package_imports_resolve_through_export_scope() { let db = db_with_root_text( diff --git a/crates/ide/src/definitions.rs b/crates/ide/src/definitions.rs index c9173dd5..49a90bb7 100644 --- a/crates/ide/src/definitions.rs +++ b/crates/ide/src/definitions.rs @@ -273,7 +273,11 @@ fn resolve_instantiation_type_name( }) .collect::>>()?, )), - ModuleResolution::Unresolved => None, + ModuleResolution::Unresolved => Some( + sema.nameres_ident(file_id, tp, NameContext::Type)? + .to_def_id(sema.db)? + .into(), + ), }; } @@ -286,6 +290,15 @@ fn resolve_instantiation_type_name( ); } + if let Some(instantiation) = + SyntaxAncestors::start_from(parent).find_map(ast::CheckerInstantiation::cast) + && rightmost_name_token(instantiation.type_()) == Some(tok) + { + return Some( + sema.nameres_ident(file_id, tp, NameContext::Type)?.to_def_id(sema.db)?.into(), + ); + } + None } @@ -330,6 +343,16 @@ fn scoped_uses_dot(scoped: ast::ScopedName<'_>) -> bool { .any(|tok| tok.kind() == syntax::Token![.]) } +fn rightmost_name_token(name: ast::Name<'_>) -> Option> { + use ast::Name::*; + match name { + IdentifierName(ident) => ident.identifier(), + IdentifierSelectName(ident) => ident.identifier(), + ScopedName(scoped) => rightmost_name_token(scoped.right()), + _ => None, + } +} + fn token_is_in_non_dot_scoped_name(parent: syntax::SyntaxNode<'_>) -> bool { SyntaxAncestors::start_from(parent) .find_map(ast::ScopedName::cast) diff --git a/crates/ide/src/document_symbols.rs b/crates/ide/src/document_symbols.rs index 5ecfbeea..37c431c9 100644 --- a/crates/ide/src/document_symbols.rs +++ b/crates/ide/src/document_symbols.rs @@ -9,6 +9,7 @@ use hir::{ DEFAULT_NAME, aggregate::{StructDef, StructId, StructKind, StructSrc}, block::{BlockId, BlockInfo, BlockItem, BlockSrc, LocalBlockId}, + checker::{CheckerDef, CheckerId, CheckerSrc}, declaration::{Declaration, DeclarationId, DeclarationSrc}, expr::declarator::{DeclId, Declarator, DeclaratorSrc, DeclsRange}, file::{ @@ -234,6 +235,9 @@ pub(crate) fn document_symbols(db: &dyn HirDb, file_id: FileId) -> Vec {} + FileItem::CheckerId(checker_id) => { + build_checker(&mut collector, checker_id, file, src_map) + } FileItem::UdpDeclId(udp_id) => build_udp_decl(&mut collector, udp_id, file, src_map), } } @@ -338,6 +342,9 @@ fn collect_module_items( ModuleItem::ClockingBlockId(clocking_block_id) => { build_clocking_block(collector, clocking_block_id, module, src_map); } + ModuleItem::CheckerId(checker_id) => { + build_checker(collector, checker_id, module, src_map); + } ModuleItem::StructId(struct_id) => build_struct(collector, struct_id, module, src_map), } } @@ -597,6 +604,24 @@ fn build_generate_block( collector.pop(); } +#[inline] +fn build_checker( + collector: &mut SymbolCollecter, + checker_id: CheckerId, + arena: &Arn, + src_map: &SrcMap, +) where + Arn: GetRef, + SrcMap: Get>, +{ + let checker = arena.get(checker_id); + let Some(src) = src_map.get(checker_id) else { + return; + }; + collector.push_symbol(&checker.name, src); + collector.pop(); +} + #[inline] fn build_clocking_block( collector: &mut SymbolCollecter, diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__checker.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__checker.snap new file mode 100644 index 00000000..fb4d08d5 --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__checker.snap @@ -0,0 +1,14 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: normalize_hover_snapshot(checker_hover.info.as_str()) +--- +Checker `c` + +```systemverilog +checker c +``` + + +--- + +from [feature.v]() line 2 diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__instance.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__instance.snap new file mode 100644 index 00000000..cb428404 --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__instance.snap @@ -0,0 +1,14 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: normalize_hover_snapshot(instance_hover.info.as_str()) +--- +Instance `u` + +```systemverilog +instance u of c +``` + + +--- + +in `top` from [feature.v]() line 6 diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__outline.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__outline.snap new file mode 100644 index 00000000..16efbe01 --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_checker_supports_instantiation_navigation_hover_and_outline__outline.snap @@ -0,0 +1,8 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: "lines.join(\"\\n\")" +--- +c Unknown container=None +top Module container=None + clk PortDecl container=Some("top") + u Instance container=Some("top") diff --git a/crates/ide/src/verilog_2005.rs b/crates/ide/src/verilog_2005.rs index 92792c05..dbf67e6d 100644 --- a/crates/ide/src/verilog_2005.rs +++ b/crates/ide/src/verilog_2005.rs @@ -2819,6 +2819,62 @@ endmodule ); } +#[test] +fn systemverilog_checker_supports_instantiation_navigation_hover_and_outline() { + let text = r#" +checker /*marker:checker_def*/c(input logic clk); +endchecker + +module top(input clk); + /*marker:checker_ref*/c /*marker:inst_ref*/u(clk); +endmodule +"#; + let (host, file_id, _clean_text, markers) = setup_marked(text); + let analysis = host.make_analysis(); + + let checker_def_range = marked_range(&markers, "checker_def", TextSize::of("c")); + let nav = analysis + .goto_definition(position(file_id, &markers, "checker_ref")) + .unwrap() + .expect("checker definition expected"); + assert!( + nav.info.iter().any(|target| target.focus_range == Some(checker_def_range)), + "checker instantiation should navigate to checker declaration: {nav:?}" + ); + + let checker_hover = analysis + .hover( + position(file_id, &markers, "checker_ref"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("checker hover expected"); + assert_hover_snapshot!( + "systemverilog_checker_supports_instantiation_navigation_hover_and_outline__checker", + checker_hover.info.as_str(), + ); + + let instance_hover = analysis + .hover( + position(file_id, &markers, "inst_ref"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("checker instance hover expected"); + assert_hover_snapshot!( + "systemverilog_checker_supports_instantiation_navigation_hover_and_outline__instance", + instance_hover.info.as_str(), + ); + + let symbols = analysis.document_symbol(file_id).unwrap(); + let mut lines = Vec::new(); + collect_symbol_lines(&symbols, 0, &mut lines); + assert_snapshot!( + "systemverilog_checker_supports_instantiation_navigation_hover_and_outline__outline", + lines.join("\n") + ); +} + #[test] fn verilog_2005_hover_uses_relative_source_label_for_absolute_workspace_path() { let dir = TestDir::new("hover-source-label"); From c82a3c32437a6114bca63a402ff6b775be75a56d Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Sat, 27 Jun 2026 15:18:17 +0800 Subject: [PATCH 5/8] feat(hir): lower covergroup declarations --- crates/hir/src/def_id.rs | 90 +++++++++++++- crates/hir/src/hir_def/covergroup.rs | 9 +- crates/hir/src/hir_def/file.rs | 52 ++++++++ crates/hir/src/hir_def/module.rs | 56 ++++++++- crates/hir/src/scope.rs | 112 ++++++++++++++++++ crates/ide/src/definitions.rs | 8 +- crates/ide/src/document_symbols.rs | 78 ++++++++++++ crates/ide/src/render.rs | 2 +- ...tiation_hover_and_outline__covergroup.snap | 14 +++ ...tiation_hover_and_outline__coverpoint.snap | 14 +++ ...nstantiation_hover_and_outline__cross.snap | 14 +++ ...antiation_hover_and_outline__instance.snap | 14 +++ ...tantiation_hover_and_outline__outline.snap | 11 ++ crates/ide/src/verilog_2005.rs | 92 ++++++++++++++ 14 files changed, 551 insertions(+), 15 deletions(-) create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__covergroup.snap create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__coverpoint.snap create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__cross.snap create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__instance.snap create mode 100644 crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__outline.snap diff --git a/crates/hir/src/def_id.rs b/crates/hir/src/def_id.rs index 5410a3d4..f80e151b 100644 --- a/crates/hir/src/def_id.rs +++ b/crates/hir/src/def_id.rs @@ -171,7 +171,21 @@ impl DefId { ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, }, - DefLoc::Covergroup(_) | DefLoc::Coverpoint(_) | DefLoc::Cross(_) => None, + DefLoc::Covergroup(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), + ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + }, + DefLoc::Coverpoint(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), + ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + }, + DefLoc::Cross(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), + ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + }, DefLoc::Stmt(InContainer { value, cont_id }) => { cont_id.to_container(db).get(value).label.clone() } @@ -263,7 +277,39 @@ impl DefId { } ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, }, - DefLoc::Covergroup(_) | DefLoc::Coverpoint(_) | DefLoc::Cross(_) => None, + DefLoc::Covergroup(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => { + let range = file_id.to_container_src_map(db).get(value)?.name_range()?; + Some(InFile::new(file_id, range)) + } + ScopeId::Module(module_id) => { + let range = module_id.to_container_src_map(db).get(value)?.name_range()?; + Some(InFile::new(module_id.file_id, range)) + } + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + }, + DefLoc::Coverpoint(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => { + let range = file_id.to_container_src_map(db).get(value)?.name_range()?; + Some(InFile::new(file_id, range)) + } + ScopeId::Module(module_id) => { + let range = module_id.to_container_src_map(db).get(value)?.name_range()?; + Some(InFile::new(module_id.file_id, range)) + } + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + }, + DefLoc::Cross(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => { + let range = file_id.to_container_src_map(db).get(value)?.name_range()?; + Some(InFile::new(file_id, range)) + } + ScopeId::Module(module_id) => { + let range = module_id.to_container_src_map(db).get(value)?.name_range()?; + Some(InFile::new(module_id.file_id, range)) + } + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + }, DefLoc::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)) @@ -355,7 +401,45 @@ impl DefId { return None; } }, - DefLoc::Covergroup(_) | DefLoc::Coverpoint(_) | DefLoc::Cross(_) => return None, + DefLoc::Covergroup(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => { + let range = file_id.to_container_src_map(db).get(value)?.range(); + InFile::new(file_id, range) + } + ScopeId::Module(module_id) => { + let range = module_id.to_container_src_map(db).get(value)?.range(); + InFile::new(module_id.file_id, range) + } + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => { + return None; + } + }, + DefLoc::Coverpoint(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => { + let range = file_id.to_container_src_map(db).get(value)?.range(); + InFile::new(file_id, range) + } + ScopeId::Module(module_id) => { + let range = module_id.to_container_src_map(db).get(value)?.range(); + InFile::new(module_id.file_id, range) + } + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => { + return None; + } + }, + DefLoc::Cross(InContainer { value, cont_id }) => match cont_id { + ScopeId::File(file_id) => { + let range = file_id.to_container_src_map(db).get(value)?.range(); + InFile::new(file_id, range) + } + ScopeId::Module(module_id) => { + let range = module_id.to_container_src_map(db).get(value)?.range(); + InFile::new(module_id.file_id, range) + } + ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => { + return None; + } + }, DefLoc::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) diff --git a/crates/hir/src/hir_def/covergroup.rs b/crates/hir/src/hir_def/covergroup.rs index 30793373..41fc6808 100644 --- a/crates/hir/src/hir_def/covergroup.rs +++ b/crates/hir/src/hir_def/covergroup.rs @@ -1,4 +1,5 @@ use la_arena::Idx; +use smallvec::SmallVec; use syntax::{ SyntaxKind, TokenKind, ast::{self, AstNode}, @@ -15,6 +16,8 @@ use crate::{ #[derive(Debug, PartialEq, Eq, Clone)] pub struct CovergroupDef { pub name: Option, + pub coverpoints: SmallVec<[CoverpointId; 4]>, + pub crosses: SmallVec<[CrossId; 2]>, } pub type CovergroupId = Idx; @@ -119,7 +122,11 @@ impl<'a> FromSourceAst<'a, ast::CoverCross<'a>> for CrossSrc { } pub fn lower_covergroup_decl(covergroup: ast::CovergroupDeclaration<'_>) -> CovergroupDef { - CovergroupDef { name: lower_ident_opt(covergroup.name()) } + CovergroupDef { + name: lower_ident_opt(covergroup.name()), + coverpoints: SmallVec::new(), + crosses: SmallVec::new(), + } } pub fn lower_coverpoint(coverpoint: ast::Coverpoint<'_>) -> CoverpointDef { diff --git a/crates/hir/src/hir_def/file.rs b/crates/hir/src/hir_def/file.rs index bda9175a..dd689d75 100644 --- a/crates/hir/src/hir_def/file.rs +++ b/crates/hir/src/hir_def/file.rs @@ -19,6 +19,10 @@ use super::{ alloc_idx_and_src, block::{BlockInfo, BlockSrc, LocalBlockId}, checker::{CheckerDef, CheckerId, CheckerSrc, lower_checker_decl}, + covergroup::{ + CovergroupDef, CovergroupId, CovergroupSrc, CoverpointDef, CoverpointSrc, CrossDef, + CrossSrc, lower_covergroup_decl, lower_coverpoint, lower_cross, + }, declaration::{ Declaration, DeclarationId, DeclarationSrc, LowerDeclaration, impl_lower_declaration, }, @@ -64,6 +68,9 @@ define_container! { library_decls: [LibraryDecl], library_includes: [LibraryInclude], checkers: [CheckerDef], + covergroups: [CovergroupDef], + coverpoints: [CoverpointDef], + crosses: [CrossDef], subroutines: [Subroutine], package_imports: [PackageImport], @@ -95,6 +102,9 @@ define_container! { library_decl_srcs: [LibraryDecl | LibraryDeclSrc], library_include_srcs: [LibraryInclude | LibraryIncludeSrc], checker_srcs: [CheckerDef | CheckerSrc], + covergroup_srcs: [CovergroupDef | CovergroupSrc], + coverpoint_srcs: [CoverpointDef | CoverpointSrc], + cross_srcs: [CrossDef | CrossSrc], subroutine_srcs: [Subroutine | SubroutineSrc], expr_srcs: [Expr | ExprSrc], event_expr_srcs: [EventExpr | EventExprSrc], @@ -119,6 +129,7 @@ define_enum_deriving_from! { LibraryDeclId(LibraryDeclId), LibraryIncludeId(LibraryIncludeId), CheckerId(CheckerId), + CovergroupId(CovergroupId), SubroutineId(LocalSubroutineId), } } @@ -136,6 +147,7 @@ impl FileSourceMap { FileItem::LibraryDeclId(idx) => self.get(*idx)?.node, FileItem::LibraryIncludeId(idx) => self.get(*idx)?.0, FileItem::CheckerId(idx) => self.get(*idx)?.node, + FileItem::CovergroupId(idx) => self.get(*idx)?.node, FileItem::SubroutineId(idx) => self.get(*idx)?.node, }) } @@ -292,6 +304,43 @@ impl LowerFileCtx<'_> { } } + fn lower_covergroup_decl( + &mut self, + covergroup_decl: ast::CovergroupDeclaration, + ) -> CovergroupId { + let mut covergroup = lower_covergroup_decl(covergroup_decl); + + for member in covergroup_decl.members().children() { + match member { + ast::Member::Coverpoint(coverpoint_ast) => { + let coverpoint = lower_coverpoint(coverpoint_ast); + let coverpoint_id = alloc_idx_and_src! { + self.file_id; + coverpoint => self.file.coverpoints, + coverpoint_ast => self.file_source_map.coverpoint_srcs, + }; + covergroup.coverpoints.push(coverpoint_id); + } + ast::Member::CoverCross(cross_ast) => { + let cross = lower_cross(cross_ast); + let cross_id = alloc_idx_and_src! { + self.file_id; + cross => self.file.crosses, + cross_ast => self.file_source_map.cross_srcs, + }; + covergroup.crosses.push(cross_id); + } + _ => {} + } + } + + alloc_idx_and_src! { + self.file_id; + covergroup => self.file.covergroups, + covergroup_decl => self.file_source_map.covergroup_srcs, + } + } + pub(crate) fn lower_file(&mut self, root: ast::CompilationUnit) { for member in root.members().children() { use ast::Member::*; @@ -335,6 +384,9 @@ impl LowerFileCtx<'_> { } .into() } + CovergroupDeclaration(covergroup_decl) => { + self.lower_covergroup_decl(covergroup_decl).into() + } _ => continue, }; self.file_source_map.items.push(idx); diff --git a/crates/hir/src/hir_def/module.rs b/crates/hir/src/hir_def/module.rs index b36ccdaf..54939053 100644 --- a/crates/hir/src/hir_def/module.rs +++ b/crates/hir/src/hir_def/module.rs @@ -35,6 +35,10 @@ use super::{ alloc_idx_and_src, block::{BlockInfo, BlockSrc, LocalBlockId}, checker::{CheckerDef, CheckerId, CheckerSrc, lower_checker_decl}, + covergroup::{ + CovergroupDef, CovergroupId, CovergroupSrc, CoverpointDef, CoverpointSrc, CrossDef, + CrossSrc, lower_covergroup_decl, lower_coverpoint, lower_cross, + }, declaration::{ Declaration, DeclarationId, DeclarationSrc, LowerDeclaration, ParamDeclKind, impl_lower_declaration, @@ -99,6 +103,9 @@ define_container! { modports: [ModportDef], clocking_blocks: [ClockingBlockDef], checkers: [CheckerDef], + covergroups: [CovergroupDef], + coverpoints: [CoverpointDef], + crosses: [CrossDef], package_imports: [PackageImport], instantiations: [Instantiation], @@ -142,6 +149,9 @@ define_container! { modport_srcs: [ModportDef | ModportSrc], clocking_block_srcs: [ClockingBlockDef | ClockingBlockSrc], checker_srcs: [CheckerDef | CheckerSrc], + covergroup_srcs: [CovergroupDef | CovergroupSrc], + coverpoint_srcs: [CoverpointDef | CoverpointSrc], + cross_srcs: [CrossDef | CrossSrc], instantiation_srcs: [Instantiation | InstantiationSrc], inst_param_assign_srcs: [ParamAssign | ParamAssignSrc], @@ -293,6 +303,7 @@ impl ModuleSourceMap { ModuleItem::ModportId(idx) => self.get(*idx)?.node, ModuleItem::ClockingBlockId(idx) => self.get(*idx)?.node, ModuleItem::CheckerId(idx) => self.get(*idx)?.node, + ModuleItem::CovergroupId(idx) => self.get(*idx)?.node, }) } } @@ -315,6 +326,7 @@ define_enum_deriving_from! { ModportId(ModportId), ClockingBlockId(ClockingBlockId), CheckerId(CheckerId), + CovergroupId(CovergroupId), } } @@ -467,6 +479,43 @@ impl LowerModuleCtx<'_> { Some(subroutine_id) } + fn lower_covergroup_decl( + &mut self, + covergroup_decl: ast::CovergroupDeclaration, + ) -> CovergroupId { + let mut covergroup = lower_covergroup_decl(covergroup_decl); + + for member in covergroup_decl.members().children() { + match member { + ast::Member::Coverpoint(coverpoint_ast) => { + let coverpoint = lower_coverpoint(coverpoint_ast); + let coverpoint_id = alloc_idx_and_src! { + self.file_id; + coverpoint => self.module.coverpoints, + coverpoint_ast => self.module_source_map.coverpoint_srcs, + }; + covergroup.coverpoints.push(coverpoint_id); + } + ast::Member::CoverCross(cross_ast) => { + let cross = lower_cross(cross_ast); + let cross_id = alloc_idx_and_src! { + self.file_id; + cross => self.module.crosses, + cross_ast => self.module_source_map.cross_srcs, + }; + covergroup.crosses.push(cross_id); + } + _ => {} + } + } + + alloc_idx_and_src! { + self.file_id; + covergroup => self.module.covergroups, + covergroup_decl => self.module_source_map.covergroup_srcs, + } + } + pub(crate) fn lower_module_decl(&mut self, decl: ast::ModuleDeclaration) { let header = decl.header(); let has_param_ports = header.parameters().is_some(); @@ -592,11 +641,8 @@ impl LowerModuleCtx<'_> { | ConcurrentAssertionMember(_) => continue, // Coverage - CovergroupDeclaration(_) - | Coverpoint(_) - | CoverCross(_) - | CoverageBins(_) - | BinsSelection(_) + CovergroupDeclaration(covergroup) => self.lower_covergroup_decl(covergroup).into(), + Coverpoint(_) | CoverCross(_) | CoverageBins(_) | BinsSelection(_) | CoverageOption(_) => continue, // Specify blocks diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 2fca40db..1d350a97 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -160,6 +160,27 @@ impl NameScope { ); } + for (covergroup_id, covergroup) in hir_file.covergroups.iter() { + scope.insert_type_opt( + &covergroup.name, + def_id(db, InContainer::new(file_id.into(), covergroup_id)), + ); + } + + for (coverpoint_id, coverpoint) in hir_file.coverpoints.iter() { + scope.insert_value_opt( + &coverpoint.name, + def_id(db, InContainer::new(file_id.into(), coverpoint_id)), + ); + } + + for (cross_id, cross) in hir_file.crosses.iter() { + scope.insert_value_opt( + &cross.name, + def_id(db, InContainer::new(file_id.into(), cross_id)), + ); + } + for (typedef_id, typedef) in hir_file.typedefs.iter() { scope.insert_type_opt( &typedef.name, @@ -210,6 +231,27 @@ impl NameScope { ); } + for (covergroup_id, covergroup) in module.covergroups.iter() { + scope.insert_type_opt( + &covergroup.name, + def_id(db, InContainer::new(module_id.into(), covergroup_id)), + ); + } + + for (coverpoint_id, coverpoint) in module.coverpoints.iter() { + scope.insert_value_opt( + &coverpoint.name, + def_id(db, InContainer::new(module_id.into(), coverpoint_id)), + ); + } + + for (cross_id, cross) in module.crosses.iter() { + scope.insert_value_opt( + &cross.name, + def_id(db, InContainer::new(module_id.into(), cross_id)), + ); + } + insert_decls_and_typedefs( &mut scope, db, @@ -766,6 +808,76 @@ endmodule assert_eq!(instance.name.as_deref(), Some("u")); } + #[test] + fn module_scope_exposes_covergroups_and_coverage_items() { + let db = db_with_root_text( + r#" +module m(input clk, input a); + covergroup cg @(posedge clk); + cp: coverpoint a; + cx: cross cp, cp; + endgroup + + cg u(); +endmodule +"#, + ); + + let module_id = db + .unit_scope() + .module_ids(&db, &ident("m")) + .unique() + .expect("module should resolve uniquely"); + let module = db.module(module_id); + let (covergroup_id, covergroup) = + module.covergroups.iter().next().expect("covergroup should lower"); + assert_eq!(covergroup.name.as_deref(), Some("cg")); + assert_eq!(covergroup.coverpoints.len(), 1); + assert_eq!(covergroup.crosses.len(), 1); + + let coverpoint_id = covergroup.coverpoints[0]; + let cross_id = covergroup.crosses[0]; + assert_eq!(module.get(coverpoint_id).name.as_deref(), Some("cp")); + assert_eq!(module.get(cross_id).name.as_deref(), Some("cx")); + + let module_scope = db.module_scope(module_id); + let covergroup_defs = module_scope + .lookup(NameContext::Type, &ident("cg")) + .expect("module scope should expose covergroup type"); + assert!(covergroup_defs.iter().any(|def_id| { + def_id.kind(&db) == DefKind::Covergroup + && def_id.as_covergroup(&db).is_some_and(|id| id.value == covergroup_id) + })); + + let coverpoint_defs = module_scope + .lookup(NameContext::Value, &ident("cp")) + .expect("module scope should expose coverpoint label"); + assert!(coverpoint_defs.iter().any(|def_id| { + matches!(def_id.loc(&db), DefLoc::Coverpoint(id) if id.value == coverpoint_id) + })); + + let cross_defs = module_scope + .lookup(NameContext::Value, &ident("cx")) + .expect("module scope should expose cross label"); + assert!( + cross_defs + .iter() + .any(|def_id| matches!(def_id.loc(&db), DefLoc::Cross(id) if id.value == cross_id)) + ); + + let instantiation = module + .instantiations + .values() + .find(|instantiation| instantiation.module_name.as_deref() == Some("cg")) + .expect("covergroup instantiation should lower into the instance arena"); + let instance = instantiation + .instances + .first() + .map(|instance_id| module.get(*instance_id)) + .expect("covergroup instantiation should lower its instance"); + assert_eq!(instance.name.as_deref(), Some("u")); + } + #[test] fn package_imports_resolve_through_export_scope() { let db = db_with_root_text( diff --git a/crates/ide/src/definitions.rs b/crates/ide/src/definitions.rs index 49a90bb7..82bbe533 100644 --- a/crates/ide/src/definitions.rs +++ b/crates/ide/src/definitions.rs @@ -273,11 +273,9 @@ fn resolve_instantiation_type_name( }) .collect::>>()?, )), - ModuleResolution::Unresolved => Some( - sema.nameres_ident(file_id, tp, NameContext::Type)? - .to_def_id(sema.db)? - .into(), - ), + ModuleResolution::Unresolved => { + Some(sema.nameres_ident(file_id, tp, NameContext::Type)?.to_def_id(sema.db)?.into()) + } }; } diff --git a/crates/ide/src/document_symbols.rs b/crates/ide/src/document_symbols.rs index 37c431c9..e9358c67 100644 --- a/crates/ide/src/document_symbols.rs +++ b/crates/ide/src/document_symbols.rs @@ -10,6 +10,10 @@ use hir::{ aggregate::{StructDef, StructId, StructKind, StructSrc}, block::{BlockId, BlockInfo, BlockItem, BlockSrc, LocalBlockId}, checker::{CheckerDef, CheckerId, CheckerSrc}, + covergroup::{ + CovergroupDef, CovergroupId, CovergroupSrc, CoverpointDef, CoverpointId, CoverpointSrc, + CrossDef, CrossId, CrossSrc, + }, declaration::{Declaration, DeclarationId, DeclarationSrc}, expr::declarator::{DeclId, Declarator, DeclaratorSrc, DeclsRange}, file::{ @@ -238,6 +242,9 @@ pub(crate) fn document_symbols(db: &dyn HirDb, file_id: FileId) -> Vec { build_checker(&mut collector, checker_id, file, src_map) } + FileItem::CovergroupId(covergroup_id) => { + build_covergroup(&mut collector, covergroup_id, file, src_map) + } FileItem::UdpDeclId(udp_id) => build_udp_decl(&mut collector, udp_id, file, src_map), } } @@ -345,6 +352,9 @@ fn collect_module_items( ModuleItem::CheckerId(checker_id) => { build_checker(collector, checker_id, module, src_map); } + ModuleItem::CovergroupId(covergroup_id) => { + build_covergroup(collector, covergroup_id, module, src_map); + } ModuleItem::StructId(struct_id) => build_struct(collector, struct_id, module, src_map), } } @@ -640,6 +650,74 @@ fn build_clocking_block( collector.pop(); } +#[inline] +fn build_covergroup( + collector: &mut SymbolCollecter, + covergroup_id: CovergroupId, + arena: &Arn, + src_map: &SrcMap, +) where + Arn: GetRef + + GetRef + + GetRef, + SrcMap: Get> + + Get> + + Get>, +{ + let covergroup = arena.get(covergroup_id); + let Some(src) = src_map.get(covergroup_id) else { + return; + }; + collector.push_symbol_with_children( + &covergroup.name, + src, + covergroup.coverpoints.len() + covergroup.crosses.len(), + ); + for &coverpoint_id in &covergroup.coverpoints { + build_coverpoint(collector, coverpoint_id, arena, src_map); + } + for &cross_id in &covergroup.crosses { + build_cross(collector, cross_id, arena, src_map); + } + collector.pop(); +} + +#[inline] +fn build_coverpoint( + collector: &mut SymbolCollecter, + coverpoint_id: CoverpointId, + arena: &Arn, + src_map: &SrcMap, +) where + Arn: GetRef, + SrcMap: Get>, +{ + let coverpoint = arena.get(coverpoint_id); + let Some(src) = src_map.get(coverpoint_id) else { + return; + }; + collector.push_symbol(&coverpoint.name, src); + collector.pop(); +} + +#[inline] +fn build_cross( + collector: &mut SymbolCollecter, + cross_id: CrossId, + arena: &Arn, + src_map: &SrcMap, +) where + Arn: GetRef, + SrcMap: Get>, +{ + let cross = arena.get(cross_id); + let Some(src) = src_map.get(cross_id) else { + return; + }; + collector.push_symbol(&cross.name, src); + collector.pop(); +} + #[inline] fn build_struct( collector: &mut SymbolCollecter, diff --git a/crates/ide/src/render.rs b/crates/ide/src/render.rs index 73a964aa..781e5c64 100644 --- a/crates/ide/src/render.rs +++ b/crates/ide/src/render.rs @@ -550,7 +550,7 @@ fn render_clocking_block_signature( let name = clocking_block.name.as_ref()?; let mut signature = format!("clocking {name}"); if !clocking_block.signals.is_empty() { - signature.push_str("\n"); + signature.push('\n'); let signals = clocking_block .signals .iter() diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__covergroup.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__covergroup.snap new file mode 100644 index 00000000..5ffc3ad5 --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__covergroup.snap @@ -0,0 +1,14 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: normalize_hover_snapshot(covergroup_hover.info.as_str()) +--- +Covergroup `cg` + +```systemverilog +covergroup cg +``` + + +--- + +in `top` from [feature.v]() line 3 diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__coverpoint.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__coverpoint.snap new file mode 100644 index 00000000..35265627 --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__coverpoint.snap @@ -0,0 +1,14 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: normalize_hover_snapshot(coverpoint_hover.info.as_str()) +--- +Coverpoint `cp` + +```systemverilog +coverpoint cp +``` + + +--- + +in `top` from [feature.v]() line 4 diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__cross.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__cross.snap new file mode 100644 index 00000000..f574486b --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__cross.snap @@ -0,0 +1,14 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: normalize_hover_snapshot(cross_hover.info.as_str()) +--- +Cross `cx` + +```systemverilog +cross cx +``` + + +--- + +in `top` from [feature.v]() line 5 diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__instance.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__instance.snap new file mode 100644 index 00000000..2a8e1275 --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__instance.snap @@ -0,0 +1,14 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: normalize_hover_snapshot(instance_hover.info.as_str()) +--- +Instance `u` + +```systemverilog +instance u of cg +``` + + +--- + +in `top` from [feature.v]() line 8 diff --git a/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__outline.snap b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__outline.snap new file mode 100644 index 00000000..75315598 --- /dev/null +++ b/crates/ide/src/snapshots/ide__verilog_2005__systemverilog_covergroup_supports_items_instantiation_hover_and_outline__outline.snap @@ -0,0 +1,11 @@ +--- +source: crates/ide/src/verilog_2005.rs +expression: "lines.join(\"\\n\")" +--- +top Module container=None + clk PortDecl container=Some("top") + a PortDecl container=Some("top") + cg Unknown container=Some("top") + cp Unknown container=Some("cg") + cx Unknown container=Some("cg") + u Instance container=Some("top") diff --git a/crates/ide/src/verilog_2005.rs b/crates/ide/src/verilog_2005.rs index dbf67e6d..7d3173b5 100644 --- a/crates/ide/src/verilog_2005.rs +++ b/crates/ide/src/verilog_2005.rs @@ -2875,6 +2875,98 @@ endmodule ); } +#[test] +fn systemverilog_covergroup_supports_items_instantiation_hover_and_outline() { + let text = r#" +module top(input clk, input a); + covergroup /*marker:covergroup_def*/cg @(posedge clk); + /*marker:coverpoint_def*/cp: coverpoint a; + /*marker:cross_def*/cx: cross /*marker:coverpoint_ref*/cp, cp; + endgroup + + /*marker:covergroup_ref*/cg /*marker:inst_ref*/u(); +endmodule +"#; + let (host, file_id, _clean_text, markers) = setup_marked(text); + let analysis = host.make_analysis(); + + let covergroup_def_range = marked_range(&markers, "covergroup_def", TextSize::of("cg")); + let covergroup_nav = analysis + .goto_definition(position(file_id, &markers, "covergroup_ref")) + .unwrap() + .expect("covergroup definition expected"); + assert!( + covergroup_nav.info.iter().any(|target| target.focus_range == Some(covergroup_def_range)), + "covergroup instantiation should navigate to covergroup declaration: {covergroup_nav:?}" + ); + + let coverpoint_def_range = marked_range(&markers, "coverpoint_def", TextSize::of("cp")); + let coverpoint_nav = analysis + .goto_definition(position(file_id, &markers, "coverpoint_ref")) + .unwrap() + .expect("coverpoint definition expected"); + assert!( + coverpoint_nav.info.iter().any(|target| target.focus_range == Some(coverpoint_def_range)), + "coverpoint reference should navigate to coverpoint label: {coverpoint_nav:?}" + ); + + let covergroup_hover = analysis + .hover( + position(file_id, &markers, "covergroup_ref"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("covergroup hover expected"); + assert_hover_snapshot!( + "systemverilog_covergroup_supports_items_instantiation_hover_and_outline__covergroup", + covergroup_hover.info.as_str(), + ); + + let coverpoint_hover = analysis + .hover( + position(file_id, &markers, "coverpoint_ref"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("coverpoint hover expected"); + assert_hover_snapshot!( + "systemverilog_covergroup_supports_items_instantiation_hover_and_outline__coverpoint", + coverpoint_hover.info.as_str(), + ); + + let cross_hover = analysis + .hover( + position(file_id, &markers, "cross_def"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("cross hover expected"); + assert_hover_snapshot!( + "systemverilog_covergroup_supports_items_instantiation_hover_and_outline__cross", + cross_hover.info.as_str(), + ); + + let instance_hover = analysis + .hover( + position(file_id, &markers, "inst_ref"), + HoverConfig { format: HoverFormat::PlainText }, + ) + .unwrap() + .expect("covergroup instance hover expected"); + assert_hover_snapshot!( + "systemverilog_covergroup_supports_items_instantiation_hover_and_outline__instance", + instance_hover.info.as_str(), + ); + + let symbols = analysis.document_symbol(file_id).unwrap(); + let mut lines = Vec::new(); + collect_symbol_lines(&symbols, 0, &mut lines); + assert_snapshot!( + "systemverilog_covergroup_supports_items_instantiation_hover_and_outline__outline", + lines.join("\n") + ); +} + #[test] fn verilog_2005_hover_uses_relative_source_label_for_absolute_workspace_path() { let dir = TestDir::new("hover-source-label"); From 15e663605d761c45a0402a6d843277421c0b58a8 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Sat, 27 Jun 2026 15:38:06 +0800 Subject: [PATCH 6/8] feat(hir): resolve clocking block members --- crates/hir/src/container.rs | 13 +++- crates/hir/src/db.rs | 6 +- crates/hir/src/def_id.rs | 90 +++++++++++++++++++---- crates/hir/src/hir_def/module/clocking.rs | 9 ++- crates/hir/src/hir_def/subroutine.rs | 2 +- crates/hir/src/scope.rs | 21 ++++++ crates/hir/src/semantics/hir_to_def.rs | 2 + crates/hir/src/semantics/pathres.rs | 26 +++++++ crates/hir/src/semantics/source_to_def.rs | 3 +- crates/hir/src/symbol.rs | 19 ++++- crates/hir/src/type_infer.rs | 7 ++ 11 files changed, 177 insertions(+), 21 deletions(-) diff --git a/crates/hir/src/container.rs b/crates/hir/src/container.rs index c5d4c825..5592919c 100644 --- a/crates/hir/src/container.rs +++ b/crates/hir/src/container.rs @@ -20,6 +20,7 @@ use crate::{ file::{FileSourceMap, HirFile}, module::{ Module, ModuleId, ModuleSourceMap, + clocking::ClockingBlockId, generate::{GenerateBlock, GenerateBlockId, GenerateBlockSourceMap}, }, stmt::{Stmt, StmtId, StmtSrc}, @@ -38,6 +39,7 @@ define_enum_deriving_from! { GenerateBlock(GenerateBlockId), Block(BlockId), Subroutine(SubroutineScope), + ClockingBlock(InModule), } } @@ -94,7 +96,7 @@ impl TryFrom for SubroutineParent { ScopeId::File(file_id) => Ok(Self::File(file_id)), ScopeId::Module(module_id) => Ok(Self::Module(module_id)), ScopeId::GenerateBlock(generate_block_id) => Ok(Self::GenerateBlock(generate_block_id)), - ScopeId::Block(_) | ScopeId::Subroutine(_) => Err(()), + ScopeId::Block(_) | ScopeId::Subroutine(_) | ScopeId::ClockingBlock(_) => Err(()), } } } @@ -202,6 +204,7 @@ impl ScopeId { ScopeId::GenerateBlock(_) => ScopeKind::GenerateBlock, ScopeId::Block(_) => ScopeKind::Block, ScopeId::Subroutine(_) => ScopeKind::Subroutine, + ScopeId::ClockingBlock(_) => ScopeKind::ClockingBlock, } } @@ -212,6 +215,7 @@ impl ScopeId { ScopeId::GenerateBlock(generate_block_id) => generate_block_id.file_id(db), ScopeId::Block(block_id) => block_id.file_id(db), ScopeId::Subroutine(subroutine) => subroutine.file_id(db), + ScopeId::ClockingBlock(clocking_block) => clocking_block.module_id.file_id(), } } @@ -222,6 +226,9 @@ impl ScopeId { ScopeId::GenerateBlock(generate_block_id) => generate_block_id.to_container(db).into(), ScopeId::Block(block_id) => block_id.to_container(db).into(), ScopeId::Subroutine(subroutine) => db.subroutine(subroutine.as_in_container()).into(), + ScopeId::ClockingBlock(_) => { + panic!("clocking block scopes do not expose a generic HIR container") + } } } @@ -236,6 +243,9 @@ impl ScopeId { ScopeId::Subroutine(subroutine) => { db.subroutine_with_source_map(subroutine.as_in_container()).1.into() } + ScopeId::ClockingBlock(_) => { + panic!("clocking block scopes do not expose a generic source map") + } } } } @@ -385,6 +395,7 @@ impl Iterator for ScopeParent<'_> { } ScopeId::Block(block_id) => Some(block_id.lookup(self.db).cont_id), ScopeId::Subroutine(subroutine) => Some(subroutine.parent_scope()), + ScopeId::ClockingBlock(clocking_block) => Some(clocking_block.module_id.into()), }; next } diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index 1f53314e..cd827afb 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -3,7 +3,7 @@ use triomphe::Arc; use crate::{ base_db::{salsa, source_db::SourceRootDb}, - container::{InContainer, InSubroutine}, + container::{InContainer, InModule, InSubroutine}, def_id::{ModuleDef, ModuleDefId}, file::HirFileId, hir_def::{ @@ -17,6 +17,7 @@ use crate::{ macro_file::{self, ExpansionInfo, MacroCallId, MacroCallLoc, MacroFileId, MacroFileLoc}, module::{ self, Module, ModuleId, ModuleSourceMap, PackageId, + clocking::ClockingBlockId, generate::{ self, GenerateBlock, GenerateBlockId, GenerateBlockLoc, GenerateBlockSourceMap, }, @@ -120,6 +121,9 @@ pub trait HirDb: InternDb { #[salsa::invoke(NameScope::module_scope_query)] fn module_scope(&self, module_id: ModuleId) -> Arc; + #[salsa::invoke(NameScope::clocking_block_scope_query)] + fn clocking_block_scope(&self, clocking_block_id: InModule) -> Arc; + #[salsa::invoke(NameScope::generate_block_scope_query)] fn generate_block_scope(&self, generate_block_id: GenerateBlockId) -> Arc; diff --git a/crates/hir/src/def_id.rs b/crates/hir/src/def_id.rs index f80e151b..13fa6c99 100644 --- a/crates/hir/src/def_id.rs +++ b/crates/hir/src/def_id.rs @@ -17,7 +17,7 @@ use crate::{ block::BlockLoc, declaration::Declaration, expr::declarator::DeclaratorParent, - module::{ModuleKind, generate::GenerateBlockLoc}, + module::{ModuleKind, clocking::ClockingSignal, generate::GenerateBlockLoc}, subroutine::{LocalSubroutineId, SubroutineSrc}, }, source_map::{IsNamedSrc, IsSrc, ToAstNode}, @@ -42,10 +42,23 @@ fn subroutine_src( let file_id = generate_block_id.lookup(db).src.file_id; Some(InFile::new(file_id, source_map.get(subroutine.value)?)) } - ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::Block(_) | ScopeId::Subroutine(_) | ScopeId::ClockingBlock(_) => None, } } +fn clocking_signal_of( + db: &dyn HirDb, + signal: InContainer, +) -> Option<(InModule, vfs::FileId)> { + let ScopeId::ClockingBlock(clocking_block) = signal.cont_id else { + return None; + }; + let module = db.module(clocking_block.module_id); + let clocking = module.get(clocking_block.value); + let signal = clocking.signals.get(signal.value.0 as usize)?.clone(); + Some((InModule::new(clocking_block.module_id, signal), clocking_block.module_id.file_id())) +} + impl DefId { #[inline] pub fn container_id(self, db: &dyn HirDb) -> ScopeId { @@ -66,6 +79,7 @@ impl DefId { DefLoc::Instance(InModule { module_id, .. }) => module_id.into(), DefLoc::Modport(InModule { module_id, .. }) => module_id.into(), DefLoc::ClockingBlock(InModule { module_id, .. }) => module_id.into(), + DefLoc::ClockingSignal(InContainer { cont_id, .. }) => cont_id, DefLoc::Checker(InContainer { cont_id, .. }) => cont_id, DefLoc::Covergroup(InContainer { cont_id, .. }) => cont_id, DefLoc::Coverpoint(InContainer { cont_id, .. }) => cont_id, @@ -114,6 +128,7 @@ impl DefId { DefLoc::Instance(_) => DefKind::Instance, DefLoc::Modport(_) => DefKind::Modport, DefLoc::ClockingBlock(_) => DefKind::ClockingBlock, + DefLoc::ClockingSignal(_) => DefKind::ClockingSignal, DefLoc::Checker(_) => DefKind::Checker, DefLoc::Covergroup(_) => DefKind::Covergroup, DefLoc::Coverpoint(_) => DefKind::Coverpoint, @@ -166,25 +181,40 @@ impl DefId { DefLoc::ClockingBlock(InModule { value, module_id }) => { module_id.to_container(db).get(value).name.clone() } + DefLoc::ClockingSignal(signal) => { + clocking_signal_of(db, signal).map(|(signal, _)| signal.value.name) + } DefLoc::Checker(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => None, }, DefLoc::Covergroup(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => None, }, DefLoc::Coverpoint(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => None, }, DefLoc::Cross(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => None, }, DefLoc::Stmt(InContainer { value, cont_id }) => { cont_id.to_container(db).get(value).label.clone() @@ -266,6 +296,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.name_range()?; Some(InFile::new(module_id.file_id, range)) } + DefLoc::ClockingSignal(signal) => { + let (signal, file_id) = clocking_signal_of(db, signal)?; + Some(InFile::new(file_id.into(), signal.value.name_range?)) + } DefLoc::Checker(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => { let range = file_id.to_container_src_map(db).get(value)?.name_range()?; @@ -275,7 +309,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.name_range()?; Some(InFile::new(module_id.file_id, range)) } - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => None, }, DefLoc::Covergroup(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => { @@ -286,7 +323,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.name_range()?; Some(InFile::new(module_id.file_id, range)) } - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => None, }, DefLoc::Coverpoint(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => { @@ -297,7 +337,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.name_range()?; Some(InFile::new(module_id.file_id, range)) } - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => None, }, DefLoc::Cross(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => { @@ -308,7 +351,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.name_range()?; Some(InFile::new(module_id.file_id, range)) } - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => None, }, DefLoc::Stmt(InContainer { value, cont_id }) => { let range = cont_id.to_container_src_map(db).get(value)?.name_range()?; @@ -388,6 +434,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.range(); InFile::new(module_id.file_id, range) } + DefLoc::ClockingSignal(signal) => { + let (signal, file_id) = clocking_signal_of(db, signal)?; + InFile::new(file_id.into(), signal.value.name_range?) + } DefLoc::Checker(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => { let range = file_id.to_container_src_map(db).get(value)?.range(); @@ -397,7 +447,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.range(); InFile::new(module_id.file_id, range) } - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => { + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => { return None; } }, @@ -410,7 +463,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.range(); InFile::new(module_id.file_id, range) } - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => { + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => { return None; } }, @@ -423,7 +479,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.range(); InFile::new(module_id.file_id, range) } - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => { + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => { return None; } }, @@ -436,7 +495,10 @@ impl DefId { let range = module_id.to_container_src_map(db).get(value)?.range(); InFile::new(module_id.file_id, range) } - ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) => { + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) => { return None; } }, diff --git a/crates/hir/src/hir_def/module/clocking.rs b/crates/hir/src/hir_def/module/clocking.rs index 995f734c..d3984ec6 100644 --- a/crates/hir/src/hir_def/module/clocking.rs +++ b/crates/hir/src/hir_def/module/clocking.rs @@ -3,6 +3,7 @@ use smallvec::SmallVec; use syntax::{ SyntaxKind, TokenKind, ast::{self, AstNode}, + has_text_range::HasTextRange, ptr::{SyntaxNodePtr, SyntaxTokenPtr}, slang_ext::AstNodeExt, }; @@ -35,8 +36,12 @@ pub type ClockingBlockId = Idx; pub struct ClockingSignal { pub name: Ident, pub dir: PortDirection, + pub name_range: Option, } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub struct ClockingSignalId(pub u32); + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct ClockingBlockSrc { pub node: SyntaxNodePtr, @@ -102,16 +107,18 @@ impl LowerClocking for LowerModuleCtx<'_> { fn lower_clocking_signals(clocking: ast::ClockingDeclaration<'_>) -> SmallVec<[ClockingSignal; 4]> { let mut signals = SmallVec::new(); + let syntax = clocking.syntax(); for item in clocking.items().children() { let ast::Member::ClockingItem(item) = item else { continue; }; let dir = lower_clocking_direction(item.direction()); for decl in item.decls().children() { + let name_range = decl.name().and_then(|name| root_token_in(syntax, name)?.text_range()); let Some(name) = lower_ident_opt(decl.name()) else { continue; }; - signals.push(ClockingSignal { name, dir }); + signals.push(ClockingSignal { name, dir, name_range }); } } signals diff --git a/crates/hir/src/hir_def/subroutine.rs b/crates/hir/src/hir_def/subroutine.rs index 859fb70e..f69322fe 100644 --- a/crates/hir/src/hir_def/subroutine.rs +++ b/crates/hir/src/hir_def/subroutine.rs @@ -352,7 +352,7 @@ pub(crate) fn subroutine_with_source_map_query( let source_map = subroutine.source_map.clone(); (Arc::new(subroutine), Arc::new(source_map)) } - ScopeId::Block(_) | ScopeId::Subroutine(_) => { + ScopeId::Block(_) | ScopeId::Subroutine(_) | ScopeId::ClockingBlock(_) => { unreachable!("subroutines are lowered only in file, module, or generate-block scopes") } } diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 1d350a97..463a6728 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -16,6 +16,7 @@ use crate::{ lower_ident_opt, module::{ Module, ModuleKind, PackageId, + clocking::{ClockingBlockId, ClockingSignalId}, generate::GenerateBlockId, port::{PortDeclId, Ports}, }, @@ -287,6 +288,26 @@ impl NameScope { Arc::new(scope) } + pub fn clocking_block_scope_query( + db: &dyn HirDb, + clocking_block_id: InModule, + ) -> Arc { + let mut scope = NameScope::default(); + let module = db.module(clocking_block_id.module_id); + let clocking_block = module.get(clocking_block_id.value); + let clocking_scope = ScopeId::ClockingBlock(clocking_block_id); + + for (idx, signal) in clocking_block.signals.iter().enumerate() { + let signal_id = ClockingSignalId(idx as u32); + scope.insert_value( + &signal.name, + def_id(db, InContainer::new(clocking_scope, signal_id)), + ); + } + + Arc::new(scope) + } + pub fn generate_block_scope_query( db: &dyn HirDb, generate_block_id: GenerateBlockId, diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index a189e5a2..b51a13fd 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -76,6 +76,7 @@ impl Source2DefCtx<'_, '_> { let subroutine = db.subroutine(subroutine_id.as_in_container()); resolve(subroutine.get(expr_id)) } + ScopeId::ClockingBlock(_) => None, } } @@ -142,6 +143,7 @@ impl Source2DefCtx<'_, '_> { ScopeId::Subroutine(subroutine_id) => { Some(self.db.subroutine(subroutine_id.as_in_container()).get(expr_id).clone()) } + ScopeId::ClockingBlock(_) => None, } } } diff --git a/crates/hir/src/semantics/pathres.rs b/crates/hir/src/semantics/pathres.rs index cc43c620..1e0a4f8a 100644 --- a/crates/hir/src/semantics/pathres.rs +++ b/crates/hir/src/semantics/pathres.rs @@ -148,6 +148,7 @@ fn resolve_child_name( pub fn descend_scope(db: &dyn HirDb, def_id: DefId) -> Option { match def_id.kind(db) { kind if kind.is_instantiable_def() => def_id.as_module(db).map(Into::into), + DefKind::ClockingBlock => def_id.as_clocking_block(db).map(Into::into), DefKind::Instance => { let instance = def_id.as_instance(db)?; instance_target_module_id(db, instance.module_id, instance.value).map(Into::into) @@ -174,6 +175,7 @@ pub(crate) fn name_scope(db: &dyn HirDb, scope_id: ScopeId) -> Arc { match scope_id { ScopeId::File(file_id) => db.file_scope(file_id), ScopeId::Module(module_id) => db.module_scope(module_id), + ScopeId::ClockingBlock(clocking_block_id) => db.clocking_block_scope(clocking_block_id), ScopeId::GenerateBlock(generate_block_id) => db.generate_block_scope(generate_block_id), ScopeId::Block(block_id) => db.block_scope(block_id), ScopeId::Subroutine(subroutine_id) => db.subroutine_scope(subroutine_id.as_in_container()), @@ -502,4 +504,28 @@ endmodule DefKind::Modport ); } + + #[test] + fn resolve_path_descends_clocking_blocks_to_signals() { + let db = db_with_root_text( + r#" +module top(input clk, input a); + clocking cb @(posedge clk); + input #1ps a; + endclocking +endmodule +"#, + ); + + let top = db + .unit_scope() + .module_ids(&db, &ident("top")) + .unique() + .expect("top module should resolve uniquely"); + + assert_eq!( + resolved_kind(&db, top.into(), &["cb", "a"], NameContext::Value), + DefKind::ClockingSignal + ); + } } diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index d8993212..7177513b 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -99,6 +99,7 @@ impl Source2DefCtx<'_, '_> { let local_block_id = *subroutine_src_map.block_srcs.get(&block_src)?; subroutine.stmts.get(local_block_id).block_id } + ScopeId::ClockingBlock(_) => return None, }; Some(block_id) @@ -180,7 +181,7 @@ impl Source2DefCtx<'_, '_> { let (_, source_map) = self.db.generate_block_with_source_map(generate_block_id); source_map.get(src) } - ScopeId::Block(_) | ScopeId::Subroutine(_) => None, + ScopeId::Block(_) | ScopeId::Subroutine(_) | ScopeId::ClockingBlock(_) => None, } } diff --git a/crates/hir/src/symbol.rs b/crates/hir/src/symbol.rs index 12f05aae..12a4d577 100644 --- a/crates/hir/src/symbol.rs +++ b/crates/hir/src/symbol.rs @@ -14,8 +14,12 @@ use crate::{ expr::declarator::DeclId, file::{config::ConfigDeclId, library::LibraryDeclId, udp::UdpDeclId}, module::{ - ModuleId, clocking::ClockingBlockId, generate::GenerateBlockId, - instantiation::InstanceId, modport::ModportId, port::NonAnsiPortId, + ModuleId, + clocking::{ClockingBlockId, ClockingSignalId}, + generate::GenerateBlockId, + instantiation::InstanceId, + modport::ModportId, + port::NonAnsiPortId, }, stmt::StmtId, subroutine::{LocalSubroutineId, SubroutinePortId}, @@ -44,6 +48,7 @@ pub enum DefLoc { Instance(InModule), Modport(InModule), ClockingBlock(InModule), + ClockingSignal(InContainer), Checker(InContainer), Covergroup(InContainer), Coverpoint(InContainer), @@ -66,6 +71,7 @@ impl_from! { DefLoc => Instance(InModule), Modport(InModule), ClockingBlock(InModule), + ClockingSignal(InContainer), Checker(InContainer), Covergroup(InContainer), Coverpoint(InContainer), @@ -180,6 +186,13 @@ impl DefId { } } + pub fn as_clocking_signal(self, db: &dyn InternDb) -> Option> { + match self.loc(db) { + DefLoc::ClockingSignal(id) => Some(id), + _ => None, + } + } + pub fn as_checker(self, db: &dyn InternDb) -> Option> { match self.loc(db) { DefLoc::Checker(id) => Some(id), @@ -227,6 +240,7 @@ pub enum DefKind { Instance, Modport, ClockingBlock, + ClockingSignal, Checker, Covergroup, Coverpoint, @@ -268,6 +282,7 @@ impl DefKind { DefKind::Instance => SymbolKind::Instance, DefKind::Modport | DefKind::ClockingBlock + | DefKind::ClockingSignal | DefKind::Checker | DefKind::Covergroup | DefKind::Coverpoint diff --git a/crates/hir/src/type_infer.rs b/crates/hir/src/type_infer.rs index 18d330f2..8491f72a 100644 --- a/crates/hir/src/type_infer.rs +++ b/crates/hir/src/type_infer.rs @@ -214,6 +214,7 @@ fn type_of_def_id(db: &dyn HirDb, def_id: DefId) -> TyResult { | DefKind::Subroutine | DefKind::NonAnsiPort | DefKind::ClockingBlock + | DefKind::ClockingSignal | DefKind::Checker | DefKind::Covergroup | DefKind::Coverpoint @@ -738,6 +739,7 @@ fn expr_of(db: &dyn HirDb, expr: InContainer) -> Option { ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(expr.value).clone()) } + ScopeId::ClockingBlock(_) => None, } } @@ -755,6 +757,7 @@ fn decl_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(decl.value).clone()) } + ScopeId::ClockingBlock(_) => None, } } @@ -772,6 +775,7 @@ fn declaration_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(decl.value).clone()) } + ScopeId::ClockingBlock(_) => None, } } @@ -789,6 +793,7 @@ fn typedef_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(typedef.value).clone()) } + ScopeId::ClockingBlock(_) => None, } } @@ -806,6 +811,7 @@ fn struct_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(struct_id.value).clone()) } + ScopeId::ClockingBlock(_) => None, } } @@ -823,6 +829,7 @@ fn stmt_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(stmt.value).clone()) } + ScopeId::ClockingBlock(_) => None, } } From 3899e8382f4161f26005e1063d8909fc4a6c91d5 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Sat, 27 Jun 2026 15:56:46 +0800 Subject: [PATCH 7/8] feat(hir): resolve checker and covergroup members --- crates/hir/src/container.rs | 103 ++++++- crates/hir/src/db.rs | 8 + crates/hir/src/def_id.rs | 322 ++++++++++++++++------ crates/hir/src/display.rs | 15 + crates/hir/src/hir_def/checker.rs | 139 +++++++++- crates/hir/src/hir_def/file.rs | 12 +- crates/hir/src/hir_def/module.rs | 12 +- crates/hir/src/hir_def/subroutine.rs | 6 +- crates/hir/src/scope.rs | 167 ++++++++++- crates/hir/src/semantics/hir_to_def.rs | 4 +- crates/hir/src/semantics/pathres.rs | 87 +++++- crates/hir/src/semantics/source_to_def.rs | 10 +- crates/hir/src/symbol.rs | 13 +- crates/hir/src/type_infer.rs | 83 +++--- 14 files changed, 833 insertions(+), 148 deletions(-) diff --git a/crates/hir/src/container.rs b/crates/hir/src/container.rs index 5592919c..8743a3ab 100644 --- a/crates/hir/src/container.rs +++ b/crates/hir/src/container.rs @@ -11,6 +11,8 @@ use crate::{ hir_def::{ aggregate::{StructDef, StructId, StructSrc}, block::{Block, BlockId, BlockInfo, BlockSourceMap, BlockSrc, LocalBlockId}, + checker::CheckerId, + covergroup::CovergroupId, declaration::{Declaration, DeclarationId, DeclarationSrc}, expr::{ Expr, ExprId, ExprSrc, @@ -40,6 +42,83 @@ define_enum_deriving_from! { Block(BlockId), Subroutine(SubroutineScope), ClockingBlock(InModule), + Checker(InFileOrModule), + Covergroup(InFileOrModule), + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub struct InFileOrModule { + pub value: T, + pub cont_id: FileOrModule, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum FileOrModule { + File(HirFileId), + Module(ModuleId), +} + +impl InFileOrModule { + pub fn new(cont_id: FileOrModule, value: T) -> Self { + Self { value, cont_id } + } + + pub fn parent_scope(&self) -> ScopeId { + self.cont_id.into() + } + + pub fn as_in_container(self) -> InContainer { + InContainer::new(self.cont_id.into(), self.value) + } +} + +impl FileOrModule { + pub fn file_id(self) -> FileId { + match self { + FileOrModule::File(file_id) => file_id.file_id(), + FileOrModule::Module(module_id) => module_id.file_id(), + } + } +} + +impl From for ScopeId { + fn from(cont_id: FileOrModule) -> Self { + match cont_id { + FileOrModule::File(file_id) => file_id.into(), + FileOrModule::Module(module_id) => module_id.into(), + } + } +} + +impl TryFrom for FileOrModule { + type Error = (); + + fn try_from(cont_id: ScopeId) -> Result { + match cont_id { + ScopeId::File(file_id) => Ok(Self::File(file_id)), + ScopeId::Module(module_id) => Ok(Self::Module(module_id)), + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => Err(()), + } + } +} + +impl TryFrom> for InFileOrModule { + type Error = (); + + fn try_from(item: InContainer) -> Result { + Ok(Self::new(FileOrModule::try_from(item.cont_id)?, item.value)) + } +} + +impl From> for InContainer { + fn from(item: InFileOrModule) -> Self { + item.as_in_container() } } @@ -96,7 +175,11 @@ impl TryFrom for SubroutineParent { ScopeId::File(file_id) => Ok(Self::File(file_id)), ScopeId::Module(module_id) => Ok(Self::Module(module_id)), ScopeId::GenerateBlock(generate_block_id) => Ok(Self::GenerateBlock(generate_block_id)), - ScopeId::Block(_) | ScopeId::Subroutine(_) | ScopeId::ClockingBlock(_) => Err(()), + ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => Err(()), } } } @@ -205,6 +288,8 @@ impl ScopeId { ScopeId::Block(_) => ScopeKind::Block, ScopeId::Subroutine(_) => ScopeKind::Subroutine, ScopeId::ClockingBlock(_) => ScopeKind::ClockingBlock, + ScopeId::Checker(_) => ScopeKind::Checker, + ScopeId::Covergroup(_) => ScopeKind::Covergroup, } } @@ -216,6 +301,8 @@ impl ScopeId { ScopeId::Block(block_id) => block_id.file_id(db), ScopeId::Subroutine(subroutine) => subroutine.file_id(db), ScopeId::ClockingBlock(clocking_block) => clocking_block.module_id.file_id(), + ScopeId::Checker(checker) => checker.cont_id.file_id(), + ScopeId::Covergroup(covergroup) => covergroup.cont_id.file_id(), } } @@ -229,6 +316,12 @@ impl ScopeId { ScopeId::ClockingBlock(_) => { panic!("clocking block scopes do not expose a generic HIR container") } + ScopeId::Checker(_) => { + panic!("checker scopes do not expose a generic HIR container") + } + ScopeId::Covergroup(_) => { + panic!("covergroup scopes do not expose a generic HIR container") + } } } @@ -246,6 +339,12 @@ impl ScopeId { ScopeId::ClockingBlock(_) => { panic!("clocking block scopes do not expose a generic source map") } + ScopeId::Checker(_) => { + panic!("checker scopes do not expose a generic source map") + } + ScopeId::Covergroup(_) => { + panic!("covergroup scopes do not expose a generic source map") + } } } } @@ -396,6 +495,8 @@ impl Iterator for ScopeParent<'_> { ScopeId::Block(block_id) => Some(block_id.lookup(self.db).cont_id), ScopeId::Subroutine(subroutine) => Some(subroutine.parent_scope()), ScopeId::ClockingBlock(clocking_block) => Some(clocking_block.module_id.into()), + ScopeId::Checker(checker) => Some(checker.parent_scope()), + ScopeId::Covergroup(covergroup) => Some(covergroup.parent_scope()), }; next } diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index cd827afb..0e3edd1f 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs @@ -8,6 +8,8 @@ use crate::{ file::HirFileId, hir_def::{ block::{self, Block, BlockId, BlockLoc, BlockSourceMap}, + checker::CheckerId, + covergroup::CovergroupId, expr::{ ExprId, data_ty::{BuiltinDataTy, BuiltinDataTyId}, @@ -124,6 +126,12 @@ pub trait HirDb: InternDb { #[salsa::invoke(NameScope::clocking_block_scope_query)] fn clocking_block_scope(&self, clocking_block_id: InModule) -> Arc; + #[salsa::invoke(NameScope::checker_scope_query)] + fn checker_scope(&self, checker_id: InContainer) -> Arc; + + #[salsa::invoke(NameScope::covergroup_scope_query)] + fn covergroup_scope(&self, covergroup_id: InContainer) -> Arc; + #[salsa::invoke(NameScope::generate_block_scope_query)] fn generate_block_scope(&self, generate_block_id: GenerateBlockId) -> Arc; diff --git a/crates/hir/src/def_id.rs b/crates/hir/src/def_id.rs index 13fa6c99..2acadabf 100644 --- a/crates/hir/src/def_id.rs +++ b/crates/hir/src/def_id.rs @@ -11,10 +11,13 @@ use utils::{ use crate::{ base_db::{intern::Lookup, salsa}, - container::{InContainer, InFile, InModule, InSubroutine, ScopeId}, + container::{FileOrModule, InContainer, InFile, InModule, InSubroutine, ScopeId}, db::HirDb, + file::HirFileId, hir_def::{ block::BlockLoc, + checker::{CheckerDef, CheckerPort, CheckerPortId}, + covergroup::{CoverpointDef, CoverpointId, CrossDef, CrossId}, declaration::Declaration, expr::declarator::DeclaratorParent, module::{ModuleKind, clocking::ClockingSignal, generate::GenerateBlockLoc}, @@ -42,7 +45,11 @@ fn subroutine_src( let file_id = generate_block_id.lookup(db).src.file_id; Some(InFile::new(file_id, source_map.get(subroutine.value)?)) } - ScopeId::Block(_) | ScopeId::Subroutine(_) | ScopeId::ClockingBlock(_) => None, + ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, } } @@ -59,6 +66,81 @@ fn clocking_signal_of( Some((InModule::new(clocking_block.module_id, signal), clocking_block.module_id.file_id())) } +fn checker_of( + db: &dyn HirDb, + checker: InContainer, +) -> Option<(CheckerDef, HirFileId)> { + match checker.cont_id { + ScopeId::File(file_id) => Some((db.hir_file(file_id).get(checker.value).clone(), file_id)), + ScopeId::Module(module_id) => { + Some((db.module(module_id).get(checker.value).clone(), module_id.file_id)) + } + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, + } +} + +fn checker_port_of( + db: &dyn HirDb, + port: InContainer, +) -> Option<(CheckerPort, HirFileId)> { + let ScopeId::Checker(checker) = port.cont_id else { + return None; + }; + let (checker, file_id) = checker_of(db, checker.as_in_container())?; + let port = checker.ports.get(port.value.0 as usize)?.clone(); + Some((port, file_id)) +} + +fn coverpoint_of( + db: &dyn HirDb, + coverpoint: InContainer, +) -> Option<(CoverpointDef, HirFileId)> { + let cont_id = match coverpoint.cont_id { + ScopeId::Covergroup(covergroup) => covergroup.parent_scope(), + cont_id => cont_id, + }; + + match cont_id { + ScopeId::File(file_id) => { + Some((db.hir_file(file_id).get(coverpoint.value).clone(), file_id)) + } + ScopeId::Module(module_id) => { + Some((db.module(module_id).get(coverpoint.value).clone(), module_id.file_id)) + } + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, + } +} + +fn cross_of(db: &dyn HirDb, cross: InContainer) -> Option<(CrossDef, HirFileId)> { + let cont_id = match cross.cont_id { + ScopeId::Covergroup(covergroup) => covergroup.parent_scope(), + cont_id => cont_id, + }; + + match cont_id { + ScopeId::File(file_id) => Some((db.hir_file(file_id).get(cross.value).clone(), file_id)), + ScopeId::Module(module_id) => { + Some((db.module(module_id).get(cross.value).clone(), module_id.file_id)) + } + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, + } +} + impl DefId { #[inline] pub fn container_id(self, db: &dyn HirDb) -> ScopeId { @@ -81,6 +163,7 @@ impl DefId { DefLoc::ClockingBlock(InModule { module_id, .. }) => module_id.into(), DefLoc::ClockingSignal(InContainer { cont_id, .. }) => cont_id, DefLoc::Checker(InContainer { cont_id, .. }) => cont_id, + DefLoc::CheckerPort(InContainer { cont_id, .. }) => cont_id, DefLoc::Covergroup(InContainer { cont_id, .. }) => cont_id, DefLoc::Coverpoint(InContainer { cont_id, .. }) => cont_id, DefLoc::Cross(InContainer { cont_id, .. }) => cont_id, @@ -130,6 +213,7 @@ impl DefId { DefLoc::ClockingBlock(_) => DefKind::ClockingBlock, DefLoc::ClockingSignal(_) => DefKind::ClockingSignal, DefLoc::Checker(_) => DefKind::Checker, + DefLoc::CheckerPort(_) => DefKind::CheckerPort, DefLoc::Covergroup(_) => DefKind::Covergroup, DefLoc::Coverpoint(_) => DefKind::Coverpoint, DefLoc::Cross(_) => DefKind::Cross, @@ -190,32 +274,25 @@ impl DefId { ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => None, + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, }, + DefLoc::CheckerPort(port) => checker_port_of(db, port).map(|(port, _)| port.name), DefLoc::Covergroup(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => None, - }, - DefLoc::Coverpoint(InContainer { value, cont_id }) => match cont_id { - ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), - ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), - ScopeId::GenerateBlock(_) - | ScopeId::Block(_) - | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => None, - }, - DefLoc::Cross(InContainer { value, cont_id }) => match cont_id { - ScopeId::File(file_id) => file_id.to_container(db).get(value).name.clone(), - ScopeId::Module(module_id) => module_id.to_container(db).get(value).name.clone(), - ScopeId::GenerateBlock(_) - | ScopeId::Block(_) - | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => None, + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, }, + DefLoc::Coverpoint(coverpoint) => { + coverpoint_of(db, coverpoint).and_then(|(coverpoint, _)| coverpoint.name) + } + DefLoc::Cross(cross) => cross_of(db, cross).and_then(|(cross, _)| cross.name), DefLoc::Stmt(InContainer { value, cont_id }) => { cont_id.to_container(db).get(value).label.clone() } @@ -312,8 +389,14 @@ impl DefId { ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => None, + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, }, + DefLoc::CheckerPort(port) => { + let (port, file_id) = checker_port_of(db, port)?; + Some(InFile::new(file_id, port.name_range?)) + } DefLoc::Covergroup(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => { let range = file_id.to_container_src_map(db).get(value)?.name_range()?; @@ -326,36 +409,80 @@ impl DefId { ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => None, + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, }, - DefLoc::Coverpoint(InContainer { value, cont_id }) => match cont_id { - ScopeId::File(file_id) => { - let range = file_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(file_id, range)) - } - ScopeId::Module(module_id) => { - let range = module_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(module_id.file_id, range)) - } - ScopeId::GenerateBlock(_) - | ScopeId::Block(_) - | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => None, - }, - DefLoc::Cross(InContainer { value, cont_id }) => match cont_id { - ScopeId::File(file_id) => { - let range = file_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(file_id, range)) + DefLoc::Coverpoint(coverpoint) => { + let (_, file_id) = coverpoint_of(db, coverpoint)?; + match coverpoint.cont_id { + ScopeId::Covergroup(covergroup) => match covergroup.cont_id { + FileOrModule::File(storage_file) => { + let range = storage_file + .to_container_src_map(db) + .get(coverpoint.value)? + .name_range()?; + Some(InFile::new(file_id, range)) + } + FileOrModule::Module(storage_module) => { + let range = storage_module + .to_container_src_map(db) + .get(coverpoint.value)? + .name_range()?; + Some(InFile::new(file_id, range)) + } + }, + ScopeId::File(storage_file) => { + let range = storage_file + .to_container_src_map(db) + .get(coverpoint.value)? + .name_range()?; + Some(InFile::new(file_id, range)) + } + ScopeId::Module(storage_module) => { + let range = storage_module + .to_container_src_map(db) + .get(coverpoint.value)? + .name_range()?; + Some(InFile::new(file_id, range)) + } + _ => None, } - ScopeId::Module(module_id) => { - let range = module_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(module_id.file_id, range)) + } + DefLoc::Cross(cross) => { + let (_, file_id) = cross_of(db, cross)?; + match cross.cont_id { + ScopeId::Covergroup(covergroup) => match covergroup.cont_id { + FileOrModule::File(storage_file) => { + let range = storage_file + .to_container_src_map(db) + .get(cross.value)? + .name_range()?; + Some(InFile::new(file_id, range)) + } + FileOrModule::Module(storage_module) => { + let range = storage_module + .to_container_src_map(db) + .get(cross.value)? + .name_range()?; + Some(InFile::new(file_id, range)) + } + }, + ScopeId::File(storage_file) => { + let range = + storage_file.to_container_src_map(db).get(cross.value)?.name_range()?; + Some(InFile::new(file_id, range)) + } + ScopeId::Module(storage_module) => { + let range = storage_module + .to_container_src_map(db) + .get(cross.value)? + .name_range()?; + Some(InFile::new(file_id, range)) + } + _ => None, } - ScopeId::GenerateBlock(_) - | ScopeId::Block(_) - | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => None, - }, + } DefLoc::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)) @@ -450,10 +577,16 @@ impl DefId { ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => { + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => { return None; } }, + DefLoc::CheckerPort(port) => { + let (port, file_id) = checker_port_of(db, port)?; + InFile::new(file_id, port.name_range?) + } DefLoc::Covergroup(InContainer { value, cont_id }) => match cont_id { ScopeId::File(file_id) => { let range = file_id.to_container_src_map(db).get(value)?.range(); @@ -466,42 +599,71 @@ impl DefId { ScopeId::GenerateBlock(_) | ScopeId::Block(_) | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => { + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => { return None; } }, - DefLoc::Coverpoint(InContainer { value, cont_id }) => match cont_id { - ScopeId::File(file_id) => { - let range = file_id.to_container_src_map(db).get(value)?.range(); - InFile::new(file_id, range) - } - ScopeId::Module(module_id) => { - let range = module_id.to_container_src_map(db).get(value)?.range(); - InFile::new(module_id.file_id, range) - } - ScopeId::GenerateBlock(_) - | ScopeId::Block(_) - | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => { - return None; - } - }, - DefLoc::Cross(InContainer { value, cont_id }) => match cont_id { - ScopeId::File(file_id) => { - let range = file_id.to_container_src_map(db).get(value)?.range(); - InFile::new(file_id, range) - } - ScopeId::Module(module_id) => { - let range = module_id.to_container_src_map(db).get(value)?.range(); - InFile::new(module_id.file_id, range) + DefLoc::Coverpoint(coverpoint) => { + let (_, file_id) = coverpoint_of(db, coverpoint)?; + match coverpoint.cont_id { + ScopeId::Covergroup(covergroup) => match covergroup.cont_id { + FileOrModule::File(storage_file) => { + let range = storage_file + .to_container_src_map(db) + .get(coverpoint.value)? + .range(); + InFile::new(file_id, range) + } + FileOrModule::Module(storage_module) => { + let range = storage_module + .to_container_src_map(db) + .get(coverpoint.value)? + .range(); + InFile::new(file_id, range) + } + }, + ScopeId::File(storage_file) => { + let range = + storage_file.to_container_src_map(db).get(coverpoint.value)?.range(); + InFile::new(file_id, range) + } + ScopeId::Module(storage_module) => { + let range = + storage_module.to_container_src_map(db).get(coverpoint.value)?.range(); + InFile::new(file_id, range) + } + _ => return None, } - ScopeId::GenerateBlock(_) - | ScopeId::Block(_) - | ScopeId::Subroutine(_) - | ScopeId::ClockingBlock(_) => { - return None; + } + DefLoc::Cross(cross) => { + let (_, file_id) = cross_of(db, cross)?; + match cross.cont_id { + ScopeId::Covergroup(covergroup) => match covergroup.cont_id { + FileOrModule::File(storage_file) => { + let range = + storage_file.to_container_src_map(db).get(cross.value)?.range(); + InFile::new(file_id, range) + } + FileOrModule::Module(storage_module) => { + let range = + storage_module.to_container_src_map(db).get(cross.value)?.range(); + InFile::new(file_id, range) + } + }, + ScopeId::File(storage_file) => { + let range = storage_file.to_container_src_map(db).get(cross.value)?.range(); + InFile::new(file_id, range) + } + ScopeId::Module(storage_module) => { + let range = + storage_module.to_container_src_map(db).get(cross.value)?.range(); + InFile::new(file_id, range) + } + _ => return None, } - }, + } DefLoc::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) diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index dd5f1aab..9b8241ef 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -130,6 +130,8 @@ impl HirDisplay for Ty { f.write_str("module") } } + Ty::Checker(def) => hir_fmt_named_def_type(f, "checker", *def), + Ty::Covergroup(def) => hir_fmt_named_def_type(f, "covergroup", *def), Ty::VirtualInterface { def, modport } => { f.write_str("virtual interface ")?; if let Some(name) = def.name(f.db) { @@ -175,6 +177,19 @@ fn hir_fmt_def_backed_type( Ok(()) } +fn hir_fmt_named_def_type( + f: &mut HirFormatter<'_>, + keyword: &str, + def: crate::symbol::DefId, +) -> Result<(), HirDisplayError> { + f.write_str(keyword)?; + if let Some(name) = def.name(f.db) { + f.write_str(" ")?; + f.write_str(&name)?; + } + Ok(()) +} + fn ty_expr_container(db: &dyn crate::db::HirDb, ty: &Ty) -> Option { match ty { Ty::Builtin(BuiltinTy::Data { container, .. }) => Some(*container), diff --git a/crates/hir/src/hir_def/checker.rs b/crates/hir/src/hir_def/checker.rs index 42f77086..29210775 100644 --- a/crates/hir/src/hir_def/checker.rs +++ b/crates/hir/src/hir_def/checker.rs @@ -1,24 +1,51 @@ use la_arena::Idx; +use smallvec::SmallVec; use syntax::{ SyntaxKind, TokenKind, ast::{self, AstNode}, + has_text_range::HasTextRange, ptr::{SyntaxNodePtr, SyntaxTokenPtr}, slang_ext::AstNodeExt, }; use utils::text_edit::TextRange; +use super::{ + alloc_idx_and_src, + declaration::{DeclarationId, LowerDeclaration}, + file::LowerFileCtx, + module::{LowerModuleCtx, port::PortDirection}, +}; use crate::{ hir_def::{Ident, lower_ident_opt}, source_map::{FromSourceAst, IsNamedSrc, IsSrc, SourceAst, root_token_in}, }; +// slang AST survey: +// - `CheckerDeclaration` owns assertion-item ports through +// `port_list().ports()`. +// - Checker body variables arrive either as `Member::CheckerDataDeclaration` +// wrapping ordinary `DataDeclaration` syntax, or directly as module-like +// data/net declaration members depending on the concrete grammar form. + #[derive(Debug, PartialEq, Eq, Clone)] pub struct CheckerDef { pub name: Option, + pub ports: SmallVec<[CheckerPort; 4]>, + pub declarations: SmallVec<[DeclarationId; 4]>, } pub type CheckerId = Idx; +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CheckerPort { + pub name: Ident, + pub dir: PortDirection, + pub name_range: Option, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub struct CheckerPortId(pub u32); + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct CheckerSrc { pub node: SyntaxNodePtr, @@ -61,5 +88,115 @@ impl<'a> FromSourceAst<'a, ast::CheckerDeclaration<'a>> for CheckerSrc { } pub fn lower_checker_decl(checker: ast::CheckerDeclaration<'_>) -> CheckerDef { - CheckerDef { name: lower_ident_opt(checker.name()) } + CheckerDef { + name: lower_ident_opt(checker.name()), + ports: lower_checker_ports(checker), + declarations: SmallVec::new(), + } +} + +pub(crate) trait LowerChecker { + fn lower_checker_decl(&mut self, checker_decl: ast::CheckerDeclaration<'_>) -> CheckerId; +} + +impl LowerChecker for LowerModuleCtx<'_> { + fn lower_checker_decl(&mut self, checker_decl: ast::CheckerDeclaration<'_>) -> CheckerId { + let mut checker = lower_checker_decl(checker_decl); + lower_checker_declarations(&mut checker, checker_decl, |member| match member { + CheckerDeclarationMember::Data(data_decl) => { + self.declaration_ctx().lower_data_decl(data_decl) + } + CheckerDeclarationMember::Net(net_decl) => { + self.declaration_ctx().lower_net_decl(net_decl) + } + }); + + alloc_idx_and_src! { + self.file_id; + checker => self.module.checkers, + checker_decl => self.module_source_map.checker_srcs, + } + } +} + +impl LowerChecker for LowerFileCtx<'_> { + fn lower_checker_decl(&mut self, checker_decl: ast::CheckerDeclaration<'_>) -> CheckerId { + let mut checker = lower_checker_decl(checker_decl); + lower_checker_declarations(&mut checker, checker_decl, |member| match member { + CheckerDeclarationMember::Data(data_decl) => { + self.declaration_ctx().lower_data_decl(data_decl) + } + CheckerDeclarationMember::Net(net_decl) => { + self.declaration_ctx().lower_net_decl(net_decl) + } + }); + + alloc_idx_and_src! { + self.file_id; + checker => self.file.checkers, + checker_decl => self.file_source_map.checker_srcs, + } + } +} + +fn lower_checker_ports(checker: ast::CheckerDeclaration<'_>) -> SmallVec<[CheckerPort; 4]> { + let mut ports = SmallVec::new(); + let syntax = checker.syntax(); + let Some(port_list) = checker.port_list() else { + return ports; + }; + + for port in port_list.ports().children() { + let name_range = port.name().and_then(|name| root_token_in(syntax, name)?.text_range()); + let Some(name) = lower_ident_opt(port.name()) else { + continue; + }; + ports.push(CheckerPort { + name, + dir: lower_checker_port_direction(port.direction()), + name_range, + }); + } + + ports +} + +fn lower_checker_declarations( + checker: &mut CheckerDef, + checker_decl: ast::CheckerDeclaration<'_>, + mut lower_member: impl FnMut(CheckerDeclarationMember<'_>) -> DeclarationId, +) { + for member in checker_decl.members().children() { + match member { + ast::Member::CheckerDataDeclaration(data_decl) => { + checker + .declarations + .push(lower_member(CheckerDeclarationMember::Data(data_decl.data()))); + } + ast::Member::DataDeclaration(data_decl) => { + checker.declarations.push(lower_member(CheckerDeclarationMember::Data(data_decl))); + } + ast::Member::NetDeclaration(net_decl) => { + checker.declarations.push(lower_member(CheckerDeclarationMember::Net(net_decl))); + } + _ => {} + } + } +} + +enum CheckerDeclarationMember<'a> { + Data(ast::DataDeclaration<'a>), + Net(ast::NetDeclaration<'a>), +} + +fn lower_checker_port_direction(direction: Option>) -> PortDirection { + direction + .and_then(|direction| match direction.kind() { + TokenKind::INPUT_KEYWORD => Some(PortDirection::Input), + TokenKind::OUTPUT_KEYWORD => Some(PortDirection::Output), + TokenKind::IN_OUT_KEYWORD => Some(PortDirection::Inout), + TokenKind::REF_KEYWORD => Some(PortDirection::Ref), + _ => None, + }) + .unwrap_or(PortDirection::Input) } diff --git a/crates/hir/src/hir_def/file.rs b/crates/hir/src/hir_def/file.rs index dd689d75..cf19cdce 100644 --- a/crates/hir/src/hir_def/file.rs +++ b/crates/hir/src/hir_def/file.rs @@ -18,7 +18,7 @@ use super::{ aggregate::{StructDef, StructId, StructSrc, lower_struct_def}, alloc_idx_and_src, block::{BlockInfo, BlockSrc, LocalBlockId}, - checker::{CheckerDef, CheckerId, CheckerSrc, lower_checker_decl}, + checker::{CheckerDef, CheckerId, CheckerSrc, LowerChecker}, covergroup::{ CovergroupDef, CovergroupId, CovergroupSrc, CoverpointDef, CoverpointSrc, CrossDef, CrossSrc, lower_covergroup_decl, lower_coverpoint, lower_cross, @@ -375,15 +375,7 @@ impl LowerFileCtx<'_> { } UdpDeclaration(udp_decl) => self.lower_udp_decl(udp_decl).into(), ConfigDeclaration(config_decl) => self.lower_config_decl(config_decl).into(), - CheckerDeclaration(checker_decl) => { - let checker = lower_checker_decl(checker_decl); - alloc_idx_and_src! { - self.file_id; - checker => self.file.checkers, - checker_decl => self.file_source_map.checker_srcs, - } - .into() - } + CheckerDeclaration(checker_decl) => self.lower_checker_decl(checker_decl).into(), CovergroupDeclaration(covergroup_decl) => { self.lower_covergroup_decl(covergroup_decl).into() } diff --git a/crates/hir/src/hir_def/module.rs b/crates/hir/src/hir_def/module.rs index 54939053..70e65236 100644 --- a/crates/hir/src/hir_def/module.rs +++ b/crates/hir/src/hir_def/module.rs @@ -34,7 +34,7 @@ use super::{ aggregate::{StructDef, StructId, StructSrc, lower_struct_def}, alloc_idx_and_src, block::{BlockInfo, BlockSrc, LocalBlockId}, - checker::{CheckerDef, CheckerId, CheckerSrc, lower_checker_decl}, + checker::{CheckerDef, CheckerId, CheckerSrc, LowerChecker}, covergroup::{ CovergroupDef, CovergroupId, CovergroupSrc, CoverpointDef, CoverpointSrc, CrossDef, CrossSrc, lower_covergroup_decl, lower_coverpoint, lower_cross, @@ -693,15 +693,7 @@ impl LowerModuleCtx<'_> { | ClassMethodPrototype(_) => continue, // Checker - CheckerDeclaration(checker_decl) => { - let checker = lower_checker_decl(checker_decl); - alloc_idx_and_src! { - self.file_id; - checker => self.module.checkers, - checker_decl => self.module_source_map.checker_srcs, - } - .into() - } + CheckerDeclaration(checker_decl) => self.lower_checker_decl(checker_decl).into(), CheckerDataDeclaration(_) => continue, // Constraints diff --git a/crates/hir/src/hir_def/subroutine.rs b/crates/hir/src/hir_def/subroutine.rs index f69322fe..a0626638 100644 --- a/crates/hir/src/hir_def/subroutine.rs +++ b/crates/hir/src/hir_def/subroutine.rs @@ -352,7 +352,11 @@ pub(crate) fn subroutine_with_source_map_query( let source_map = subroutine.source_map.clone(); (Arc::new(subroutine), Arc::new(source_map)) } - ScopeId::Block(_) | ScopeId::Subroutine(_) | ScopeId::ClockingBlock(_) => { + ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => { unreachable!("subroutines are lowered only in file, module, or generate-block scopes") } } diff --git a/crates/hir/src/scope.rs b/crates/hir/src/scope.rs index 463a6728..7e039e07 100644 --- a/crates/hir/src/scope.rs +++ b/crates/hir/src/scope.rs @@ -5,12 +5,14 @@ use triomphe::Arc; use utils::get::{Get, GetRef}; use crate::{ - container::{InContainer, InFile, InModule, InSubroutine, ScopeId}, + container::{InContainer, InFile, InFileOrModule, InModule, InSubroutine, ScopeId}, db::HirDb, file::HirFileId, hir_def::{ PackageImport, block::BlockInfo, + checker::{CheckerDef, CheckerId, CheckerPortId}, + covergroup::{CovergroupDef, CovergroupId}, declaration::DeclarationId, expr::declarator::{DeclId, Declarator, DeclaratorParent}, lower_ident_opt, @@ -308,6 +310,102 @@ impl NameScope { Arc::new(scope) } + pub fn checker_scope_query( + db: &dyn HirDb, + checker_id: InContainer, + ) -> Arc { + let mut scope = NameScope::default(); + let Some(checker) = checker_def(db, checker_id) else { + return Arc::new(scope); + }; + let Ok(checker_scope_id) = InFileOrModule::try_from(checker_id) else { + return Arc::new(scope); + }; + let checker_scope = ScopeId::Checker(checker_scope_id); + + for (idx, port) in checker.ports.iter().enumerate() { + scope.insert_value( + &port.name, + def_id(db, InContainer::new(checker_scope, CheckerPortId(idx as u32))), + ); + } + + let container = checker_id.cont_id.to_container(db); + for declaration_id in &checker.declarations { + let declaration = container.get(*declaration_id); + for decl_id in declaration.decls() { + let decl = container.get(decl_id); + scope.insert_value_opt( + &decl.name, + def_id(db, InContainer::new(checker_id.cont_id, decl_id)), + ); + } + } + + Arc::new(scope) + } + + pub fn covergroup_scope_query( + db: &dyn HirDb, + covergroup_id: InContainer, + ) -> Arc { + let mut scope = NameScope::default(); + let Some(covergroup) = covergroup_def(db, covergroup_id) else { + return Arc::new(scope); + }; + let Ok(covergroup_scope_id) = InFileOrModule::try_from(covergroup_id) else { + return Arc::new(scope); + }; + let covergroup_scope = ScopeId::Covergroup(covergroup_scope_id); + + match covergroup_id.cont_id { + ScopeId::File(file_id) => { + let file = db.hir_file(file_id); + for coverpoint_id in &covergroup.coverpoints { + let coverpoint = file.get(*coverpoint_id); + scope.insert_value_opt( + &coverpoint.name, + def_id(db, InContainer::new(covergroup_scope, *coverpoint_id)), + ); + } + + for cross_id in &covergroup.crosses { + let cross = file.get(*cross_id); + scope.insert_value_opt( + &cross.name, + def_id(db, InContainer::new(covergroup_scope, *cross_id)), + ); + } + } + ScopeId::Module(module_id) => { + let module = db.module(module_id); + for coverpoint_id in &covergroup.coverpoints { + let coverpoint = module.get(*coverpoint_id); + scope.insert_value_opt( + &coverpoint.name, + def_id(db, InContainer::new(covergroup_scope, *coverpoint_id)), + ); + } + + for cross_id in &covergroup.crosses { + let cross = module.get(*cross_id); + scope.insert_value_opt( + &cross.name, + def_id(db, InContainer::new(covergroup_scope, *cross_id)), + ); + } + } + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => {} + } + + Arc::new(scope) + } + pub fn generate_block_scope_query( db: &dyn HirDb, generate_block_id: GenerateBlockId, @@ -423,6 +521,35 @@ impl NameScope { } } +fn checker_def(db: &dyn HirDb, checker_id: InContainer) -> Option { + match checker_id.cont_id { + ScopeId::File(file_id) => Some(db.hir_file(file_id).get(checker_id.value).clone()), + ScopeId::Module(module_id) => Some(db.module(module_id).get(checker_id.value).clone()), + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, + } +} + +fn covergroup_def( + db: &dyn HirDb, + covergroup_id: InContainer, +) -> Option { + match covergroup_id.cont_id { + ScopeId::File(file_id) => Some(db.hir_file(file_id).get(covergroup_id.value).clone()), + ScopeId::Module(module_id) => Some(db.module(module_id).get(covergroup_id.value).clone()), + ScopeId::GenerateBlock(_) + | ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, + } +} + struct PackageExportSignatureBuilder<'a> { db: &'a dyn HirDb, package_id: PackageId, @@ -576,7 +703,7 @@ mod tests { }, source_root::{SourceRoot, SourceRootId}, }, - container::ScopeId, + container::{InContainer, ScopeId}, db::{HirDb, HirDbStorage, InternDbStorage}, hir_def::Ident, semantics::pathres::resolve_name, @@ -796,6 +923,7 @@ endmodule let db = db_with_root_text( r#" checker c(input logic clk); + logic sig; endchecker module m; @@ -809,6 +937,26 @@ endmodule .lookup(NameContext::Type, &ident("c")) .expect("checker should be visible as a type"); assert!(checker_defs.iter().any(|def_id| def_id.kind(&db) == DefKind::Checker)); + let checker_id = checker_defs + .iter() + .copied() + .find_map(|def_id| def_id.as_checker(&db)) + .expect("checker definition should have a concrete id"); + let checker_scope = db.checker_scope(checker_id); + assert!( + checker_scope + .lookup(NameContext::Value, &ident("clk")) + .expect("checker scope should expose checker ports") + .iter() + .any(|def_id| def_id.kind(&db) == DefKind::CheckerPort) + ); + assert!( + checker_scope + .lookup(NameContext::Value, &ident("sig")) + .expect("checker scope should expose checker declarations") + .iter() + .any(|def_id| def_id.kind(&db) == DefKind::Variable) + ); let module_id = db .unit_scope() @@ -886,6 +1034,21 @@ endmodule .any(|def_id| matches!(def_id.loc(&db), DefLoc::Cross(id) if id.value == cross_id)) ); + let covergroup_scope = + db.covergroup_scope(InContainer::new(module_id.into(), covergroup_id)); + let scoped_coverpoint_defs = covergroup_scope + .lookup(NameContext::Value, &ident("cp")) + .expect("covergroup scope should expose coverpoint label"); + assert!(scoped_coverpoint_defs.iter().any(|def_id| { + matches!(def_id.loc(&db), DefLoc::Coverpoint(id) if matches!(id.cont_id, ScopeId::Covergroup(_)) && id.value == coverpoint_id) + })); + let scoped_cross_defs = covergroup_scope + .lookup(NameContext::Value, &ident("cx")) + .expect("covergroup scope should expose cross label"); + assert!(scoped_cross_defs.iter().any(|def_id| { + matches!(def_id.loc(&db), DefLoc::Cross(id) if matches!(id.cont_id, ScopeId::Covergroup(_)) && id.value == cross_id) + })); + let instantiation = module .instantiations .values() diff --git a/crates/hir/src/semantics/hir_to_def.rs b/crates/hir/src/semantics/hir_to_def.rs index b51a13fd..5037767c 100644 --- a/crates/hir/src/semantics/hir_to_def.rs +++ b/crates/hir/src/semantics/hir_to_def.rs @@ -76,7 +76,7 @@ impl Source2DefCtx<'_, '_> { let subroutine = db.subroutine(subroutine_id.as_in_container()); resolve(subroutine.get(expr_id)) } - ScopeId::ClockingBlock(_) => None, + ScopeId::ClockingBlock(_) | ScopeId::Checker(_) | ScopeId::Covergroup(_) => None, } } @@ -143,7 +143,7 @@ impl Source2DefCtx<'_, '_> { ScopeId::Subroutine(subroutine_id) => { Some(self.db.subroutine(subroutine_id.as_in_container()).get(expr_id).clone()) } - ScopeId::ClockingBlock(_) => None, + ScopeId::ClockingBlock(_) | ScopeId::Checker(_) | ScopeId::Covergroup(_) => None, } } } diff --git a/crates/hir/src/semantics/pathres.rs b/crates/hir/src/semantics/pathres.rs index 1e0a4f8a..8ea81897 100644 --- a/crates/hir/src/semantics/pathres.rs +++ b/crates/hir/src/semantics/pathres.rs @@ -147,11 +147,21 @@ fn resolve_child_name( pub fn descend_scope(db: &dyn HirDb, def_id: DefId) -> Option { match def_id.kind(db) { - kind if kind.is_instantiable_def() => def_id.as_module(db).map(Into::into), + DefKind::Module | DefKind::Interface | DefKind::Program => { + def_id.as_module(db).map(Into::into) + } DefKind::ClockingBlock => def_id.as_clocking_block(db).map(Into::into), + DefKind::Checker => { + def_id.as_checker(db).and_then(|checker| checker.try_into().ok()).map(ScopeId::Checker) + } + DefKind::Covergroup => def_id + .as_covergroup(db) + .and_then(|covergroup| covergroup.try_into().ok()) + .map(ScopeId::Covergroup), DefKind::Instance => { let instance = def_id.as_instance(db)?; - instance_target_module_id(db, instance.module_id, instance.value).map(Into::into) + let target = instance_target_def_id(db, instance.module_id, instance.value)?; + descend_scope(db, target) } DefKind::Block => def_id.as_block(db).map(Into::into), DefKind::GenerateBlock => def_id.as_generate_block(db).map(Into::into), @@ -159,16 +169,20 @@ pub fn descend_scope(db: &dyn HirDb, def_id: DefId) -> Option { } } -pub(crate) fn instance_target_module_id( +pub(crate) fn instance_target_def_id( db: &dyn HirDb, module_id: ModuleId, instance_id: InstanceId, -) -> Option { +) -> Option { let module = db.module(module_id); let instance = module.get(instance_id); let instantiation = module.get(instance.parent); let module_name = instantiation.module_name.as_ref()?; - db.unit_scope().module_ids(db, module_name).unique() + resolve_name(db, module_id.into(), module_name, NameContext::Type)? + .def_ids() + .iter() + .copied() + .find(|def_id| def_id.kind(db).is_instantiable_def()) } pub(crate) fn name_scope(db: &dyn HirDb, scope_id: ScopeId) -> Arc { @@ -176,6 +190,8 @@ pub(crate) fn name_scope(db: &dyn HirDb, scope_id: ScopeId) -> Arc { ScopeId::File(file_id) => db.file_scope(file_id), ScopeId::Module(module_id) => db.module_scope(module_id), ScopeId::ClockingBlock(clocking_block_id) => db.clocking_block_scope(clocking_block_id), + ScopeId::Checker(checker_id) => db.checker_scope(checker_id.as_in_container()), + ScopeId::Covergroup(covergroup_id) => db.covergroup_scope(covergroup_id.as_in_container()), ScopeId::GenerateBlock(generate_block_id) => db.generate_block_scope(generate_block_id), ScopeId::Block(block_id) => db.block_scope(block_id), ScopeId::Subroutine(subroutine_id) => db.subroutine_scope(subroutine_id.as_in_container()), @@ -528,4 +544,65 @@ endmodule DefKind::ClockingSignal ); } + + #[test] + fn resolve_path_descends_checker_instances_to_ports_and_members() { + let db = db_with_root_text( + r#" +checker c(input logic clk); + logic sig; +endchecker + +module top; + c u(); +endmodule +"#, + ); + + let top = db + .unit_scope() + .module_ids(&db, &ident("top")) + .unique() + .expect("top module should resolve uniquely"); + + assert_eq!( + resolved_kind(&db, top.into(), &["u", "clk"], NameContext::Value), + DefKind::CheckerPort + ); + assert_eq!( + resolved_kind(&db, top.into(), &["u", "sig"], NameContext::Value), + DefKind::Variable + ); + } + + #[test] + fn resolve_path_descends_covergroup_instances_to_coverage_items() { + let db = db_with_root_text( + r#" +module top(input clk, input a); + covergroup cg @(posedge clk); + cp: coverpoint a; + cx: cross cp; + endgroup + + cg u(); +endmodule +"#, + ); + + let top = db + .unit_scope() + .module_ids(&db, &ident("top")) + .unique() + .expect("top module should resolve uniquely"); + + assert_eq!( + resolved_kind(&db, top.into(), &["u", "cp"], NameContext::Value), + DefKind::Coverpoint + ); + assert_eq!( + resolved_kind(&db, top.into(), &["u", "cx"], NameContext::Value), + DefKind::Cross + ); + } } diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 7177513b..b7c06954 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -99,7 +99,9 @@ impl Source2DefCtx<'_, '_> { let local_block_id = *subroutine_src_map.block_srcs.get(&block_src)?; subroutine.stmts.get(local_block_id).block_id } - ScopeId::ClockingBlock(_) => return None, + ScopeId::ClockingBlock(_) | ScopeId::Checker(_) | ScopeId::Covergroup(_) => { + return None; + } }; Some(block_id) @@ -181,7 +183,11 @@ impl Source2DefCtx<'_, '_> { let (_, source_map) = self.db.generate_block_with_source_map(generate_block_id); source_map.get(src) } - ScopeId::Block(_) | ScopeId::Subroutine(_) | ScopeId::ClockingBlock(_) => None, + ScopeId::Block(_) + | ScopeId::Subroutine(_) + | ScopeId::ClockingBlock(_) + | ScopeId::Checker(_) + | ScopeId::Covergroup(_) => None, } } diff --git a/crates/hir/src/symbol.rs b/crates/hir/src/symbol.rs index 12a4d577..05f833be 100644 --- a/crates/hir/src/symbol.rs +++ b/crates/hir/src/symbol.rs @@ -9,7 +9,7 @@ use crate::{ hir_def::{ Ident, block::BlockId, - checker::CheckerId, + checker::{CheckerId, CheckerPortId}, covergroup::{CovergroupId, CoverpointId, CrossId}, expr::declarator::DeclId, file::{config::ConfigDeclId, library::LibraryDeclId, udp::UdpDeclId}, @@ -50,6 +50,7 @@ pub enum DefLoc { ClockingBlock(InModule), ClockingSignal(InContainer), Checker(InContainer), + CheckerPort(InContainer), Covergroup(InContainer), Coverpoint(InContainer), Cross(InContainer), @@ -73,6 +74,7 @@ impl_from! { DefLoc => ClockingBlock(InModule), ClockingSignal(InContainer), Checker(InContainer), + CheckerPort(InContainer), Covergroup(InContainer), Coverpoint(InContainer), Cross(InContainer), @@ -200,6 +202,13 @@ impl DefId { } } + pub fn as_checker_port(self, db: &dyn InternDb) -> Option> { + match self.loc(db) { + DefLoc::CheckerPort(id) => Some(id), + _ => None, + } + } + pub fn as_covergroup(self, db: &dyn InternDb) -> Option> { match self.loc(db) { DefLoc::Covergroup(id) => Some(id), @@ -242,6 +251,7 @@ pub enum DefKind { ClockingBlock, ClockingSignal, Checker, + CheckerPort, Covergroup, Coverpoint, Cross, @@ -273,6 +283,7 @@ impl DefKind { DefKind::Subroutine => SymbolKind::Fn, DefKind::NonAnsiPort => SymbolKind::NonAnsiPortLabel, DefKind::SubroutinePort | DefKind::Port => SymbolKind::PortDecl, + DefKind::CheckerPort => SymbolKind::PortDecl, DefKind::Typedef => SymbolKind::Typedef, DefKind::Net => SymbolKind::NetDecl, DefKind::Variable => SymbolKind::DataDecl, diff --git a/crates/hir/src/type_infer.rs b/crates/hir/src/type_infer.rs index 8491f72a..956ac1d2 100644 --- a/crates/hir/src/type_infer.rs +++ b/crates/hir/src/type_infer.rs @@ -20,8 +20,8 @@ use crate::{ subroutine::SubroutinePortId, typedef::TypedefId, }, - semantics::pathres::{PathResolution, instance_target_module_id, resolve_name}, - symbol::{DefId, DefKind, DefLoc, NameContext}, + semantics::pathres::{PathResolution, instance_target_def_id, resolve_name}, + symbol::{DefId, DefKind, DefLoc, NameContext, NameScope}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -45,6 +45,8 @@ pub enum Ty { Chandle, Alias { typedef: InContainer, target: Box }, Module(ModuleId), + Checker(DefId), + Covergroup(DefId), VirtualInterface { def: DefId, modport: Option }, GenerateBlock(GenerateBlockId), Block(crate::hir_def::block::BlockId), @@ -159,7 +161,10 @@ fn type_of_def_id(db: &dyn HirDb, def_id: DefId) -> TyResult { .map(|module_id| TyResult::new(Ty::Module(module_id))) .unwrap_or_else(|| TyResult::new(Ty::Unknown)), DefKind::Interface => TyResult::new(Ty::VirtualInterface { def: def_id, modport: None }), + DefKind::Checker => TyResult::new(Ty::Checker(def_id)), + DefKind::Covergroup => TyResult::new(Ty::Covergroup(def_id)), DefKind::Port + | DefKind::CheckerPort | DefKind::Variable | DefKind::Net | DefKind::Param @@ -178,17 +183,18 @@ fn type_of_def_id(db: &dyn HirDb, def_id: DefId) -> TyResult { .unwrap_or_else(|| TyResult::new(Ty::Unknown)), DefKind::Instance => def_id .as_instance(db) - .and_then(|instance| instance_target_module_id(db, instance.module_id, instance.value)) - .map(|module_id| { - let module_kind = db.hir_file(module_id.file_id).get(module_id.value).kind; - if module_kind == ModuleKind::Interface { - TyResult::new(Ty::VirtualInterface { - def: DefId::new(db, module_id), - modport: None, - }) - } else { - TyResult::new(Ty::Module(module_id)) + .and_then(|instance| instance_target_def_id(db, instance.module_id, instance.value)) + .map(|target| match target.kind(db) { + DefKind::Interface => { + TyResult::new(Ty::VirtualInterface { def: target, modport: None }) } + DefKind::Module | DefKind::Program => target + .as_module(db) + .map(|module_id| TyResult::new(Ty::Module(module_id))) + .unwrap_or_else(|| TyResult::new(Ty::Unknown)), + DefKind::Checker => TyResult::new(Ty::Checker(target)), + DefKind::Covergroup => TyResult::new(Ty::Covergroup(target)), + _ => TyResult::new(Ty::Unknown), }) .unwrap_or_else(|| TyResult::new(Ty::Unknown)), DefKind::Modport => def_id @@ -215,8 +221,6 @@ fn type_of_def_id(db: &dyn HirDb, def_id: DefId) -> TyResult { | DefKind::NonAnsiPort | DefKind::ClockingBlock | DefKind::ClockingSignal - | DefKind::Checker - | DefKind::Covergroup | DefKind::Coverpoint | DefKind::Cross | DefKind::Stmt => TyResult::new(Ty::Unknown), @@ -256,6 +260,8 @@ pub fn members_of_ty(db: &dyn HirDb, ty: &Ty) -> Vec { Ty::Struct(struct_id) => struct_members(db, *struct_id), Ty::Union(def_id) => union_members(db, *def_id), Ty::Module(module_id) => module_members(db, *module_id), + Ty::Checker(def_id) => checker_members(db, *def_id), + Ty::Covergroup(def_id) => covergroup_members(db, *def_id), Ty::VirtualInterface { def, .. } => { def.as_module(db).map(|module_id| module_members(db, module_id)).unwrap_or_default() } @@ -303,6 +309,8 @@ pub fn type_class(db: &dyn HirDb, ty: &Ty) -> Option { | Ty::Event | Ty::Chandle | Ty::Module(_) + | Ty::Checker(_) + | Ty::Covergroup(_) | Ty::VirtualInterface { .. } | Ty::GenerateBlock(_) | Ty::Block(_) => None, @@ -376,6 +384,8 @@ pub fn packed_bit_width(db: &dyn HirDb, ty: &Ty) -> Option { | Ty::Event | Ty::Chandle | Ty::Module(_) + | Ty::Checker(_) + | Ty::Covergroup(_) | Ty::VirtualInterface { .. } | Ty::GenerateBlock(_) | Ty::Block(_) => None, @@ -596,23 +606,30 @@ fn module_members(db: &dyn HirDb, module_id: ModuleId) -> Vec { members } +fn checker_members(db: &dyn HirDb, def_id: DefId) -> Vec { + let Some(checker_id) = def_id.as_checker(db) else { + return Vec::new(); + }; + scope_members(db, &db.checker_scope(checker_id)) +} + +fn covergroup_members(db: &dyn HirDb, def_id: DefId) -> Vec { + let Some(covergroup_id) = def_id.as_covergroup(db) else { + return Vec::new(); + }; + scope_members(db, &db.covergroup_scope(covergroup_id)) +} + fn generate_block_members(db: &dyn HirDb, generate_block_id: GenerateBlockId) -> Vec { - let mut members: Vec<_> = db - .generate_block_scope(generate_block_id) - .iter_listing() - .filter_map(|(name, defs)| { - let origin = PathResolution::from_def_ids(defs)?; - let ty = type_of_path_resolution_impl(db, origin.clone()).ty; - Some(TyMember { name: name.clone(), ty, origin: Some(origin) }) - }) - .collect(); - sort_members(&mut members); - members + scope_members(db, &db.generate_block_scope(generate_block_id)) } fn block_members(db: &dyn HirDb, block_id: crate::hir_def::block::BlockId) -> Vec { - let mut members: Vec<_> = db - .block_scope(block_id) + scope_members(db, &db.block_scope(block_id)) +} + +fn scope_members(db: &dyn HirDb, scope: &NameScope) -> Vec { + let mut members: Vec<_> = scope .iter_listing() .filter_map(|(name, defs)| { let origin = PathResolution::from_def_ids(defs)?; @@ -739,7 +756,7 @@ fn expr_of(db: &dyn HirDb, expr: InContainer) -> Option { ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(expr.value).clone()) } - ScopeId::ClockingBlock(_) => None, + ScopeId::ClockingBlock(_) | ScopeId::Checker(_) | ScopeId::Covergroup(_) => None, } } @@ -757,7 +774,7 @@ fn decl_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(decl.value).clone()) } - ScopeId::ClockingBlock(_) => None, + ScopeId::ClockingBlock(_) | ScopeId::Checker(_) | ScopeId::Covergroup(_) => None, } } @@ -775,7 +792,7 @@ fn declaration_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(decl.value).clone()) } - ScopeId::ClockingBlock(_) => None, + ScopeId::ClockingBlock(_) | ScopeId::Checker(_) | ScopeId::Covergroup(_) => None, } } @@ -793,7 +810,7 @@ fn typedef_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(typedef.value).clone()) } - ScopeId::ClockingBlock(_) => None, + ScopeId::ClockingBlock(_) | ScopeId::Checker(_) | ScopeId::Covergroup(_) => None, } } @@ -811,7 +828,7 @@ fn struct_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(struct_id.value).clone()) } - ScopeId::ClockingBlock(_) => None, + ScopeId::ClockingBlock(_) | ScopeId::Checker(_) | ScopeId::Covergroup(_) => None, } } @@ -829,7 +846,7 @@ fn stmt_of( ScopeId::Subroutine(subroutine_id) => { Some(db.subroutine(subroutine_id.as_in_container()).get(stmt.value).clone()) } - ScopeId::ClockingBlock(_) => None, + ScopeId::ClockingBlock(_) | ScopeId::Checker(_) | ScopeId::Covergroup(_) => None, } } From c803e8caab31d79655ca5eb1acb2bf136a07e801 Mon Sep 17 00:00:00 2001 From: hongjr03 Date: Sat, 27 Jun 2026 15:58:38 +0800 Subject: [PATCH 8/8] fix(ide): handle construct member scopes --- crates/ide/src/completion/engine/expr.rs | 18 ++++++++++++++++++ crates/ide/src/references/search.rs | 24 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/crates/ide/src/completion/engine/expr.rs b/crates/ide/src/completion/engine/expr.rs index 72cc5f47..8a460a4b 100644 --- a/crates/ide/src/completion/engine/expr.rs +++ b/crates/ide/src/completion/engine/expr.rs @@ -113,6 +113,24 @@ fn collect_container_names( match container_id { ScopeId::File(file_id) => collect_file_names(db, file_id, names), ScopeId::Module(module_id) => collect_module_names(db, module_id, names), + ScopeId::ClockingBlock(clocking_block_id) => { + let scope = db.clocking_block_scope(clocking_block_id); + for (ident, defs) in scope.iter_listing() { + collect_def_names(db, ident, defs, names); + } + } + ScopeId::Checker(checker_id) => { + let scope = db.checker_scope(checker_id.as_in_container()); + for (ident, defs) in scope.iter_listing() { + collect_def_names(db, ident, defs, names); + } + } + ScopeId::Covergroup(covergroup_id) => { + let scope = db.covergroup_scope(covergroup_id.as_in_container()); + for (ident, defs) in scope.iter_listing() { + collect_def_names(db, ident, defs, names); + } + } ScopeId::GenerateBlock(generate_block_id) => { let scope = db.generate_block_scope(generate_block_id); for (ident, defs) in scope.iter_listing() { diff --git a/crates/ide/src/references/search.rs b/crates/ide/src/references/search.rs index e5e90790..5909db65 100644 --- a/crates/ide/src/references/search.rs +++ b/crates/ide/src/references/search.rs @@ -100,6 +100,30 @@ impl SearchScope { Self::all(db) } } + ScopeId::ClockingBlock(clocking_block_id) => { + let def_id = DefId::new(db, clocking_block_id); + if let Some(InFile { file_id, value: range }) = def_id.range(db) { + Self::single_range(file_id.file_id(), range) + } else { + Self::all(db) + } + } + ScopeId::Checker(checker_id) => { + let def_id = DefId::new(db, checker_id.as_in_container()); + if let Some(InFile { file_id, value: range }) = def_id.range(db) { + Self::single_range(file_id.file_id(), range) + } else { + Self::all(db) + } + } + ScopeId::Covergroup(covergroup_id) => { + let def_id = DefId::new(db, covergroup_id.as_in_container()); + if let Some(InFile { file_id, value: range }) = def_id.range(db) { + Self::single_range(file_id.file_id(), range) + } else { + Self::all(db) + } + } } }