Skip to content

Kotlin: receiver-typed member calls, and park them for cross-repo merges - #3389

Open
xiongjianxu wants to merge 11 commits into
Graphify-Labs:v8from
xiongjianxu:feat/kotlin-cross-repo-member-calls
Open

Kotlin: receiver-typed member calls, and park them for cross-repo merges#3389
xiongjianxu wants to merge 11 commits into
Graphify-Labs:v8from
xiongjianxu:feat/kotlin-cross-repo-member-calls

Conversation

@xiongjianxu

Copy link
Copy Markdown
Contributor

Fixes #3388. Two cherry-pickable commits: the single-repo resolver, then the park that
lets a merged graph finish what one build cannot.

Commit 1 — resolve Kotlin member calls through the receiver's declared type

Kotlin had no receiver typing at all, so greeter.greet() across files produced no edge.
This builds the per-file table (kotlin_type_table) from every place a receiver's type is
written, and adds _resolve_kotlin_member_calls, which takes the single class/object
declaring that type and emits calls to its member — EXTRACTED when the receiver names
the type in source (Registry.register()), INFERRED when the type came from the table.

Table sources, all four needed:

source why it cannot be dropped
primary constructor parameter class App(private val greeter: Greeter) declares no property, so nothing else in the walk ever names the type
property declaration private val greeter: Greeter = Greeter()
function parameter fun run(greeter: Greeter)
local binding val greeter = Greeter() — the type is only implied by the capitalized call head

