packages/deriver/tests/Integration/CallableReplacementTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Integration;
6
7use Deriver\Deriver\Candidate\Snapshot;
8use Deriver\Deriver\Evaluation;
9use Deriver\Deriver\EvaluationOperation;
10use Deriver\Deriver\Evaluator\Call\Evaluators;
11use Deriver\Deriver\Evaluator\EvaluatorConfig;
12use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
13use Deriver\Deriver\Evaluator\Request\ReturnQuery;
14use Deriver\Deriver\Evaluator\TargetProfile;
15use Deriver\Deriver\Evaluator\Term;
16use Deriver\Deriver\Lowerer\Declaration\Index;
17use Deriver\Deriver\MicroEvaluator;
18use Deriver\Deriver\MicroEvaluatorConversion;
19use Deriver\Deriver\Parser\Parser;
20use Deriver\Deriver\Parser\ParserConfig;
21use Deriver\Deriver\Publication\CandidateQuery;
22use Deriver\Deriver\Resolver\Resolver;
23use Deriver\Deriver\Sources\Source;
24use PHPUnit\Framework\Attributes\CoversNothing;
25use PHPUnit\Framework\Attributes\DataProvider;
26use PHPUnit\Framework\TestCase;
27
28#[CoversNothing]
29final class CallableReplacementTest extends TestCase
30{
31    /**
32     * @return iterable<string, array{string, string, string}>
33     */
34    public static function providerCalls(): iterable
35    {
36        yield 'direct function' => ['function subject($unused){return expensive();}', 'subject', 'subject'];
37        yield 'direct static method' => ['class Subject{static function run($unused){return expensive();}}', 'Subject::run', 'Subject::run'];
38        yield 'direct instance method' => ['class Subject{function run($unused){return expensive();}}', 'Subject::run', 'Subject::run'];
39        yield 'function call' => ['function replacement($unused){return expensive();} function subject(){return replacement(expensive());}', 'subject', 'replacement'];
40        yield 'static method call' => ['class Subject{static function run($unused){return expensive();}} function subject(){return Subject::run(expensive());}', 'subject', 'Subject::run'];
41        yield 'instance method call' => ['class Subject{function run($unused){return expensive();}} function subject(){return (new Subject())->run(expensive());}', 'subject', 'Subject::run'];
42        yield 'conditional definition' => ['if(condition()){function subject(){return expensive();}}else{function subject(){return expensive();}}', 'subject', 'subject'];
43    }
44
45    #[DataProvider('providerCalls')]
46    public function testReplacesTheSelectedCallableBeforeItsBody(string $source, string $symbol, string $replaced): void
47    {
48        $evaluators = [new MicroEvaluator('replace', '1', EvaluationOperation::Function, $replaced, static fn (): Term => Term::constant(42))];
49        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('branches.php', '<?php ' . $source)));
50        $program = new Index('branches', [$tree]);
51        $target = new TargetProfile();
52        $settings = new EvaluatorConfig($target, evaluators: array_map((new MicroEvaluatorConversion())->convert(...), $evaluators));
53        $query = new CandidateQuery(new DefinitionIndex($program), $settings, new Evaluators($target), new Snapshot('branches', [], [], $target, 'environment'));
54        $result = $query->derive(new ReturnQuery($symbol));
55        self::assertSame([42], array_map(static fn ($candidate) => $candidate->result, iterator_to_array($result)));
56        self::assertSame([], $result->statistics->expandedBodies);
57        self::assertArrayNotHasKey($replaced . ':$unused', $result->statistics->expandedReferences);
58    }
59
60    /**
61     * @return iterable<string, array{int|string}>
62     */
63    public static function providerParameters(): iterable
64    {
65        yield 'position' => [0];
66        yield 'name' => ['value'];
67    }
68
69    #[DataProvider('providerParameters')]
70    public function testDirectReplacementDemandsOnlyItsSelectedParameter(int|string $key): void
71    {
72        $source = 'function subject($value,$unused){return expensive();} function caller(){return subject(42,expensive());}';
73        $evaluators = [new MicroEvaluator('replace', '1', EvaluationOperation::Function, 'subject', static fn (Evaluation $evaluation): Term => $evaluation->input($key))];
74        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('branches.php', '<?php ' . $source)));
75        $program = new Index('branches', [$tree]);
76        $target = new TargetProfile();
77        $settings = new EvaluatorConfig($target, evaluators: array_map((new MicroEvaluatorConversion())->convert(...), $evaluators));
78        $query = new CandidateQuery(new DefinitionIndex($program), $settings, new Evaluators($target), new Snapshot('branches', [], [], $target, 'environment'));
79        $result = $query->derive(new ReturnQuery('subject'));
80        self::assertSame([42], array_map(static fn ($candidate) => $candidate->result, iterator_to_array($result)));
81        self::assertSame([], $result->statistics->expandedBodies);
82    }
83}
84