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
@@ -2525,32 +2525,36 @@ export class TreeSitterExtractor {
|
||||
calleeName = methodName;
|
||||
}
|
||||
} else if (
|
||||
(this.language === 'cpp' || this.language === 'c' || this.language === 'kotlin') &&
|
||||
(this.language === 'cpp' ||
|
||||
this.language === 'c' ||
|
||||
this.language === 'kotlin' ||
|
||||
this.language === 'swift') &&
|
||||
receiver &&
|
||||
receiver.type === 'call_expression'
|
||||
) {
|
||||
// Receiver that is itself a call — `Foo::instance().bar()`,
|
||||
// `openSession()->run()`, `mgr.view().render()` (C/C++), or
|
||||
// `Foo.getInstance().bar()` (Kotlin). Keep the inner call so
|
||||
// resolution can infer bar()'s class from what the inner call
|
||||
// RETURNS (#645/#608). Encode as `<innerCallee>().<method>`; the
|
||||
// `().` marker never appears in an ordinary ref, so the resolver
|
||||
// `Foo.getInstance().bar()` (Kotlin) / `Foo.make().draw()` (Swift).
|
||||
// Keep the inner call so resolution can infer bar()'s class from what
|
||||
// the inner call RETURNS (#645/#608). Encode as `<innerCallee>().<method>`;
|
||||
// the `().` marker never appears in an ordinary ref, so the resolver
|
||||
// can detect and split it. Other languages keep the bare-name
|
||||
// behavior (dropping the receiver) below.
|
||||
let innerCallee: string;
|
||||
let reencode: boolean;
|
||||
if (this.language === 'kotlin') {
|
||||
// tree-sitter-kotlin has no field names — the inner callee is the
|
||||
if (this.language === 'kotlin' || this.language === 'swift') {
|
||||
// tree-sitter-kotlin/swift expose the inner callee as the
|
||||
// call_expression's first named child (a navigation_expression
|
||||
// `Foo.getInstance`, or a bare identifier for a free call).
|
||||
// `Foo.getInstance`, or a bare identifier for a free/constructor call).
|
||||
const innerNav = receiver.namedChild(0);
|
||||
innerCallee = innerNav ? getNodeText(innerNav, this.source).replace(/\s+/g, '') : '';
|
||||
// Only re-encode a CLASS / companion-factory chain, whose receiver
|
||||
// chain starts with a capitalized type (`Foo.getInstance().bar()`).
|
||||
// An instance chain (`list.filter{}.map{}`) has a lowercase receiver
|
||||
// whose type we can't recover here — re-encoding it would only drop
|
||||
// the edge (no chain resolution, no bare-name fallback), regressing
|
||||
// recall in fluent codebases. Leave those to the bare-name path.
|
||||
// Only re-encode a CLASS / companion-factory / constructor chain,
|
||||
// whose receiver chain starts with a capitalized type
|
||||
// (`Foo.getInstance().bar()`, `Foo().bar()`). An instance chain
|
||||
// (`list.filter{}.map{}`) has a lowercase receiver whose type we
|
||||
// can't recover here — re-encoding it would only drop the edge (no
|
||||
// chain resolution, no bare-name fallback), regressing recall in
|
||||
// fluent codebases. Leave those to the bare-name path.
|
||||
reencode = /^[A-Z]/.test(innerCallee);
|
||||
} else {
|
||||
const innerFn = getChildByField(receiver, 'function');
|
||||
|
||||
Reference in New Issue
Block a user