packages/deriver/tests/Unit/Deriver/Evaluator/Micro/RequestTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Micro;
6
7use Deriver\Deriver\Contributions;
8use Deriver\Deriver\DeriverConfig;
9use Deriver\Deriver\Evaluator\Call\Evaluators;
10use Deriver\Deriver\Evaluator\Call\Registrations;
11use Deriver\Deriver\Evaluator\Evaluator;
12use Deriver\Deriver\Evaluator\EvaluatorConfig;
13use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
14use Deriver\Deriver\Evaluator\Micro\Request;
15use Deriver\Deriver\Evaluator\Request\Budget;
16use Deriver\Deriver\Evaluator\Scope\Context;
17use Deriver\Deriver\Evaluator\Scope\Frame;
18use Deriver\Deriver\Evaluator\TargetProfile;
19use Deriver\Deriver\Evaluator\Term;
20use Deriver\Deriver\Lowerer\Declaration\Index;
21use Deriver\Deriver\MicroEvaluatorConversion;
22use Deriver\Deriver\Parser\Node\File;
23use Deriver\Deriver\Parser\Parser;
24use Deriver\Deriver\Parser\ParserConfig;
25use Deriver\Deriver\Providers\ProviderExpansion;
26use Deriver\Deriver\Resolver\Resolver;
27use Deriver\Deriver\Sources\Path;
28use Deriver\Deriver\Sources\Source;
29use Deriver\Deriver\Sources\Sources;
30use PHPUnit\Framework\Attributes\CoversNothing;
31use PHPUnit\Framework\TestCase;
32
33#[CoversNothing]
34final class RequestTest extends TestCase
35{
36    public function testInputMemoizesADemandedNamedArgumentWithoutOpeningItsSibling(): void
37    {
38        $source = 'function target(){return call(unused:missing(),name:42);}';
39        $configuration = new DeriverConfig();
40        $budget = new Budget();
41        $contributions = new Contributions();
42        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
43        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
44        $contributions1 = $providers->contributions;
45        $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);
46        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
47        $snapshotId = 'test';
48        $input = $providers->input;
49        $parser = new Parser(new ParserConfig($target->version));
50        $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));
51        $program = new Index($snapshotId, $trees);
52        $engine = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
53        $symbol = 'target';
54        $graph = $engine->context->index->graph($symbol);
55        self::assertNotNull($graph);
56        $frame = new Frame($graph, 'source:' . $symbol);
57        $calls = array_values(array_filter($frame->graph->definitions, static fn ($instruction): bool => $instruction->operation === 'invoke'));
58        $request = new Request($engine, $frame, $calls[1], 20, '\CALL');
59        self::assertSame('call', $request->name());
60        self::assertSame([], $request->inputs());
61        $first = $request->input('name');
62        self::assertSame(42, $first->native());
63        self::assertSame($first, $request->input('name'));
64        self::assertSame(['name' => $first], $request->inputs());
65        self::assertSame(0, $engine->context->referenceExpansions);
66    }
67    public function testNameNormalizesResolvedFunctionsWithoutChangingOperators(): void
68    {
69        $source = 'function target(){return 2+3;}';
70        $configuration = new DeriverConfig();
71        $budget = new Budget();
72        $contributions = new Contributions();
73        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
74        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
75        $contributions1 = $providers->contributions;
76        $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);
77        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
78        $snapshotId = 'test';
79        $input = $providers->input;
80        $parser = new Parser(new ParserConfig($target->version));
81        $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));
82        $program = new Index($snapshotId, $trees);
83        $engine = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
84        $symbol = 'target';
85        $graph = $engine->context->index->graph($symbol);
86        self::assertNotNull($graph);
87        $frame = new Frame($graph, 'source:' . $symbol);
88        $operation = 'binary';
89        $instruction1 = array_values(array_filter($frame->graph->definitions, static fn ($instruction1): bool => $instruction1->operation === $operation))[0] ?? null;
90        $instruction = $instruction1;
91        self::assertNotNull($instruction);
92        self::assertSame('+', (new Request($engine, $frame, $instruction, 20))->name());
93        self::assertSame('app\answer', (new Request($engine, $frame, $instruction, 20, '\App\ANSWER'))->name());
94    }
95    public function testInputsRetainsOnlyDemandedOperands(): void
96    {
97        $source = 'function target(){return missing()+3;}';
98        $configuration = new DeriverConfig();
99        $budget = new Budget();
100        $contributions = new Contributions();
101        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
102        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
103        $contributions1 = $providers->contributions;
104        $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);
105        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
106        $snapshotId = 'test';
107        $input = $providers->input;
108        $parser = new Parser(new ParserConfig($target->version));
109        $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));
110        $program = new Index($snapshotId, $trees);
111        $engine = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
112        $symbol = 'target';
113        $graph = $engine->context->index->graph($symbol);
114        self::assertNotNull($graph);
115        $frame = new Frame($graph, 'source:' . $symbol);
116        $operation = 'binary';
117        $instruction = array_values(array_filter($frame->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
118        self::assertNotNull($instruction);
119        $request = new Request($engine, $frame, $instruction, 20);
120        $right = $request->input(1);
121        self::assertSame(3, $right->native());
122        self::assertSame([1 => $right], $request->inputs());
123        self::assertSame(0, $engine->context->referenceExpansions);
124    }
125    public function testOperationKeepsInstructionAndSiteDemandsDistinct(): void
126    {
127        $source = 'function subject(){return 42;}';
128        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
129        $index = new DefinitionIndex(new Index('s', [$tree]));
130        $config = new EvaluatorConfig();
131        $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
132        $graph = $index->graph('subject');
133        self::assertNotNull($graph);
134        $frame = new Frame($graph, 'source:subject');
135        $instruction = $graph->definitions[$graph->returns()[0][1]];
136        $request = new Request($engine, $frame, $instruction, 32);
137        self::assertSame('constant', $request->operation());
138    }
139
140    public function testSourceKeepsInstructionAndSiteDemandsDistinct(): void
141    {
142        $source = 'function subject(){return 42;}';
143        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
144        $index = new DefinitionIndex(new Index('s', [$tree]));
145        $config = new EvaluatorConfig();
146        $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
147        $graph = $index->graph('subject');
148        self::assertNotNull($graph);
149        $frame = new Frame($graph, 'source:subject');
150        $instruction = $graph->definitions[$graph->returns()[0][1]];
151        $request = new Request($engine, $frame, $instruction, 32);
152        self::assertSame($instruction->source, $request->source());
153    }
154
155    public function testInstructionKeepsInstructionAndSiteDemandsDistinct(): void
156    {
157        $source = 'function subject(){return 42;}';
158        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
159        $index = new DefinitionIndex(new Index('s', [$tree]));
160        $config = new EvaluatorConfig();
161        $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
162        $graph = $index->graph('subject');
163        self::assertNotNull($graph);
164        $frame = new Frame($graph, 'source:subject');
165        $instruction = $graph->definitions[$graph->returns()[0][1]];
166        $request = new Request($engine, $frame, $instruction, 32);
167        self::assertSame($instruction, $request->instruction());
168    }
169
170    public function testTruthKeepsInstructionAndSiteDemandsDistinct(): void
171    {
172        $source = 'function subject(){return +42;}';
173        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
174        $index = new DefinitionIndex(new Index('s', [$tree]));
175        $config = new EvaluatorConfig();
176        $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
177        $graph = $index->graph('subject');
178        self::assertNotNull($graph);
179        $frame = new Frame($graph, 'source:subject');
180        $instruction = $graph->definitions[$graph->returns()[0][1]];
181        $request = new Request($engine, $frame, $instruction, 32);
182        self::assertSame(true, $request->truth(0)->native());
183        self::assertSame(42, $request->input(0)->native());
184        self::assertCount(2, $request->inputs());
185    }
186
187    public function testRegisterRejectsAbsentInputsWithoutExpandingSource(): void
188    {
189        $source = 'function subject(){return +42;}';
190        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
191        $index = new DefinitionIndex(new Index('s', [$tree]));
192        $config = new EvaluatorConfig();
193        $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
194        $graph = $index->graph('subject');
195        self::assertNotNull($graph);
196        $frame = new Frame($graph, 'source:subject');
197        $request = new Request($engine, $frame, $graph->definitions[$graph->returns()[0][1]], 32);
198        $this->expectException(\Deriver\Exception\EvaluationContractException::class);
199        $request->register(4);
200    }
201
202}
203