packages/deriver/tests/Integration/ExecutionValueTest.php
1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Integration;
6
7use Deriver\Deriver\Contributions;
8use Deriver\Deriver\DeriverConfig;
9use Deriver\Deriver\Evaluator\Call\CallDecision;
10use Deriver\Deriver\Evaluator\Call\CallDescription;
11use Deriver\Deriver\Evaluator\Call\CallDescriptor;
12use Deriver\Deriver\Evaluator\Call\CallEvaluator;
13use Deriver\Deriver\Evaluator\Call\Evaluators;
14use Deriver\Deriver\Evaluator\Call\Plan\Action;
15use Deriver\Deriver\Evaluator\Call\Plan\Expression;
16use Deriver\Deriver\Evaluator\Call\Plan\ExpressionKind;
17use Deriver\Deriver\Evaluator\Call\Plan\SemanticPlan;
18use Deriver\Deriver\Evaluator\Call\Registrations;
19use Deriver\Deriver\Evaluator\Declaration\Parameter;
20use Deriver\Deriver\Evaluator\Declaration\Signature;
21use Deriver\Deriver\Evaluator\EvaluatorConfig;
22use Deriver\Deriver\Evaluator\Evidence\Frontier;
23use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
24use Deriver\Deriver\Evaluator\Intrinsic\IntrinsicDescriptor;
25use Deriver\Deriver\Evaluator\Intrinsic\PureIntrinsic;
26use Deriver\Deriver\Evaluator\Request\EntryPoint;
27use Deriver\Deriver\Evaluator\Request\QueryScope;
28use Deriver\Deriver\Evaluator\Request\ReturnQuery;
29use Deriver\Deriver\Evaluator\TargetProfile;
30use Deriver\Deriver\Evaluator\Term;
31use Deriver\Deriver\Evaluator\TermKind;
32use Deriver\Deriver\Execution\QueryExecution;
33use Deriver\Deriver\Lowerer\Declaration\Index;
34use Deriver\Deriver\MicroEvaluatorConversion;
35use Deriver\Deriver\Parser\Node\File;
36use Deriver\Deriver\Parser\Parser;
37use Deriver\Deriver\Parser\ParserConfig;
38use Deriver\Deriver\Providers\ProviderExpansion;
39use Deriver\Deriver\Resolver\Resolver;
40use Deriver\Deriver\SnapshotIdentity;
41use Deriver\Deriver\Sources\Path;
42use Deriver\Deriver\Sources\Source;
43use Deriver\Deriver\Sources\Sources;
44use Override;
45use PHPUnit\Framework\Attributes\CoversNothing;
46use PHPUnit\Framework\Attributes\DataProvider;
47use PHPUnit\Framework\Attributes\Small;
48use PHPUnit\Framework\TestCase;
49
50#[CoversNothing]
51#[Small]
52final class ExecutionValueTest extends TestCase
53{
54 public function testDerivedReturnsPreserveSuppliedInputs(): void
55 {
56 $input = new Sources([new Source('fixture.php', '<?php function target($input){return "prefix:".$input;}')]);
57 $configuration = new DeriverConfig();
58 $contributions = new Contributions();
59 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
60 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
61 $contributions1 = $providers->contributions;
62 $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);
63 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
64 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
65 $snapshotId = $identity->id;
66 $input1 = $providers->input;
67 $parser = new Parser(new ParserConfig($target->version));
68 $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input1, false));
69 $program = new Index($snapshotId, $trees);
70 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
71 $query = new ReturnQuery('target', QueryScope::fromEntrypoints([new EntryPoint('target', [Term::constant('input-value')])]));
72 $result = $session->derive($query);
73 self::assertSame('prefix:input-value', $result->normalOutcomes[0]->values['return']->native());
74 }
75 public function testNonUtf8BytesAndFloatingPointSpecialValuesRemainExact(): void
76 {
77 $input = new Sources([new Source('fixture.php', '<?php function target($input){return $input;}')]);
78 $configuration = new DeriverConfig();
79 $contributions = new Contributions();
80 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
81 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
82 $contributions1 = $providers->contributions;
83 $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);
84 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
85 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
86 $snapshotId = $identity->id;
87 $input1 = $providers->input;
88 $parser = new Parser(new ParserConfig($target->version));
89 $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input1, false));
90 $program = new Index($snapshotId, $trees);
91 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
92 $query = new ReturnQuery('target', QueryScope::fromEntrypoints([new EntryPoint('target', [Term::array(['bytes' => Term::constant("\xff\x00"), 'infinity' => Term::constant(INF), 'negative-zero' => Term::constant(-0.0)])])]));
93 $value = $session->derive($query)->normalOutcomes[0]->values['return'];
94 self::assertSame("\xff\x00", $value->operands['bytes']->native());
95 self::assertSame(INF, $value->operands['infinity']->native());
96 $zero = $value->operands['negative-zero']->native();
97 self::assertIsFloat($zero);
98 self::assertSame('8000000000000000', bin2hex(pack('E', $zero)));
99 }
100 public function testNonUtf8IdentifierMetadataRemainsExact(): void
101 {
102 $source = "<?php function target(){return \xe8;}";
103 $symbol = 'target';
104 $input = new Sources([new Source('fixture.php', $source)]);
105 $configuration = new DeriverConfig();
106 $contributions = new Contributions();
107 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
108 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
109 $contributions1 = $providers->contributions;
110 $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);
111 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
112 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
113 $snapshotId = $identity->id;
114 $input1 = $providers->input;
115 $parser = new Parser(new ParserConfig($target->version));
116 $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($input1, false));
117 $program = new Index($snapshotId, $trees);
118 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
119 self::assertSame("constant:\xe8", $result->frontiers[0]->operation);
120 }
121 public function testStorageGraphPreservesReturnedObjectCyclesAndLocalAliases(): void
122 {
123 $source = '<?php class B{public ?B $self=null;public int $value=3;} function target(){$b=new B;$alias=$b;$b->self=$b;$x=1;$y=&$x;return $b;}';
124 $symbol = 'target';
125 $input = new Sources([new Source('fixture.php', $source)]);
126 $configuration = new DeriverConfig();
127 $contributions = new Contributions();
128 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
129 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
130 $contributions1 = $providers->contributions;
131 $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);
132 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
133 $identity1 = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
134 $snapshotId = $identity1->id;
135 $input1 = $providers->input;
136 $parser = new Parser(new ParserConfig($target->version));
137 $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($input1, false));
138 $program = new Index($snapshotId, $trees);
139 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity1->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
140 $outcome = $result->normalOutcomes[0];
141 $identity = $outcome->values['return']->literal;
142 self::assertIsString($identity);
143 self::assertSame($identity, $outcome->storage->cells['object:' . $identity]->operands['self']->literal);
144 self::assertSame(3, $outcome->storage->cells['object:' . $identity]->operands['value']->native());
145 self::assertSame($outcome->storage->bindings['x']->literal, $outcome->storage->bindings['y']->literal);
146 self::assertSame(TermKind::Object, $outcome->values['return']->kind);
147 }
148 public function testExceptionalStorageIncludesPropertiesWrittenBeforeTheThrow(): void
149 {
150 $source = '<?php class B{public int $value=1;} function target(){$b=new B;$b->value=4;throw new Error("failure");}';
151 $symbol = 'target';
152 $input = new Sources([new Source('fixture.php', $source)]);
153 $configuration = new DeriverConfig();
154 $contributions = new Contributions();
155 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
156 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
157 $contributions1 = $providers->contributions;
158 $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);
159 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
160 $identity1 = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
161 $snapshotId = $identity1->id;
162 $input1 = $providers->input;
163 $parser = new Parser(new ParserConfig($target->version));
164 $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($input1, false));
165 $program = new Index($snapshotId, $trees);
166 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity1->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
167 $outcome = $result->exceptionalOutcomes[0];
168 $identity = $outcome->state['b']->literal;
169 self::assertIsString($identity);
170 self::assertSame(4, $outcome->storage->cells['object:' . $identity]->operands['value']->native());
171 self::assertNotEmpty($outcome->evidence);
172 }
173 public function testCustomIntrinsicResultsPreserveReturnedValues(): void
174 {
175 $intrinsic = self::createStub(PureIntrinsic::class);
176 $intrinsic->method('descriptor')->willReturn(new IntrinsicDescriptor('example.custom', '1', 'example.custom', 1, [0]));
177 $intrinsic->method('evaluate')->willReturn(Term::constant('derived-value'));
178 $signature = new Signature([new Parameter('value', 'string')]);
179 $plan = new SemanticPlan([Action::returns(new Expression(ExpressionKind::Intrinsic, 'example.custom', [Expression::parameter('value')]))]);
180 $evaluator = new class (new CallDescriptor('example.custom', '1', 'derive_custom', $signature), $plan) implements CallEvaluator {
181 /**
182 * @param CallDescriptor $metadata Stable evaluator registration
183 * @param SemanticPlan $plan Ordered API semantics
184 */
185 public function __construct(public readonly CallDescriptor $metadata, public readonly SemanticPlan $plan)
186 {
187 }
188 /**
189 * @return CallDescriptor Stable registration
190 */
191 #[Override]
192 public function descriptor(): CallDescriptor
193 {
194 return $this->metadata;
195 }
196 /**
197 * @param CallDescription $call Selected target
198 * @return CallDecision Declarative fixture semantics
199 */
200 #[Override]
201 public function evaluate(CallDescription $call): CallDecision
202 {
203 return CallDecision::handled($this->plan);
204 }
205 };
206 $input = new Sources([new Source('fixture.php', '<?php function target(string $input){return derive_custom($input);}')]);
207 $configuration = new DeriverConfig();
208 $contributions = new Contributions(evaluators: [$evaluator], intrinsics: [$intrinsic]);
209 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
210 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
211 $contributions1 = $providers->contributions;
212 $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);
213 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
214 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
215 $snapshotId = $identity->id;
216 $input1 = $providers->input;
217 $parser = new Parser(new ParserConfig($target->version));
218 $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input1, false));
219 $program = new Index($snapshotId, $trees);
220 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
221 $result = $session->derive(new ReturnQuery('target', QueryScope::fromEntrypoints([new EntryPoint('target', [Term::constant('input-value')])])));
222 self::assertSame([], $result->frontiers);
223 self::assertSame('derived-value', $result->normalOutcomes[0]->values['return']->native());
224 }
225 #[DataProvider('providerDerivedSelection')]
226 public function testDerivedSelectionsPreserveValues(string $body, string $expected): void
227 {
228 $input = new Sources([new Source('fixture.php', '<?php function target(string $input){' . $body . '}')]);
229 $configuration = new DeriverConfig();
230 $contributions = new Contributions();
231 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
232 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
233 $contributions1 = $providers->contributions;
234 $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);
235 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
236 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
237 $snapshotId = $identity->id;
238 $input1 = $providers->input;
239 $parser = new Parser(new ParserConfig($target->version));
240 $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input1, false));
241 $program = new Index($snapshotId, $trees);
242 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
243 $result = $session->derive(new ReturnQuery('target', QueryScope::fromEntrypoints([new EntryPoint('target', [Term::constant('xx')])])));
244 self::assertSame([], $result->frontiers);
245 self::assertSame($expected, $result->normalOutcomes[0]->values['return']->native());
246 }
247 /**
248 * @return iterable<string,array{string,string}>
249 */
250 public static function providerDerivedSelection(): iterable
251 {
252 yield 'filter predicate selection' => ['return array_values(array_filter(["selected"],fn($value)=>$input==="xx"))[0];', 'selected'];
253 yield 'replacement result' => ['return str_replace("x","y",$input);', 'yy'];
254 yield 'array keys' => ['return array_keys([$input=>1])[0];', 'xx'];
255 yield 'stored array keys' => ['$a=array_keys([$input=>1]);return $a[0];', 'xx'];
256 yield 'lookup key' => ['return ["xx"=>"selected"][$input];', 'selected'];
257 yield 'foreach key' => ['foreach([$input=>1] as $key=>$value){return $key;}return "missing";', 'xx'];
258 yield 'foreach value' => ['foreach(array_keys([$input=>1]) as $value){return $value;}return "missing";', 'xx'];
259 yield 'foreach reference' => ['$a=array_keys([$input=>1]);foreach($a as &$value){return $value;}return "missing";', 'xx'];
260 yield 'destructuring' => ['[$value]=array_keys([$input=>1]);return $value;', 'xx'];
261 yield 'reference' => ['$a=array_keys([$input=>1]);$value=&$a[0];return $value;', 'xx'];
262 }
263 #[DataProvider('providerCustomAggregateSelection')]
264 public function testCustomAggregateSelectionsPreserveValues(string $body, mixed $expected): void
265 {
266 $intrinsic = self::createStub(PureIntrinsic::class);
267 $intrinsic->method('descriptor')->willReturn(new IntrinsicDescriptor('example.custom', '1', 'example.custom', 1, [0]));
268 $intrinsic->method('evaluate')->willReturn(Term::fromNative(['keep' => 'derived-value', 'drop' => 'discarded']));
269 $signature = new Signature([new Parameter('value', 'string')]);
270 $plan = new SemanticPlan([Action::returns(new Expression(ExpressionKind::Intrinsic, 'example.custom', [Expression::parameter('value')]))]);
271 $evaluator = new class (new CallDescriptor('example.custom', '1', 'derive_custom', $signature), $plan) implements CallEvaluator {
272 /**
273 * @param CallDescriptor $metadata Stable evaluator registration
274 * @param SemanticPlan $plan Ordered API semantics
275 */
276 public function __construct(public readonly CallDescriptor $metadata, public readonly SemanticPlan $plan)
277 {
278 }
279 /**
280 * @return CallDescriptor Stable registration
281 */
282 #[Override]
283 public function descriptor(): CallDescriptor
284 {
285 return $this->metadata;
286 }
287 /**
288 * @param CallDescription $call Selected target
289 * @return CallDecision Declarative fixture semantics
290 */
291 #[Override]
292 public function evaluate(CallDescription $call): CallDecision
293 {
294 return CallDecision::handled($this->plan);
295 }
296 };
297 $input = new Sources([new Source('fixture.php', '<?php function target(string $input){$a=derive_custom($input);' . $body . '}')]);
298 $configuration = new DeriverConfig();
299 $contributions = new Contributions(evaluators: [$evaluator], intrinsics: [$intrinsic]);
300 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
301 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
302 $contributions1 = $providers->contributions;
303 $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);
304 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
305 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
306 $snapshotId = $identity->id;
307 $input1 = $providers->input;
308 $parser = new Parser(new ParserConfig($target->version));
309 $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input1, false));
310 $program = new Index($snapshotId, $trees);
311 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
312 $result = $session->derive(new ReturnQuery('target', QueryScope::fromEntrypoints([new EntryPoint('target', [Term::constant('input-value')])])));
313 self::assertSame([], $result->frontiers);
314 self::assertSame($expected, $result->normalOutcomes[0]->values['return']->native());
315 }
316 /**
317 * @return iterable<string,array{string,string|array<string,string>}>
318 */
319 public static function providerCustomAggregateSelection(): iterable
320 {
321 $array = ['keep' => 'derived-value', 'drop' => 'discarded'];
322 yield 'offset' => ['return $a["keep"];', 'derived-value'];
323 yield 'map identity' => ['return array_map(null,$a);', $array];
324 yield 'map callback' => ['return array_map(fn($value)=>$value,$a);', $array];
325 yield 'map element selection' => ['return array_map(null,$a)["keep"];', 'derived-value'];
326 yield 'filter default' => ['return array_filter($a);', $array];
327 yield 'filter callback' => ['return array_filter($a,fn($value)=>true);', $array];
328 yield 'filter element selection' => ['return array_filter($a)["keep"];', 'derived-value'];
329 yield 'reduce callback' => ['return array_reduce($a,fn($carry,$value)=>$carry??$value,null);', 'derived-value'];
330 yield 'unset' => ['unset($a["drop"]);return $a;', ['keep' => 'derived-value']];
331 yield 'reference' => ['$value=&$a["keep"];return $value;', 'derived-value'];
332 yield 'foreach value' => ['foreach($a as $value){return $value;}return null;', 'derived-value'];
333 yield 'foreach reference' => ['foreach($a as &$value){return $value;}return null;', 'derived-value'];
334 }
335 #[DataProvider('providerExistence')]
336 public function testExistenceResultsMatchSuppliedValues(string $body): void
337 {
338 $input = new Sources([new Source('fixture.php', '<?php function target(string $input){' . $body . '}')]);
339 $configuration = new DeriverConfig();
340 $contributions = new Contributions();
341 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
342 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
343 $contributions1 = $providers->contributions;
344 $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);
345 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
346 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
347 $snapshotId = $identity->id;
348 $input1 = $providers->input;
349 $parser = new Parser(new ParserConfig($target->version));
350 $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input1, false));
351 $program = new Index($snapshotId, $trees);
352 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
353 $result = $session->derive(new ReturnQuery('target', QueryScope::fromEntrypoints([new EntryPoint('target', [Term::constant('xx')])])));
354 self::assertSame([], $result->frontiers);
355 self::assertFalse($result->normalOutcomes[0]->values['return']->native());
356 }
357 /**
358 * @return iterable<string,array{string}>
359 */
360 public static function providerExistence(): iterable
361 {
362 yield 'string length' => ['return isset($input[3]);'];
363 yield 'invalid string key' => ['$s="abc";return isset($s[$input]);'];
364 yield 'ternary' => ['return $input === "xx" ? false : true;'];
365 yield 'short circuit' => ['return $input === "yy" && true;'];
366 yield 'absent array key' => ['$a=["public"=>1];return isset($a[$input]);'];
367 }
368}
369