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 @@ -676,9 +676,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 @@ -3024,8 +3024,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 @@ -320,11 +320,13 @@ bool cbm_weak_member_unique_name_exempt(bool is_python, bool receiver_is_self_at
bool cbm_suppress_weak_local_binding_call(bool enabled, bool callee_is_locally_bound,
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
59 changes: 39 additions & 20 deletions src/pipeline/registry.c
Original file line number Diff line number Diff line change
Expand Up @@ -697,14 +697,21 @@ bool cbm_suppress_weak_local_binding_call(bool enabled, bool callee_is_locally_b

static bool js_ts_family(CBMLanguage lang) {
return lang == CBM_LANG_JAVASCRIPT || lang == CBM_LANG_TYPESCRIPT || lang == CBM_LANG_TSX ||
lang == CBM_LANG_ARKTS;
lang == CBM_LANG_ARKTS || lang == CBM_LANG_VUE || lang == CBM_LANG_SVELTE ||
lang == CBM_LANG_ASTRO || lang == CBM_LANG_HTML;
}

/* C and C++ are one family for cross-language checks: .h maps to CBM_LANG_CPP
* in the extension table, so a .c file referencing a symbol declared in its
* own header would otherwise read as a language boundary. */
* own header would otherwise read as a language boundary. CUDA and Objective-C
* use the same front end for header calls (#1702 census addendum). */
static bool c_cpp_family(CBMLanguage lang) {
return lang == CBM_LANG_C || lang == CBM_LANG_CPP;
return lang == CBM_LANG_C || lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA ||
lang == CBM_LANG_OBJC;
}

static bool jvm_family(CBMLanguage lang) {
return lang == CBM_LANG_JAVA || lang == CBM_LANG_KOTLIN || lang == CBM_LANG_GROOVY;
}

static const char *path_basename(const char *path) {
Expand All @@ -721,30 +728,30 @@ static const char *path_basename(const char *path) {
return slash ? slash + 1 : path;
}

/* Build and configuration languages have no cross-language call semantics: a
* Makefile's `$(eval ...)` or a CMake `function(...)` names nothing in a C
* file, so a bare-name bind into another language is always a collision
* (2026-09-16 probe: kernel Makefile targets bound to `sk_psock.eval`). */
static bool build_config_language(CBMLanguage lang) {
return lang == CBM_LANG_MAKEFILE || lang == CBM_LANG_CMAKE || lang == CBM_LANG_YAML ||
lang == CBM_LANG_TOML || lang == CBM_LANG_JSON || lang == CBM_LANG_INI ||
lang == CBM_LANG_DOCKERFILE;
/* Target language from basename alone is wrong for extensions whose real
* language is decided by file content during discovery (.m → MATLAB in the
* table but often Objective-C, etc.). Never suppress on filename guess alone. */
static bool target_lang_from_filename_ambiguous(const char *target_file_path) {
const char *base = path_basename(target_file_path);
const char *dot = strrchr(base, '.');
if (!dot || dot == base) {
return false;
}
return strcmp(dot, ".m") == 0 || strcmp(dot, ".cls") == 0 || strcmp(dot, ".inc") == 0 ||
strcmp(dot, ".cfc") == 0 || strcmp(dot, ".frm") == 0;
}

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 — except for a build
* or configuration caller, where even a unique match into another
* language is a collision by construction. */
if (!strategy) {
return false;
}
bool config_caller = build_config_language(caller_lang);
if (strcmp(strategy, "suffix_match") != 0 &&
!(config_caller && strcmp(strategy, "unique_name") == 0)) {
* candidates==1 case of the same class (#1572, Python
* `from unittest.mock import patch` binding to a unique TSX `patch`).
* Build/config callers have no cross-language call semantics, so their
* unique_name collisions are covered by the same guard. */
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 All @@ -757,9 +764,18 @@ bool cbm_suppress_cross_language_suffix_match(CBMLanguage caller_lang, const cha
if (caller_lang == target_lang) {
return false;
}
if (target_lang_from_filename_ambiguous(target_file_path)) {
return false;
}
if (js_ts_family(caller_lang) && js_ts_family(target_lang)) {
return false;
}
if (c_cpp_family(caller_lang) && c_cpp_family(target_lang)) {
return false;
}
if (jvm_family(caller_lang) && jvm_family(target_lang)) {
return false;
}
return true;
}

Expand All @@ -785,6 +801,9 @@ bool cbm_suppress_cross_language_ref(CBMLanguage caller_lang, const char *target
if (caller_lang == target_lang) {
return false;
}
if (target_lang_from_filename_ambiguous(target_file_path)) {
return false;
}
if (js_ts_family(caller_lang) && js_ts_family(target_lang)) {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pipeline.c
Original file line number Diff line number Diff line change
Expand Up @@ -8102,7 +8102,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[] = {"class Store:\n"
Expand Down
54 changes: 48 additions & 6 deletions tests/test_registry.c
Original file line number Diff line number Diff line change
Expand Up @@ -831,9 +831,10 @@ TEST(cross_language_config_caller_drops_unique_name_too) {
cbm_suppress_cross_language_suffix_match(CBM_LANG_CMAKE, "src/main.c", "unique_name"));
ASSERT_TRUE(
cbm_suppress_cross_language_suffix_match(CBM_LANG_YAML, "app/models.py", "unique_name"));
/* A code caller's unique_name into another language is still #1572. */
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(
CBM_LANG_PYTHON, "web/src/pages/Editor.js", "unique_name"));
/* A code caller's unique_name into another language is #1572. */
ASSERT_TRUE(cbm_suppress_cross_language_suffix_match(CBM_LANG_PYTHON,
"web/src/pages/Editor.js",
"unique_name"));
/* Same-language config targets are untouched. */
ASSERT_FALSE(
cbm_suppress_cross_language_suffix_match(CBM_LANG_MAKEFILE, "lib/Makefile", "unique_name"));
Expand Down Expand Up @@ -912,7 +913,7 @@ TEST(registry_tie_break_is_independent_of_registration_order) {

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(
Expand All @@ -921,8 +922,6 @@ TEST(cross_language_suffix_match_drops_py_vs_js) {
cbm_suppress_cross_language_suffix_match(CBM_LANG_BASH, "cli/main.py", "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,
Expand Down Expand Up @@ -987,6 +986,48 @@ TEST(go_bare_ref_never_binds_field) {
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"));
/* C/C++, JVM, and Vue→TS families stay exempt for unique_name. */
ASSERT_FALSE(
cbm_suppress_cross_language_suffix_match(CBM_LANG_C, "bpf/probe.h", "unique_name"));
ASSERT_FALSE(
cbm_suppress_cross_language_suffix_match(CBM_LANG_GROOVY, "buildSrc/Foo.java",
"unique_name"));
ASSERT_FALSE(cbm_suppress_cross_language_suffix_match(CBM_LANG_VUE, "services/Queue.ts",
"unique_name"));
/* #1702 addendum: CUDA / ObjC / HTML inline script stay in-family. */
ASSERT_FALSE(
cbm_suppress_cross_language_suffix_match(CBM_LANG_CUDA, "kernels/vec.h", "unique_name"));
ASSERT_FALSE(
cbm_suppress_cross_language_suffix_match(CBM_LANG_OBJC, "include/Foo.h", "unique_name"));
ASSERT_FALSE(
cbm_suppress_cross_language_suffix_match(CBM_LANG_HTML, "static/app.js", "unique_name"));
/* .m targets are MATLAB by extension table but often ObjC — do not suppress. */
ASSERT_FALSE(
cbm_suppress_cross_language_suffix_match(CBM_LANG_OBJC, "models/Foo.m", "unique_name"));
PASS();
}

TEST(dynamic_suppress_drops_weak_method_matches) {
/* #592/#606/#1276: a 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 @@ -1244,6 +1285,7 @@ SUITE(registry) {
RUN_TEST(registry_tie_break_is_independent_of_registration_order);
RUN_TEST(cross_language_ref_drops_go_vs_c);
RUN_TEST(go_bare_ref_never_binds_field);
RUN_TEST(cross_language_unique_name_drops_py_vs_tsx);
RUN_TEST(dynamic_suppress_drops_weak_method_matches);
RUN_TEST(dynamic_suppress_keeps_high_confidence_and_non_methods);
RUN_TEST(python_builtin_member_table_matches_builtin_type_methods);
Expand Down
Loading