From 6044e19e7eb4ff978539b579b3bf5d522c105b60 Mon Sep 17 00:00:00 2001 From: Andrew Hundt Date: Wed, 1 Jul 2026 10:39:06 -0400 Subject: [PATCH] fix(extract): restore HDL call edges Add a shared Verilog/SystemVerilog leaf-identifier resolver for wrapped subroutine and tf_call nodes, and include SystemVerilog subroutine_call nodes exposed by the vendored grammar. Evidence: AST probes confirmed the wrapper shapes, failed-first canaries produced 252 passed / 2 failed before the fix, and the focused extraction suite now passes 254/254. Signed-off-by: Andrew Hundt --- internal/cbm/extract_calls.c | 31 +++++++++++++++++++++++++++++++ internal/cbm/lang_specs.c | 2 +- tests/test_extraction.c | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/internal/cbm/extract_calls.c b/internal/cbm/extract_calls.c index caa41dc43..72dd1494f 100644 --- a/internal/cbm/extract_calls.c +++ b/internal/cbm/extract_calls.c @@ -834,6 +834,33 @@ static char *extract_vhdl_callee(CBMArena *a, TSNode node, const char *source, c return NULL; } +static char *extract_first_leaf_identifier(CBMArena *a, TSNode node, const char *source) { + TSNode cur = node; + enum { CALLEE_LEAF_DESCENT_LIMIT = 8 }; + for (int depth = 0; depth < CALLEE_LEAF_DESCENT_LIMIT && !ts_node_is_null(cur); depth++) { + const char *k = ts_node_type(cur); + if (strcmp(k, "simple_identifier") == 0 || strcmp(k, "identifier") == 0 || + strcmp(k, "word") == 0 || strcmp(k, "name") == 0 || strcmp(k, "qid") == 0) { + char *t = cbm_node_text(a, cur, source); + return (t && t[0]) ? t : NULL; + } + if (ts_node_named_child_count(cur) == 0) { + return NULL; + } + cur = ts_node_named_child(cur, 0); + } + return NULL; +} + +// HDL callees are often wrapped by subroutine/tf/hierarchical identifier nodes. +static char *extract_hdl_callee(CBMArena *a, TSNode node, const char *source, const char *nk) { + if (strcmp(nk, "function_subroutine_call") != 0 && strcmp(nk, "subroutine_call") != 0 && + strcmp(nk, "tf_call") != 0 && strcmp(nk, "system_tf_call") != 0) { + return NULL; + } + return extract_first_leaf_identifier(a, node, source); +} + // Make builtins are represented as function_call nodes; $(shell ...) is shell_function. static char *extract_make_callee(CBMArena *a, TSNode node, const char *source, const char *nk) { if (strcmp(nk, "shell_function") == 0) { @@ -943,6 +970,10 @@ static char *extract_callee_lang_specific(CBMArena *a, TSNode node, const char * char *c = extract_vhdl_callee(a, node, source, nk); return c ? c : extract_scripting_callee(a, node, source, lang, nk); } + if (lang == CBM_LANG_VERILOG || lang == CBM_LANG_SYSTEMVERILOG) { + char *c = extract_hdl_callee(a, node, source, nk); + return c ? c : extract_scripting_callee(a, node, source, lang, nk); + } if (lang == CBM_LANG_CLOJURE || lang == CBM_LANG_COMMONLISP || lang == CBM_LANG_SCHEME || lang == CBM_LANG_FENNEL || lang == CBM_LANG_RACKET || lang == CBM_LANG_EMACSLISP) { diff --git a/internal/cbm/lang_specs.c b/internal/cbm/lang_specs.c index 28c3d5c1e..120618816 100644 --- a/internal/cbm/lang_specs.c +++ b/internal/cbm/lang_specs.c @@ -1384,7 +1384,7 @@ static const char *systemverilog_class_types[] = {"class_declaration", "type_declaration", NULL}; static const char *systemverilog_call_types[] = {"function_subroutine_call", "system_tf_call", - "method_call", NULL}; + "subroutine_call", "method_call", NULL}; static const char *systemverilog_import_types[] = { "package_import_declaration", "extends", "import", "include", "include_statement", "instance", "use_clause", NULL}; diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 4e6fd809e..d71639a52 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -1260,6 +1260,40 @@ TEST(vhdl_function_call_edge) { PASS(); } +TEST(verilog_function_call_edge) { + CBMFileResult *r = extract("module m;\n" + " function integer foo;\n" + " input integer x;\n" + " begin foo = x; end\n" + " endfunction\n" + " integer y;\n" + " initial begin\n" + " y = foo(1);\n" + " end\n" + "endmodule\n", + CBM_LANG_VERILOG, "t", "mod.v"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_call_exact(r, "foo")); + cbm_free_result(r); + PASS(); +} + +TEST(systemverilog_subroutine_call_edge) { + CBMFileResult *r = extract("module m;\n" + " function int foo(); return 1; endfunction\n" + " initial begin\n" + " foo();\n" + " end\n" + "endmodule\n", + CBM_LANG_SYSTEMVERILOG, "t", "mod.sv"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_call_exact(r, "foo")); + cbm_free_result(r); + PASS(); +} + /* --- Fortran --- */ TEST(fortran_function) { /* Fortran subroutine name extraction is incomplete — just verify no crash */ @@ -3456,6 +3490,8 @@ SUITE(extraction) { RUN_TEST(nickel_function_application_edge); RUN_TEST(func_function_application_edge); RUN_TEST(vhdl_function_call_edge); + RUN_TEST(verilog_function_call_edge); + RUN_TEST(systemverilog_subroutine_call_edge); RUN_TEST(fortran_function); /* OOP/Systems variants */