Completes Swift in the #750 chained-call series (after Java #751, Kotlin #752, C# #753, conformance #754). Two parts: 1. Swift chained-call resolution (the #645/#608 mechanism): capture Swift return types (positional, member types -> last segment), encode capitalized-receiver chains `Foo.make().draw()` / `Foo(args).draw()`, resolve+validate via the shared matchDottedCallChain (+ constructor branch). Fixes the decoy wrong-edge bug where a chained method dropped to a bare name and attached to a same-named method on an unrelated class. 2. Nested-type extension naming fix: `extension KF.Builder: KFOptionSetter` parsed as a class_declaration named `KF.Builder` (dot) — inconsistent with the type's own declaration `KF::Builder` (name `Builder`) — so the extension's conformances and members were invisible to a chained call on the type. A Swift resolveName now names a nested-type extension by its last segment (`Builder`), so its `implements`/`extends` edges and methods are found by the supertype walk (conformance #754) and the simple-name method match. Validated: synthetic decoy + args + constructor + absent-method tests; full suite green; nested-extension repro (`KF.url().onSuccess()` resolves via conformance to the protocol method). Real-repo A/B vs main (conformance) — Alamofire and Kingfisher both **0 added / 0 removed, node count unchanged**: NEUTRAL and SAFE. The prior -168 Kingfisher regression (from the naming inconsistency) is eliminated; Swift's unique-named fluent methods already resolved by bare name, so the chain path lands the same edges — the value here is decoy-collision correctness, the nested-extension naming fix, and consistency with the other four languages. EXTRACTION_VERSION 9 -> 10. 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
48d4654e8d
commit
7c7f0dd56f
@@ -2403,6 +2403,72 @@ class Caller {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Swift chained static-factory call resolution (#645/#608 mechanism)', () => {
|
||||
function callerNamesOf(qualifiedName: string): string[] {
|
||||
const target = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName);
|
||||
if (!target) return [];
|
||||
const names = cg
|
||||
.getIncomingEdges(target.id)
|
||||
.filter((e) => e.kind === 'calls')
|
||||
.map((e) => cg.getNode(e.source)?.name)
|
||||
.filter((n): n is string => !!n);
|
||||
return [...new Set(names)].sort();
|
||||
}
|
||||
|
||||
it('resolves Foo.make().draw() via the factory return type, never a same-named decoy', async () => {
|
||||
// Aaa sorts first and has a same-named draw() — without the fix Swift dropped
|
||||
// the receiver to a bare `draw` and attached to Aaa (a wrong edge).
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'Main.swift'),
|
||||
`class Aaa { func draw() {} }
|
||||
class Foo {
|
||||
static func make() -> Foo { return Foo() }
|
||||
func draw() {}
|
||||
}
|
||||
func runCaller() { Foo.make().draw() }
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
expect(callerNamesOf('Foo::draw')).toEqual(['runCaller']);
|
||||
expect(callerNamesOf('Aaa::draw')).toEqual([]);
|
||||
});
|
||||
|
||||
it('resolves a constructor chain Foo().draw() and an args factory chain Foo.build(c).render()', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'Main.swift'),
|
||||
`class Config {}
|
||||
class Foo {
|
||||
static func build(_ c: Config) -> Foo { return Foo() }
|
||||
func draw() {}
|
||||
func render() {}
|
||||
}
|
||||
func runCaller() {
|
||||
Foo().draw()
|
||||
Foo.build(Config()).render()
|
||||
}
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
expect(callerNamesOf('Foo::draw')).toEqual(['runCaller']);
|
||||
expect(callerNamesOf('Foo::render')).toEqual(['runCaller']);
|
||||
});
|
||||
|
||||
it('creates NO edge when the factory return type lacks the method (silent miss, not a wrong edge)', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'Main.swift'),
|
||||
`class Foo {
|
||||
static func make() -> Foo { return Foo() }
|
||||
}
|
||||
class Other { func onlyOther() {} }
|
||||
func runCaller() { Foo.make().onlyOther() }
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
// Foo has no onlyOther() — must not mis-attach to the same-named Other::onlyOther.
|
||||
expect(callerNamesOf('Other::onlyOther')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Chained call resolves a method on a supertype (conformance, #750)', () => {
|
||||
function callerNamesOf(qualifiedName: string): string[] {
|
||||
const target = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName);
|
||||
|
||||
Reference in New Issue
Block a user