mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-07 08:43:35 +08:00
Merge pull request #447 from isc-tdyar/fix/string-dispatch-dead-code
fix(extract): string-dispatch CALLS edges never emitted — detection was in dead code
This commit is contained in:
@@ -584,7 +584,6 @@ void cbm_channels_push(CBMChannelArray *arr, CBMArena *a, CBMChannel ch);
|
||||
// --- Sub-extractor entry points ---
|
||||
|
||||
void cbm_extract_definitions(CBMExtractCtx *ctx);
|
||||
void cbm_extract_calls(CBMExtractCtx *ctx);
|
||||
void cbm_extract_imports(CBMExtractCtx *ctx);
|
||||
void cbm_extract_usages(CBMExtractCtx *ctx);
|
||||
void cbm_extract_semantic(CBMExtractCtx *ctx);
|
||||
|
||||
@@ -58,10 +58,25 @@ static const char *strip_quotes(CBMArena *a, const char *text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Callee suffixes for IRIS Python interop string-dispatch. Kept at file scope
|
||||
// (not inside the function) to satisfy cppcheck variableScope.
|
||||
static const char *s_py_dispatch_suffixes[] = {".classMethodValue", ".classMethodVoid",
|
||||
".classMethodBoolean", ".classMethodObject", NULL};
|
||||
|
||||
// Per-language callee-suffix dispatch table — returns a NULL-terminated list of
|
||||
// method-name suffixes whose calls should be resolved by extracting class+method
|
||||
// from the first two string arguments (e.g. IRIS Python interop). Kept here
|
||||
// rather than in CBMLangSpec to avoid -Wmissing-field-initializers across 155
|
||||
// language rows.
|
||||
const char **cbm_string_dispatch_suffixes(CBMLanguage lang) {
|
||||
if (lang == CBM_LANG_PYTHON) {
|
||||
return s_py_dispatch_suffixes;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Forward declarations
|
||||
static void walk_calls(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec);
|
||||
static char *extract_callee_name(CBMArena *a, TSNode node, const char *source, CBMLanguage lang);
|
||||
static void extract_jsx_refs(CBMExtractCtx *ctx, TSNode node);
|
||||
static char *gotemplate_callee(CBMArena *a, TSNode node, const char *source);
|
||||
|
||||
// Lean 4: check if an apply node is inside a type annotation.
|
||||
@@ -735,20 +750,6 @@ static const char *strip_and_validate_string_arg(CBMArena *a, char *text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Extract first string argument from a call's arguments node.
|
||||
static const char *extract_first_string_arg(CBMExtractCtx *ctx, TSNode args) {
|
||||
uint32_t nc = ts_node_named_child_count(args);
|
||||
for (uint32_t ai = 0; ai < nc && ai < MAX_POSITIONAL_SCAN; ai++) {
|
||||
TSNode arg = ts_node_named_child(args, ai);
|
||||
const char *ak = ts_node_type(arg);
|
||||
if (is_string_like(ak)) {
|
||||
char *text = cbm_node_text(ctx->arena, arg, ctx->source);
|
||||
return strip_and_validate_string_arg(ctx->arena, text);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Return the (dequoted) first string-literal child of a node, or NULL.
|
||||
static char *gotemplate_string_child(CBMArena *a, TSNode parent, const char *source) {
|
||||
TSNode s = cbm_find_child_by_kind(parent, "interpreted_string_literal");
|
||||
@@ -791,72 +792,21 @@ static char *gotemplate_callee(CBMArena *a, TSNode node, const char *source) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Walk AST for call nodes (iterative)
|
||||
static void walk_calls(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
|
||||
TSNodeStack stack;
|
||||
ts_nstack_init(&stack, ctx->arena, CBM_SZ_512);
|
||||
ts_nstack_push(&stack, ctx->arena, root);
|
||||
|
||||
while (stack.count > 0) {
|
||||
TSNode node = ts_nstack_pop(&stack);
|
||||
const char *kind = ts_node_type(node);
|
||||
|
||||
if (cbm_kind_in_set(node, spec->call_node_types)) {
|
||||
char *callee = extract_callee_name(ctx->arena, node, ctx->source, ctx->language);
|
||||
if (callee && callee[0] && !cbm_is_keyword(callee, ctx->language)) {
|
||||
CBMCall call = {0};
|
||||
call.callee_name = callee;
|
||||
call.enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, node);
|
||||
|
||||
TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments"));
|
||||
if (!ts_node_is_null(args)) {
|
||||
call.first_string_arg = extract_first_string_arg(ctx, args);
|
||||
}
|
||||
cbm_calls_push(&ctx->result->calls, ctx->arena, call);
|
||||
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++;
|
||||
}
|
||||
|
||||
if (ctx->language == CBM_LANG_TSX || ctx->language == CBM_LANG_JAVASCRIPT) {
|
||||
if (strcmp(kind, "jsx_self_closing_element") == 0 ||
|
||||
strcmp(kind, "jsx_opening_element") == 0) {
|
||||
extract_jsx_refs(ctx, node);
|
||||
}
|
||||
}
|
||||
|
||||
ts_nstack_push_children(&stack, ctx->arena, node);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract JSX component references (uppercase = component, lowercase = HTML)
|
||||
static void extract_jsx_refs(CBMExtractCtx *ctx, TSNode node) {
|
||||
TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("name"));
|
||||
if (ts_node_is_null(name_node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
char *name = cbm_node_text(ctx->arena, name_node, ctx->source);
|
||||
if (!name || !name[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only uppercase names are components
|
||||
if (name[0] < 'A' || name[0] > 'Z') {
|
||||
return;
|
||||
}
|
||||
|
||||
CBMCall call = {0};
|
||||
call.callee_name = name;
|
||||
call.enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, node);
|
||||
cbm_calls_push(&ctx->result->calls, ctx->arena, call);
|
||||
}
|
||||
|
||||
void cbm_extract_calls(CBMExtractCtx *ctx) {
|
||||
const CBMLangSpec *spec = cbm_lang_spec(ctx->language);
|
||||
if (!spec || !spec->call_node_types || !spec->call_node_types[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
walk_calls(ctx, ctx->root, spec);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// --- Unified handler: called once per node by the cursor walk ---
|
||||
@@ -1204,6 +1154,26 @@ void handle_calls(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, Walk
|
||||
}
|
||||
|
||||
cbm_calls_push(&ctx->result->calls, ctx->arena, call);
|
||||
|
||||
const char **dispatch_suffixes = cbm_string_dispatch_suffixes(ctx->language);
|
||||
if (dispatch_suffixes && !ts_node_is_null(args)) {
|
||||
const char *cn = call.callee_name;
|
||||
size_t len = cn ? strlen(cn) : 0;
|
||||
for (const char **nm = dispatch_suffixes; *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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,13 @@ typedef struct {
|
||||
const CBMEmbeddedLangSpec *embedded_imports;
|
||||
} CBMLangSpec;
|
||||
|
||||
// Returns a NULL-terminated list of callee-name suffixes that indicate a
|
||||
// string-dispatch call for a given language (e.g. ".classMethodValue" for
|
||||
// Python/IRIS), or NULL if the language has no such dispatch pattern.
|
||||
// Kept out of CBMLangSpec to avoid -Wmissing-field-initializers across 155
|
||||
// language rows; the table lives in extract_calls.c next to the dispatch code.
|
||||
const char **cbm_string_dispatch_suffixes(CBMLanguage lang);
|
||||
|
||||
// Get the language spec for a given language. Returns NULL for unsupported.
|
||||
const CBMLangSpec *cbm_lang_spec(CBMLanguage lang);
|
||||
|
||||
|
||||
@@ -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",
|
||||
@@ -3100,6 +3114,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);
|
||||
|
||||
Reference in New Issue
Block a user