Ports the #645/#608 chained-receiver mechanism to Objective-C. A message send whose receiver is itself a message send — `[[Foo create] doIt]` — used to drop the receiver, so `doIt` name-matched a same-named method on an unrelated class (commonly a test helper's `init` or an Apple-SDK method). - objc.ts: getReturnType reads the method's `method_type`, SKIPPING nullability / ARC qualifiers (`nonnull instancetype` must yield instancetype, not `nonnull`). - tree-sitter.ts: the message_expression branch now re-encodes a chained send `[[Foo create] doIt]` as `Foo.create().doIt` when the inner receiver is a capitalized class and the outer selector is unary. - name-matcher.ts: `objc` joins the dotted-chain gate + CHAIN_LANGUAGES. A class-message factory returns an instance of the RECEIVER class by convention (`instancetype`), so when the factory's own return type isn't recoverable (`alloc`/`new`/`shared…` return instancetype, or aren't user nodes), the receiver's type is the class itself — this resolves the ubiquitous `[[X alloc] init]` and singleton chains. resolveMethodOnType validates against the class and its supertypes, so a wrong inference yields no edge. Validation: 4 synthetic tests (factory+decoy, superclass conformance, absent-method safety, the nonnull-instancetype singleton). Real-repo A/B on SDWebImage (208 files): +35 / -75 — all corrections (the -75 are wrong `init` mis-matches to a test helper / wrong class, retargeted to the right class's init in the +35, plus 2 Apple-SDK chains on unindexed classes). db stable, no node explosion. EXTRACTION_VERSION 14->15. Full suite green. 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
16c73e2b0e
commit
d21d2dfa50
@@ -37,7 +37,7 @@ const SUPERTYPE_BEARING_KINDS = new Set<Node['kind']>([
|
||||
* second pass. Dotted-receiver languages resolve via matchDottedCallChain; the
|
||||
* `::`-receiver ones (Rust) via matchScopedCallChain.
|
||||
*/
|
||||
const CHAIN_LANGUAGES = new Set(['java', 'kotlin', 'csharp', 'swift', 'rust', 'go', 'scala', 'dart']);
|
||||
const CHAIN_LANGUAGES = new Set(['java', 'kotlin', 'csharp', 'swift', 'rust', 'go', 'scala', 'dart', 'objc']);
|
||||
const SCOPED_CHAIN_LANGUAGES = new Set(['rust']);
|
||||
|
||||
/** The extractor's chained-receiver encoding: `<inner>().<method>`. */
|
||||
|
||||
@@ -673,7 +673,23 @@ export function matchDottedCallChain(
|
||||
const factoryMethod = inner.slice(lastDot + 1);
|
||||
if (!factoryClass || !factoryMethod) return null;
|
||||
const ret = lookupCalleeReturnType(`${factoryClass}::${factoryMethod}`, ref, context);
|
||||
if (!ret) return null;
|
||||
if (!ret) {
|
||||
// Objective-C: a class-message factory — `[X alloc]`, `[X new]`,
|
||||
// `[X sharedFoo]` — returns an instance of the RECEIVER class `X` by
|
||||
// convention (`instancetype`). So when the factory's own return type isn't
|
||||
// recoverable (its selector returns `instancetype`, or `alloc`/`new` aren't
|
||||
// user-defined nodes at all), the receiver's type is the class `X` itself.
|
||||
// This resolves the ubiquitous `[[X alloc] init]` and singleton chains.
|
||||
// resolveMethodOnType validates against X (and its supertypes), so a class
|
||||
// whose method actually lives elsewhere yields NO edge, not a wrong one — and
|
||||
// crucially this does NOT fire when a concrete return type WAS captured but
|
||||
// simply lacks the method (that already returned null above: absent-method
|
||||
// safety, so a same-named decoy is still never matched).
|
||||
if (ref.language === 'objc' && /^[A-Z]/.test(factoryClass)) {
|
||||
return resolveMethodOnType(factoryClass, method, ref, context, 0.8, 'instance-method', importedFqnOf(factoryClass, ref, context));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return resolveMethodOnType(ret, method, ref, context, 0.85, 'instance-method', importedFqnOf(ret, ref, context));
|
||||
}
|
||||
|
||||
@@ -1123,11 +1139,12 @@ export function matchReference(
|
||||
}
|
||||
|
||||
// 1d. Dotted chained static-factory / fluent call (Java / Kotlin / C# / Swift /
|
||||
// Go / Scala / Dart) — `Foo.getInstance().bar()` encoded as `Foo.getInstance().bar`,
|
||||
// Go's bare-factory `New().Method()` as `New().Method`, Scala's companion factory
|
||||
// `Foo.create().bar()`, or Dart's static factory / factory-constructor
|
||||
// `Foo.create().bar()` (#645/#608 mechanism). Resolve the method's class from the
|
||||
// inner call's declared return type, then validate it.
|
||||
// Go / Scala / Dart / Objective-C) — `Foo.getInstance().bar()` encoded as
|
||||
// `Foo.getInstance().bar`, Go's bare-factory `New().Method()` as `New().Method`,
|
||||
// Scala's companion factory, Dart's static factory / factory-constructor, or
|
||||
// ObjC's chained message send `[[Foo create] doIt]` encoded as `Foo.create().doIt`
|
||||
// (#645/#608 mechanism). Resolve the method's class from the inner call's
|
||||
// declared return type, then validate it.
|
||||
if (
|
||||
ref.language === 'java' ||
|
||||
ref.language === 'kotlin' ||
|
||||
@@ -1135,7 +1152,8 @@ export function matchReference(
|
||||
ref.language === 'swift' ||
|
||||
ref.language === 'go' ||
|
||||
ref.language === 'scala' ||
|
||||
ref.language === 'dart'
|
||||
ref.language === 'dart' ||
|
||||
ref.language === 'objc'
|
||||
) {
|
||||
result = matchDottedCallChain(ref, context);
|
||||
if (result) return result;
|
||||
|
||||
Reference in New Issue
Block a user