mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-29 06:46:49 +08:00
fix(java,csharp): a call on a new object reaches the class the file imports, not a namesake (#2864)
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 44adef82 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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
43a4f5b9f9
commit
d02a88d052
@@ -26050,6 +26050,9 @@ export function matchNewReceiverCall(ref: UnresolvedRef, context: ResolutionCont
|
||||
const shape = ref.referenceName.match(NEW_RECEIVER_SHAPE);
|
||||
const method = shape?.[2];
|
||||
if (!cls || !method) return null;
|
||||
// Java's and C#'s no-argument `new Foo().bar()` reach here from the
|
||||
// conformance pass; their own rules pick among same-named classes.
|
||||
if (ref.language === 'java' || ref.language === 'csharp') return matchJavaCsharpNewReceiverCall(cls, shape![1]!, method, ref, context);
|
||||
// Several classes may share the name: the one the call's file imports
|
||||
// under the written name is the class constructed (tsImportedTypeFile).
|
||||
const onClass = resolveMethodOnType(cls, method, ref, context, 0.9, 'instance-method', tsImportedTypeFile(shape![1]!, ref, context));
|
||||
@@ -26067,6 +26070,36 @@ export function matchNewReceiverCall(ref: UnresolvedRef, context: ResolutionCont
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Java or C# `new Foo().bar()`. With one project class of the name, its
|
||||
* method or an inherited one, as for any typed call. With several, the class
|
||||
* the language picks, or none: Java's written package (`new a.b.Foo()`), the
|
||||
* file's single-type import, else the caller's own package; C#'s one type the
|
||||
* file's namespaces and usings can see (isCsharpTypeVisible). Then only that
|
||||
* class's own method: an inherited one is looked up by name, across every
|
||||
* class of the name.
|
||||
*/
|
||||
function matchJavaCsharpNewReceiverCall(cls: string, written: string, method: string, ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
|
||||
const twins = context.getNodesByName(cls).filter((n) =>
|
||||
(n.kind === 'class' || n.kind === 'struct') && sameLanguageFamily(n.language, ref.language));
|
||||
if (twins.length <= 1) return resolveMethodOnType(cls, method, ref, context, 0.9, 'instance-method');
|
||||
const dirOf = (file: string) => file.slice(0, file.lastIndexOf('/'));
|
||||
let chosen: Node | undefined;
|
||||
if (ref.language === 'csharp') {
|
||||
const visible = twins.filter((n) => isCsharpTypeVisible(n, ref, context));
|
||||
if (visible.length === 1) chosen = visible[0];
|
||||
} else {
|
||||
const fqn = written.includes('.') ? written : importedFqnOf(cls, ref, context);
|
||||
const byPath = (n: Node) => n.filePath.replace(/\.java$/, '').endsWith('/' + fqn!.replace(/\./g, '/'));
|
||||
const ownPackage = twins.filter((n) => dirOf(n.filePath) === dirOf(ref.filePath));
|
||||
chosen = fqn ? twins.find(byPath) : ownPackage.length === 1 ? ownPackage[0] : undefined;
|
||||
}
|
||||
if (!chosen) return null;
|
||||
const own = context.getNodesInFile(chosen.filePath).find((n) =>
|
||||
n.kind === 'method' && n.name === method && n.qualifiedName === `${chosen!.qualifiedName}::${method}`);
|
||||
return own ? { original: ref, targetNodeId: own.id, confidence: 0.9, resolvedBy: 'instance-method' } : null;
|
||||
}
|
||||
|
||||
/** Resolve the implementation inside the identified store, not a namesake or
|
||||
* an interface signature elsewhere in the project. Import resolution already
|
||||
* follows aliases/barrels; containment already excludes nested action locals. */
|
||||
|
||||
Reference in New Issue
Block a user