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