mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-11 09:25:35 +08:00
fix(python): prefer member Function on absolute from-import IMPORTS
resolve_module often lands on the Module for pkg.mod.symbol paths. When the member Function/Class exists, IMPORTS must target it so \`from M import f as g\` + g() can CALLS→f (Yui bridge_execute shape). Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com>
This commit is contained in:
@@ -1520,6 +1520,31 @@ const cbm_gbuf_node_t *cbm_pipeline_resolve_import_node(const cbm_pipeline_ctx_t
|
||||
const cbm_gbuf_node_t *target = target_qn ? cbm_gbuf_find_by_qn(ctx->gbuf, target_qn) : NULL;
|
||||
free(target_qn);
|
||||
if (target) {
|
||||
/* Python/TS from-import of a member: module_path is often
|
||||
* "pkg.mod.symbol" while resolve_module lands on the Module node
|
||||
* "pkg.mod". Prefer the member Function/Class when it exists —
|
||||
* required for `from M import f as g` so CALLS can import_map to f
|
||||
* (local_name g ≠ f). */
|
||||
if (target->label &&
|
||||
(strcmp(target->label, "Module") == 0 || strcmp(target->label, "File") == 0)) {
|
||||
char symbuf[256];
|
||||
const char *sym = import_candidate_symbol(imp->module_path, symbuf, sizeof(symbuf));
|
||||
if (sym && sym[0] && strcmp(sym, "*") != 0) {
|
||||
const char *mod_tail = import_last_segment(target->qualified_name
|
||||
? target->qualified_name
|
||||
: "");
|
||||
if (!mod_tail || strcmp(mod_tail, sym) != 0) {
|
||||
char member_qn[CBM_SZ_512];
|
||||
snprintf(member_qn, sizeof(member_qn), "%s.%s", target->qualified_name, sym);
|
||||
const cbm_gbuf_node_t *member = cbm_gbuf_find_by_qn(ctx->gbuf, member_qn);
|
||||
if (member && import_targetable_label(member->label) &&
|
||||
strcmp(member->label, "Module") != 0 &&
|
||||
strcmp(member->label, "File") != 0) {
|
||||
return member;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
@@ -456,8 +456,7 @@ 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. */
|
||||
/* Python: relative aliased from-import must CALLS the real def (execute). */
|
||||
TEST(contract_python_aliased_from_import_calls) {
|
||||
static const LangFile f[] = {
|
||||
{"gate.py", "def execute(x):\n return x\n"},
|
||||
@@ -504,6 +503,56 @@ TEST(contract_python_aliased_from_import_calls) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Python: absolute aliased from-import must CALLS the real def (Yui router shape). */
|
||||
TEST(contract_python_absolute_aliased_from_import_calls) {
|
||||
static const LangFile f[] = {
|
||||
{"services/satori_bridge/__init__.py", ""},
|
||||
{"services/satori_bridge/gate.py", "def execute(x):\n return x\n"},
|
||||
{"services/yui_core/__init__.py", ""},
|
||||
{"services/yui_core/router.py",
|
||||
"from services.satori_bridge.gate import execute as bridge_execute\n\n\n"
|
||||
"def submit_task(y):\n return bridge_execute(y)\n"},
|
||||
{"services/__init__.py", ""}};
|
||||
LangProj lp;
|
||||
cbm_store_t *store = lang_index_files(&lp, f, 5);
|
||||
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[] = {
|
||||
@@ -1674,6 +1723,7 @@ SUITE(lang_contract) {
|
||||
RUN_TEST(contract_kotlin_methods);
|
||||
RUN_TEST(contract_python_relative_import);
|
||||
RUN_TEST(contract_python_aliased_from_import_calls);
|
||||
RUN_TEST(contract_python_absolute_aliased_from_import_calls);
|
||||
RUN_TEST(contract_typescript_relative_import);
|
||||
|
||||
/* Graph-level breadth across all grammars (P4). */
|
||||
|
||||
Reference in New Issue
Block a user