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
@@ -2482,6 +2482,33 @@ export class TreeSitterExtractor {
|
||||
} else {
|
||||
calleeName = methodName;
|
||||
}
|
||||
} else if (receiverField && receiverField.type === 'message_expression' && /^\w+$/.test(methodName)) {
|
||||
// Chained message send `[[Foo create] doIt]` — the receiver is itself a
|
||||
// class message. Recover the inner `Class.selector` and encode
|
||||
// `Class.selector().doIt` so resolution infers doIt's class from what
|
||||
// `Class.selector` RETURNS (#645/#608). Only a CLASS-factory chain
|
||||
// (capitalized inner receiver); a unary outer selector is required
|
||||
// because the chain resolver's method part is `\w+` (no `:`). An
|
||||
// instance chain (`[[obj foo] bar]`, lowercase inner) stays bare.
|
||||
const innerRecv = getChildByField(receiverField, 'receiver');
|
||||
const innerRecvName = innerRecv ? getNodeText(innerRecv, this.source) : '';
|
||||
if (innerRecv?.type === 'identifier' && /^[A-Z]/.test(innerRecvName)) {
|
||||
const innerKw: string[] = [];
|
||||
for (let i = 0; i < receiverField.namedChildCount; i++) {
|
||||
if (receiverField.fieldNameForNamedChild(i) === 'method') {
|
||||
const kw = receiverField.namedChild(i);
|
||||
if (kw) innerKw.push(getNodeText(kw, this.source));
|
||||
}
|
||||
}
|
||||
let innerColon = false;
|
||||
for (let i = 0; i < receiverField.childCount; i++) {
|
||||
if (receiverField.child(i)?.type === ':') { innerColon = true; break; }
|
||||
}
|
||||
const innerSelector = innerColon ? innerKw.map((k) => `${k}:`).join('') : innerKw[0];
|
||||
calleeName = innerSelector ? `${innerRecvName}.${innerSelector}().${methodName}` : methodName;
|
||||
} else {
|
||||
calleeName = methodName;
|
||||
}
|
||||
} else {
|
||||
calleeName = methodName;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user