mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-27 06:53:11 +08:00
fix(py-lsp): resolve dict-dispatch calls funcs["a"](v) to the dispatch-table target
A Python call through a dict dispatch table, funcs["a"](v), produced no CALLS
edge at all: the call's function field is a subscript, which the textual
callee extractor did not handle, so no textual call existed for the py-LSP's
lsp_dict_dispatch resolution to attach to (callable=0, strategy absent).
Three coordinated changes, the same reason-field pattern used for func_ptr and
ctor refs:
- extract_calls: for a Python call whose function field is a subscript with an
identifier base and a literal string key, emit the base identifier ("funcs")
as the textual callee so a callable-sourced CALLS edge exists. Gated to that
exact shape so other subscript calls (arr[i]()) are unaffected.
- py_lsp: the dict-dispatch resolution stashes the subscript base identifier in
reason (the textual callee differs from the resolved target "foo"), via a new
py_emit_resolved_call_reason; py_emit_resolved_call delegates with NULL.
- lsp_resolve: admit lsp_dict_dispatch into the reason-matched join so the
resolution binds to the textual call site.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
@@ -983,6 +983,25 @@ static char *extract_callee_lang_specific(CBMArena *a, TSNode node, const char *
|
||||
CBMLanguage lang) {
|
||||
const char *nk = ts_node_type(node);
|
||||
|
||||
/* Python dict-dispatch call `funcs["a"](v)`: the call's `function` field is a
|
||||
* subscript whose base is the identifier holding the dispatch table. Emit the
|
||||
* base identifier ("funcs") as the textual callee so a CALLS edge exists; the
|
||||
* py-LSP resolves it to the real target and joins via `reason` (lsp_resolve.h,
|
||||
* lsp_dict_dispatch). Gated to the literal-string-key shape the LSP handles so
|
||||
* other subscript calls (arr[i]()) are unaffected. */
|
||||
if (lang == CBM_LANG_PYTHON && strcmp(nk, "call") == 0) {
|
||||
TSNode fnf = ts_node_child_by_field_name(node, TS_FIELD("function"));
|
||||
if (!ts_node_is_null(fnf) && strcmp(ts_node_type(fnf), "subscript") == 0) {
|
||||
TSNode val = ts_node_child_by_field_name(fnf, TS_FIELD("value"));
|
||||
TSNode idx = ts_node_child_by_field_name(fnf, TS_FIELD("subscript"));
|
||||
if (!ts_node_is_null(val) && !ts_node_is_null(idx) &&
|
||||
strcmp(ts_node_type(val), "identifier") == 0 &&
|
||||
strcmp(ts_node_type(idx), "string") == 0) {
|
||||
return cbm_node_text(a, val, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lang == CBM_LANG_JSONNET) {
|
||||
char *c = extract_jsonnet_callee(a, node, source, nk);
|
||||
return c ? c : extract_scripting_callee(a, node, source, lang, nk);
|
||||
|
||||
@@ -324,8 +324,9 @@ static const char *py_lookup_dict_dispatch(PyLSPContext *ctx, const char *var, c
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void py_emit_resolved_call(PyLSPContext *ctx, const char *callee_qn, const char *strategy,
|
||||
float confidence) {
|
||||
static void py_emit_resolved_call_reason(PyLSPContext *ctx, const char *callee_qn,
|
||||
const char *strategy, float confidence,
|
||||
const char *reason) {
|
||||
if (!ctx || !ctx->resolved_calls || !callee_qn || !ctx->enclosing_func_qn)
|
||||
return;
|
||||
// Dedupe by (caller, callee). Bounded-window scan: most duplicate
|
||||
@@ -354,9 +355,15 @@ static void py_emit_resolved_call(PyLSPContext *ctx, const char *callee_qn, cons
|
||||
rc.callee_qn = cbm_arena_strdup(ctx->arena, callee_qn);
|
||||
rc.strategy = strategy;
|
||||
rc.confidence = confidence;
|
||||
rc.reason = reason ? cbm_arena_strdup(ctx->arena, reason) : NULL;
|
||||
cbm_resolvedcall_push(ctx->resolved_calls, ctx->arena, rc);
|
||||
}
|
||||
|
||||
static void py_emit_resolved_call(PyLSPContext *ctx, const char *callee_qn, const char *strategy,
|
||||
float confidence) {
|
||||
py_emit_resolved_call_reason(ctx, callee_qn, strategy, confidence, NULL);
|
||||
}
|
||||
|
||||
/* ── helpers: registry-driven attribute lookup with depth cap ──── */
|
||||
|
||||
static const CBMRegisteredFunc *py_lookup_attribute_depth(PyLSPContext *ctx, const char *type_qn,
|
||||
@@ -1664,7 +1671,10 @@ static void py_emit_call_for(PyLSPContext *ctx, TSNode call_node) {
|
||||
if (var_name && k_text) {
|
||||
const char *tgt = py_lookup_dict_dispatch(ctx, var_name, k_text);
|
||||
if (tgt) {
|
||||
py_emit_resolved_call(ctx, tgt, "lsp_dict_dispatch", 0.86f);
|
||||
/* The textual callee of `funcs["a"](v)` is the subscript base
|
||||
* identifier ("funcs"), not the resolved target ("foo"), so
|
||||
* stash it in `reason` for the join (see lsp_resolve.h). */
|
||||
py_emit_resolved_call_reason(ctx, tgt, "lsp_dict_dispatch", 0.86f, var_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,8 @@ static inline const CBMResolvedCall *cbm_pipeline_find_lsp_resolution(
|
||||
(strcmp(rc->strategy, "lsp_func_ptr") == 0 ||
|
||||
strcmp(rc->strategy, "lsp_dll_resolve") == 0 ||
|
||||
strcmp(rc->strategy, "lsp_method_ref_ctor") == 0 ||
|
||||
strcmp(rc->strategy, "lsp_method_ref_ctor_synth") == 0) &&
|
||||
strcmp(rc->strategy, "lsp_method_ref_ctor_synth") == 0 ||
|
||||
strcmp(rc->strategy, "lsp_dict_dispatch") == 0) &&
|
||||
strcmp(cbm_lsp_bare_segment(rc->reason), call_short) == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user