mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-28 22:52:00 +08:00
fix(resolution): a call to an overload of the calling method reaches it (#2212)
A method that delegates to another overload of itself - `toInstant(instant)` returning `toInstant(instant, Instant.EPOCH)`, `DeserializeXNode(value)` returning `DeserializeXNode(value, null)` - bound to itself, since every overload shares the name: the fuller overload never listed its convenience overloads among its callers, and its impact missed them. When a name match lands on the calling method itself, in a language that overloads by arity (C#, Java, Kotlin, Swift, C++, Scala, Dart, VB.NET), the call's argument count is read and compared with the method's parameters (defaults and varargs / `params` / `...` counted); if they cannot take it, the one same-owner overload they fit is the target. Real recursion (`depth(n - 1)`) keeps its self-edge. Self-loops -> overload edges: commons-lang 637 -> 457, Newtonsoft 303 -> 196, halo 173 -> 116, okhttp 248 -> 208, Alamofire 147 -> 120, gson 74 -> 55, AutoMapper 71 -> 57 (edge counts unchanged; overloads share a qualified name, so only the target moves). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5c01f017cf
commit
c20283e2ce
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* A method calling its own name with arguments its own parameters cannot
|
||||
* take calls another overload — `toInstant(instant)` delegating to
|
||||
* `toInstant(instant, Instant.EPOCH)` — not itself: the self-edge hid the
|
||||
* convenience overloads from the full overload's callers (commons-lang had
|
||||
* 180 such, Newtonsoft 107).
|
||||
*/
|
||||
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { CodeGraph } from '../src';
|
||||
|
||||
let root = '';
|
||||
let cg: CodeGraph;
|
||||
|
||||
beforeAll(async () => {
|
||||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-self-overload-'));
|
||||
const files: Record<string, string> = {
|
||||
'src/main/java/app/Instants.java': `package app;
|
||||
|
||||
public class Instants {
|
||||
public static Object toInstant(final Object instant) {
|
||||
return toInstant(instant, null);
|
||||
}
|
||||
|
||||
public static Object toInstant(final Object instant, final Object defaultInstant) {
|
||||
return instant != null ? instant : defaultInstant;
|
||||
}
|
||||
|
||||
public static int depth(final int n) {
|
||||
return n <= 0 ? 0 : depth(n - 1);
|
||||
}
|
||||
}
|
||||
`,
|
||||
'src/Json/Convert.cs': `namespace App
|
||||
{
|
||||
public static class Convert
|
||||
{
|
||||
public static string DeserializeXNode(string value)
|
||||
{
|
||||
return DeserializeXNode(value, null);
|
||||
}
|
||||
|
||||
public static string DeserializeXNode(string value, string root)
|
||||
{
|
||||
return value + root;
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
for (const [rel, content] of Object.entries(files)) {
|
||||
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, rel), content);
|
||||
}
|
||||
cg = await CodeGraph.init(root, { index: true });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cg?.close();
|
||||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
/** `callerLine -> targetLine` of the calls between same-named methods of a file. */
|
||||
const selfNamed = (file: string, name: string) => {
|
||||
const nodes = cg.getNodesInFile(file).filter((n) => n.name === name);
|
||||
const ids = new Set(nodes.map((n) => n.id));
|
||||
return cg.getOutgoingEdgesFrom([...ids]).filter((e) => e.kind === 'calls' && ids.has(e.target))
|
||||
.map((e) => `${cg.getNode(e.source)!.startLine} -> ${cg.getNode(e.target)!.startLine}`);
|
||||
};
|
||||
|
||||
describe('a method calling its own name', () => {
|
||||
it('Java: reaches the overload the arguments fit; real recursion stays', () => {
|
||||
expect(selfNamed('src/main/java/app/Instants.java', 'toInstant')).toEqual(['4 -> 8']);
|
||||
expect(selfNamed('src/main/java/app/Instants.java', 'depth')).toEqual(['12 -> 12']);
|
||||
});
|
||||
|
||||
it('C#: reaches the overload the arguments fit', () => {
|
||||
expect(selfNamed('src/Json/Convert.cs', 'DeserializeXNode')).toEqual(['5 -> 10']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user