Skip to content

fix(scala): resolve Outer.Inner(...) to the Scala type, never another language's constructor - #1951

Closed
danusha2345 wants to merge 2 commits into
colbymchenry:mainfrom
danusha2345:fix/scala-apply-vs-java-ctor
Closed

danusha2345 wants to merge 2 commits into
colbymchenry:mainfrom
danusha2345:fix/scala-apply-vs-java-ctor

Conversation

@danusha2345

Copy link
Copy Markdown
Contributor

Problem

A Scala call to a nested case class or companion, RemoteNode.Obfuscated("x"), can resolve to a Java class's explicit constructor. Scala nodes are named Outer::Inner, so the qualified-name lookup misses a reference written Outer.Inner, and the call falls through two paths:

  1. Strategy 3. With no Scala method of that name, it widens to all JVM languages and picks by receiver-word overlap. It lands on something like play.mvc::WebSocket::Accepted::Accepted.
  2. The typed-receiver path. In def f(): T = T.Inner(x), the Scala local-declaration pattern reads the return annotation as the receiver's type, and the lookup returns the Java Inner::Inner constructor.

Fix

  • Scala type first. Before strategy 3, a Scala call whose member is capitalized resolves to the Scala type Outer::Inner when that's unambiguous: all matches in one file (class plus companion), or one in the caller's own file. It's recorded as instantiates.
  • No other-language constructors for Scala. Neither path may hand a Scala reference another language's constructor. Real Java static and instance methods are still candidates.

