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