Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/pipeline/pass_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -635,9 +635,10 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call,
if (!target_node || source_node->id == target_node->id) {
return 0;
}
/* #725: suffix_match is language-agnostic and will attach a Python
* Store.commit() call to a JS function named commit (or a Bash main
* to a Python main). Drop that weak cross-language edge. */
/* #725/#1572: suffix_match and unique_name are language-agnostic and
* will attach a Python Store.commit() call to a JS function named
* commit, or a Python `with patch(...)` to a unique TSX `patch`. Drop
* those weak cross-language edges. */
if (cbm_suppress_cross_language_suffix_match(lang, target_node->file_path, res.strategy)) {
return 0;
}
Expand Down
5 changes: 3 additions & 2 deletions src/pipeline/pass_parallel.c
Original file line number Diff line number Diff line change
Expand Up @@ -2541,8 +2541,9 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
memory_order_relaxed);
if (target_node && source_node->id != target_node->id &&
cbm_suppress_cross_language_suffix_match(lang, target_node->file_path, res.strategy)) {
/* #725: same guard as pass_calls.c — do not emit a suffix_match
* CALLS edge across a language boundary. */
/* #725/#1572: same guard as pass_calls.c — do not emit a
* suffix_match or unique_name CALLS edge across a language
* boundary. */
continue;
}
if (!target_node || source_node->id == target_node->id) {
Expand Down
12 changes: 7 additions & 5 deletions src/pipeline/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,13 @@ bool cbm_perl_suppress_generic_match(bool is_perl, bool is_method, const char *c
* Pure; unit-tested in test_registry.c. */
bool cbm_tsjs_suppress_weak_method_match(bool is_tsjs, bool is_method, const char *strategy);

/* #725: drop a suffix_match CALLS edge when the caller language and the
* target file's language disagree. unique_name (candidates == 1) is #1572
* and is left alone; same_module / import_map / lsp_* are kept. JS/TS/TSX
* are one family so a .ts helper calling a .tsx function is not dropped.
* Pure; unit-tested in test_registry.c. */
/* #725/#1572: drop a suffix_match or unique_name CALLS edge when the caller
* language and the target file's language disagree. suffix_match is the
* import-distance winner among many same-named symbols (#725);
* unique_name is the candidates==1 case of the same class (#1572).
* same_module / import_map / lsp_* are kept. JS/TS/TSX are one family so a
* .ts helper calling a .tsx function is not dropped. Pure; unit-tested in
* test_registry.c. */
bool cbm_suppress_cross_language_suffix_match(CBMLanguage caller_lang, const char *target_file_path,
const char *strategy);

Expand Down
10 changes: 6 additions & 4 deletions src/pipeline/registry.c
Original file line number Diff line number Diff line change
Expand Up @@ -476,10 +476,12 @@ static const char *path_basename(const char *path) {
bool cbm_suppress_cross_language_suffix_match(CBMLanguage caller_lang, const char *target_file_path,
const char *strategy) {
/* Two same-named symbols in different languages: suffix_match picks one
* winner by import-distance and attaches every bare-name call to it
* (#725, Bash/Python main, JS/Python commit). unique_name is the
* candidates==1 case (#1572) and is not this guard. */
if (!strategy || strcmp(strategy, "suffix_match") != 0) {
* winner by import-distance (#725, Bash/Python main, JS/Python commit).
* unique_name is the candidates==1 case of the same class (#1572,
* Python `from unittest.mock import patch` binding to a unique TSX
* `patch`). */
if (!strategy ||
(strcmp(strategy, "suffix_match") != 0 && strcmp(strategy, "unique_name") != 0)) {
return false;
}
if (caller_lang == CBM_LANG_COUNT || !target_file_path || !target_file_path[0]) {
Expand Down
79 changes: 78 additions & 1 deletion tests/test_pipeline.c
Original file line number Diff line number Diff line change
Expand Up @@ -6034,7 +6034,7 @@ TEST(pipeline_python_cross_module_call) {
/* #725: two same-named symbols across languages must not share CALLS edges.
* Python Store.commit is the real callee of save(); the JS Editor.commit
* function is a distinct binding and must have no inbound CALLS from Python.
* unique_name (candidates==1) is #1572 and is not this claim. */
* unique_name (candidates==1) is the #1572 pipeline test. */
TEST(pipeline_cross_language_same_name_does_not_share_calls_issue725) {
const char *files[] = {"store.py", "app.py", "web/src/pages/Editor.js"};
const char *contents[] = {
Expand Down Expand Up @@ -6116,6 +6116,82 @@ TEST(pipeline_cross_language_same_name_does_not_share_calls_issue725) {
PASS();
}

/* #1572: unique_name (candidates==1) must not bind a Python mock.patch
* call to the only project symbol named `patch` when that symbol is TSX.
* The issue fixture is two files: frontend/Panel.tsx and backend/test_thing.py. */
TEST(pipeline_cross_language_unique_name_does_not_share_calls_issue1572) {
const char *files[] = {"frontend/Panel.tsx", "backend/test_thing.py"};
const char *contents[] = {
"function patch(x: string) {\n"
" return x;\n"
"}\n",

"from unittest.mock import patch\n"
"\n"
"def test_one():\n"
" with patch(\"os.path.exists\"):\n"
" pass\n"
"\n"
"def test_two():\n"
" with patch(\"os.path.join\"):\n"
" pass\n"};

if (setup_lang_repo(files, contents, 2) != 0)
FAIL("tmpdir");
char db[512];
snprintf(db, sizeof(db), "%s/test.db", g_lang_tmpdir);

cbm_pipeline_t *p = cbm_pipeline_new(g_lang_tmpdir, db, CBM_MODE_FULL);
ASSERT_NOT_NULL(p);
ASSERT_EQ(cbm_pipeline_run(p), 0);

cbm_store_t *s = cbm_store_open_path(db);
ASSERT_NOT_NULL(s);
const char *proj = cbm_pipeline_project_name(p);

cbm_node_t *patches = NULL;
int npatch = 0;
cbm_store_find_nodes_by_name(s, proj, "patch", &patches, &npatch);
ASSERT_GT(npatch, 0);

int64_t tsx_id = 0;
for (int i = 0; i < npatch; i++) {
if (patches[i].file_path && strstr(patches[i].file_path, "Panel.tsx"))
tsx_id = patches[i].id;
}
ASSERT_TRUE(tsx_id != 0);

cbm_edge_t *into_tsx = NULL;
int ntsx = 0;
cbm_store_find_edges_by_target_type(s, tsx_id, "CALLS", &into_tsx, &ntsx);
ASSERT_EQ(ntsx, 0);

const char *callers[] = {"test_one", "test_two"};
for (int c = 0; c < 2; c++) {
cbm_node_t *fns = NULL;
int nfn = 0;
cbm_store_find_nodes_by_name(s, proj, callers[c], &fns, &nfn);
ASSERT_GT(nfn, 0);
cbm_edge_t *from_fn = NULL;
int nfrom = 0;
cbm_store_find_edges_by_source_type(s, fns[0].id, "CALLS", &from_fn, &nfrom);
for (int i = 0; i < nfrom; i++) {
ASSERT_TRUE(from_fn[i].target_id != tsx_id);
}
if (from_fn)
cbm_store_free_edges(from_fn, nfrom);
cbm_store_free_nodes(fns, nfn);
}

if (into_tsx)
cbm_store_free_edges(into_tsx, ntsx);
cbm_store_free_nodes(patches, npatch);
cbm_store_close(s);
cbm_pipeline_free(p);
teardown_lang_repo();
PASS();
}

TEST(pipeline_go_type_classification) {
/* Port of TestGoTypeClassification */
const char *files[] = {"types.go"};
Expand Down Expand Up @@ -12021,6 +12097,7 @@ SUITE(pipeline) {
RUN_TEST(pipeline_swift_cross_package_import);
RUN_TEST(pipeline_python_cross_module_call);
RUN_TEST(pipeline_cross_language_same_name_does_not_share_calls_issue725);
RUN_TEST(pipeline_cross_language_unique_name_does_not_share_calls_issue1572);
RUN_TEST(pipeline_go_type_classification);
RUN_TEST(pipeline_go_grouped_types);
RUN_TEST(pipeline_kotlin_project);
Expand Down
29 changes: 26 additions & 3 deletions tests/test_registry.c
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ TEST(perl_suppress_keeps_high_confidence_and_genuine_calls) {

TEST(cross_language_suffix_match_drops_py_vs_js) {
/* #725: two same-named symbols in different languages. suffix_match is the
* strategy that collapses them; unique_name is #1572 and must stay. */
* strategy that collapses them. unique_name is covered by the #1572 test. */
ASSERT_TRUE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "web/src/pages/Editor.js",
"suffix_match"));
ASSERT_TRUE(cbm_suppress_cross_language_suffix_match(CBM_LANG_JAVASCRIPT, "store.py",
Expand All @@ -772,8 +772,6 @@ TEST(cross_language_suffix_match_drops_py_vs_js) {
"suffix_match"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "store.py",
"suffix_match"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "web/src/pages/Editor.js",
"unique_name"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "web/src/pages/Editor.js",
"same_module"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "web/src/pages/Editor.js",
Expand All @@ -789,6 +787,30 @@ TEST(cross_language_suffix_match_drops_py_vs_js) {
PASS();
}

TEST(cross_language_unique_name_drops_py_vs_tsx) {
/* #1572: unique_name is the candidates==1 case of the same class as
* suffix_match. Python `from unittest.mock import patch` must not bind
* to a unique TSX `function patch`. */
ASSERT_TRUE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "frontend/Panel.tsx",
"unique_name"));
ASSERT_TRUE(cbm_suppress_cross_language_suffix_match(CBM_LANG_TSX, "backend/test_thing.py",
"unique_name"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "backend/test_thing.py",
"unique_name"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "frontend/Panel.tsx",
"same_module"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "frontend/Panel.tsx",
"import_map"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON, "frontend/Panel.tsx",
"lsp_direct"));
/* JS/TS/TSX are one family — a .ts caller of a .tsx unique_name stays. */
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_TYPESCRIPT, "frontend/Panel.tsx",
"unique_name"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_JAVASCRIPT, "frontend/Panel.tsx",
"unique_name"));
PASS();
}

TEST(tsjs_suppress_drops_weak_method_matches) {
/* #592/#606: a TS/JS member call whose receiver the LSP could not type, that
* landed via a WEAK short-name strategy, is generic-resolver noise → drop.
Expand Down Expand Up @@ -922,6 +944,7 @@ SUITE(registry) {
RUN_TEST(perl_suppress_drops_weak_builtin_and_method_matches);
RUN_TEST(perl_suppress_keeps_high_confidence_and_genuine_calls);
RUN_TEST(cross_language_suffix_match_drops_py_vs_js);
RUN_TEST(cross_language_unique_name_drops_py_vs_tsx);
RUN_TEST(tsjs_suppress_drops_weak_method_matches);
RUN_TEST(tsjs_suppress_keeps_high_confidence_and_non_methods);
}
Loading