Design calls:

  1. member_receiver is stamped only for the 2-segment navigation case, and Kotlin is
    excluded from the capitalized-receiver deferral.
    A call either resolves in-file or
    falls through to raw_calls; deferring would move today's in-file Foo.bar() hits into
    raw_calls and regress them. Not deferring keeps in-file behaviour byte-identical and
    only enriches the raw_calls entry on an in-file miss — which is the only situation
    where the receiver type is of any use. A >= 3-segment chain is an FQN and still goes
    to _resolve_kotlin_qualified_calls (Kotlin: fully-qualified call expressions produce no calls edge (same-package control isolates the qualified form) #2550); the broader lang="kotlin" tag cannot
    poach its calls, since that pass requires qualified_prefix.
  2. First binding of a name wins, across all three table sites (Swift overwrites).
    Without it fun other(greeter: Other) clobbers the class's own
    private val greeter: Greeter and redirects the property's calls to Other.greet. The
    table is flat per file, so a parameter shadowing a property has to lose.
  3. calls only — no references fallback to the type node when no member matches
    (Swift has one). Smaller blast radius, and such an edge adds nothing the type-reference
    walk already emits.
  4. Both receiver arms resolve, unlike TS/JS where only the table-typed arm does.
    Kotlin imports a class name into scope rather than a module alias, so a capitalized
    receiver genuinely is a type, not a namespace.

Commit 2 — park what this build cannot answer (#3152)

A receiver typed to a class with zero declarations in this corpus is parked on the
caller as a metadata.unresolved_calls entry (names only, never node ids — those are
rewritten by the #1529 remap and again by repo prefixing). > 1 declarations stays
dropped: local ambiguity is not something merging can narrow. The merge pass then binds
the entry when exactly one declaration in another repo answers it.

_LANG_SUFFIXES["kotlin"] is {.kt, .kts, .java}. The JVM classpath is one namespace, so
a Kotlin module calling a Java library in another repo is a genuine member call; excluding
.java would drop the most common Android two-repo shape. The reverse (java accepting
.kt) is left alone here — adding it could make an existing Java↔Java pair ambiguous and
so remove an edge that lands today.

Precision costs, measured

  • The per-file table is flat, so a parameter in one method and a property in another that
    share a name collapse to one entry. First-binding-wins makes the property the winner,
    which is the safer of the two; the loser's calls go unresolved rather than mis-bound.
  • Android/JVM framework receivers (Log.d(), Build.VERSION) are in neither
    _KOTLIN_BUILTIN_TYPES nor _JAVA_BUILTIN_TYPES, so they park. In a merge they can bind
    to a same-named class another repo declares — the single-definition guard limits the
    damage but does not rule it out.

Verification

  • tests/test_kotlin_receiver_member_calls.py — 10 cases, one per type source plus the
    negatives that must stay unresolved (untyped Any receiver, two same-named classes, a
    builtin Regex shadowed by a local class, FQN still reaching Kotlin: fully-qualified call expressions produce no calls edge (same-package control isolates the qualified form) #2550's pass). 6 of the 10
    fail on v8.
  • tests/test_cross_repo_member_calls.py — a kotlin-primary-constructor arm in the
    per-language park→merge test, plus unit cases pinning that a Kotlin call binds to a Java
    declaration and does not bind to a Swift one.
  • Full suite: 5322 passed, 93 skipped, no new failures. ruff check graphify tests clean.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin cross-file member-call resolution: _resolve_kotlin_member_calls looks up a receiver's declared type in the per-file kotlin_type_table (or treats a capitalized receiver as the type for companion/object/static calls), then emits a calls edge to the single class/object declaring that method — EXTRACTED when the type is named in source, INFERRED when pulled from the table. Bails on ambiguous multi-definition types and skips Kotlin/Java/global builtin types; a receiver typed to a class declared nowhere in the corpus is parked on the caller for a later merge rather than dropped. Extends the kotlin cross-repo suffix set to include .java so Kotlin calls can bind against Java declarations, and registers the new resolver alongside the existing qualified-call pass.

Worth a look

  • Kotlin overloads are resolved to an arbitrary methodgraphify/extract.py:4467 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Existing non-call edge suppresses required call edgegraphify/extract.py:4496 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • _kotlin_constructor_type returns None after first non-matching call_expression headgraphify/extractors/engine.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin local shadowing is ignoredgraphify/extractors/engine.py:887 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2167 functions depend on the 524 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 546 callers, 43 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 44 more — each is listed as a finding

Verification — 2167 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2002 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 51 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@xiongjianxu

Copy link
Copy Markdown
Contributor Author

Self-audit pass over the bot review. Three fixes pushed, one finding I am not acting on.

1. The declaration index was corpus-wide. A same-named class in an unrelated language could answer a Kotlin receiver, and its presence made type_defs non-empty so the parking branch never ran — the call was dropped instead of handed to the merge. Now gated with _lang_family(...) == "jvm", the interop-family map the shared cross-file resolver already uses. .java stays in on purpose: one classpath means a Kotlin module over a Java library is a real answer, not a collision — the dominant shape in a part-migrated Android codebase. Two tests pin the pair: test_a_java_class_answers_a_kotlin_receiver and test_a_class_from_an_unrelated_language_never_answers_a_kotlin_receiver. _LANG_SUFFIXES["kotlin"] in cross_repo_calls now carries the same family, so a call resolves the same way locally and at merge time.

2. The INFERRED score was off the rubric. It emitted 0.8, which references/extraction-spec.md rules out; tests/test_inferred_confidence_rubric.py only greps the bare literal, so the conditional expression slipped past it. Now 0.85, the high-confidence rung, matching the sibling resolvers.

3. _kotlin_constructor_type read like a bug. The continue-then-unconditional-return None was correct — only the first call_expression child counts — but nothing said so. Rewritten as a next(...) pick over the children; same behaviour, no control flow to decode.

Not acting on — method_index keeps the last member of a name per type. _resolve_csharp_member_calls does exactly the same, and for Kotlin the collision is an overload, where the graph has no basis to prefer one signature. Refusing to emit anything would lose a genuine call edge in the common case; picking arbitrarily among true overloads keeps it. (Go's resolver in the sibling PR requires len(targets) == 1 instead, because Go has no overloads and a collision there means two different types were folded.)

Verification: ruff check graphify tests clean; full suite 5324 passed / 93 skipped, with only the four pre-existing tests/test_ollama_retry_cap.py env failures that v8 also shows here.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin receiver-typed member-call resolution: _resolve_kotlin_member_calls looks a receiver up in the per-file kotlin_type_table, and when exactly one JVM-family class/object declares that type, emits a calls edge to its member — EXTRACTED for a source-named type like Registry.register(), INFERRED (0.85) for a table-inferred receiver. Treats a receiver whose type is declared nowhere in the corpus by parking the call on the caller for a later merge to finish, and skips builtin, ambiguous (multiple-definition), and self-referential targets. Widens the cross-repo Kotlin suffix set to the whole JVM family (.java, .scala, .groovy, .gradle) so a Kotlin call can bind to a Java/JVM declaration on the shared classpath.

Worth a look

  • Overloaded Kotlin methods resolve to an arbitrary targetgraphify/extract.py:4467 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin member resolver picks an arbitrary overloadgraphify/extract.py:4473 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Non-call edges suppress Kotlin call emissiongraphify/extract.py:4505 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • DFS pop ordering breaks documented 'first binding wins' semanticsgraphify/extractors/engine.py:883 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2169 functions depend on the 526 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 546 callers, 43 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 44 more — each is listed as a finding

Verification — 2169 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2004 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 51 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin receiver-typed member-call resolution: a new _resolve_kotlin_member_calls resolver looks each greeter.greet() receiver up in the per-file kotlin_type_table, and when exactly one JVM-family class/object declares that type, emits a calls edge to its member (EXTRACTED when the receiver spells the type, INFERRED when it comes from the table); ambiguous or builtin types are skipped, and a receiver whose type is declared nowhere in the corpus is parked on the caller for a later merged graph. Treats Kotlin's cross-repo call surface as the whole JVM classpath by resolving .kt/.kts calls against .java, .scala, .groovy, and .gradle declarations too. Adds Kotlin type/name node helpers (_kotlin_property_name, _kotlin_head_type_name) and a _KOTLIN_BUILTIN_TYPES set feeding the builtin filter.

No blocking issues surfaced. 16 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2170 functions depend on the 527 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 546 callers, 43 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 44 more — each is listed as a finding

Verification — 2170 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2005 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 51 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@xiongjianxu

Copy link
Copy Markdown
Contributor Author

Thanks — the ordering finding was real and is now fixed in 2489847.

_kotlin_local_var_types pop order (engine.py:883) — fixed. The helper pushed children
onto a LIFO stack in document order, so siblings popped reversed and the last same-named
local won, the opposite of the "first binding wins" the docstring promises. Reproduced with two
svc bindings in sibling branches of one body, the candidate classes in separate files so only
the receiver pass can answer:

fun run(flag: Boolean) {
    if (flag) {
        val svc: Alpha = Alpha()
        svc.doThing()          // before: bound to Beta.doThing
    } else {
        val svc: Beta = Beta()
    }
}

Fix is stack.extend(reversed(n.children)), pinned by
test_the_first_binding_of_a_name_wins_over_a_later_sibling_branch, which fails on the previous
commit and passes now. The pre-existing _cpp_local_var_types / _swift_local_var_types carry
the same latent pattern and the same docstring claim; they are out of this PR's scope and left
untouched.

The other three findings are unchanged from the previous round and remain deliberate:

  • arbitrary overload pick (extract.py:4467/4473) and relation-agnostic existing_pairs
    (4505)
    — both follow _resolve_csharp_member_calls, the pass this one is modelled on. A
    single-target requirement per (type, name) is the god-node guard; picking among overloads
    would need parameter types the extractor does not record, so bailing is the honest behaviour.
  • Full suite green: 5325 passed, 93 skipped; ruff clean.

@xiongjianxu
xiongjianxu force-pushed the feat/kotlin-cross-repo-member-calls branch from 2489847 to 08124af Compare September 10, 2026 02:18

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin receiver-typed member-call resolution: _resolve_kotlin_member_calls looks up a receiver in the per-file kotlin_type_table, takes the single JVM-family declaration of that type, and emits a calls edge to its member — EXTRACTED when the receiver spells the type in source, INFERRED when the type came from the table. Treats a capitalized receiver as the type itself (companion/static or object singleton), skips builtin and ambiguous multi-definition types, and parks receivers whose type this corpus declares nowhere for a later merged graph. Widens the Kotlin cross-repo suffix set to the whole JVM family (.java, .scala, .groovy, .gradle) so a Kotlin call can bind to a Java-library declaration over one classpath.

Worth a look

  • Overloaded Kotlin methods resolve to an arbitrary targetgraphify/extract.py:4530 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Non-call edges suppress required Kotlin call edgesgraphify/extract.py:4564 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin function parameter receiver types are stored in a file-global tablegraphify/extractors/engine.py:4629 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2206 functions depend on the 535 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 565 callers, 43 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 45 more — each is listed as a finding

Verification — 2206 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2041 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

106 of 263 test file(s) selected (40%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_member_calls.py — impact, changed-test
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_global_graph.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • tests/test_inherited_field_receivers.py — impact
  • tests/test_java_member_calls.py — impact
  • tests/test_java_type_resolution.py — impact
  • … and 56 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 52 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@xiongjianxu
xiongjianxu force-pushed the feat/kotlin-cross-repo-member-calls branch from 08124af to e636769 Compare September 11, 2026 02:33

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin receiver-typed member-call resolution: a new _resolve_kotlin_member_calls pass looks each greeter.greet() receiver up in the per-file kotlin_type_table (or treats a capitalized receiver as the type itself for companion/object calls), resolves through the single declaring class, and emits a calls edge — EXTRACTED when the type is named in source, INFERRED (0.85) when it comes from the table — since the shared cross-file pass skips member calls and left these unresolved. Parks a receiver whose type is declared nowhere in the corpus on the caller for a later merged graph rather than dropping it, and skips builtin and ambiguous multi-definition types. Widens Kotlin's cross-repo suffix set and definition matching to the whole JVM family (.java, .scala, .groovy, .gradle) so a Kotlin call resolves against interop declarations on the shared classpath.

Worth a look

  • Duplicate Kotlin member names resolve arbitrarilygraphify/extract.py:4650 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin member-call resolver arbitrarily picks one overloaded methodgraphify/extract.py:4661 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Capitalized Kotlin variables bypass the type tablegraphify/extract.py:4662 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin member-call dedupe suppresses calls when any non-call edge already connects the same nodesgraphify/extract.py:4663 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Plain Kotlin constructor parameters are treated as class propertiesgraphify/extractors/engine.py:865 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2237 functions depend on the 539 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 587 callers, 44 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 46 more — each is listed as a finding

Verification — 2237 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2067 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

109 of 269 test file(s) selected (41%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_member_calls.py — impact, changed-test
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_global_add_tag_inference.py — impact
  • tests/test_global_graph.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • tests/test_inherited_field_receivers.py — impact
  • tests/test_issue_3405_python_resolution.py — impact
  • … and 59 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 53 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@xiongjianxu

Copy link
Copy Markdown
Contributor Author

Rebased onto v8 (0.9.58) — the conflict was upstream's is_py_local_data guard (#3405 Part 3/4) wrapping the same rc_entry block; the Kotlin tag now sits inside it, keyed on ts_module rather than on kotlin_qualified_prefix, since receiver-typed resolution needs lang == "kotlin" even when no dotted prefix is present.

Two of the five findings were real. Both are fixed with a test that fails on the parent commit.

1. Plain Kotlin constructor parameters were treated as class propertiesengine.py:865, fixed in 9f04f81. A class_parameter without val/var declares no member; it is in scope only in initializers and init blocks. Because the class node is visited before its body, that parameter claimed the name first and the real property never got it:

class App(raw: Raw) {
    private val raw = Wrapper(raw)
    fun run() { raw.doThing() }   // before: bound to Raw.doThing
}

Dropping plain parameters outright would have been wrong in the other direction — class App(greeter: Greeter) { val name = greeter.greet() } is a call the initializer walk does resolve — so the name is recorded as weak and only a body property of the same name may take it back. Both halves are pinned: test_a_property_outranks_a_plain_constructor_parameter_of_the_same_name and test_a_plain_constructor_parameter_still_types_an_initializer_receiver.

2. Capitalized receivers bypassed the type tableextract.py:4662, fixed in e634f57. The capitalized spelling means "the type itself" only because nothing of that name is bound; checking the table first is what makes that inference sound:

val Greeter = Other()
Greeter.greet()   // before: bound to class Greeter

test_a_binding_in_scope_outranks_the_class_of_the_same_name fails on 9f04f81 and passes now. Registry.register() on an object is unaffected — an object declaration puts nothing in the receiver table.

The other three are unchanged from the previous rounds and remain deliberate:

  • duplicate member names / arbitrary overload pick (extract.py:4650, 4661) — one finding in two places. method_index keeps the last member of a name per type, exactly as _resolve_csharp_member_calls does. For Kotlin a collision is an overload, and choosing between overloads needs parameter types the extractor does not record; emitting nothing would lose a real call edge in the common case, so the god-node guard stays at the type level (len(type_defs) != 1), not the member level.
  • relation-agnostic existing_pairs (extract.py:4663) — verbatim what the C# resolver treats as "already edged". Diverging here would make the two passes disagree about that for no gain inside this PR.

Verification: ruff check graphify tests clean; full suite 5453 passed / 95 skipped, deselecting only the pre-existing test_ts_normalizer_scales_linearly_on_large_files timing flake (it fails on unmodified v8 in a full run too) and tests/test_ollama_retry_cap.py (no openai in this env).

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin receiver-typed member-call resolution so a call like greeter.greet() whose method lives in another file now emits a calls edge, resolving the receiver through the per-file kotlin_type_table (or treating a capitalized unbound receiver as the type itself for companion/object/static calls). Edges are marked EXTRACTED at score 1.0 when the receiver names the type in source and INFERRED at 0.85 when the type comes from the table; ambiguous multi-definition types bail as a god-node guard, and receivers whose type is declared nowhere in the corpus are parked on the caller for the merge pass. Extends the Kotlin cross-repo suffix set to the full JVM family (.java, .scala, .groovy, .gradle) so classpath interop counts as a valid answer.

Worth a look

  • Duplicate Kotlin member names are resolved arbitrarilygraphify/extract.py:4669 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Existing non-call edges suppress required Kotlin call edgesgraphify/extract.py:4673 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin local receiver types ignore scope and declaration ordergraphify/extractors/engine.py:6291 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2240 functions depend on the 542 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 587 callers, 44 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 46 more — each is listed as a finding

Verification — 2240 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2070 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

109 of 269 test file(s) selected (41%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_member_calls.py — impact, changed-test
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_global_add_tag_inference.py — impact
  • tests/test_global_graph.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • tests/test_inherited_field_receivers.py — impact
  • tests/test_issue_3405_python_resolution.py — impact
  • … and 59 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 53 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@xiongjianxu

Copy link
Copy Markdown
Contributor Author

Third finding was real too — fixed in a1ea487.

Kotlin local receiver types ignored scopeengine.py:6291. The table was flat per file, so a name bound in two bodies was one name and the second function inherited the first one's type:

class App {
    fun a() { val svc = Alpha(); svc.doThing() }   // Alpha.doThing
    fun b() { val svc = Beta();  svc.doThing() }   // also Alpha.doThing — wrong
}

svc / client / repo appearing in two methods of one class is the common case, not a corner, so this was producing wrong edges rather than merely conservative ones.

Parameters and locals now live in a per-caller table and travel on the raw call as receiver_type — the shape _resolve_java_member_calls and _resolve_csharp_member_calls already read, so the resolver gained no new mechanism. Class properties and primary-constructor parameters stay in the file-wide kotlin_type_table, because they genuinely are file-wide. Lookup order is caller scope → class bindings → the capitalized spelling.

Two tests: test_a_local_in_one_function_does_not_type_a_receiver_in_another pins the case above, and test_the_first_binding_of_a_name_wins is replaced by test_a_parameter_types_its_own_function_without_retyping_the_property, which now asserts both halves — the property answers in run(), the same-named parameter answers in other(). The old test only pinned the property half because a flat table could not do better.

Declaration order within one body is unchanged and already covered: a local must be declared before use in Kotlin, and the sibling-branch case is pinned by test_the_first_binding_of_a_name_wins_over_a_later_sibling_branch.

The remaining two findings (extract.py:4669 arbitrary overload pick, 4673 relation-agnostic existing_pairs) are unchanged from the previous rounds and stay deliberate for the reasons given there.

Verification: ruff check graphify tests clean; full suite 5454 passed / 95 skipped, deselecting only the pre-existing test_ts_normalizer_scales_linearly_on_large_files timing flake and tests/test_ollama_retry_cap.py (no openai in this env).

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin receiver-typed member-call resolution so a call like greeter.greet() whose method lives in another file now emits a calls edge instead of nothing. _resolve_kotlin_member_calls resolves the receiver through its per-caller receiver_type, then the file-level kotlin_type_table (properties and primary-constructor params), then a capitalized spelling treated as the type itself; it marks the edge EXTRACTED (1.0) when the type is named in source and INFERRED (0.85) when it came from a table, bails on ambiguous multi-definition targets, skips builtins, and parks receivers whose type this corpus declares nowhere for a later merged-graph pass. Extends the kotlin cross-repo suffix set to the whole JVM family (.java, .scala, .groovy, .gradle) so interop calls resolve across one classpath.

Worth a look

  • Overloaded Kotlin methods resolve to an arbitrary targetgraphify/extract.py:4655 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin member resolver arbitrarily chooses one overloadgraphify/extract.py:4658 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Qualified Kotlin receiver types do not match bare class declarationsgraphify/extract.py:4676 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin two-segment nav now sets member_receiver, may regress in-file Foo.bar() resolutiongraphify/extractors/engine.py:5575 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin class-level receiver type falls back to function-scoped table only, class-bound names never emit receiver_typegraphify/extractors/engine.py:6050 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2241 functions depend on the 543 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 587 callers, 44 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 46 more — each is listed as a finding

Verification — 2241 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2071 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

109 of 269 test file(s) selected (41%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_member_calls.py — impact, changed-test
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_global_add_tag_inference.py — impact
  • tests/test_global_graph.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • tests/test_inherited_field_receivers.py — impact
  • tests/test_issue_3405_python_resolution.py — impact
  • … and 59 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 53 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@xiongjianxu

Copy link
Copy Markdown
Contributor Author

Thanks — one real defect out of the five, fixed in 2ffe2bf.

Qualified receiver types (finding 3) — real, and worse than reported. _kotlin_collect_type_refs reports a dotted user_type's first identifier, so private val greeter: com.example.Greeter and fun run(greeter: com.example.Greeter) both typed the receiver com. Probed on the parent commit:

== qualified annotation
    parked on .run() [{'callee': 'greet', 'receiver_type': 'com', 'lang': 'kotlin', 'line': 'L5'}]
== qualified param
    parked on .run() [{'callee': 'greet', 'receiver_type': 'com', 'lang': 'kotlin', 'line': 'L4'}]

No edge, and a package segment parked as if it were a type — so the miss travels into the merged graph as a fact and link_cross_repo_member_calls can never finish the call. The fix takes the last segment on the receiver-table paths only (_kotlin_head_type_name, plus the function-parameter site that picked the ref inline). The collector itself is untouched: it also drives the references edges, whose type / parameter_type / generic_arg contexts want every segment. The builtin filter rides along on the tail, so fun run(r: kotlin.text.Regex) parks nothing instead of parking kotlin.

Three tests, all three failing on a1ea487:

  • test_a_qualified_property_annotation_names_the_type_in_its_last_segment
  • test_a_qualified_parameter_type_names_the_type_in_its_last_segment
  • test_a_qualified_builtin_type_parks_no_package_segment

Two-segment navigation regressing in-file Foo.bar() (finding 4) — no. Kotlin never defers a member call to the cross-file pass (_kotlin_keeps_in_file), so the in-file cases resolve in the same build and are asserted:

Probed both shapes directly (in-file object navigation, in-file class + property receiver): both emit EXTRACTED edges.

Class-bound names never emitting receiver_type (finding 5) — no. The resolver computes type_name from rc.get("receiver_type") or the file-level table and parks that, so a class-level binding parks its type just like a parameter's. test_a_class_from_an_unrelated_language_never_answers_a_kotlin_receiver asserts exactly this: the receiver is typed by a primary-constructor parameter (class-level, not in receiver_type) and the parked entry is {"callee": "greet", "receiver_type": "Greeter", "lang": "kotlin", "line": "L2"}.

Arbitrary overload pick (findings 1–2) — same finding as previous rounds, declined for the same reason: the len(type_defs) != 1 / single-target guard is deliberate parity with the Java and C# resolvers already on v8. Picking one of several same-named candidates is the failure mode those guards exist to prevent; changing the policy is a cross-language decision, not a Kotlin patch.

Full suite on 2ffe2bf: 5457 passed, 95 skipped (test_ollama_retry_cap ignored — no local openai; test_ts_normalizer_scales_linearly_on_large_files deselected, a pre-existing timing flake that fails on unmodified v8 in a full run and passes in isolation). ruff check graphify tests clean.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds cross-file Kotlin member-call resolution: _resolve_kotlin_member_calls types a call's receiver from the caller's params/locals, then the per-file kotlin_type_table of class bindings, then treats a capitalized bare receiver as the type itself, and emits a calls edge to the single class/object declaring that type — EXTRACTED when the type is named in source, INFERRED when it came from a table. Receivers whose type this corpus declares nowhere are parked on the caller for a later merged graph, and builtin/ambiguous (multi-definition) types are skipped rather than guessed. Widens Kotlin's cross-repo suffix set to the whole JVM family (.java, .scala, .groovy, .gradle) so Kotlin-over-Java interop resolves.

Worth a look

  • companion/static call heuristic misfires on lowercase object singletonsgraphify/extract.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Qualified Kotlin receiver types do not match bare declarationsgraphify/extract.py:4677 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Collected Kotlin function parameter receiver types are never readgraphify/extractors/engine.py:4848 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2247 functions depend on the 549 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 587 callers, 44 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 46 more — each is listed as a finding

Verification — 2247 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2077 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

109 of 269 test file(s) selected (41%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_member_calls.py — impact, changed-test
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_global_add_tag_inference.py — impact
  • tests/test_global_graph.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • tests/test_inherited_field_receivers.py — impact
  • tests/test_issue_3405_python_resolution.py — impact
  • … and 59 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 53 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

The shared cross-file pass skips member calls, so `greeter.greet()` on a receiver
whose class is declared in another file produced no edge at all — the Kotlin twin
of the Swift gap in Graphify-Labs#1356. Kotlin had no receiver typing to fall back on: the
engine exported no per-file type table for it, and the call site stamped no
receiver.

The extractor now builds `kotlin_type_table` from the four places a Kotlin name
gets a declared type — a primary-constructor parameter, a property, a function
parameter, and a local `val`/`var` binding (annotated, or constructed with a
capitalized head). First binding wins, so a parameter named like a property
cannot redirect the property's own calls. Two-segment `recv.method()` chains now
stamp `member_receiver`, and every Kotlin raw_call carries `lang="kotlin"`.

`_resolve_kotlin_member_calls` reads the table, takes the single class or object
declaring that type, and emits the `calls` edge to its member. A capitalized
receiver is the type itself (`Registry.register()`), which is exact and stays
EXTRACTED; a table-typed one is INFERRED. Kotlin builtins are excluded so a local
`class Regex` cannot answer for `kotlin.text.Regex`.

Kotlin is excluded from the capitalized-receiver deferral: `Foo.bar()` resolves
in-file today and the receiver type is only usable once the bare name misses
locally, which already leaves the target unresolved. Three-segment chains stay
with the fully-qualified pass (Graphify-Labs#2550).
A Kotlin receiver typed to a class this build declares nowhere is a call into
another repository, not a mistake. The resolver held the receiver type and
dropped the call, so graph.json — the only artifact merge-graphs and global add
read — recorded nothing and no merge-time pass could recover it.

Those calls are now parked on the caller node by name, and the merge pass binds
them when the type resolves to exactly one declaration in another repo. A Kotlin
entry accepts a `.java` declaration as well: the JVM classpath is one namespace,
and a Kotlin module over a Java library is the common Android shape.
A corpus-wide type index let a same-named class in an unrelated language answer a
Kotlin receiver, and it hid from the parking branch that nothing on the classpath
declares the type; `.java` stays in, since interop makes it a real answer. The
INFERRED score was 0.8, off the discrete scale the spec fixes.
… wins

_kotlin_local_var_types pushed children onto a LIFO stack in document order,
so siblings popped reversed and the LAST same-named local won — the opposite
of the documented "first binding wins". A call in one if-branch bound to the
type declared in the sibling else-branch.
A `class_parameter` without `val`/`var` declares no member — it is in scope only in
initializers and `init` blocks — so `class App(raw: Raw) { val raw = Wrapper(raw) }`
bound every `raw.doThing()` in a method body to `Raw`, not `Wrapper`. The parameter's
name is now recorded as weak and a body property of the same name takes it back, while
plain parameters keep typing the receivers written in initializers.
…a type

A capitalized receiver was taken to be the type itself without checking scope, so a
local shadowing a class name (`val Greeter = Other()`) sent `Greeter.greet()` to the
class instead of to `Other`. The table now answers first and the capitalized spelling
is the fallback, which is what makes it a companion/`object` call in the first place.
…nction

The receiver table was flat per file, so `svc` bound in two function bodies was one
name and the second function inherited the first one's type:

    fun a() { val svc = Alpha(); svc.doThing() }   // Alpha.doThing
    fun b() { val svc = Beta();  svc.doThing() }   // also Alpha.doThing

Parameters and locals now live in a per-caller table and travel on the raw call as
`receiver_type`, the shape the Java and C# resolvers already read; class properties and
primary-constructor parameters stay file-wide, because they are. A parameter therefore
types the receivers in its own body without retyping a property's calls elsewhere.
`private val greeter: com.example.Greeter` and `fun run(greeter: com.example.Greeter)`
both typed the receiver `com`, because the shared reference collector reports a dotted
`user_type`'s first identifier. The call resolved to nothing and the package segment was
parked as the receiver's type, so the miss travelled into the merged graph as a fact.

The receiver-table paths now take the last segment, keeping the builtin filter so
`kotlin.text.Regex` parks nothing rather than parking `kotlin`. The collector itself is
untouched: it also drives the `references` edges, whose contexts want every segment.
@xiongjianxu
xiongjianxu force-pushed the feat/kotlin-cross-repo-member-calls branch from 2ffe2bf to 3694b1e Compare September 11, 2026 05:35

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.


Graphify review — findings

Adds cross-file resolution of Kotlin receiver-typed member calls: a new _resolve_kotlin_member_calls resolver looks up the receiver's type via the caller's params/locals (receiver_type), then the file's kotlin_type_table, then the source spelling, and emits a calls edge to the sole class/object declaring that type — EXTRACTED when the type is named in source, INFERRED (0.85) when it comes from a table. Bails on ambiguous multi-definition types (god-node guard), skips builtins, and parks receivers whose type is declared nowhere in the corpus for a later merged-graph pass (#3152). Widens Kotlin's cross-repo suffix set to the JVM family (.java, .scala, .groovy, .gradle) so a Kotlin call can be answered by any classpath-mate declaration.

Worth a look

  • Kotlin member resolver arbitrarily chooses one overloadgraphify/extract.py:4656 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin member resolver guesses one target for overloaded methodsgraphify/extract.py:4673 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin member resolver suppresses calls when any other edge already connects the nodesgraphify/extract.py:4675 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2247 functions depend on the 549 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 587 callers, 44 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 46 more — each is listed as a finding

Verification — 2247 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2077 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

109 of 269 test file(s) selected (41%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_member_calls.py — impact, changed-test
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_global_add_tag_inference.py — impact
  • tests/test_global_graph.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • tests/test_inherited_field_receivers.py — impact
  • tests/test_issue_3405_python_resolution.py — impact
  • … and 59 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

· 1 grounded finding(s) anchored inline below; 53 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

`val greeter = com.example.Greeter()` names the type only in the initializer, and the
constructor head of a qualified call is a navigation expression, not an identifier, so the
local stayed untyped and the member call on it resolved to nothing.

The capitalized-head test now runs on the last segment, which is what keeps `repo.load()`
from typing the local it binds.
@xiongjianxu

Copy link
Copy Markdown
Contributor Author

One of the three findings on 2ffe2bf is real and is fixed in b241de4; the other two do not hold.

Qualified constructor calls (the extract.py:4677 finding, in the shape the previous fix missed) — real, fixed. My last commit taught the receiver table to read a dotted annotation to its last segment, but the initializer path was still stuck on a bare identifier: a qualified constructor call is a navigation_expression, not an identifier, so _kotlin_constructor_type returned None. Probed on 2ffe2bf:

== qualified annotation            .run() -> .greet() @Greeter.kt INFERRED
== qualified nullable annotation   .run() -> .greet() @Greeter.kt INFERRED
== qualified parameter             .run() -> .greet() @Greeter.kt INFERRED
== val g = com.example.Greeter()   (nothing)

b241de4 runs the existing capitalized-head test on the last segment. Two tests, the first failing on 3694b1e:

  • test_a_qualified_constructor_call_types_an_unannotated_local
  • test_a_qualified_method_call_does_not_type_a_localval g = repo.load() still binds nothing, which is what the capitalized-tail requirement is for

Lowercase object singletons — declining. Confirmed the behaviour, it is a miss and not a wrong edge:

== object registry + registry.register()   (nothing)
== object Registry + Registry.register()   .run() -> .register() @Registry.kt EXTRACTED

The receiver[:1].isupper() test is not a Kotlin heuristic I added — it is the shared convention of eight receiver resolvers already on v8 (extract.py lines 3386, 3553, 3670, 3819, 4052, 4227, 4424 and this one). Removing it for Kotlin means any unbound lowercase receiver whose name happens to match a declaration binds to it — an inherited or delegated property named registry would bind to object registry on the strength of the name alone. That trade is a cross-language policy decision, not a Kotlin patch, and object registry is against Kotlin's own naming convention. A miss here costs one edge; the alternative fabricates them.

"Collected Kotlin function parameter receiver types are never read" — does not hold. kotlin_receiver_types is read at the call-expression site (kotlin_receiver_types.get(caller_nid, {}).get(member_receiver or "")) and stamped onto the raw call as receiver_type, which is the first thing _resolve_kotlin_member_calls consults. Two tests fail if that read is removed, because parameters are deliberately no longer written to the file-level table:

  • test_a_function_parameter_types_the_receiver
  • test_a_parameter_types_its_own_function_without_retyping_the_property — asserts {".run()": {"Greeter.kt"}, ".other()": {"Other.kt"}}, i.e. the parameter answers only inside its own function

On the 3694b1e review — that push was an author-identity rewrite (my commits were authored under an email not linked to my GitHub account). The tree hash is identical to 2ffe2bf, verified before pushing, so its three findings are the overload-pick and existing_pairs pair declined in the previous rounds on the same C#/Java-parity grounds.

Full suite on b241de4: 5459 passed, 95 skipped (test_ollama_retry_cap ignored, no local openai; the TS normalizer timing test deselected — it fails on unmodified v8 in a full run and passes in isolation). ruff check graphify tests clean.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin receiver-typed member-call resolution: greeter.greet() where the method lives in another file now emits a calls edge by resolving the receiver through its type — caller parameters/locals first, then the file's kotlin_type_table class bindings, then a capitalized spelling treated as the type itself (companion/object/static). Marks the edge EXTRACTED (score 1.0) when the receiver names the type in source and INFERRED (0.85) when the type came from a table, bailing on ambiguous multi-definition types and skipping builtin receiver types; a receiver typed to a class this corpus declares nowhere is parked on the caller for a later merge rather than dropped. Widens Kotlin cross-repo call matching to the whole JVM family (.java, .scala, .groovy, .gradle) so a Kotlin call can be answered by a Java library declaration on the shared classpath.

Worth a look

  • Kotlin cross-repo suffix list must include .kt for candidate matchinggraphify/cross_repo_calls.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin member resolver arbitrarily selects one overloadgraphify/extract.py:4657 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Non-call edges suppress required Kotlin call edgesgraphify/extract.py:4659 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin receiver types are stored file-wide across classesgraphify/extractors/engine.py:4414 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2251 functions depend on the 553 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 587 callers, 44 callees
  • new: _rebuild_code() — 115 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 46 more — each is listed as a finding

Verification — 2251 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 2081 function(s) in the blast radius were not formally verified this run

Test selection

Test selection

109 of 269 test file(s) selected (41%) via static blast radius.

  • tests/test_astro_extraction.py — impact
  • tests/test_astro_import_ids.py — impact
  • tests/test_build.py — impact
  • tests/test_builtin_global_type_refs.py — impact
  • tests/test_case_sensitive_resolution.py — impact
  • tests/test_cjs_module_extension.py — impact
  • tests/test_cpp_nested_and_cli.py — impact
  • tests/test_cpp_objc_cross_file_calls.py — impact
  • tests/test_cross_extension_reexport_self_cycle.py — impact
  • tests/test_cross_language_call_resolution.py — impact
  • tests/test_cross_repo_member_calls.py — impact, changed-test
  • tests/test_csharp_call_site_generic_args.py — impact
  • tests/test_csharp_enum_members.py — impact
  • tests/test_csharp_field_generic_args.py — impact
  • tests/test_csharp_generic_callsites.py — impact
  • tests/test_csharp_interface_dispatch.py — impact
  • tests/test_csharp_member_calls.py — impact
  • tests/test_csharp_member_nodes.py — impact
  • tests/test_csharp_object_creation.py — impact
  • tests/test_csharp_partial_classes.py — impact
  • tests/test_csharp_type_resolution.py — impact
  • tests/test_definition_file_portability.py — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_duplicate_annotation_edges.py — impact
  • tests/test_extract.py — impact
  • tests/test_extract_cache_location.py — impact
  • tests/test_file_label_disambiguation.py — impact
  • tests/test_file_node_id_spec.py — impact
  • tests/test_forwarding_review_findings.py — impact
  • tests/test_global_add_tag_inference.py — impact
  • tests/test_global_graph.py — impact
  • tests/test_go_builtin_call_targets.py — impact
  • tests/test_go_qualified_resolution.py — impact
  • tests/test_import_extension_resolution.py — impact
  • tests/test_import_self_loops.py — impact
  • tests/test_imported_export_forwarding.py — impact
  • tests/test_incremental.py — impact
  • tests/test_indirect_call_arrow_single_param_shadow.py — impact
  • tests/test_indirect_call_catch_binding_shadow.py — impact
  • tests/test_indirect_call_external_import_shadow.py — impact
  • tests/test_indirect_call_for_of_binding_shadow.py — impact
  • tests/test_indirect_call_function_expression_shadow.py — impact
  • tests/test_indirect_call_nested_closure_shadow.py — impact
  • tests/test_indirect_dispatch.py — impact
  • tests/test_indirect_dispatch_assign_return.py — impact
  • tests/test_indirect_dispatch_getattr.py — impact
  • tests/test_inferred_confidence_rubric.py — impact
  • tests/test_inherited_field_receivers.py — impact
  • tests/test_issue_3405_python_resolution.py — impact
  • … and 59 more

Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 53 more finding(s) on lines outside this diff (see the check run).

"line": "L2"}]


def test_a_kotlin_call_binds_to_a_java_declaration():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_a_kotlin_call_binds_to_a_java_declaration()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kotlin: member calls on a typed receiver produce no edge across files, and nothing across repos

1 participant