mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
5f1a635714
#2212 moved a method's call to its own name onto the sibling overload the arguments fit. The same holds for every caller: a match landing on an overload whose parameters cannot take the call's arguments moves to the one same-owner overload they fit - commons-lang's `HashCodeBuilder.reflectionHashCode(this)` from the three-parameter overload indexed first to the `(Object, String...)` one, Newtonsoft's `SetToken(token, value)` from `SetToken(token)`, halo's four-argument `menuItem(...)` from the one-argument `menuItem`. Overload sets and arities are read once per target node, and a target with no same-owner sibling costs one lookup, so indexing time is unchanged (commons-lang 4.67s vs 4.70s, Newtonsoft 4.04s vs 4.05s, wasm both arms). C++ keeps only the self-call case: its overload sets (templates, SFINAE priority tags, `data()` on any container) move targets unreliably. Targets moved between same-named overloads: commons-lang 2,334, Newtonsoft 697, halo 286, gson 232, okhttp 188, Alamofire 50; nlohmann json and fmt unchanged. Edge counts unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
110 lines
3.6 KiB
TypeScript
110 lines
3.6 KiB
TypeScript
/**
|
|
* A call that lands on an overload its arguments cannot fit is to the one
|
|
* sibling overload they do: `toInstant(instant)` delegating to
|
|
* `toInstant(instant, Instant.EPOCH)` bound to itself (commons-lang had 180
|
|
* such self-edges, Newtonsoft 107), and `HashCodeBuilder.reflectionHashCode(this)`
|
|
* to the first overload indexed, a three-parameter one.
|
|
*/
|
|
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/main/java/app/HashCodeBuilder.java': `package app;
|
|
|
|
public class HashCodeBuilder {
|
|
public static int reflectionHashCode(final int initial, final int multiplier, final Object object) {
|
|
return initial * multiplier;
|
|
}
|
|
|
|
public static int reflectionHashCode(final Object object, final String... excludeFields) {
|
|
return 1;
|
|
}
|
|
}
|
|
`,
|
|
'src/main/java/app/Point.java': `package app;
|
|
|
|
public class Point {
|
|
public int hash() {
|
|
return HashCodeBuilder.reflectionHashCode(this);
|
|
}
|
|
}
|
|
`,
|
|
'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('from another class: reaches the overload the arguments fit, not the first indexed', () => {
|
|
const hash = cg.getNodesInFile('src/main/java/app/Point.java').find((n) => n.name === 'hash')!;
|
|
const lines = cg.getOutgoingEdges(hash.id).filter((e) => e.kind === 'calls').map((e) => cg.getNode(e.target)!)
|
|
.filter((t) => t.name === 'reflectionHashCode').map((t) => t.startLine);
|
|
expect(lines).toEqual([8]);
|
|
});
|
|
|
|
it('C#: reaches the overload the arguments fit', () => {
|
|
expect(selfNamed('src/Json/Convert.cs', 'DeserializeXNode')).toEqual(['5 -> 10']);
|
|
});
|
|
});
|