packages/deriver/tests/Unit/Deriver/Evaluator/Expression/StorageTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Expression;
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\Enumeration\Choices;
12use Deriver\Deriver\Evaluator\Evaluator;
13use Deriver\Deriver\Evaluator\EvaluatorConfig;
14use Deriver\Deriver\Evaluator\Evidence\Frontier;
15use Deriver\Deriver\Evaluator\Expression\Storage;
16use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
17use Deriver\Deriver\Evaluator\Request\Budget;
18use Deriver\Deriver\Evaluator\Request\ReturnQuery;
19use Deriver\Deriver\Evaluator\Scalar\Identity;
20use Deriver\Deriver\Evaluator\Scope\Context;
21use Deriver\Deriver\Evaluator\Scope\Frame;
22use Deriver\Deriver\Evaluator\TargetProfile;
23use Deriver\Deriver\Evaluator\Term;
24use Deriver\Deriver\Lowerer\Declaration\Index;
25use Deriver\Deriver\MicroEvaluatorConversion;
26use Deriver\Deriver\Parser\Node\File;
27use Deriver\Deriver\Parser\Parser;
28use Deriver\Deriver\Parser\ParserConfig;
29use Deriver\Deriver\Providers\ProviderExpansion;
30use Deriver\Deriver\Publication\CandidateQuery;
31use Deriver\Deriver\Resolver\Resolver;
32use Deriver\Deriver\SnapshotIdentity;
33use Deriver\Deriver\Sources\Path;
34use Deriver\Deriver\Sources\Source;
35use Deriver\Deriver\Sources\Sources;
36use PHPUnit\Framework\Attributes\CoversNothing;
37use PHPUnit\Framework\TestCase;
38
39#[CoversNothing]
40final class StorageTest extends TestCase
41{
42    public function testSearchSharesTheSameDefinitionAcrossDependencyPaths(): void
43    {
44        $source = new Source('candidate.php', '<?php function target(){$x=1;$x=2;return $x;}');
45        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.1')))->parse($source));
46        $index = new DefinitionIndex(new Index('test', [$tree]));
47        $engine = new Evaluator(new Context($index, new EvaluatorConfig(), new Evaluators(), new Budget()));
48        $graph = $index->graph('target');
49        self::assertNotNull($graph);
50        $frame = new Frame($graph, 'source:target');
51        $read = array_values(array_filter($graph->definitions, static fn ($instruction): bool => $instruction->operation === 'read'))[0];
52        [$block, $offset] = $graph->positions[$read->result];
53        $storage = new Storage($engine);
54
55        $value = $storage->search($frame, $read->operands[0], $block, $offset, 20);
56        $work = $engine->context->workUnits;
57
58        self::assertSame(2, $value->native());
59        self::assertSame($value, $storage->search($frame, $read->operands[0], $block, $offset, 10));
60        self::assertSame($work, $engine->context->workUnits);
61    }
62
63    public function testReadSelectsTheReachingDefinition(): void
64    {
65        $source = 'function target(){$x=1;$x=2;return $x;}';
66        $configuration = new DeriverConfig();
67        $budget = new Budget();
68        $contributions = new Contributions();
69        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
70        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
71        $contributions1 = $providers->contributions;
72        $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);
73        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
74        $snapshotId = 'test';
75        $input = $providers->input;
76        $parser = new Parser(new ParserConfig($target->version));
77        $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));
78        $program = new Index($snapshotId, $trees);
79        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
80        $symbol = 'target';
81        $graph = $e->context->index->graph($symbol);
82        self::assertNotNull($graph);
83        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
84        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
85            [$value, $_] = $alternative;
86            $values[(new Identity())->key($value)] = $value;
87            return $values;
88        }, []);
89        self::assertCount(1, $values);
90        $resolvedValue = array_values($values)[0];
91        self::assertInstanceOf(Term::class, $resolvedValue);
92        self::assertSame(2, $resolvedValue->native());
93    }
94    public function testModifiedFindsEscapingLocals(): void
95    {
96        $source = 'function target($x){unknown($x);return $x;}';
97        $configuration = new DeriverConfig();
98        $budget = new Budget();
99        $contributions = new Contributions();
100        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
101        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
102        $contributions1 = $providers->contributions;
103        $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);
104        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
105        $snapshotId = 'test';
106        $input = $providers->input;
107        $parser = new Parser(new ParserConfig($target->version));
108        $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));
109        $program = new Index($snapshotId, $trees);
110        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
111        $symbol = 'target';
112        $graph = $e->context->index->graph($symbol);
113        self::assertNotNull($graph);
114        self::assertTrue((new Storage($e))->modified(new Frame($graph, 'source:' . $symbol), 'x'));
115    }
116    public function testSearchFollowsBranchSpecificWrites(): void
117    {
118        $source = 'function target(){if(true){$x=4;}else{$x=6;}return $x;}';
119        $configuration = new DeriverConfig();
120        $budget = new Budget();
121        $contributions = new Contributions();
122        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
123        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
124        $contributions1 = $providers->contributions;
125        $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);
126        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
127        $snapshotId = 'test';
128        $input = $providers->input;
129        $parser = new Parser(new ParserConfig($target->version));
130        $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));
131        $program = new Index($snapshotId, $trees);
132        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
133        $symbol = 'target';
134        $graph = $e->context->index->graph($symbol);
135        self::assertNotNull($graph);
136        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
137        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
138            [$value, $_] = $alternative;
139            $values[(new Identity())->key($value)] = $value;
140            return $values;
141        }, []);
142        self::assertCount(2, $values);
143        $values = array_values($values);
144        self::assertInstanceOf(Term::class, $values[0]);
145        self::assertInstanceOf(Term::class, $values[1]);
146        self::assertSame([4, 6], [$values[0]->native(), $values[1]->native()]);
147    }
148    public function testWritePreservesCompoundArithmetic(): void
149    {
150        $source = 'function target(){$x=3;$x+=4;return $x;}';
151        $configuration = new DeriverConfig();
152        $budget = new Budget();
153        $contributions = new Contributions();
154        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
155        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
156        $contributions1 = $providers->contributions;
157        $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);
158        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
159        $snapshotId = 'test';
160        $input = $providers->input;
161        $parser = new Parser(new ParserConfig($target->version));
162        $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));
163        $program = new Index($snapshotId, $trees);
164        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
165        $symbol = 'target';
166        $graph = $e->context->index->graph($symbol);
167        self::assertNotNull($graph);
168        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
169        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
170            [$value, $_] = $alternative;
171            $values[(new Identity())->key($value)] = $value;
172            return $values;
173        }, []);
174        self::assertCount(1, $values);
175        $resolvedValue = array_values($values)[0];
176        self::assertInstanceOf(Term::class, $resolvedValue);
177        self::assertSame(7, $resolvedValue->native());
178    }
179    public function testKeySeparatesDifferentLocals(): void
180    {
181        $source = 'function target($x,$y){return $x+$y;}';
182        $configuration = new DeriverConfig();
183        $budget = new Budget();
184        $contributions = new Contributions();
185        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
186        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
187        $contributions1 = $providers->contributions;
188        $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);
189        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
190        $snapshotId = 'test';
191        $input = $providers->input;
192        $parser = new Parser(new ParserConfig($target->version));
193        $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));
194        $program = new Index($snapshotId, $trees);
195        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
196        $symbol = 'target';
197        $graph = $e->context->index->graph($symbol);
198        self::assertNotNull($graph);
199        $f = new Frame($graph, 'source:' . $symbol);
200        $operation = 'local';
201        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
202        $a = $instruction;
203        self::assertNotNull($a);
204        self::assertSame('local:x', (new Storage($e))->key($f, $a->result));
205    }
206    public function testTypeDoesNotRestoreAnOverwrittenParameter(): void
207    {
208        $source = 'function target(PDO $x){$x=3;return $x;}';
209        $configuration = new DeriverConfig();
210        $budget = new Budget();
211        $contributions = new Contributions();
212        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
213        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
214        $contributions1 = $providers->contributions;
215        $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);
216        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
217        $snapshotId = 'test';
218        $input = $providers->input;
219        $parser = new Parser(new ParserConfig($target->version));
220        $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));
221        $program = new Index($snapshotId, $trees);
222        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
223        $symbol = 'target';
224        $graph = $e->context->index->graph($symbol);
225        self::assertNotNull($graph);
226        $f = new Frame($graph, 'source:' . $symbol);
227        $operation = 'read';
228        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
229        $r = $instruction;
230        self::assertNotNull($r);
231        $a = $f->graph->definitions[$r->operands[0]];
232        self::assertSame('mixed', (new Storage($e))->type($f, $a));
233    }
234    public function testInitialRetainsTheInputIdentity(): void
235    {
236        $source = 'function target($x){return $x;}';
237        $configuration = new DeriverConfig();
238        $budget = new Budget();
239        $contributions = new Contributions();
240        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
241        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
242        $contributions1 = $providers->contributions;
243        $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);
244        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
245        $snapshotId = 'test';
246        $input = $providers->input;
247        $parser = new Parser(new ParserConfig($target->version));
248        $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));
249        $program = new Index($snapshotId, $trees);
250        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
251        $symbol = 'target';
252        $graph = $e->context->index->graph($symbol);
253        self::assertNotNull($graph);
254        $f = new Frame($graph, 'source:' . $symbol);
255        $operation = 'local';
256        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
257        $a = $instruction;
258        self::assertNotNull($a);
259        self::assertSame('$x', (new Storage($e))->initial($f, $a->result, 64)->literal);
260    }
261    public function testElementWriteUpdatesTheDemandedParentArray(): void
262    {
263        $source = 'function target(){$a=[1];$a[0]=7;return $a;}';
264        $configuration = new DeriverConfig();
265        $budget = new Budget();
266        $contributions = new Contributions();
267        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
268        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
269        $contributions1 = $providers->contributions;
270        $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);
271        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
272        $snapshotId = 'test';
273        $input = $providers->input;
274        $parser = new Parser(new ParserConfig($target->version));
275        $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));
276        $program = new Index($snapshotId, $trees);
277        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
278        $symbol = 'target';
279        $graph = $e->context->index->graph($symbol);
280        self::assertNotNull($graph);
281        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
282        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
283            [$value, $_] = $alternative;
284            $values[(new Identity())->key($value)] = $value;
285            return $values;
286        }, []);
287        self::assertCount(1, $values);
288        $resolvedValue = array_values($values)[0];
289        self::assertInstanceOf(Term::class, $resolvedValue);
290        self::assertSame([7], $resolvedValue->native());
291    }
292    public function testReachingKeepsTheSelectedStorageOrigin(): void
293    {
294        $source = 'function target($x){$v=3;if($x){$a=1;}else{$a=2;}return $v;}';
295        $configuration = new DeriverConfig();
296        $budget = new Budget();
297        $contributions = new Contributions();
298        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
299        $prepared1Configuration = $configuration;
300        $prepared1Contributions = $contributions;
301        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
302        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
303        $contributions1 = $prepared1Providers->contributions;
304        $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);
305        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
306        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
307        $snapshotId = $prepared1Identity->id;
308        $input = $prepared1Providers->input;
309        $parser = new Parser(new ParserConfig($prepared1Target->version));
310        $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));
311        $prepared1Program = new Index($snapshotId, $trees);
312        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
313        self::assertSame(3, (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget))[0]->result);
314    }
315    public function testDeclarationKeepsTheSelectedStorageOrigin(): void
316    {
317        $source = 'function target(){global $v;$v=7;return $v;}';
318        $configuration = new DeriverConfig();
319        $budget = new Budget();
320        $contributions = new Contributions();
321        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
322        $prepared1Configuration = $configuration;
323        $prepared1Contributions = $contributions;
324        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
325        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
326        $contributions1 = $prepared1Providers->contributions;
327        $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);
328        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
329        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
330        $snapshotId = $prepared1Identity->id;
331        $input = $prepared1Providers->input;
332        $parser = new Parser(new ParserConfig($prepared1Target->version));
333        $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));
334        $prepared1Program = new Index($snapshotId, $trees);
335        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
336        self::assertSame(7, (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget))[0]->result);
337    }
338    public function testLookupReadsACompletedAssignment(): void
339    {
340        $source = 'function target(){$x=7;return $x;}';
341        $configuration = new DeriverConfig();
342        $budget = new Budget();
343        $contributions = new Contributions();
344        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
345        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
346        $contributions1 = $providers->contributions;
347        $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);
348        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
349        $snapshotId = 'test';
350        $input = $providers->input;
351        $parser = new Parser(new ParserConfig($target->version));
352        $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));
353        $program = new Index($snapshotId, $trees);
354        $engine = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
355        $symbol = 'target';
356        $graph = $engine->context->index->graph($symbol);
357        self::assertNotNull($graph);
358        $frame = new Frame($graph, 'source:' . $symbol);
359        $operation = 'read';
360        $instruction = array_values(array_filter($frame->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
361        $read = $instruction;
362        self::assertNotNull($read);
363        [$block, $offset] = $frame->graph->positions[$read->result];
364        self::assertSame(7, (new Storage($engine))->lookup($frame, $read->operands[0], $block, $offset, 20)->native());
365    }
366    public function testBranchesWriteLocalRecognizesConditionalAssignments(): void
367    {
368        $source = 'function target($flag){if($flag){$x=1;}else{$x=2;}return $x;}';
369        $configuration = new DeriverConfig();
370        $budget = new Budget();
371        $contributions = new Contributions();
372        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
373        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
374        $contributions1 = $providers->contributions;
375        $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);
376        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
377        $snapshotId = 'test';
378        $input = $providers->input;
379        $parser = new Parser(new ParserConfig($target->version));
380        $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));
381        $program = new Index($snapshotId, $trees);
382        $engine = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
383        $symbol = 'target';
384        $graph = $engine->context->index->graph($symbol);
385        self::assertNotNull($graph);
386        $frame = new Frame($graph, 'source:' . $symbol);
387        $instructions = array_values(array_filter($frame->graph->definitions, static fn ($instruction): bool => $instruction->operation === 'read' && $frame->graph->definitions[$instruction->operands[0]]->name === 'x'));
388        $read = $instructions[0];
389        [$block] = $frame->graph->positions[$read->result];
390        self::assertTrue((new Storage($engine))->branchesWriteLocal($frame, $read->operands[0], $frame->graph->predecessors[$block]));
391    }
392    public function testMergeSharesIdenticalPredecessorValues(): void
393    {
394        $source = 'function subject(){return 42;}';
395        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
396        $index = new DefinitionIndex(new Index('s', [$tree]));
397        $config = new EvaluatorConfig();
398        $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
399        $graph = $index->graph('subject');
400        self::assertNotNull($graph);
401        $frame = new Frame($graph, 'source:subject');
402        $value = Term::constant(42);
403        self::assertSame($value, (new Storage($engine))->merge([[$value, $frame, 0], [$value, $frame, 0]], 0, 32));
404    }
405
406}
407