Completes the #1125 fix. The same typed-parameter gap fixed for TS/JS existed in every other language whose localReceiverTypePatterns only matched keyword-anchored locals (let/var/:=/= new) and never the bare parameter form: - Rust: the `:`-annotation pattern required `let`, so `fn use(lg: &Logger)` didn't match. Dropped the `let` anchor (still covers `let lg: Logger`), keeping the `&?mut?` handling — now covers params and closures `|lg: T|`. - Go: only `lg := T{}` / `var lg T` matched; a parameter/method-receiver `func use(lg Logger)` / `func (l Logger) M()` (name-before-type, no keyword) didn't. Added a PascalCase-guarded `ident Type` pattern — the guard plus the existing enclosing-scope bound (excludes package-level struct fields) keep the keyword-free shape from matching unrelated pairs. - Dart: the type-before-name pattern's trailing `[=;]` missed a parameter's `)`/`,`. Widened to `[=;,)]`, mirroring Java/C#. - PHP: only `$lg = new T` matched; a typed param `function use(Logger $lg)` (also `?Logger`, `\App\Logger`, `&$lg`, `catch (E $e)`) didn't. Added a type-before-$var pattern. Reserved words can't be class names, so the looser lowercase-allowing capture yields no wrong edges. Every pattern still relies on resolveMethodOnType validating the inferred type actually declares the method (no edge on a mis-inference) — the same safety net the already-covered languages use. Verified with a deterministic probe: all four now disambiguate two same-named methods via the typed param (Java + Kotlin as passing controls), full suite green (1930), no regressions. Adds a parameterized regression test (Rust/Go/Dart/PHP), associating method to type by qualifiedName so it holds where the method sits outside the type's line range (Rust impl, Go decl). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
385001398b
commit
cf86fe8198
+1
-1
@@ -31,7 +31,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
- Graph traversal and blast-radius results no longer drop or miscount relationships in a handful of edge cases. When a symbol could be reached by more than one path, an impact/blast-radius query could leave out a direct dependency between two symbols that were already linked another way; separately, the lower-level graph traversal used by the library API could keep only one of several relationships between the same pair of symbols (for example a symbol that both calls and references another), count a caller reached through two different call sites twice, or return slightly more results than the requested size limit on a very highly-connected symbol. These were long-standing and mostly masked by later de-duplication, so day-to-day query results were largely unaffected, but the traversal now returns the complete, correctly-bounded set. Thanks @inth3shadows for the precise, individually-traced reports. (#1086, #1087, #1088, #1089, #1090)
|
- Graph traversal and blast-radius results no longer drop or miscount relationships in a handful of edge cases. When a symbol could be reached by more than one path, an impact/blast-radius query could leave out a direct dependency between two symbols that were already linked another way; separately, the lower-level graph traversal used by the library API could keep only one of several relationships between the same pair of symbols (for example a symbol that both calls and references another), count a caller reached through two different call sites twice, or return slightly more results than the requested size limit on a very highly-connected symbol. These were long-standing and mostly masked by later de-duplication, so day-to-day query results were largely unaffected, but the traversal now returns the complete, correctly-bounded set. Thanks @inth3shadows for the precise, individually-traced reports. (#1086, #1087, #1088, #1089, #1090)
|
||||||
- Method calls to same-named classes in different files now resolve to the right definition. If two files each declared, say, a `Logger` class with its own `log()` method, a call could be linked to whichever definition happened to be indexed first — so a call in one file wrongly pointed at the class in another, mixing up that method's callers and blast radius. This affected calls written as `obj.log()`, `Logger.log()`, and `Logger::log()` across many languages, including C++, Python, TypeScript, Java, C#, and Rust. When a method name is ambiguous, CodeGraph now prefers the definition in the calling file itself — the correct target in the common case — while Java/Kotlin calls that an `import` already pins to another file are unaffected. Thanks @inth3shadows for the minimal repro and root-cause analysis. (#1079)
|
- Method calls to same-named classes in different files now resolve to the right definition. If two files each declared, say, a `Logger` class with its own `log()` method, a call could be linked to whichever definition happened to be indexed first — so a call in one file wrongly pointed at the class in another, mixing up that method's callers and blast radius. This affected calls written as `obj.log()`, `Logger.log()`, and `Logger::log()` across many languages, including C++, Python, TypeScript, Java, C#, and Rust. When a method name is ambiguous, CodeGraph now prefers the definition in the calling file itself — the correct target in the common case — while Java/Kotlin calls that an `import` already pins to another file are unaffected. Thanks @inth3shadows for the minimal repro and root-cause analysis. (#1079)
|
||||||
- Live auto-sync now gives up cleanly if indexing keeps failing, instead of retrying forever in the background. If a repeatable indexing error kept every sync from completing — for example a file that reliably crashes one language's parser, a full disk, or a corrupt database — a long-running server or daemon would retry every couple of seconds indefinitely, quietly wasting work and filling the logs while the graph silently stopped updating. Auto-sync now backs off after repeated failures and, if they persist, disables itself with an actionable message (naming the underlying error and pointing you to run `codegraph sync`), so the stalled index is surfaced rather than hidden — matching how prolonged file-lock contention and watch-limit exhaustion already degrade. A single successful sync resets everything, so an occasional transient hiccup is unaffected. Thanks @inth3shadows for the precise, code-traced report. (#1127)
|
- Live auto-sync now gives up cleanly if indexing keeps failing, instead of retrying forever in the background. If a repeatable indexing error kept every sync from completing — for example a file that reliably crashes one language's parser, a full disk, or a corrupt database — a long-running server or daemon would retry every couple of seconds indefinitely, quietly wasting work and filling the logs while the graph silently stopped updating. Auto-sync now backs off after repeated failures and, if they persist, disables itself with an actionable message (naming the underlying error and pointing you to run `codegraph sync`), so the stalled index is surfaced rather than hidden — matching how prolonged file-lock contention and watch-limit exhaustion already degrade. A single successful sync resets everything, so an occasional transient hiccup is unaffected. Thanks @inth3shadows for the precise, code-traced report. (#1127)
|
||||||
- TypeScript/JavaScript method calls made through a typed function parameter now resolve to the right method. A call like `function use(lg: Logger) { lg.log(); }` wasn't linking to `Logger`'s `log()` — CodeGraph inferred a receiver's type from a local declaration (`const lg: Logger`) but not from a bare typed parameter, so when another class defined a method of the same name the call was dropped from callers and impact/blast-radius. Typed parameters are now recognized the same way, including generic types (`repo: Repository<User>`), matching how Java, C#, Kotlin, Swift, and Scala already handled it. Thanks @inth3shadows for the isolated repro and root-cause analysis. (#1125)
|
- Method calls made through a typed function parameter now resolve to the right method in more languages. A call like `function use(lg: Logger) { lg.log(); }` wasn't linking to `Logger`'s `log()` — CodeGraph inferred a receiver's type from a local declaration but not from a bare typed parameter, so when another class defined a method of the same name the call was dropped from callers and impact/blast-radius. Typed parameters (and method receivers) are now recognized in TypeScript, JavaScript, Rust, Go, Dart, and PHP — including TypeScript generic types (`repo: Repository<User>`) — matching how Java, C#, Kotlin, Swift, Scala, Python, and Pascal already handled it, so the parameter case is now covered across every language that has typed parameters. Thanks @inth3shadows for the isolated repro and root-cause analysis. (#1125)
|
||||||
|
|
||||||
## [1.1.6] - 2026-06-30
|
## [1.1.6] - 2026-06-30
|
||||||
|
|
||||||
|
|||||||
@@ -1775,6 +1775,51 @@ func main() {
|
|||||||
expect(otherCallers).toContain('useOther');
|
expect(otherCallers).toContain('useOther');
|
||||||
expect(otherCallers).not.toContain('use');
|
expect(otherCallers).not.toContain('use');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The same typed-parameter gap existed in every language whose pattern set
|
||||||
|
// only matched keyword-anchored locals (let/var/:=/= new), not the bare
|
||||||
|
// parameter form — Rust, Go, Dart, PHP (#1125). Each case: two classes
|
||||||
|
// sharing a method name + two functions taking one as a typed param; a
|
||||||
|
// correct fix routes each call to its OWN type's method (the collision is
|
||||||
|
// load-bearing — a single class resolves via the same-name fallback either
|
||||||
|
// way). Method↔type association is by qualifiedName, robust where the method
|
||||||
|
// lives outside the type's line range (Rust `impl`, Go method decl).
|
||||||
|
const typedParamCases: Array<{
|
||||||
|
lang: string; file: string; method: string; callerA: string; callerB: string; src: string;
|
||||||
|
}> = [
|
||||||
|
{ lang: 'Rust (fn f(x: &T))', file: 'svc.rs', method: 'log', callerA: 'use_it', callerB: 'use_other',
|
||||||
|
src: `pub struct Logger { n: i32 }\nimpl Logger { pub fn log(&self) -> i32 { self.n } }\npub struct Other { n: i32 }\nimpl Other { pub fn log(&self) -> i32 { self.n } }\npub fn use_it(lg: &Logger) -> i32 { lg.log() }\npub fn use_other(o: &Other) -> i32 { o.log() }\n` },
|
||||||
|
{ lang: 'Go (func f(x T))', file: 'svc.go', method: 'Log', callerA: 'UseIt', callerB: 'UseOther',
|
||||||
|
src: `package a\ntype Logger struct{}\nfunc (l Logger) Log() int { return 1 }\ntype Other struct{}\nfunc (o Other) Log() int { return 2 }\nfunc UseIt(lg Logger) int { return lg.Log() }\nfunc UseOther(o Other) int { return o.Log() }\n` },
|
||||||
|
{ lang: 'Dart (T f(U x))', file: 'svc.dart', method: 'log', callerA: 'useIt', callerB: 'useOther',
|
||||||
|
src: `class Logger { int log() { return 1; } }\nclass Other { int log() { return 2; } }\nint useIt(Logger lg) { return lg.log(); }\nint useOther(Other o) { return o.log(); }\n` },
|
||||||
|
{ lang: 'PHP (f(T $x))', file: 'svc.php', method: 'log', callerA: 'useIt', callerB: 'useOther',
|
||||||
|
src: `<?php\nclass Logger { function log() { return 1; } }\nclass Other { function log() { return 2; } }\nfunction useIt(Logger $lg) { return $lg->log(); }\nfunction useOther(Other $o) { return $o->log(); }\n` },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const c of typedParamCases) {
|
||||||
|
it(`infers a typed-parameter receiver, disambiguating same-named methods — ${c.lang} (#1125)`, async () => {
|
||||||
|
fs.writeFileSync(path.join(tempDir, c.file), c.src);
|
||||||
|
cg = await CodeGraph.init(tempDir, { index: true });
|
||||||
|
cg.resolveReferences();
|
||||||
|
|
||||||
|
const methods = cg.getNodesByKind('method').filter((n) => n.name === c.method);
|
||||||
|
expect(methods.length, `${c.lang}: both ${c.method} methods indexed`).toBe(2);
|
||||||
|
|
||||||
|
const loggerLog = methods.find((m) => /Logger/.test(m.qualifiedName ?? ''));
|
||||||
|
const otherLog = methods.find((m) => /Other/.test(m.qualifiedName ?? ''));
|
||||||
|
expect(loggerLog, `${c.lang}: Logger's ${c.method}`).toBeDefined();
|
||||||
|
expect(otherLog, `${c.lang}: Other's ${c.method}`).toBeDefined();
|
||||||
|
|
||||||
|
const loggerCallers = cg.getCallers(loggerLog!.id).map((x) => x.node.name);
|
||||||
|
const otherCallers = cg.getCallers(otherLog!.id).map((x) => x.node.name);
|
||||||
|
|
||||||
|
expect(loggerCallers, `${c.lang}: Logger callers`).toContain(c.callerA);
|
||||||
|
expect(loggerCallers, `${c.lang}: Logger callers`).not.toContain(c.callerB);
|
||||||
|
expect(otherCallers, `${c.lang}: Other callers`).toContain(c.callerB);
|
||||||
|
expect(otherCallers, `${c.lang}: Other callers`).not.toContain(c.callerA);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Name Matcher: kind bias for new ref kinds', () => {
|
describe('Name Matcher: kind bias for new ref kinds', () => {
|
||||||
|
|||||||
@@ -1102,12 +1102,21 @@ function localReceiverTypePatterns(language: Language, r: string): RegExp[] {
|
|||||||
case 'rust':
|
case 'rust':
|
||||||
return [
|
return [
|
||||||
new RegExp(`\\blet\\s+(?:mut\\s+)?${r}\\b(?:\\s*:[^=]+)?=\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // let lg = Logger::new()/Logger{}/Logger
|
new RegExp(`\\blet\\s+(?:mut\\s+)?${r}\\b(?:\\s*:[^=]+)?=\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // let lg = Logger::new()/Logger{}/Logger
|
||||||
new RegExp(`\\blet\\s+(?:mut\\s+)?${r}\\s*:\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // let lg: Logger
|
// No `let`, so this covers a `let lg: Logger` binding AND a typed
|
||||||
|
// parameter (`fn use(lg: &Logger)`, a closure `|lg: Logger|`) — the
|
||||||
|
// parameter case the old `let`-anchored pattern excluded (#1125).
|
||||||
|
new RegExp(`\\b${r}\\s*:\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // lg: Logger (binding or typed param)
|
||||||
];
|
];
|
||||||
case 'go':
|
case 'go':
|
||||||
return [
|
return [
|
||||||
new RegExp(`\\b${r}\\b\\s*:=\\s*&?([A-Za-z_][\\w.]*)\\s*{`), // lg := Logger{} / &Logger{}
|
new RegExp(`\\b${r}\\b\\s*:=\\s*&?([A-Za-z_][\\w.]*)\\s*{`), // lg := Logger{} / &Logger{}
|
||||||
new RegExp(`\\bvar\\s+${r}\\s+\\*?([A-Za-z_][\\w.]*)`), // var lg Logger / *Logger
|
new RegExp(`\\bvar\\s+${r}\\s+\\*?([A-Za-z_][\\w.]*)`), // var lg Logger / *Logger
|
||||||
|
// A typed parameter / method receiver (`func use(lg Logger)`,
|
||||||
|
// `func (l Logger) M()`) — name-before-type with no `var`/`:=` (#1125).
|
||||||
|
// PascalCase-guarded (unlike the anchored patterns above) to keep the
|
||||||
|
// keyword-free `ident Type` shape from matching unrelated pairs; the
|
||||||
|
// enclosing-scope bound already excludes package-level struct fields.
|
||||||
|
new RegExp(`\\b${r}\\s+\\*?([A-Z][\\w.]*)`), // func use(lg Logger) / (l Logger)
|
||||||
];
|
];
|
||||||
case 'ruby':
|
case 'ruby':
|
||||||
return [
|
return [
|
||||||
@@ -1121,11 +1130,18 @@ function localReceiverTypePatterns(language: Language, r: string): RegExp[] {
|
|||||||
case 'dart':
|
case 'dart':
|
||||||
return [
|
return [
|
||||||
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // var lg = Logger(...)
|
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // var lg = Logger(...)
|
||||||
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;]`), // Logger lg = ...
|
// Trailing `[=;,)]` (not just `[=;]`) so a typed parameter — `Logger lg)`
|
||||||
|
// / `Logger lg,` — matches too, not only `Logger lg = ...` / `Logger lg;`
|
||||||
|
// (#1125). Mirrors Java/C#.
|
||||||
|
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg = ... / param
|
||||||
];
|
];
|
||||||
case 'php':
|
case 'php':
|
||||||
return [
|
return [
|
||||||
new RegExp(`\\$?${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $lg = new Logger()
|
new RegExp(`\\$?${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $lg = new Logger()
|
||||||
|
// A typed parameter (`function use(Logger $lg)`, `?Logger $lg`,
|
||||||
|
// `\\App\\Logger $lg`, `&$lg` by-ref) and a typed `catch (E $e)` — the
|
||||||
|
// type sits before the `$`-variable (#1125). Namespace `\\` allowed.
|
||||||
|
new RegExp(`\\b([A-Za-z_\\\\][\\w\\\\]*)\\s+&?\\$${r}\\b`), // Logger $lg (typed param)
|
||||||
];
|
];
|
||||||
case 'lua':
|
case 'lua':
|
||||||
case 'luau':
|
case 'luau':
|
||||||
|
|||||||
Reference in New Issue
Block a user