mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(python): resolve aliased from-import CALLS to real def
from M import f as g + g() must emit CALLS→f, not invent M.g. Use resolve_import_node in sequential import maps, and prefer an exact registry hit when the IMPORTS target is already the def QN. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com>
This commit is contained in:
1 parent
08f6d1d027
commit
7fda9f7115
4 files changed
+96
-13
No files matched your search
@@ -114,20 +114,23 @@ static int build_import_map(cbm_pipeline_ctx_t *ctx, const char *rel_path,
|
||||
*out_vals = NULL;
|
||||
*out_count = 0;
|
||||
|
||||
/* Fast path: build from cached extraction result (no JSON parsing) */
|
||||
/* Fast path: build from cached extraction via the same resolver used for
|
||||
* IMPORTS edges. Do NOT use cbm_pipeline_fqn_module(module_path) — Python
|
||||
* from-imports store "pkg.symbol" (and aliases) in module_path, which is
|
||||
* not a filesystem rel-path and misses the def node. */
|
||||
if (result && result->imports.count > 0) {
|
||||
const char **keys = calloc((size_t)result->imports.count, sizeof(const char *));
|
||||
const char **vals = calloc((size_t)result->imports.count, sizeof(const char *));
|
||||
int count = 0;
|
||||
char *file_qn = cbm_pipeline_fqn_compute(ctx->project_name, rel_path, "__file__");
|
||||
|
||||
for (int i = 0; i < result->imports.count; i++) {
|
||||
const CBMImport *imp = &result->imports.items[i];
|
||||
if (!imp->local_name || !imp->local_name[0] || !imp->module_path) {
|
||||
continue;
|
||||
}
|
||||
char *target_qn = cbm_pipeline_fqn_module(ctx->project_name, imp->module_path);
|
||||
const cbm_gbuf_node_t *target = cbm_gbuf_find_by_qn(ctx->gbuf, target_qn);
|
||||
free(target_qn);
|
||||
const cbm_gbuf_node_t *target =
|
||||
cbm_pipeline_resolve_import_node(ctx, rel_path, file_qn, imp, NULL);
|
||||
if (!target) {
|
||||
continue;
|
||||
}
|
||||
@@ -135,11 +138,18 @@ static int build_import_map(cbm_pipeline_ctx_t *ctx, const char *rel_path,
|
||||
vals[count] = target->qualified_name; /* borrowed from gbuf */
|
||||
count++;
|
||||
}
|
||||
free(file_qn);
|
||||
|
||||
*out_keys = keys;
|
||||
*out_vals = vals;
|
||||
*out_count = count;
|
||||
return 0;
|
||||
if (count > 0) {
|
||||
*out_keys = keys;
|
||||
*out_vals = vals;
|
||||
*out_count = count;
|
||||
return 0;
|
||||
}
|
||||
free((void *)keys);
|
||||
free((void *)vals);
|
||||
/* Fall through to IMPORTS-edge scan when extraction paths did not
|
||||
* resolve (should be rare once resolve_import_node is used). */
|
||||
}
|
||||
|
||||
/* Slow path: scan graph buffer IMPORTS edges + parse JSON properties */
|
||||
|
||||
@@ -619,11 +619,12 @@ static cbm_resolution_t resolve_import_map(const cbm_registry_t *r, const char *
|
||||
* resolved.requireAdmin — not just resolved, which would point at the
|
||||
* module node and miss the function entirely. */
|
||||
/* Direct hit ONLY for suffix-less callees (an aliased direct-symbol
|
||||
* import called bare: `from m import f as g; g()` — #875/#979). With a
|
||||
* suffix present (`imported.method()`), returning the bare base here
|
||||
* would swallow the suffix and bind the call to the imported symbol's
|
||||
* own node (a Variable/Class/module) instead of base.method — exactly
|
||||
* the mis-resolution the comment above warns about. That regressed
|
||||
* import called bare: `from m import f as g; g()` — #875/#979; Yui
|
||||
* `import execute as bridge_execute`). With a suffix present
|
||||
* (`imported.method()`), returning the bare base here would swallow
|
||||
* the suffix and bind the call to the imported symbol's own node
|
||||
* (a Variable/Class/module) instead of base.method — exactly the
|
||||
* mis-resolution the comment above warns about. That regressed
|
||||
* django-scale graphs by ~11K CALLS/TESTS edges (Signal.send calls
|
||||
* degraded to edges onto the signal variables themselves). #1000 */
|
||||
if (!suffix || !suffix[0]) {
|
||||
|
||||
@@ -456,6 +456,54 @@ TEST(contract_python_relative_import) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Python: aliased from-import must CALLS the real def (execute), not a ghost
|
||||
* named after the local alias (bridge_execute). Mirrors Yui G1. */
|
||||
TEST(contract_python_aliased_from_import_calls) {
|
||||
static const LangFile f[] = {
|
||||
{"gate.py", "def execute(x):\n return x\n"},
|
||||
{"router.py",
|
||||
"from .gate import execute as bridge_execute\n\n\n"
|
||||
"def submit_task(y):\n return bridge_execute(y)\n"}};
|
||||
LangProj lp;
|
||||
cbm_store_t *store = lang_index_files(&lp, f, 2);
|
||||
ASSERT_TRUE(store != NULL);
|
||||
cbm_node_t *nodes = NULL;
|
||||
int ncount = 0;
|
||||
ASSERT_EQ(cbm_store_find_nodes_by_name(store, lp.project, "submit_task", &nodes, &ncount),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_TRUE(ncount >= 1);
|
||||
char **callers = NULL;
|
||||
char **callees = NULL;
|
||||
int n_callers = 0;
|
||||
int n_callees = 0;
|
||||
ASSERT_EQ(cbm_store_node_neighbor_names(store, nodes[0].id, 32, &callers, &n_callers, &callees,
|
||||
&n_callees),
|
||||
0);
|
||||
int saw_execute = 0;
|
||||
int saw_alias_ghost = 0;
|
||||
for (int i = 0; i < n_callees; i++) {
|
||||
if (callees[i] && strcmp(callees[i], "execute") == 0) {
|
||||
saw_execute = 1;
|
||||
}
|
||||
if (callees[i] && strcmp(callees[i], "bridge_execute") == 0) {
|
||||
saw_alias_ghost = 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n_callers; i++) {
|
||||
free(callers[i]);
|
||||
}
|
||||
for (int i = 0; i < n_callees; i++) {
|
||||
free(callees[i]);
|
||||
}
|
||||
free(callers);
|
||||
free(callees);
|
||||
cbm_store_free_nodes(nodes, ncount);
|
||||
lang_cleanup(&lp, store);
|
||||
ASSERT_TRUE(saw_execute);
|
||||
ASSERT_FALSE(saw_alias_ghost);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* TypeScript: a relative import resolves to a sibling module → IMPORTS edge. */
|
||||
TEST(contract_typescript_relative_import) {
|
||||
static const LangFile f[] = {
|
||||
@@ -1625,6 +1673,7 @@ SUITE(lang_contract) {
|
||||
RUN_TEST(contract_java_methods);
|
||||
RUN_TEST(contract_kotlin_methods);
|
||||
RUN_TEST(contract_python_relative_import);
|
||||
RUN_TEST(contract_python_aliased_from_import_calls);
|
||||
RUN_TEST(contract_typescript_relative_import);
|
||||
|
||||
/* Graph-level breadth across all grammars (P4). */
|
||||
|
||||
@@ -386,6 +386,28 @@ TEST(resolve_import_map_bare_alias) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* from M import execute as bridge_execute + bridge_execute() — IMPORTS target is
|
||||
* already the def QN. Must CALLS→execute, not invent …M.bridge_execute.
|
||||
* Yui G1 shape (alongside resolve_import_map_bare_alias). */
|
||||
TEST(resolve_import_map_aliased_from_import) {
|
||||
cbm_registry_t *r = cbm_registry_new();
|
||||
cbm_registry_add(r, "execute", "proj.services.satori_bridge.gate.execute", "Function");
|
||||
/* Alias ghost must not win if somehow registered. */
|
||||
cbm_registry_add(r, "bridge_execute", "proj.services.satori_bridge.gate.bridge_execute",
|
||||
"Function");
|
||||
|
||||
const char *keys[] = {"bridge_execute"};
|
||||
const char *vals[] = {"proj.services.satori_bridge.gate.execute"};
|
||||
|
||||
cbm_resolution_t res =
|
||||
cbm_registry_resolve(r, "bridge_execute", "proj.services.yui_core.router", keys, vals, 1);
|
||||
ASSERT_STR_EQ(res.qualified_name, "proj.services.satori_bridge.gate.execute");
|
||||
ASSERT_STR_EQ(res.strategy, "import_map");
|
||||
|
||||
cbm_registry_free(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(resolve_unique_name) {
|
||||
cbm_registry_t *r = cbm_registry_new();
|
||||
cbm_registry_add(r, "UniqueFunc", "proj.deep.path.UniqueFunc", "Function");
|
||||
@@ -860,6 +882,7 @@ SUITE(registry) {
|
||||
RUN_TEST(resolve_import_map);
|
||||
RUN_TEST(resolve_import_map_bare_function);
|
||||
RUN_TEST(resolve_import_map_bare_alias);
|
||||
RUN_TEST(resolve_import_map_aliased_from_import);
|
||||
RUN_TEST(resolve_import_map_alias_with_suffix_hits_method);
|
||||
RUN_TEST(resolve_unique_name);
|
||||
RUN_TEST(resolve_unresolved);
|
||||
|
||||
Reference in new issue
Block a user