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