Measured on playframework

  • 4 edges lost, all wrong: ApplicationLoader.Context(...) → the Java constructor while the Scala Context is imported.
  • 101 instantiates gained, all to Scala nested case classes (RemoteNode.Obfuscated/Ip/Unknown, NodePort.*, HttpChunk.*, ServerProvider.Context…). The call text matches in all 101, checked by script, and 30+ were checked against their imports by hand.
  • With Java records indexed as classes (fix(java): index records as classes with component fields and accessors #1946) and the Play fix (the companion PR), Scala→Java-constructor edges go from 146 to 0.

Known limits, unchanged from main:

  • Scala new JavaFoo(...) doesn't resolve to the Java class.
  • A Scala 3 creator application on a Java class (JFoo(x) without new) now stays unlinked rather than guessed.

Tests

Three new cases in __tests__/resolution.test.ts fail on main. The control, a Scala → Java static WebSocket.fromString, passes before and after. The full suite passes.

🤖 Generated with Claude Code

@danusha2345
danusha2345 force-pushed the fix/scala-apply-vs-java-ctor branch 2 times, most recently from f291409 to ebec4b1 Compare September 29, 2026 10:33
@danusha2345

Copy link
Copy Markdown
Contributor Author

Rebased onto main. The isOtherLanguageConstructor filter in strategy 3 and its test are dropped — main no longer guesses the Java constructor there. What remains (4 of the 6 original cases still fail on main): resolving Outer.Inner(...) to the Scala type Outer::Inner, and never taking a constructor from a typed path.

🤖 Generated with Claude Code

@danusha2345
danusha2345 force-pushed the fix/scala-apply-vs-java-ctor branch 4 times, most recently from 61f9b26 to 8924138 Compare October 7, 2026 16:04
@danusha2345
danusha2345 force-pushed the fix/scala-apply-vs-java-ctor branch 2 times, most recently from 62774d0 to a59afd3 Compare October 15, 2026 17:48
@danusha2345
danusha2345 force-pushed the fix/scala-apply-vs-java-ctor branch 3 times, most recently from e680b3b to cdc4ee5 Compare October 21, 2026 13:53
danusha2345 pushed a commit to danusha2345/codegraph that referenced this pull request Oct 21, 2026
…ymchenry#1921, colbymchenry#1935, colbymchenry#1955, colbymchenry#1957, colbymchenry#1965, colbymchenry#2575, colbymchenry#2595, colbymchenry#2597, colbymchenry#2603–colbymchenry#2608, colbymchenry#2611 landed upstream)

Conflicts: files owned by the foreign PRs now in main (colbymchenry#1921's Python import
resolver and tests, colbymchenry#1935's class-strategy method) take main's version; our
colbymchenry#1954/colbymchenry#1942/colbymchenry#1945/colbymchenry#1939/colbymchenry#1715/colbymchenry#1951/colbymchenry#2308 hunks sit beside main's new Rust and
Python blocks as in the rebased PR branches; java.rs keeps the integration's
`constant_declaration` fields under main's initializer walk; three CHANGELOG
lines the merged PRs had already contributed are deduplicated, and the method
blocks the merge duplicated in queries.ts and extraction/index.ts (the
resolution-edge queries and the sync resurrection helpers, present once on
main) are kept once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@danusha2345
danusha2345 force-pushed the fix/scala-apply-vs-java-ctor branch from cdc4ee5 to ec23f4b Compare October 21, 2026 17:55
danusha2345 and others added 2 commits October 22, 2026 10:45
… guessed Java constructor

A Scala `Outer.Inner(args)` call (case-class / companion apply) misses the
qualified-name lookup — Scala nodes are named `Outer::Inner`, the ref
`Outer.Inner` — and fell through to matchMethodCall's Strategy 3. With no
Scala method of that name, Strategy 3 widens to every language and picks by
receiver-word overlap, so `WebSocket.Accepted(...)` or
`ApplicationLoader.Context(...)` landed on a same-worded Java constructor
(`play.mvc::WebSocket::Accepted::Accepted`). A second route to the same
target: in `def f(): T = T.Inner(x)` the local-declaration pattern reads the
return annotation as `T`'s type (`Inner`), and resolveMethodOnType hands back
the Java `Inner::Inner` constructor.

- Before Strategy 3, a Scala call with a capitalized member resolves to the
  Scala type `Outer::Inner` when it is unambiguous (every match in one file —
  class + companion — or one in the call site's file). The calls->class edge
  is stored as `instantiates`, like a bare `Foo(x)` apply.
- The typed-receiver path no longer hands a Scala ref another language's
  constructor; a Java class is constructed from Scala via the import /
  `new` paths, not this guess. Real Java static / instance methods are
  still offered. (Strategy 3's cross-language pick of that constructor is
  already ruled out on main.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a module

Upstream colbymchenry#2029 indexes a Scala `object` as kind `module`, so the
Outer.Inner(...) apply lookup, which accepted only class/trait, stopped
resolving a call to a nested object that defines `apply` and left it
without an edge. Accept a Scala `module` too, and rank it after a class
or trait of the same name so a case class still wins over its companion
object whatever the declaration order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danusha2345
danusha2345 force-pushed the fix/scala-apply-vs-java-ctor branch from ec23f4b to bc58308 Compare October 22, 2026 10:45
colbymchenry pushed a commit that referenced this pull request Oct 27, 2026
…object it builds

Scala qualified names carry no package (Outer::Inner), so a call written
Outer.Inner(args), a case-class or companion apply, found no type by its
written name. It fell through to the name strategies, and under a return
type annotation (def f(): WebSocket = WebSocket.Accepted(flow)) the
local-declaration reader took WebSocket for the receiver's type and handed
over an unrelated Java constructor, Other::Accepted::Accepted.

Before the receiver-word strategy, a Scala call whose member is
capitalized now resolves to the Scala type Outer::Inner when that is
unambiguous: every match in one file (a class and its companion), or one
in the call's own file; a class or trait wins over a same-named object.
It is recorded as instantiates for a class, as a call for an object. The
typed-receiver path no longer hands a Scala call another language's
constructor. Real Java static methods are still reached from Scala.

Validation:
- resolution.test.ts: 5 cases, 4 red on main; the fifth (a Scala call
  to a real Java static) passes on both and guards the Java side.
- A/B against main 81559dc (with #1950 and #1936): scalachess +77/-0,
  http4s +371/-1 (the one a retarget from a wrong defaults::Host to the
  Origin.Host case class), playframework +291/-1 (the one a retarget:
  Writeable's HttpEntity.Strict(...) from the Java play.http constructor
  to the Scala case class), play-samples +2 (two case-class applies #1950
  unlinked from Java constructors); CMAK, click and gin identical. Every
  addition is a Scala case-class or object apply; sampled all right
  (Uci.Move, Header.Raw, H2Frame.Data, RemoteNode.Ip,
  ApplicationLoader.Context, Request.Connection over headers.Connection).
- Cost (http4s, median of 3 interleaved fresh indexes, on main
  69af19a): wall 8.6 s on main vs 7.8 s, process CPU 27.5 s vs
  26.2 s, within noise. The A/B gave the same edges on both bases.

Original PR #1951 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry

Copy link
Copy Markdown
Owner

Thanks @danusha2345! This landed as #2924 with you as the commit author, unchanged apart from the CHANGELOG wording. On playframework it now links 291 Scala case-class applies like RemoteNode.Ip(...) and HttpEntity.Strict(...), and Writeable's HttpEntity.Strict no longer lands on the Java constructor; scalachess gains 77 and http4s 371, with Java-only and Go controls unchanged. Thanks for the careful tests, especially the guard for real Java statics. It ships in the next release.

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.

2 participants