Skip to content

java: a var local never names an external type called var (#1401) - #1563

Merged
swapnilpaliwal-sd merged 1 commit into
0.1.3from
fix/1401-var-local-type
Sep 28, 2026
Merged

swapnilpaliwal-sd merged 1 commit into
0.1.3from
fix/1401-var-local-type

Conversation

@swapnilpaliwal-sd

Copy link
Copy Markdown
Contributor

Fixes #1401

What was wrong. The parser records var h = r.lookup(k) as a LOCAL_VARIABLE type reference written var. No import or declaration resolves that name, so graph/java/engine/resolution/external-types.dl minted external:var for it and typed the local with it. Every call on a var local then got an external:var.<m> boundary:

  • beside the real target when flow typing also knew the type, which turned a single target into multi_inferred;
  • alone when it did not, which reported an unresolved call as a library call.

The change. Both edits are in external-types.dl.

  • candidate_ref skips a reference written var.
  • A new expr_type("external", …) clause gives a var local the external type of its initializer. For example, var f = source() where Format source() is not staged types f as Format, the same as a declared Format f. The clause applies only to locals flagged isVarInferred, and a reassignment does not widen the type, because a var's type is fixed at the declaration (JLS 14.4.1).

Without the second clause, removing external:var turned those receivers into declared unknowns. On the dev project it gives 341 var sites their real external label.

Tests.

  • graph/test/java/cases/61-var-local-type covers each var shape next to its declared control: a client call's return, a static factory, new, new then reassigned, a literal, enhanced-for, a try-with-resources resource, an unstaged platform call, a client method with an external return type, and lambda var parameters.
  • 36-generic-two-hop loses three external:var boundaries that sat beside the real library targets.
  • bin/axiomcode test java: 64 passed, 0 failed.

Measurement. Engine only, no library staged (the default index), same IR before and after.

set project external:var multi -> known var-only -> real boundary var-only -> declared unknown non-var sites changed
dev 1,360 files 3,842 -> 0 1,612 341 1,626 30
dev 1,386 files 10 -> 0 0 1 8 0
holdout 2,795 files 453 -> 0 101 36 311 7
holdout 847 files 63 -> 0 8 4 28 0

No var site lost a real target. The declared unknowns are sites whose initializer type is not known in a client-only build: library calls, generic Class<T> lookups, and unresolved receivers.

The non-var changes come from argument and assignment-target typing, which used to read the fake type:

  • 1,360-file project (30 sites): 22 are better (the right overload is chosen, multi_inferred narrows to known_edge, or a spurious Object.equals/toString boundary is dropped), 4 are neutral (known_edge to an anonymous implementation becomes known_edge to the interface method it implements) and 4 are worse.
  • 2,795-file project (7 sites): 5 narrow to the right overload, 1 spurious Object.toString boundary is dropped, and 1 boundary that existed only through the fake type becomes a declared unknown.

What got worse. On the dev project, 4 sites whose argument is a var of unknown type lose precision, though the correct target is still present. One goes from known_edge to multi_inferred over 2 overloads, and three fans grow by 2 to 3 candidates. Before the fix, external:var ruled out the client-typed overloads by accident.

var s = "abc"; s.length() is a declared unknown in a client-only build, the same answer the engine gives for "abc".length() written inline. With the JDK staged, the literal is typed java.lang.String and the var follows it.

Solve cost. Dev project, warm, back to back: solve 26 s before, 26 s after.

The parser records `var h = r.lookup(k)` as a LOCAL_VARIABLE type
reference written `var`. Nothing resolves that name, so
external-types.dl minted `external:var` and typed every var receiver
with it: each call on a var local got an `external:var.<m>` boundary,
beside its real target when the local was in-source typed, alone when
it was not.

candidate_ref now skips a reference written `var`, and a var local
takes the external type of its initializer (`var f = source()` with an
unstaged return type) the way a declared local takes its written one.

Case 61-var-local-type pins each var shape against its declared
control. 36-generic-two-hop loses three external:var boundaries that
sat beside the real library targets.
@swapnilpaliwal-sd
swapnilpaliwal-sd merged commit 9b5783a into 0.1.3 Sep 28, 2026
10 of 11 checks passed
@swapnilpaliwal-sd
swapnilpaliwal-sd deleted the fix/1401-var-local-type branch September 28, 2026 02:20
qtwang pushed a commit to qtwang/axiomcodegraph that referenced this pull request Oct 11, 2026
…I#1563

AxiomCodeAI#1563 added a Java case that both ground-truth oracles agree on, but left graph/test/java/expected/oracle-agreement.txt at 45 cases, so the engine (java) job fails its oracle-agreement check on 0.1.3. CI measured 46 cases compared, 46 agreeing, 0 disagreements; this records that. Refs AxiomCodeAI#1401.
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.

1 participant