A method called through a PHP fluent static factory — `ApiClient::for($c)->createOrder()`, the canonical Laravel per-credential/per-tenant client idiom — produced no `calls` edge: the receiver of `->createOrder` is the `Cls::for(...)` static call, whose result type was never recovered, so the edge was dropped and `codegraph_callers` returned nothing. Same shape as the C++ singleton/factory fix (#645), reusing its return_type column + the chained-call mechanism: - Capture PHP return types (getReturnType): `: self` / `: static` / `$this` stored as the `self` marker, a concrete `: Type` as its short name, primitives/unions dropped. - Encode the chained scoped-call receiver as `Cls::for().method` so the resolver can split it (PHP-gated, in extractCall). - New matchPhpCallChain: look up the factory's return type (`self` → the factory's own class; concrete → that class), then resolve AND validate the method on it — a wrong inference yields no edge, never a wrong one. EXTRACTION_VERSION 4->5 (re-index to populate PHP return types + chained edges). Validated on koel (1383 PHP files): node count identical (no explosion), 0 edges lost, +80 chained-call edges recovered; synthetic tests cover the self-factory, concrete-return, namespace, decoy, and absent-method cases. 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
75ae1e8bd9
commit
eb5960b535
@@ -2406,6 +2406,30 @@ end
|
||||
});
|
||||
});
|
||||
|
||||
describe('PHP return type capture (#608)', () => {
|
||||
it('captures self/static factory returns as the `self` marker; primitives as undefined', () => {
|
||||
const code = `<?php
|
||||
class ApiClient {
|
||||
public static function for(string $c): self { return new self; }
|
||||
public static function make(): static { return new static; }
|
||||
public function send(array $p): array { return []; }
|
||||
}`;
|
||||
const result = extractFromSource('ApiClient.php', code);
|
||||
expect(result.nodes.find((n) => n.name === 'for' && n.kind === 'method')?.returnType).toBe('self');
|
||||
expect(result.nodes.find((n) => n.name === 'make' && n.kind === 'method')?.returnType).toBe('self');
|
||||
// `array` is not a class to chain on → no return type recorded.
|
||||
expect(result.nodes.find((n) => n.name === 'send' && n.kind === 'method')?.returnType).toBeUndefined();
|
||||
});
|
||||
|
||||
it('captures a concrete return type as its short class name', () => {
|
||||
const code = `<?php
|
||||
namespace App;
|
||||
class WidgetFactory { public static function make(): Widget { return new Widget(); } }`;
|
||||
const result = extractFromSource('WidgetFactory.php', code);
|
||||
expect(result.nodes.find((n) => n.name === 'make' && n.kind === 'method')?.returnType).toBe('Widget');
|
||||
});
|
||||
});
|
||||
|
||||
describe('C/C++ return type capture (#645)', () => {
|
||||
it('captures the normalized return type of a C++ method/function', () => {
|
||||
const code = `
|
||||
|
||||
Reference in New Issue
Block a user