A call whose receiver is itself a call — a factory / singleton / builder that returns an object — should produce a calls edge to the chained method:
Foo.getInstance().bar(); // bar() should resolve to Foo::bar
This was fixed for C++ (#645) and PHP (#608). A probe of the other statically-typed README languages found the same gap in all of them — and in 7 of the 9 the chained method currently resolves to the wrong class (a same-named method on an unrelated type), which is a correctness bug, not just missing coverage.
The fix is the 3-part #645/#608 mechanism, per language: capture the factory's declared return type, preserve the chained receiver at extraction, then resolve and validate the chained method on the inferred type — so a wrong inference produces no edge, never a wrong one.
| Language |
Probe result (Foo.factory().method()) |
Status |
| Java |
edge missing |
✅ #751 |
| Kotlin |
wrong edge → same-named decoy |
✅ #752 |
| C# |
edge missing |
✅ #753 |
| Swift |
wrong edge → same-named decoy |
✅ #755 |
| Rust |
wrong edge → same-named decoy |
✅ #757 |
| Go |
wrong edge → same-named decoy (same-package New().Method(); cross-package pkg.New() was #469) |
✅ #760 |
| Scala |
wrong edge → same-named decoy |
✅ #761 |
| Dart |
wrong edge → same-named decoy (factory-constructor call also unresolved) |
✅ #762 |
| TypeScript |
wrong edge → same-named decoy |
⏭️ evaluated, skipped (see note) |
Dynamically-typed languages can't use this approach (no declared return types to read): Ruby and plain JS are out of scope; Python is partial (optional -> T hints, see #578).
Each language is validated the same way before merge: synthetic decoy + absent-method safety tests, then a real-repo A/B on a public OSS repo (node count unchanged, 0 edges lost, +N chained edges recovered, precision spot-checked).
TypeScript — evaluated and consciously skipped
The deterministic chain mechanism was fully implemented and probe-validated for TS (5 synthetic tests passed, including decoy-collision precision and a no-regression instance-chain guard), but real-repo A/B showed a net recall regression, so it was not shipped:
| Repo |
A/B (unique calls edges) |
| typeorm (495 files) |
+0 / −6 |
| nestjs/nest (600 files) |
+0 / −164 |
Why TS is different from the other 8 languages. The mechanism resolves a chained call from the factory's declared return type. TS code leans heavily on type inference — e.g. NestJS's Test.createTestingModule(metadata) { return new TestingModuleBuilder(...) } has no : TestingModuleBuilder annotation — so the factory's return type can't be recovered, the re-encoded chain can't resolve, and it drops the bare-name edge the existing resolver found. A bare-name fallback (the Go-fallback pattern, runaway-safe) recovered most but not all of them; ambiguous same-named methods across files (compile on both ModuleCompiler and TestingModuleBuilder) still resolved differently than baseline. The removed edges were mostly wrong (baseline mis-resolved .compile() to ModuleCompiler::compile), so it's precision-positive but recall-negative — the opposite of the recall-first invariant.
Zero added edges on both real repos confirms the marginal value is near-nil on real TS: method names are unique enough that the existing bare-name resolution already lands them, and the strict return-type annotations the mechanism needs are too often absent. The synthetic decoy win is real but doesn't occur in practice.
Conclusion: 8 of 9 statically-typed languages + conformance shipped (#751, #752, #753, #754, #755, #757, #760, #761, #762). TypeScript is the one language where gradual typing makes the deterministic chain mechanism a net loss, so it is intentionally not shipped.
A call whose receiver is itself a call — a factory / singleton / builder that returns an object — should produce a
callsedge to the chained method:This was fixed for C++ (#645) and PHP (#608). A probe of the other statically-typed README languages found the same gap in all of them — and in 7 of the 9 the chained method currently resolves to the wrong class (a same-named method on an unrelated type), which is a correctness bug, not just missing coverage.
The fix is the 3-part #645/#608 mechanism, per language: capture the factory's declared return type, preserve the chained receiver at extraction, then resolve and validate the chained method on the inferred type — so a wrong inference produces no edge, never a wrong one.
Foo.factory().method())New().Method(); cross-packagepkg.New()was #469)Dynamically-typed languages can't use this approach (no declared return types to read): Ruby and plain JS are out of scope; Python is partial (optional
-> Thints, see #578).Each language is validated the same way before merge: synthetic decoy + absent-method safety tests, then a real-repo A/B on a public OSS repo (node count unchanged, 0 edges lost, +N chained edges recovered, precision spot-checked).
TypeScript — evaluated and consciously skipped
The deterministic chain mechanism was fully implemented and probe-validated for TS (5 synthetic tests passed, including decoy-collision precision and a no-regression instance-chain guard), but real-repo A/B showed a net recall regression, so it was not shipped:
callsedges)Why TS is different from the other 8 languages. The mechanism resolves a chained call from the factory's declared return type. TS code leans heavily on type inference — e.g. NestJS's
Test.createTestingModule(metadata) { return new TestingModuleBuilder(...) }has no: TestingModuleBuilderannotation — so the factory's return type can't be recovered, the re-encoded chain can't resolve, and it drops the bare-name edge the existing resolver found. A bare-name fallback (the Go-fallback pattern, runaway-safe) recovered most but not all of them; ambiguous same-named methods across files (compileon bothModuleCompilerandTestingModuleBuilder) still resolved differently than baseline. The removed edges were mostly wrong (baseline mis-resolved.compile()toModuleCompiler::compile), so it's precision-positive but recall-negative — the opposite of the recall-first invariant.Zero added edges on both real repos confirms the marginal value is near-nil on real TS: method names are unique enough that the existing bare-name resolution already lands them, and the strict return-type annotations the mechanism needs are too often absent. The synthetic decoy win is real but doesn't occur in practice.
Conclusion: 8 of 9 statically-typed languages + conformance shipped (#751, #752, #753, #754, #755, #757, #760, #761, #762). TypeScript is the one language where gradual typing makes the deterministic chain mechanism a net loss, so it is intentionally not shipped.