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

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator;
6
7use Deriver\Deriver\Candidate;
8use Deriver\Deriver\Candidate\CandidateStatus;
9use Deriver\Deriver\Contributions;
10use Deriver\Deriver\DeriverConfig;
11use Deriver\Deriver\Evaluator\Call\Evaluators;
12use Deriver\Deriver\Evaluator\Call\Registrations;
13use Deriver\Deriver\Evaluator\Calls;
14use Deriver\Deriver\Evaluator\Enumeration\Choices;
15use Deriver\Deriver\Evaluator\Evaluator;
16use Deriver\Deriver\Evaluator\EvaluatorConfig;
17use Deriver\Deriver\Evaluator\Evidence\Frontier;
18use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
19use Deriver\Deriver\Evaluator\Request\Budget;
20use Deriver\Deriver\Evaluator\Request\ReturnQuery;
21use Deriver\Deriver\Evaluator\Scalar\Identity;
22use Deriver\Deriver\Evaluator\Scope\Context;
23use Deriver\Deriver\Evaluator\Scope\Frame;
24use Deriver\Deriver\Evaluator\TargetProfile;
25use Deriver\Deriver\Evaluator\Term;
26use Deriver\Deriver\Evaluator\TermKind;
27use Deriver\Deriver\Lowerer\Declaration\Index;
28use Deriver\Deriver\MicroEvaluatorConversion;
29use Deriver\Deriver\Parser\Node\File;
30use Deriver\Deriver\Parser\Parser;
31use Deriver\Deriver\Parser\ParserConfig;
32use Deriver\Deriver\Providers\ProviderExpansion;
33use Deriver\Deriver\Publication\CandidateQuery;
34use Deriver\Deriver\Resolver\Resolver;
35use Deriver\Deriver\SnapshotIdentity;
36use Deriver\Deriver\Sources\Path;
37use Deriver\Deriver\Sources\Source;
38use Deriver\Deriver\Sources\SourceRef;
39use Deriver\Deriver\Sources\Sources;
40use PHPUnit\Framework\Attributes\CoversNothing;
41use PHPUnit\Framework\TestCase;
42use WeakMap;
43
44#[CoversNothing]
45final class CallsTest extends TestCase
46{
47    public function testValueExpandsOnlyADemandedCall(): void
48    {
49        $source = 'function one(){return 1;}function target(){return one()+2;}';
50        $configuration = new DeriverConfig();
51        $budget = new Budget();
52        $contributions = new Contributions();
53        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
54        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
55        $contributions1 = $providers->contributions;
56        $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);
57        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
58        $snapshotId = 'test';
59        $input = $providers->input;
60        $parser = new Parser(new ParserConfig($target->version));
61        $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));
62        $program = new Index($snapshotId, $trees);
63        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
64        $symbol = 'target';
65        $graph = $e->context->index->graph($symbol);
66        self::assertNotNull($graph);
67        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
68        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
69            [$value, $_] = $alternative;
70            $values[(new Identity())->key($value)] = $value;
71            return $values;
72        }, []);
73        self::assertCount(1, $values);
74        $resolvedValue = array_values($values)[0];
75        self::assertInstanceOf(Term::class, $resolvedValue);
76        self::assertSame(3, $resolvedValue->native());
77    }
78    public function testCapturesReadTheCreationDefinition(): void
79    {
80        $source = 'function target(){$x=7;$f=function()use($x){return $x;};$x=9;return $f();}';
81        $configuration = new DeriverConfig();
82        $budget = new Budget();
83        $contributions = new Contributions();
84        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
85        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
86        $contributions1 = $providers->contributions;
87        $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);
88        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
89        $snapshotId = 'test';
90        $input = $providers->input;
91        $parser = new Parser(new ParserConfig($target->version));
92        $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));
93        $program = new Index($snapshotId, $trees);
94        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
95        $symbol = 'target';
96        $graph = $e->context->index->graph($symbol);
97        self::assertNotNull($graph);
98        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
99        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
100            [$value, $_] = $alternative;
101            $values[(new Identity())->key($value)] = $value;
102            return $values;
103        }, []);
104        self::assertCount(1, $values);
105        $resolvedValue = array_values($values)[0];
106        self::assertInstanceOf(Term::class, $resolvedValue);
107        self::assertSame(7, $resolvedValue->native());
108    }
109    public function testPassThroughRecursionClosesOnlyProvenUnchangedBindings(): void
110    {
111        $source = 'function target($x){return target($x);}';
112        $configuration = new DeriverConfig();
113        $budget = new Budget();
114        $contributions = new Contributions();
115        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
116        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
117        $contributions1 = $providers->contributions;
118        $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);
119        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
120        $snapshotId = 'test';
121        $input = $providers->input;
122        $parser = new Parser(new ParserConfig($target->version));
123        $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));
124        $program = new Index($snapshotId, $trees);
125        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
126        $symbol = 'target';
127        $graph = $e->context->index->graph($symbol);
128        self::assertNotNull($graph);
129        $r = $e->returns(new Frame($graph, 'source:' . $symbol), 64);
130        self::assertSame(TermKind::Operation, $r->kind);
131        self::assertNotEmpty($r->operands);
132    }
133    public function testBindKeepsNamedArgumentsLazy(): void
134    {
135        $source = 'function named($a,$b){return $a;}function target(){return named(b:missing(),a:4);}';
136        $configuration = new DeriverConfig();
137        $budget = new Budget();
138        $contributions = new Contributions();
139        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
140        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
141        $contributions1 = $providers->contributions;
142        $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);
143        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
144        $snapshotId = 'test';
145        $input = $providers->input;
146        $parser = new Parser(new ParserConfig($target->version));
147        $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));
148        $program = new Index($snapshotId, $trees);
149        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
150        $symbol = 'target';
151        $graph = $e->context->index->graph($symbol);
152        self::assertNotNull($graph);
153        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
154        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
155            [$value, $_] = $alternative;
156            $values[(new Identity())->key($value)] = $value;
157            return $values;
158        }, []);
159        self::assertCount(1, $values);
160        $resolvedValue = array_values($values)[0];
161        self::assertInstanceOf(Term::class, $resolvedValue);
162        self::assertSame(4, $resolvedValue->native());
163    }
164    public function testSelectedEffectUpdatesOnlyAReferenceArgument(): void
165    {
166        $source = 'function change(&$x){$x=8;}function target(){$x=1;change($x);return $x;}';
167        $configuration = new DeriverConfig();
168        $budget = new Budget();
169        $contributions = new Contributions();
170        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
171        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
172        $contributions1 = $providers->contributions;
173        $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);
174        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
175        $snapshotId = 'test';
176        $input = $providers->input;
177        $parser = new Parser(new ParserConfig($target->version));
178        $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));
179        $program = new Index($snapshotId, $trees);
180        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
181        $symbol = 'target';
182        $graph = $e->context->index->graph($symbol);
183        self::assertNotNull($graph);
184        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
185        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
186            [$value, $_] = $alternative;
187            $values[(new Identity())->key($value)] = $value;
188            return $values;
189        }, []);
190        self::assertCount(1, $values);
191        $resolvedValue = array_values($values)[0];
192        self::assertInstanceOf(Term::class, $resolvedValue);
193        self::assertSame(8, $resolvedValue->native());
194        self::assertSame(['change' => 1], $e->context->bodies);
195    }
196    public function testFinalStorageKeepsTheLastWriteAtEachReturn(): void
197    {
198        $source = 'function change(&$x){$x=2;$x=3;return 7;}function target(){$x=1;change($x);return $x;}';
199        $configuration = new DeriverConfig();
200        $budget = new Budget();
201        $contributions = new Contributions();
202        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
203        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
204        $contributions1 = $providers->contributions;
205        $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);
206        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
207        $snapshotId = 'test';
208        $input = $providers->input;
209        $parser = new Parser(new ParserConfig($target->version));
210        $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));
211        $program = new Index($snapshotId, $trees);
212        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
213        $symbol = 'target';
214        $graph = $e->context->index->graph($symbol);
215        self::assertNotNull($graph);
216        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
217        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
218            [$value, $_] = $alternative;
219            $values[(new Identity())->key($value)] = $value;
220            return $values;
221        }, []);
222        self::assertCount(1, $values);
223        $resolvedValue = array_values($values)[0];
224        self::assertInstanceOf(Term::class, $resolvedValue);
225        self::assertSame(3, $resolvedValue->native());
226    }
227    public function testExpandResolvesAnAlreadySelectedTarget(): void
228    {
229        $source = 'function one(){return 1;}function target(){return one();}';
230        $configuration = new DeriverConfig();
231        $budget = new Budget();
232        $contributions = new Contributions();
233        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
234        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
235        $contributions1 = $providers->contributions;
236        $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);
237        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
238        $snapshotId = 'test';
239        $input = $providers->input;
240        $parser = new Parser(new ParserConfig($target->version));
241        $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));
242        $program = new Index($snapshotId, $trees);
243        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
244        $symbol = 'target';
245        $graph = $e->context->index->graph($symbol);
246        self::assertNotNull($graph);
247        $f = new Frame($graph, 'source:' . $symbol);
248        $operation = 'invoke';
249        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
250        $call = $instruction;
251        self::assertNotNull($call);
252        self::assertSame(1, (new Calls($e))->expand($f, $call, 'one', null, 64)->native());
253    }
254    public function testSignatureKeepsTypeErrorsIndependentOfTheBody(): void
255    {
256        $source = 'function one(int $x){return 1;}function target(){return one([]);}';
257        $configuration = new DeriverConfig();
258        $budget = new Budget();
259        $contributions = new Contributions();
260        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
261        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
262        $contributions1 = $providers->contributions;
263        $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);
264        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
265        $snapshotId = 'test';
266        $input = $providers->input;
267        $parser = new Parser(new ParserConfig($target->version));
268        $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));
269        $program = new Index($snapshotId, $trees);
270        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
271        $symbol = 'target';
272        $graph = $e->context->index->graph($symbol);
273        self::assertNotNull($graph);
274        $f = new Frame($graph, 'source:' . $symbol);
275        $operation = 'invoke';
276        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
277        $call = $instruction;
278        self::assertNotNull($call);
279        $symbol1 = 'one';
280        $graph1 = $e->context->index->graph($symbol1);
281        self::assertNotNull($graph1);
282        $error = (new Calls($e))->signature($f, $call, (new Frame($graph1, 'source:' . $symbol1))->graph, 64);
283        self::assertNotNull($error);
284        self::assertSame('TypeError', $error->literal);
285    }
286    public function testUnknownWritePreservesAReadBeforeTheCall(): void
287    {
288        $source = 'function target(){$x=3;missing($x);return $x;}';
289        $configuration = new DeriverConfig();
290        $budget = new Budget();
291        $contributions = new Contributions();
292        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
293        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
294        $contributions1 = $providers->contributions;
295        $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);
296        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
297        $snapshotId = 'test';
298        $input = $providers->input;
299        $parser = new Parser(new ParserConfig($target->version));
300        $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));
301        $program = new Index($snapshotId, $trees);
302        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
303        $symbol = 'target';
304        $graph = $e->context->index->graph($symbol);
305        self::assertNotNull($graph);
306        $f = new Frame($graph, 'source:' . $symbol);
307        $operation = 'invoke';
308        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
309        $call = $instruction;
310        self::assertNotNull($call);
311        $operation1 = 'local';
312        $instruction1 = array_values(array_filter($f->graph->definitions, static fn ($instruction1): bool => $instruction1->operation === $operation1))[0] ?? null;
313        $a = $instruction1;
314        self::assertNotNull($a);
315        $v = (new Calls($e))->unknownWrite($f, $call, $a->result, 64);
316        self::assertNotNull($v);
317        self::assertSame(3, $v->operands[0]->literal);
318    }
319    public function testEffectUsesTheConcreteReceiverForReferenceWrites(): void
320    {
321        $source = 'class A{function change(&$x){$x=1;}}class B extends A{function change(&$x){$x=7;}}function apply(A $b){$x=0;$b->change($x);return $x;}function target(){return apply(new B);}';
322        $configuration = new DeriverConfig();
323        $budget = new Budget();
324        $contributions = new Contributions();
325        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
326        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
327        $contributions1 = $providers->contributions;
328        $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);
329        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
330        $snapshotId = 'test';
331        $input = $providers->input;
332        $parser = new Parser(new ParserConfig($target->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        $program = new Index($snapshotId, $trees);
335        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
336        $symbol = 'target';
337        $graph = $e->context->index->graph($symbol);
338        self::assertNotNull($graph);
339        $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
340        $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
341            [$value, $_] = $alternative;
342            $values[(new Identity())->key($value)] = $value;
343            return $values;
344        }, []);
345        self::assertCount(1, $values);
346        $resolvedValue = array_values($values)[0];
347        self::assertInstanceOf(Term::class, $resolvedValue);
348        self::assertSame(7, $resolvedValue->native());
349        self::assertArrayNotHasKey('A::change', $e->context->bodies);
350    }
351    public function testPassedIgnoresStorageNotPassedToTheCall(): void
352    {
353        $source = 'function target(){$safe=3;missing(1);return $safe;}';
354        $configuration = new DeriverConfig();
355        $budget = new Budget();
356        $contributions = new Contributions();
357        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
358        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
359        $contributions1 = $providers->contributions;
360        $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);
361        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
362        $snapshotId = 'test';
363        $input = $providers->input;
364        $parser = new Parser(new ParserConfig($target->version));
365        $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));
366        $program = new Index($snapshotId, $trees);
367        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
368        $symbol = 'target';
369        $graph = $e->context->index->graph($symbol);
370        self::assertNotNull($graph);
371        $f = new Frame($graph, 'source:' . $symbol);
372        $operation = 'invoke';
373        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
374        $operation1 = 'local';
375        $instruction1 = array_values(array_filter($f->graph->definitions, static fn ($instruction1): bool => $instruction1->operation === $operation1))[0] ?? null;
376        self::assertNotNull($instruction);
377        self::assertNotNull($instruction1);
378        self::assertNull((new Calls($e))->passed($f, $instruction, $instruction1->result));
379    }
380    public function testStableEnvironmentRejectsMutableObjectInputs(): void
381    {
382        $source = 'class Box{public $n=4;}function target(Box $box){$box->n--;return target($box);}';
383        $configuration = new DeriverConfig();
384        $budget = new Budget();
385        $contributions = new Contributions();
386        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
387        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
388        $contributions1 = $providers->contributions;
389        $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);
390        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
391        $snapshotId = 'test';
392        $input = $providers->input;
393        $parser = new Parser(new ParserConfig($target->version));
394        $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));
395        $program = new Index($snapshotId, $trees);
396        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
397        $symbol = 'target';
398        $graph = $e->context->index->graph($symbol);
399        self::assertNotNull($graph);
400        $f = new Frame($graph, 'source:' . $symbol);
401        self::assertFalse((new Calls($e))->stableEnvironment($f));
402    }
403    public function testInvokePreservesCallDependencies(): void
404    {
405        $source = 'function f($x){return $x+1;}function target(){return f(4);}';
406        $configuration = new DeriverConfig();
407        $budget = new Budget();
408        $contributions = new Contributions();
409        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
410        $prepared1Configuration = $configuration;
411        $prepared1Contributions = $contributions;
412        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
413        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
414        $contributions1 = $prepared1Providers->contributions;
415        $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);
416        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
417        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
418        $snapshotId = $prepared1Identity->id;
419        $input = $prepared1Providers->input;
420        $parser = new Parser(new ParserConfig($prepared1Target->version));
421        $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));
422        $prepared1Program = new Index($snapshotId, $trees);
423        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
424        $result = (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget));
425        self::assertSame(5, $result[0]->result);
426    }
427    public function testMissingPreservesCallDependencies(): void
428    {
429        $source = 'function target(){return external(1);}';
430        $configuration = new DeriverConfig();
431        $budget = new Budget();
432        $contributions = new Contributions();
433        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
434        $prepared1Configuration = $configuration;
435        $prepared1Contributions = $contributions;
436        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
437        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
438        $contributions1 = $prepared1Providers->contributions;
439        $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);
440        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
441        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
442        $snapshotId = $prepared1Identity->id;
443        $input = $prepared1Providers->input;
444        $parser = new Parser(new ParserConfig($prepared1Target->version));
445        $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));
446        $prepared1Program = new Index($snapshotId, $trees);
447        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
448        $result = (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget));
449        self::assertSame(CandidateStatus::Partials, $result[0]->type);
450        $frontiers = array_reduce(iterator_to_array($result, false), static function (array $frontiers, Candidate $candidate): array {
451            if ($candidate->result instanceof Term) {
452                $graph = $candidate->result;
453                $snapshot = $candidate->evidence[0]->snapshot;
454                $pending = [$graph];
455                $seen = new WeakMap();
456                $frontiers1 = [];
457                while ($pending !== []) {
458                    $node = array_pop($pending);
459                    if (isset($seen[$node])) {
460                        continue;
461                    }
462                    $seen[$node] = true;
463                    array_push($pending, ...array_values($node->operands));
464                    $reason = $node->attributes['reason'] ?? null;
465                    if (!is_string($reason)) {
466                        continue;
467                    }
468                    $source2 = new SourceRef($snapshot->id, (string) ($node->attributes['source'] ?? ''), (int) ($node->attributes['start'] ?? 0), (int) ($node->attributes['end'] ?? $node->attributes['start'] ?? 0));
469                    $identity = (string) ($node->attributes['identity'] ?? (new Identity())->key($node));
470                    $frontiers1[$identity] = new Frontier($reason, $source2, (string) $node->literal, ['value'], [$identity], $node, $reason);
471                }
472                array_push($frontiers, ...array_values($frontiers1));
473            }
474            return $frontiers;
475        }, []);
476        self::assertContains('MISSING_SOURCE', array_column($frontiers, 'code'));
477    }
478}
479