Skip to content

fix(java,csharp): a call on a new object reaches the class the file imports, not a namesake - #2864

Merged
colbymchenry merged 1 commit into
mainfrom
claude/fix-2854-java-csharp-twin-classes
Oct 23, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/fix-2854-java-csharp-twin-classes

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

A Java or C# method called on a newly constructed object, new Foo().bar(), now links to the class the language picks when several packages or namespaces declare a class of that name, and to nothing when the language itself can't tell. A follow-up to #2854.

Root cause

#2854 sends a constructed receiver, new Foo().m, to a new resolver, and the conformance pass routes that shape there for every language whose chain calls it retries. Java and C# emit the shape for a no-argument constructor (new SqlSessionFactoryBuilder().build(reader)), so they started linking those calls too: mybatis-3 +265, and Newtonsoft.Json, serilog and jellyfin +36, all of them right. But the hint for which Foo was a file path from the TS/JS import reader. For these languages, resolveMethodOnType reads that hint as a dotted package name, and C#'s using names namespaces rather than classes. So with two classes named Foo, the first indexed one won, at 0.9, whatever the file imported:

import b.Foo;
int f() { return new Foo().bar(); }   // linked a.Foo::bar

A call the language itself rejects as ambiguous (import a.*; import b.*;, using A; using B;) linked one too.

Fix

matchNewReceiverCall hands Java and C# to matchJavaCsharpNewReceiverCall:

  • One project class of the name: resolves as before, including an inherited method.
  • Several, in Java: the written package (new a.Foo()), then the file's single-type import, then the caller's own package.
  • Several, in C#: the one type the file's namespaces and usings can see (isCsharpTypeVisible).
  • Then: only that class's own method links; otherwise nothing.

Validation

  • New test, new-receiver-twin-classes.test.ts (Java and C#):

    • the class the file imports, a written package (Java), and the caller's own package or namespace;
    • nothing when the language can't pick (import a.*; import b.*;, using A; using B;);
    • a class with a name of its own still links.

    On main 44adef8 it fails 4 of 4 with the kernel and with CODEGRAPH_KERNEL=0: import b.Foo linked a.Foo::bar, using B linked A.Foo::Bar, and both ambiguous calls linked the a/A copy. It passes 4 of 4 here with both engines. new-expression-receiver and kernel-tsjs-parity pass with the kernel.

  • A/B against main 44adef8 (graph dumps by natural keys, kernel per arm): flask, mybatis-3 (Java), Newtonsoft.Json, serilog and jellyfin (C#) are byte-identical. All 301 Java/C# links fix(js,ts): a call on a new C() receiver resolves through C #2854 added in those repos stay. That includes mybatis-3's CglibProxyFactory/JavassistProxyFactory calls, whose class has a same-named copy in another package; they resolve to the caller's own package as before. Main has since moved to 43a4f5b by commits that touch only file scope, the type hierarchy and the context builder; the rebase left the changed lines identical.

  • Suite (CODEGRAPH_KERNEL_EXPECT=1 npx vitest run --maxWorkers=4 --minWorkers=1, CODEGRAPH_NO_DAEMON unset, on this commit): 762 files, 9,069 tests, 8,987 passed, 0 failed, 82 skipped.

🤖 Generated with Claude Code

…mports, not a namesake

PR #2854 sends Java's and C#'s no-argument new Foo().bar() calls, which
the chain conformance pass retries, to the constructed-receiver resolver.
That links them (mybatis-3 +265, Newtonsoft.Json, serilog and jellyfin
+36, all right), but the hint for which Foo it passed was a file path,
which the resolver reads for these languages as a dotted package name,
and C#'s using names namespaces, not classes. So of two classes named
Foo the first indexed one won, whatever the file imported: import b.Foo;
new Foo().bar() linked a.Foo::bar at 0.9, and so did a call the language
itself cannot resolve, after import a.*; import b.*;.

When several project classes share the name, Java and C# now pick as
the language does: Java the written package (new a.Foo()), the file's
single-type import, else the caller's own package; C# the one type the
file's namespaces and usings can see. Only that class's own method links,
otherwise nothing. A name only one class has resolves as before,
inherited methods included.

Validation: the new test fails 4 of 4 on main 44adef8 with the kernel
and with CODEGRAPH_KERNEL=0 (import b.Foo linked a.Foo::bar, and the
ambiguous calls linked a.Foo and A.Foo), and passes here with both;
new-expression-receiver and kernel-tsjs-parity pass. A/B against main,
kernel per arm: flask, mybatis-3, Newtonsoft.Json, serilog and jellyfin
byte-identical, so the 301 links PR #2854 added there stay, mybatis-3's
2 calls on a class with a same-named copy in another package among them,
which resolve to the caller's own package as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry
colbymchenry merged commit d02a88d into main Oct 23, 2026
@colbymchenry
colbymchenry deleted the claude/fix-2854-java-csharp-twin-classes branch October 23, 2026 20:40
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