packages/deriver/tests/Unit/Deriver/Evaluator/CallEvaluationTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator;
6
7use Deriver\Deriver\Contributions;
8use Deriver\Deriver\DeriverConfig;
9use Deriver\Deriver\Evaluator\Call\CallDecision;
10use Deriver\Deriver\Evaluator\Call\CallDescription;
11use Deriver\Deriver\Evaluator\Call\CallDescriptor;
12use Deriver\Deriver\Evaluator\Call\CallEvaluator;
13use Deriver\Deriver\Evaluator\Call\Evaluators;
14use Deriver\Deriver\Evaluator\Call\Plan\Action;
15use Deriver\Deriver\Evaluator\Call\Plan\Expression;
16use Deriver\Deriver\Evaluator\Call\Plan\SemanticPlan;
17use Deriver\Deriver\Evaluator\Call\Registrations;
18use Deriver\Deriver\Evaluator\CallEvaluation;
19use Deriver\Deriver\Evaluator\Declaration\Parameter;
20use Deriver\Deriver\Evaluator\Declaration\Signature;
21use Deriver\Deriver\Evaluator\Enumeration\Choices;
22use Deriver\Deriver\Evaluator\Evaluator;
23use Deriver\Deriver\Evaluator\EvaluatorConfig;
24use Deriver\Deriver\Evaluator\Evidence\Frontier;
25use Deriver\Deriver\Evaluator\Index\BodyIndex;
26use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
27use Deriver\Deriver\Evaluator\Micro\Request;
28use Deriver\Deriver\Evaluator\Request\Budget;
29use Deriver\Deriver\Evaluator\Request\ReturnQuery;
30use Deriver\Deriver\Evaluator\Scalar\Identity;
31use Deriver\Deriver\Evaluator\Scope\Context;
32use Deriver\Deriver\Evaluator\Scope\Frame;
33use Deriver\Deriver\Evaluator\TargetProfile;
34use Deriver\Deriver\Evaluator\Term;
35use Deriver\Deriver\Lowerer\Declaration\Index;
36use Deriver\Deriver\MicroEvaluatorConversion;
37use Deriver\Deriver\Parser\Node\File;
38use Deriver\Deriver\Parser\Parser;
39use Deriver\Deriver\Parser\ParserConfig;
40use Deriver\Deriver\Providers\ProviderExpansion;
41use Deriver\Deriver\Publication\CandidateQuery;
42use Deriver\Deriver\Resolver\Resolver;
43use Deriver\Deriver\SnapshotIdentity;
44use Deriver\Deriver\Sources\Path;
45use Deriver\Deriver\Sources\Source;
46use Deriver\Deriver\Sources\Sources;
47use PHPUnit\Framework\Attributes\CoversNothing;
48use PHPUnit\Framework\TestCase;
49
50#[CoversNothing]
51final class CallEvaluationTest extends TestCase
52{
53    public function testEvaluateReplacesSourceWithoutDemandingUnusedInputs(): void
54    {
55        $value = Expression::literal(Term::constant(7));
56        $source = 'function heavy($input,$unused){return missing();}function target(){return heavy(missing(),missing());}';
57        $configuration = new DeriverConfig();
58        $budget = new Budget();
59        $contributions = new Contributions(evaluators: [new class ($value) implements CallEvaluator {
60            /**
61             * Captures the dependencies used by this component.
62             */
63            public function __construct(private readonly Expression $value)
64            {
65            }
66            /**
67             * Verifies descriptor.
68             */
69            public function descriptor(): CallDescriptor
70            {
71                return new CallDescriptor('heavy.override', '1', 'heavy', new Signature([new Parameter('input'), new Parameter('unused')]));
72            }
73            /**
74             * Verifies describe.
75             */
76            public function evaluate(CallDescription $call): CallDecision
77            {
78                return CallDecision::handled(new SemanticPlan([Action::returns($this->value)]));
79            }
80        }]);
81        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
82        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
83        $contributions1 = $providers->contributions;
84        $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
85        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
86        $snapshotId = 'test';
87        $input = $providers->input;
88        $parser = new Parser(new ParserConfig($target->version));
89        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input, false));
90        $program = new Index($snapshotId, $trees);
91        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
92        $symbol = 'target';
93        $graph = $e->context->index->graph($symbol);
94        self::assertNotNull($graph);
95        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
96        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
97            [$value1, $_] = $alternative;
98            $values[(new Identity())->key($value1)] = $value1;
99            return $values;
100        }, []);
101        self::assertCount(1, $values);
102        $resolvedValue = array_values($values)[0];
103        self::assertInstanceOf(Term::class, $resolvedValue);
104        self::assertSame(7, $resolvedValue->native());
105        self::assertSame(0, $e->context->bodyExpansions);
106    }
107    public function testEvaluateSelectsBuiltInFunctionSemantics(): void
108    {
109        $source = 'function target(){return strlen("abc");}';
110        $configuration = new DeriverConfig();
111        $budget = new Budget();
112        $contributions = new Contributions();
113        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
114        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
115        $contributions1 = $providers->contributions;
116        $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
117        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
118        $snapshotId = 'test';
119        $input = $providers->input;
120        $parser = new Parser(new ParserConfig($target->version));
121        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input, false));
122        $program = new Index($snapshotId, $trees);
123        $engine = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
124        $symbol = 'target';
125        $graph = $engine->context->index->graph($symbol);
126        self::assertNotNull($graph);
127        $frame = new Frame($graph, 'source:' . $symbol);
128        $operation = 'invoke';
129        $instruction = array_values(array_filter($frame->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
130        self::assertNotNull($instruction);
131        $request = new Request($engine, $frame, $instruction, 20, 'strlen');
132        $body = (new CallEvaluation($engine))->evaluate($request);
133        self::assertInstanceOf(BodyIndex::class, $body);
134    }
135    public function testActualBindsNamedArguments(): void
136    {
137        $source = 'function helper($a,$b){return $b;}function target(){return helper(b:7,a:9);}';
138        $configuration = new DeriverConfig();
139        $budget = new Budget();
140        $contributions = new Contributions();
141        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
142        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
143        $contributions1 = $providers->contributions;
144        $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
145        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
146        $snapshotId = 'test';
147        $input = $providers->input;
148        $parser = new Parser(new ParserConfig($target->version));
149        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input, false));
150        $program = new Index($snapshotId, $trees);
151        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
152        $symbol = 'target';
153        $graph = $e->context->index->graph($symbol);
154        self::assertNotNull($graph);
155        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
156        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
157            [$value, $_] = $alternative;
158            $values[(new Identity())->key($value)] = $value;
159            return $values;
160        }, []);
161        self::assertCount(1, $values);
162        $resolvedValue = array_values($values)[0];
163        self::assertInstanceOf(Term::class, $resolvedValue);
164        self::assertSame(7, $resolvedValue->native());
165    }
166    public function testDemandUsesActualBindingsBeforeSelectingAPlan(): void
167    {
168        $source = 'function target(){return count([1,2,3]);}';
169        $configuration = new DeriverConfig();
170        $budget = new Budget();
171        $contributions = new Contributions();
172        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
173        $prepared1Configuration = $configuration;
174        $prepared1Contributions = $contributions;
175        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
176        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
177        $contributions1 = $prepared1Providers->contributions;
178        $prepared1Evaluators = new Evaluators(new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $prepared1Configuration->standardEvaluators);
179        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
180        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
181        $snapshotId = $prepared1Identity->id;
182        $input = $prepared1Providers->input;
183        $parser = new Parser(new ParserConfig($prepared1Target->version));
184        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input, false));
185        $prepared1Program = new Index($snapshotId, $trees);
186        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
187        self::assertSame(3, (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget))[0]->result);
188    }
189}
190