From 4afdfcbaaa52d1a4b576f5d16a20d4a1add656a1 Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Wed, 16 Sep 2026 14:28:54 +0530 Subject: [PATCH] Add regression tests for the cross file Kotlin object member call fix Covers three cases: the new cross file resolution (Config.load() called from another file resolves to object Config), a same file control to prove the existing in file resolution is unaffected, and an ambiguous receiver defined in two packages that must yield no edge under the exactly one candidate guard. Co-Authored-By: Claude Sonnet 5 (cherry picked from commit f22f17b57526457ac98d97e3d3fdce5bc9ff8e2e) --- tests/test_kotlin_grammar.py | 85 ++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/tests/test_kotlin_grammar.py b/tests/test_kotlin_grammar.py index dabc4dc03..3ba4c24da 100644 --- a/tests/test_kotlin_grammar.py +++ b/tests/test_kotlin_grammar.py @@ -243,6 +243,91 @@ def test_kotlin_fq_call_to_ambiguous_name_yields_no_edge(tmp_path): "guard must refuse to pick" +# ── #1698: object/class qualified member calls across files ───────────────── + +_OBJECT_CALL_CORPUS = { + "config/Config.kt": ( + "package com.demo.config\n" + "\n" + "object Config {\n" + " fun load() { }\n" + "}\n" + ), + "app/App.kt": ( + "package com.demo.app\n" + "\n" + "import com.demo.config.Config\n" + "\n" + "fun Start() {\n" + " Config.load()\n" + "}\n" + ), +} + + +def test_kotlin_object_member_call_resolves_across_files(tmp_path): + r = _extract(tmp_path, _OBJECT_CALL_CORPUS) + start = _find(r, "Start()") + load = _find(r, ".load()") + calls = _edges(r, "calls") + assert (start, load) in calls, \ + "`Config.load()` in another file must resolve to `object Config`'s method" + edge = next( + e for e in r["edges"] + if e["relation"] == "calls" and e["source"] == start and e["target"] == load + ) + assert edge["confidence"] == "EXTRACTED" + + +def test_kotlin_object_member_call_same_file_control_unaffected(tmp_path): + r = _extract(tmp_path, { + "Same.kt": ( + "package com.demo.same\n" + "\n" + "object Config {\n" + " fun load() { }\n" + "}\n" + "\n" + "fun Start() {\n" + " Config.load()\n" + "}\n" + ), + }) + start = _find(r, "Start()") + load = _find(r, ".load()") + assert (start, load) in _edges(r, "calls"), \ + "the pre existing same file object member call resolution must be unaffected" + + +def test_kotlin_object_member_call_ambiguous_receiver_yields_no_edge(tmp_path): + r = _extract(tmp_path, { + "one/One.kt": ( + "package com.demo.one\n" + "\n" + "object Config {\n" + " fun load() { }\n" + "}\n" + ), + "two/Two.kt": ( + "package com.demo.two\n" + "\n" + "object Config {\n" + " fun load() { }\n" + "}\n" + ), + "callr/Caller.kt": ( + "package com.demo.callr\n" + "\n" + "fun Start() {\n" + " Config.load()\n" + "}\n" + ), + }) + start = _find(r, "Start()") + assert not {t for s, t in _edges(r, "calls") if s == start}, \ + "`Config` exists in two packages — the exactly one candidate guard must refuse to pick" + + # ── #2551: one-line type bodies + ERROR recovery ───────────────────────────── def test_kotlin_partial_parse_warns_with_file_and_line(tmp_path, capsys):