diff --git a/internal/cbm/extract_calls.c b/internal/cbm/extract_calls.c index 98b924b8..00a3e6ed 100644 --- a/internal/cbm/extract_calls.c +++ b/internal/cbm/extract_calls.c @@ -740,6 +740,23 @@ static char *gotemplate_callee(CBMArena *a, TSNode node, const char *source) { return NULL; } +static const char *extract_nth_string_arg(CBMExtractCtx *ctx, TSNode args, uint32_t n) { + uint32_t nc = ts_node_named_child_count(args); + uint32_t found = 0; + for (uint32_t ai = 0; ai < nc && ai < MAX_POSITIONAL_SCAN + n; ai++) { + TSNode arg = ts_node_named_child(args, ai); + const char *ak = ts_node_type(arg); + if (is_string_like(ak)) { + if (found == n) { + char *text = cbm_node_text(ctx->arena, arg, ctx->source); + return strip_and_validate_string_arg(ctx->arena, text); + } + found++; + } + } + return NULL; +} + // Walk AST for call nodes (iterative) static void walk_calls(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) { TSNodeStack stack; @@ -1145,6 +1162,28 @@ void handle_calls(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, Walk } cbm_calls_push(&ctx->result->calls, ctx->arena, call); + + if (ctx->language == CBM_LANG_PYTHON && !ts_node_is_null(args)) { + const char *cn = call.callee_name; + size_t len = cn ? strlen(cn) : 0; + static const char *iris_dispatch[] = {".classMethodValue", ".classMethodVoid", + ".classMethodBoolean", ".classMethodObject", + NULL}; + for (const char **nm = iris_dispatch; *nm; nm++) { + size_t nlen = strlen(*nm); + if (len >= nlen && strcmp(cn + len - nlen, *nm) == 0) { + const char *cls = extract_nth_string_arg(ctx, args, 0); + const char *mth = extract_nth_string_arg(ctx, args, 1); + if (cls && mth) { + CBMCall xcall = {0}; + xcall.callee_name = cbm_arena_sprintf(ctx->arena, "%s.%s", cls, mth); + xcall.enclosing_func_qn = call.enclosing_func_qn; + cbm_calls_push(&ctx->result->calls, ctx->arena, xcall); + } + break; + } + } + } } } diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 0308372c..c09c2264 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -2019,6 +2019,20 @@ TEST(python_calls) { PASS(); } +TEST(python_iris_classMethodValue) { + CBMFileResult *r = extract( + "import iris\n" + "iris_obj = iris.cls('%Library.ObjectScript')\n" + "def call_bfs(n):\n" + " return iris_obj.classMethodValue('Graph.KG.TraversalBFS', 'BFSFastJson', n)\n", + CBM_LANG_PYTHON, "t", "store.py"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_call(r, "Graph.KG.TraversalBFS.BFSFastJson")); + cbm_free_result(r); + PASS(); +} + TEST(go_calls) { CBMFileResult *r = extract("package main\nimport \"fmt\"\nfunc main() { fmt.Println(\"hello\") }\n", @@ -2986,6 +3000,7 @@ SUITE(extraction) { /* Cross-cutting */ RUN_TEST(python_calls); + RUN_TEST(python_iris_classMethodValue); RUN_TEST(go_calls); RUN_TEST(python_imports); RUN_TEST(js_imports);