packages/deriver/tests/Unit/Deriver/Evaluator/Execution/EvaluatorTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution;
6
7use Deriver\Deriver\Contributions;
8use Deriver\Deriver\DeriverConfig;
9use Deriver\Deriver\Evaluator\Call\BoundInstruction;
10use Deriver\Deriver\Evaluator\Call\Evaluators;
11use Deriver\Deriver\Evaluator\Call\Extensions;
12use Deriver\Deriver\Evaluator\Call\Registrations;
13use Deriver\Deriver\Evaluator\Call\StateRegistry;
14use Deriver\Deriver\Evaluator\EvaluatorConfig;
15use Deriver\Deriver\Evaluator\Evidence\Frontier;
16use Deriver\Deriver\Evaluator\Execution\Call\ArgumentBinding;
17use Deriver\Deriver\Evaluator\Execution\Call\ArgumentOrder;
18use Deriver\Deriver\Evaluator\Execution\Call\CallableCheck;
19use Deriver\Deriver\Evaluator\Execution\Call\CallExecutor;
20use Deriver\Deriver\Evaluator\Execution\Call\CallResolution;
21use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Resolution;
22use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target;
23use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Transfer;
24use Deriver\Deriver\Evaluator\Execution\Call\TypeBinding;
25use Deriver\Deriver\Evaluator\Execution\Call\TypeCheck;
26use Deriver\Deriver\Evaluator\Execution\Completion\Alternative;
27use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
28use Deriver\Deriver\Evaluator\Execution\Completion\CompletionKind;
29use Deriver\Deriver\Evaluator\Execution\Completion\Exceptional;
30use Deriver\Deriver\Evaluator\Execution\Completion\StorageSnapshot;
31use Deriver\Deriver\Evaluator\Execution\Constraints;
32use Deriver\Deriver\Evaluator\Execution\Context;
33use Deriver\Deriver\Evaluator\Execution\Control\ExceptionMatch;
34use Deriver\Deriver\Evaluator\Execution\Control\Handler;
35use Deriver\Deriver\Evaluator\Execution\Control\HandlerPhase;
36use Deriver\Deriver\Evaluator\Execution\Control\LoopConvergence;
37use Deriver\Deriver\Evaluator\Execution\Control\ObservationLimit;
38use Deriver\Deriver\Evaluator\Execution\Control\ResidualPaths;
39use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
40use Deriver\Deriver\Evaluator\Execution\Control\Unwinding;
41use Deriver\Deriver\Evaluator\Execution\Demand\Cell;
42use Deriver\Deriver\Evaluator\Execution\Demand\Components;
43use Deriver\Deriver\Evaluator\Execution\Demand\Discovery;
44use Deriver\Deriver\Evaluator\Execution\Demand\Key;
45use Deriver\Deriver\Evaluator\Execution\Demand\Table;
46use Deriver\Deriver\Evaluator\Execution\Dependencies;
47use Deriver\Deriver\Evaluator\Execution\Evaluator;
48use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
49use Deriver\Deriver\Evaluator\Execution\Havoc;
50use Deriver\Deriver\Evaluator\Execution\InstructionTransfer;
51use Deriver\Deriver\Evaluator\Execution\Memory\Location;
52use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
53use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
54use Deriver\Deriver\Evaluator\Execution\Memory\Slots\SlotReference;
55use Deriver\Deriver\Evaluator\Execution\Memory\StorageCapture;
56use Deriver\Deriver\Evaluator\Execution\ObservationCollector;
57use Deriver\Deriver\Evaluator\Execution\Operation\Conversions;
58use Deriver\Deriver\Evaluator\Execution\Operation\ScalarErrors;
59use Deriver\Deriver\Evaluator\Execution\State;
60use Deriver\Deriver\Evaluator\Execution\Summary\CompletionRecord;
61use Deriver\Deriver\Evaluator\Execution\Summary\Evaluation;
62use Deriver\Deriver\Evaluator\Execution\Summary\Invocation;
63use Deriver\Deriver\Evaluator\Execution\Summary\Isolation;
64use Deriver\Deriver\Evaluator\Execution\Trace\Derivation;
65use Deriver\Deriver\Evaluator\Execution\Transfer\PureStep;
66use Deriver\Deriver\Evaluator\Execution\Transfer\ReferenceAssignment;
67use Deriver\Deriver\Evaluator\Request\Budget;
68use Deriver\Deriver\Evaluator\Request\QueryScope;
69use Deriver\Deriver\Evaluator\Request\ReturnQuery;
70use Deriver\Deriver\Evaluator\Scalar\Arrays;
71use Deriver\Deriver\Evaluator\Scalar\Identity;
72use Deriver\Deriver\Evaluator\Scalar\NumericString;
73use Deriver\Deriver\Evaluator\Scalar\Operations;
74use Deriver\Deriver\Evaluator\Scalar\StringPrefix;
75use Deriver\Deriver\Evaluator\TargetProfile;
76use Deriver\Deriver\Evaluator\Term;
77use Deriver\Deriver\Evaluator\TermKind;
78use Deriver\Deriver\Execution\QueryExecution;
79use Deriver\Deriver\Lowerer\AggregateLowering;
80use Deriver\Deriver\Lowerer\CallLowering;
81use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
82use Deriver\Deriver\Lowerer\Declaration\CallableSource;
83use Deriver\Deriver\Lowerer\Declaration\ConstantSignatures;
84use Deriver\Deriver\Lowerer\Declaration\Index;
85use Deriver\Deriver\Lowerer\Declaration\Indexer;
86use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassDeclaration;
87use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
88use Deriver\Deriver\Lowerer\ExpressionLowering;
89use Deriver\Deriver\Lowerer\Graph\BasicBlock;
90use Deriver\Deriver\Lowerer\Graph\CallableGraph;
91use Deriver\Deriver\Lowerer\Graph\ExceptionRegion;
92use Deriver\Deriver\Lowerer\Graph\Instruction;
93use Deriver\Deriver\Lowerer\Graph\Terminator;
94use Deriver\Deriver\Lowerer\Graph\TerminatorKind;
95use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
96use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
97use Deriver\Deriver\Lowerer\InstructionEmitter;
98use Deriver\Deriver\Lowerer\Lowerer;
99use Deriver\Deriver\Lowerer\LoweringScope;
100use Deriver\Deriver\Lowerer\StatementLowering;
101use Deriver\Deriver\MicroEvaluatorConversion;
102use Deriver\Deriver\Parser\Node\File;
103use Deriver\Deriver\Parser\Parser;
104use Deriver\Deriver\Parser\ParserConfig;
105use Deriver\Deriver\Parser\TargetSyntax;
106use Deriver\Deriver\Providers\ProviderExpansion;
107use Deriver\Deriver\Resolver\AssignmentPatterns;
108use Deriver\Deriver\Resolver\ClassScope;
109use Deriver\Deriver\Resolver\ExpressionValidation;
110use Deriver\Deriver\Resolver\MagicContext;
111use Deriver\Deriver\Resolver\Resolver;
112use Deriver\Deriver\SnapshotIdentity;
113use Deriver\Deriver\Sources\LineMap;
114use Deriver\Deriver\Sources\Path;
115use Deriver\Deriver\Sources\Source;
116use Deriver\Deriver\Sources\SourceRef;
117use Deriver\Deriver\Sources\Sources;
118use Deriver\Exception\ParserException;
119use Deriver\Exception\ResolutionException;
120use PHPUnit\Framework\Attributes\CoversClass;
121use PHPUnit\Framework\Attributes\DataProvider;
122use PHPUnit\Framework\Attributes\Small;
123use PHPUnit\Framework\Attributes\UsesClass;
124use PHPUnit\Framework\TestCase;
125
126/**
127 * @covers \Deriver\Deriver\Evaluator\Execution\Evaluator
128 */
129#[UsesClass(Resolver::class)]
130#[UsesClass(ExpressionValidation::class)]
131#[UsesClass(ResolutionException::class)]
132#[UsesClass(ParserConfig::class)]
133#[UsesClass(ParserException::class)]
134#[CoversClass(Evaluator::class)]
135#[UsesClass(Arrays::class)]
136#[UsesClass(AggregateLowering::class)]
137#[UsesClass(Constraints::class)]
138#[UsesClass(BasicBlock::class)]
139#[UsesClass(CallableGraph::class)]
140#[UsesClass(CallableIdentity::class)]
141#[UsesClass(ClassDeclaration::class)]
142#[UsesClass(ExceptionRegion::class)]
143#[UsesClass(Instruction::class)]
144#[UsesClass(Terminator::class)]
145#[UsesClass(ArgumentBinding::class)]
146#[UsesClass(ArgumentOrder::class)]
147#[UsesClass(CallExecutor::class)]
148#[UsesClass(CallResolution::class)]
149#[UsesClass(CallableCheck::class)]
150#[UsesClass(Builtins::class)]
151#[UsesClass(Dispatch::class)]
152#[UsesClass(Resolution::class)]
153#[UsesClass(Target::class)]
154#[UsesClass(Transfer::class)]
155#[UsesClass(TypeBinding::class)]
156#[UsesClass(TypeCheck::class)]
157#[UsesClass(Completion::class)]
158#[UsesClass(Context::class)]
159#[UsesClass(ExceptionMatch::class)]
160#[UsesClass(LoopConvergence::class)]
161#[UsesClass(Handler::class)]
162#[UsesClass(ObservationLimit::class)]
163#[UsesClass(ResidualPaths::class)]
164#[UsesClass(StateJoin::class)]
165#[UsesClass(Unwinding::class)]
166#[UsesClass(Cell::class)]
167#[UsesClass(Components::class)]
168#[UsesClass(Discovery::class)]
169#[UsesClass(Key::class)]
170#[UsesClass(Table::class)]
171#[UsesClass(Dependencies::class)]
172#[UsesClass(Havoc::class)]
173#[UsesClass(InstructionTransfer::class)]
174#[UsesClass(SlotReference::class)]
175#[UsesClass(ObservationCollector::class)]
176#[UsesClass(Conversions::class)]
177#[UsesClass(ScalarErrors::class)]
178#[UsesClass(State::class)]
179#[UsesClass(CompletionRecord::class)]
180#[UsesClass(Evaluation::class)]
181#[UsesClass(Invocation::class)]
182#[UsesClass(Isolation::class)]
183#[UsesClass(PureStep::class)]
184#[UsesClass(ReferenceAssignment::class)]
185#[UsesClass(Location::class)]
186#[UsesClass(Materialization::class)]
187#[UsesClass(Memory::class)]
188#[UsesClass(StorageCapture::class)]
189#[UsesClass(Extensions::class)]
190#[UsesClass(Evaluators::class)]
191#[UsesClass(StateRegistry::class)]
192#[UsesClass(DeriverConfig::class)]
193#[UsesClass(Sources::class)]
194#[UsesClass(Path::class)]
195#[UsesClass(Source::class)]
196#[UsesClass(TargetProfile::class)]
197#[UsesClass(Budget::class)]
198#[UsesClass(QueryScope::class)]
199#[UsesClass(ReturnQuery::class)]
200#[UsesClass(SourceRef::class)]
201#[UsesClass(Alternative::class)]
202#[UsesClass(Derivation::class)]
203#[UsesClass(Exceptional::class)]
204#[UsesClass(Frontier::class)]
205#[UsesClass(StorageSnapshot::class)]
206#[UsesClass(Parser::class)]
207#[UsesClass(File::class)]
208#[UsesClass(CallLowering::class)]
209#[UsesClass(Lowerer::class)]
210#[UsesClass(ExpressionLowering::class)]
211#[UsesClass(InstructionEmitter::class)]
212#[UsesClass(LoweringScope::class)]
213#[UsesClass(StatementLowering::class)]
214#[UsesClass(ConstantSignatures::class)]
215#[UsesClass(CallableSource::class)]
216#[UsesClass(Indexer::class)]
217#[UsesClass(Index::class)]
218#[UsesClass(Composition::class)]
219#[UsesClass(LineMap::class)]
220#[UsesClass(MagicContext::class)]
221#[UsesClass(AssignmentPatterns::class)]
222#[UsesClass(ClassScope::class)]
223#[UsesClass(TargetSyntax::class)]
224#[UsesClass(Identity::class)]
225#[UsesClass(NumericString::class)]
226#[UsesClass(Operations::class)]
227#[UsesClass(StringPrefix::class)]
228#[UsesClass(Term::class)]
229#[Small]
230final class EvaluatorTest extends TestCase
231{
232    public function testRunReturnsTheObservedValue(): void
233    {
234        $source = '<?php function target(){return 1;}';
235        $budget = new Budget();
236        $configuration = new DeriverConfig();
237        $contributions = new Contributions();
238        $input = new Sources([new Source('fixture.php', $source)]);
239        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
240        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
241        $contributions1 = $providers->contributions;
242        $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);
243        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
244        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
245        $snapshotId = $identity->id;
246        $input1 = $providers->input;
247        $parser = new Parser(new ParserConfig($target->version));
248        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
249        $program1 = new Index($snapshotId, $trees);
250        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
251        $snapshotId1 = 'test';
252        $input2 = new Sources([new Source('fixture.php', $source)]);
253        $profile = $execution->configuration->target;
254        $parser1 = new Parser(new ParserConfig($profile->version));
255        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
256        $program = new Index($snapshotId1, $trees1);
257        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
258        $machine = new Evaluator($context);
259        $body = $context->program->callable('target');
260        self::assertNotNull($body);
261        $states = $machine->run($body, new State());
262        self::assertCount(1, $states);
263        self::assertSame(CompletionKind::Return, $states[0]->completion->kind);
264        self::assertSame(1, $states[0]->completion->value?->native());
265    }
266    public function testReturnedEnforcesTheDeclaredReturnType(): void
267    {
268        $source = '<?php declare(strict_types=1);function target():int{}';
269        $budget = new Budget();
270        $configuration = new DeriverConfig();
271        $contributions = new Contributions();
272        $input = new Sources([new Source('fixture.php', $source)]);
273        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
274        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
275        $contributions1 = $providers->contributions;
276        $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);
277        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
278        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
279        $snapshotId = $identity->id;
280        $input1 = $providers->input;
281        $parser = new Parser(new ParserConfig($target->version));
282        $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));
283        $program1 = new Index($snapshotId, $trees);
284        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
285        $snapshotId1 = 'test';
286        $input2 = new Sources([new Source('fixture.php', $source)]);
287        $profile = $execution->configuration->target;
288        $parser1 = new Parser(new ParserConfig($profile->version));
289        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
290        $program = new Index($snapshotId1, $trees1);
291        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
292        $body = $context->program->callable('target');
293        self::assertNotNull($body);
294        $state = new State();
295        $state->completion = new Completion(CompletionKind::Return, Term::constant('invalid'));
296        $paths = (new Evaluator($context))->returned($body, $state);
297        self::assertSame(CompletionKind::Throw, $paths[0]->completion->kind);
298        self::assertSame('TypeError', $paths[0]->completion->value?->literal);
299    }
300    public function testBlockTransfersOnlyDemandedDefinitions(): void
301    {
302        $source = '<?php function target(){return 1;}';
303        $budget = new Budget();
304        $configuration = new DeriverConfig();
305        $contributions = new Contributions();
306        $input = new Sources([new Source('fixture.php', $source)]);
307        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
308        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
309        $contributions1 = $providers->contributions;
310        $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);
311        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
312        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
313        $snapshotId = $identity->id;
314        $input1 = $providers->input;
315        $parser = new Parser(new ParserConfig($target->version));
316        $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));
317        $program1 = new Index($snapshotId, $trees);
318        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
319        $snapshotId1 = 'test';
320        $input2 = new Sources([new Source('fixture.php', $source)]);
321        $profile = $execution->configuration->target;
322        $parser1 = new Parser(new ParserConfig($profile->version));
323        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
324        $program = new Index($snapshotId1, $trees1);
325        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
326        $machine = new Evaluator($context);
327        $body = $context->program->callable('target');
328        self::assertNotNull($body);
329        $context->demands[$body] = (new Discovery($context))->instructions($body);
330        $paths = $machine->block($body, new State());
331        self::assertSame(1, $paths[0]->completion->value?->native());
332    }
333    public function testStepRecordsAConstantDefinition(): void
334    {
335        $source = '<?php function target(){return 1;}';
336        $budget = new Budget();
337        $configuration = new DeriverConfig();
338        $contributions = new Contributions();
339        $input = new Sources([new Source('fixture.php', $source)]);
340        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
341        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
342        $contributions1 = $providers->contributions;
343        $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);
344        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
345        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
346        $snapshotId = $identity->id;
347        $input1 = $providers->input;
348        $parser = new Parser(new ParserConfig($target->version));
349        $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));
350        $program1 = new Index($snapshotId, $trees);
351        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
352        $snapshotId1 = 'test';
353        $input2 = new Sources([new Source('fixture.php', $source)]);
354        $profile = $execution->configuration->target;
355        $parser1 = new Parser(new ParserConfig($profile->version));
356        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
357        $program = new Index($snapshotId1, $trees1);
358        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
359        $machine = new Evaluator($context);
360        $body = $context->program->callable('target');
361        self::assertNotNull($body);
362        $instruction = $body->blocks[0]->instructions[0];
363        $paths = $machine->step($body, $instruction, new State());
364        self::assertSame(1, $paths[0]->value($instruction->result)->native());
365        self::assertArrayHasKey($instruction->id, $context->evidence);
366    }
367    public function testTerminateUsesTheAlreadyEvaluatedReturnRegister(): void
368    {
369        $source = '<?php function target(){return 1;}';
370        $budget = new Budget();
371        $configuration = new DeriverConfig();
372        $contributions = new Contributions();
373        $input = new Sources([new Source('fixture.php', $source)]);
374        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
375        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
376        $contributions1 = $providers->contributions;
377        $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);
378        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
379        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
380        $snapshotId = $identity->id;
381        $input1 = $providers->input;
382        $parser = new Parser(new ParserConfig($target->version));
383        $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));
384        $program1 = new Index($snapshotId, $trees);
385        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
386        $snapshotId1 = 'test';
387        $input2 = new Sources([new Source('fixture.php', $source)]);
388        $profile = $execution->configuration->target;
389        $parser1 = new Parser(new ParserConfig($profile->version));
390        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
391        $program = new Index($snapshotId1, $trees1);
392        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
393        $machine = new Evaluator($context);
394        $body = $context->program->callable('target');
395        self::assertNotNull($body);
396        $state = new State();
397        $state->registers['result'] = Term::constant(3);
398        $paths = $machine->terminate($body, new Terminator(TerminatorKind::Return, 'result'), $state);
399        self::assertSame(3, $paths[0]->completion->value?->native());
400    }
401    public function testBranchEliminatesAContradictedConstantPath(): void
402    {
403        $source = '<?php function target(){return 1;}';
404        $budget = new Budget();
405        $configuration = new DeriverConfig();
406        $contributions = new Contributions();
407        $input = new Sources([new Source('fixture.php', $source)]);
408        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
409        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
410        $contributions1 = $providers->contributions;
411        $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);
412        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
413        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
414        $snapshotId = $identity->id;
415        $input1 = $providers->input;
416        $parser = new Parser(new ParserConfig($target->version));
417        $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));
418        $program1 = new Index($snapshotId, $trees);
419        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
420        $snapshotId1 = 'test';
421        $input2 = new Sources([new Source('fixture.php', $source)]);
422        $profile = $execution->configuration->target;
423        $parser1 = new Parser(new ParserConfig($profile->version));
424        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
425        $program = new Index($snapshotId1, $trees1);
426        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
427        $machine = new Evaluator($context);
428        $body = $context->program->callable('target');
429        self::assertNotNull($body);
430        $state = new State();
431        $state->registers['condition'] = Term::constant(false);
432        $paths = $machine->branch(new Terminator(TerminatorKind::Branch, 'condition', [1, 2]), $state);
433        self::assertCount(1, $paths);
434        self::assertSame(2, $paths[0]->block);
435    }
436    public function testExecuteTransfersOnceWithoutPublishingSpeculativeObservations(): void
437    {
438        $source = '<?php function target(){return 1;}';
439        $budget = new Budget();
440        $configuration = new DeriverConfig();
441        $contributions = new Contributions();
442        $input = new Sources([new Source('fixture.php', $source)]);
443        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
444        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
445        $contributions1 = $providers->contributions;
446        $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);
447        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
448        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
449        $snapshotId = $identity->id;
450        $input1 = $providers->input;
451        $parser = new Parser(new ParserConfig($target->version));
452        $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));
453        $program1 = new Index($snapshotId, $trees);
454        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
455        $snapshotId1 = 'test';
456        $input2 = new Sources([new Source('fixture.php', $source)]);
457        $profile = $execution->configuration->target;
458        $parser1 = new Parser(new ParserConfig($profile->version));
459        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
460        $program = new Index($snapshotId1, $trees1);
461        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
462        $symbol = 'target';
463        $body = $context->program->callable($symbol);
464        self::assertNotNull($body);
465        $context->demands[$body] = (new Discovery($context))->instructions($body);
466        $paths = (new Evaluator($context))->execute($body, new State());
467        self::assertSame(1, $paths[0]->completion->value?->literal);
468        self::assertSame([], $context->normal);
469    }
470    public function testRecursiveBoundsSymbolicActivationsBeforeConcreteOnes(): void
471    {
472        $source = '<?php function target(){return 1;}';
473        $budget = new Budget(recursion: 3, symbolicRecursion: 1);
474        $configuration = new DeriverConfig();
475        $contributions = new Contributions();
476        $input = new Sources([new Source('fixture.php', $source)]);
477        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
478        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
479        $contributions1 = $providers->contributions;
480        $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);
481        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
482        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
483        $snapshotId = $identity->id;
484        $input1 = $providers->input;
485        $parser = new Parser(new ParserConfig($target->version));
486        $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));
487        $program1 = new Index($snapshotId, $trees);
488        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
489        $snapshotId1 = 'test';
490        $input2 = new Sources([new Source('fixture.php', $source)]);
491        $profile = $execution->configuration->target;
492        $parser1 = new Parser(new ParserConfig($profile->version));
493        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
494        $program = new Index($snapshotId1, $trees1);
495        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
496        $machine = new Evaluator($context);
497        $symbolic = new State();
498        $symbolic->memory->write($symbolic->local('n'), Term::parameter('n', 'int'));
499        $concrete = new State();
500        $concrete->memory->write($concrete->local('n'), Term::constant(1));
501        $concrete->memory->write($concrete->local('this'), new Term(TermKind::Object, 'object:1', attributes: ['class' => 'A']));
502        $context->active['f'] = 1;
503        self::assertFalse($machine->recursive('f', $symbolic));
504        $context->active['f'] = 2;
505        self::assertTrue($machine->recursive('f', $symbolic));
506        self::assertFalse($machine->recursive('f', $concrete));
507        $context->active['f'] = 4;
508        self::assertTrue($machine->recursive('f', $concrete));
509    }
510    public function testRunSealsSymbolicRecursionWithoutHavockingTheCaller(): void
511    {
512        $source = '<?php function target(array $c){return f($c);} function f(array $c){return $c ? "x" . f($c) : "";}';
513        $budget = new Budget(symbolicRecursion: 2);
514        $configuration = new DeriverConfig();
515        $contributions = new Contributions();
516        $input = new Sources([new Source('fixture.php', $source)]);
517        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
518        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
519        $contributions1 = $providers->contributions;
520        $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);
521        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
522        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
523        $snapshotId = $identity->id;
524        $input1 = $providers->input;
525        $parser = new Parser(new ParserConfig($target->version));
526        $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));
527        $program1 = new Index($snapshotId, $trees);
528        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
529        $snapshotId1 = 'test';
530        $input2 = new Sources([new Source('fixture.php', $source)]);
531        $profile = $execution->configuration->target;
532        $parser1 = new Parser(new ParserConfig($profile->version));
533        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
534        $program = new Index($snapshotId1, $trees1);
535        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
536        $state = new State();
537        $caller = $state->memory->allocate(Term::constant('caller'));
538        $state->memory->write($state->local('c'), Term::parameter('c', 'array'));
539        $body = $context->program->callable('f');
540        self::assertNotNull($body);
541        $paths = (new Evaluator($context))->run($body, $state);
542        self::assertContains('recursive-specialization', array_column($context->frontiers, 'operation'));
543        self::assertSame(['caller'], array_values(array_unique(array_map(static fn (State $path): string|int|float|bool|null => $path->memory->read($caller)->literal, $paths))));
544        $returned = array_map(static fn (State $path): array => (new StringPrefix())->known($path->completion->value ?? Term::constant(null)), array_values(array_filter($paths, static fn (State $path): bool => $path->completion->kind === CompletionKind::Return)));
545        self::assertContains(['', true], $returned);
546        self::assertContains(['xx', false], $returned);
547    }
548    /**
549     * @param string $type Declared return type
550     * @param bool $strict Calling-file mode
551     * @param Term|null $value Returned value or implicit null
552     * @param string $completion Expected completion kind
553     * @param mixed $expected Expected scalar or exception name
554     */
555    #[DataProvider('providerReturnTypes')]
556    public function testReturnedValidatesFinalValuesUnderTheDeclaringFileMode(string $type, bool $strict, ?Term $value, string $completion, mixed $expected): void
557    {
558        $source1 = '<?php function target(){return 1;}';
559        $budget = new Budget();
560        $configuration = new DeriverConfig();
561        $contributions = new Contributions();
562        $input = new Sources([new Source('fixture.php', $source1)]);
563        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
564        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
565        $contributions1 = $providers->contributions;
566        $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);
567        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
568        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
569        $snapshotId = $identity->id;
570        $input1 = $providers->input;
571        $parser = new Parser(new ParserConfig($target->version));
572        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
573        $program1 = new Index($snapshotId, $trees);
574        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
575        $snapshotId1 = 'test';
576        $input2 = new Sources([new Source('fixture.php', $source1)]);
577        $profile = $execution->configuration->target;
578        $parser1 = new Parser(new ParserConfig($profile->version));
579        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
580        $program = new Index($snapshotId1, $trees1);
581        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
582        $symbol = 'target';
583        $fixtureCallable1 = $context->program->callable($symbol);
584        self::assertNotNull($fixtureCallable1);
585        $source = $fixtureCallable1->source;
586        $body = new CallableGraph('target', [], [], $source, $type, strict: $strict);
587        $state = new State();
588        $state->completion = new Completion(CompletionKind::Return, $value);
589        $paths = (new Evaluator($context))->returned($body, $state);
590        self::assertCount(1, $paths);
591        self::assertSame(CompletionKind::from($completion), $paths[0]->completion->kind);
592        self::assertSame($expected, $paths[0]->completion->value?->literal);
593    }
594    /**
595     * @return iterable<string,array{string,bool,Term|null,string,mixed}>
596     */
597    public static function providerReturnTypes(): iterable
598    {
599        yield 'weak integer' => ['int', false, Term::constant('12'), 'return', 12];
600        yield 'strict integer' => ['int', true, Term::constant('12'), 'throw', 'TypeError'];
601        yield 'exact integer' => ['int', true, Term::constant(12), 'return', 12];
602        yield 'integer to float' => ['float', true, Term::constant(12), 'return', 12.0];
603        yield 'null accepted' => ['int|null', true, null, 'return', null];
604        yield 'null rejected' => ['int', false, null, 'throw', 'TypeError'];
605        yield 'void' => ['void', true, null, 'return', null];
606        yield 'mixed' => ['mixed', true, Term::constant('abc'), 'return', 'abc'];
607        yield 'never' => ['never', false, null, 'throw', 'TypeError'];
608        yield 'weak boolean' => ['bool', false, Term::constant(''), 'return', false];
609        yield 'strict false' => ['false', true, Term::constant(false), 'return', false];
610    }
611    public function testReturnedKeepsExceptionsOutsideReturnTypeValidation(): void
612    {
613        $source1 = '<?php function target(){return 1;}';
614        $budget = new Budget();
615        $configuration = new DeriverConfig();
616        $contributions = new Contributions();
617        $input = new Sources([new Source('fixture.php', $source1)]);
618        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
619        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
620        $contributions1 = $providers->contributions;
621        $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);
622        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
623        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
624        $snapshotId = $identity->id;
625        $input1 = $providers->input;
626        $parser = new Parser(new ParserConfig($target->version));
627        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
628        $program1 = new Index($snapshotId, $trees);
629        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
630        $snapshotId1 = 'test';
631        $input2 = new Sources([new Source('fixture.php', $source1)]);
632        $profile = $execution->configuration->target;
633        $parser1 = new Parser(new ParserConfig($profile->version));
634        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
635        $program = new Index($snapshotId1, $trees1);
636        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
637        $symbol = 'target';
638        $fixtureCallable1 = $context->program->callable($symbol);
639        self::assertNotNull($fixtureCallable1);
640        $source = $fixtureCallable1->source;
641        $body = new CallableGraph('target', [], [], $source, 'never');
642        $state = new State();
643        $completion = new Completion(CompletionKind::Throw, new Term(TermKind::Throwable, 'RuntimeException'));
644        $state->completion = $completion;
645        self::assertSame([$state], (new Evaluator($context))->returned($body, $state));
646        self::assertSame($completion, $state->completion);
647    }
648    public function testReturnedCoercesSharedCellsAndRetainsReferenceIdentity(): void
649    {
650        $source1 = '<?php function target(){return 1;}';
651        $budget = new Budget();
652        $configuration = new DeriverConfig();
653        $contributions = new Contributions();
654        $input = new Sources([new Source('fixture.php', $source1)]);
655        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
656        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
657        $contributions1 = $providers->contributions;
658        $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);
659        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
660        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
661        $snapshotId = $identity->id;
662        $input1 = $providers->input;
663        $parser = new Parser(new ParserConfig($target->version));
664        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
665        $program1 = new Index($snapshotId, $trees);
666        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
667        $snapshotId1 = 'test';
668        $input2 = new Sources([new Source('fixture.php', $source1)]);
669        $profile = $execution->configuration->target;
670        $parser1 = new Parser(new ParserConfig($profile->version));
671        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
672        $program = new Index($snapshotId1, $trees1);
673        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
674        $symbol = 'target';
675        $fixtureCallable1 = $context->program->callable($symbol);
676        self::assertNotNull($fixtureCallable1);
677        $source = $fixtureCallable1->source;
678        $body = new CallableGraph('target', [], [], $source, 'int', byReference: true);
679        $state = new State();
680        $cell = $state->memory->allocate(Term::constant('12'));
681        $reference = new Term(TermKind::Cell, $cell->root);
682        $state->completion = new Completion(CompletionKind::Return, $reference);
683        $paths = (new Evaluator($context))->returned($body, $state);
684        self::assertCount(1, $paths);
685        self::assertSame(CompletionKind::Return, $paths[0]->completion->kind);
686        self::assertSame($reference, $paths[0]->completion->value);
687        self::assertSame(12, $paths[0]->memory->read($cell)->native());
688    }
689    public function testReturnedKeepsThePreCoercionCellOnAnUncertainTypeError(): void
690    {
691        $source1 = '<?php function target(){return 1;}';
692        $budget = new Budget();
693        $configuration = new DeriverConfig();
694        $contributions = new Contributions();
695        $input1 = new Sources([new Source('fixture.php', $source1)]);
696        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
697        $providers = new ProviderExpansion($input1, array_map(Term::fromNative(...), $configuration->environment), $contributions);
698        $contributions1 = $providers->contributions;
699        $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);
700        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
701        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
702        $snapshotId = $identity->id;
703        $input2 = $providers->input;
704        $parser = new Parser(new ParserConfig($target->version));
705        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
706        $program1 = new Index($snapshotId, $trees);
707        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
708        $snapshotId1 = 'test';
709        $input3 = new Sources([new Source('fixture.php', $source1)]);
710        $profile = $execution->configuration->target;
711        $parser1 = new Parser(new ParserConfig($profile->version));
712        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input3, false));
713        $program = new Index($snapshotId1, $trees1);
714        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
715        $symbol = 'target';
716        $fixtureCallable1 = $context->program->callable($symbol);
717        self::assertNotNull($fixtureCallable1);
718        $source = $fixtureCallable1->source;
719        $body = new CallableGraph('target', [], [], $source, 'int', byReference: true);
720        $state = new State();
721        $input = Term::parameter('input');
722        $cell = $state->memory->allocate($input);
723        $reference = new Term(TermKind::Cell, $cell->root);
724        $state->completion = new Completion(CompletionKind::Return, $reference);
725        $paths = (new Evaluator($context))->returned($body, $state);
726        self::assertCount(2, $paths);
727        self::assertSame(CompletionKind::Return, $paths[0]->completion->kind);
728        self::assertSame($reference, $paths[0]->completion->value);
729        self::assertSame(TermKind::TypeRefinement, $paths[0]->memory->read($cell)->kind);
730        self::assertSame('int', $paths[0]->memory->read($cell)->attributes['type']);
731        self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
732        self::assertSame('TypeError', $paths[1]->completion->value?->literal);
733        self::assertSame($input, $paths[1]->memory->read($cell));
734    }
735    public function testReturnedResolvesLateStaticTypesAgainstTheBoundReceiverClass(): void
736    {
737        $source1 = '<?php class Base{}class Child extends Base{}function target(){}';
738        $budget = new Budget();
739        $configuration = new DeriverConfig();
740        $contributions = new Contributions();
741        $input = new Sources([new Source('fixture.php', $source1)]);
742        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
743        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
744        $contributions1 = $providers->contributions;
745        $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);
746        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
747        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
748        $snapshotId = $identity->id;
749        $input1 = $providers->input;
750        $parser = new Parser(new ParserConfig($target->version));
751        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
752        $program1 = new Index($snapshotId, $trees);
753        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
754        $snapshotId1 = 'test';
755        $input2 = new Sources([new Source('fixture.php', $source1)]);
756        $profile = $execution->configuration->target;
757        $parser1 = new Parser(new ParserConfig($profile->version));
758        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
759        $program = new Index($snapshotId1, $trees1);
760        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
761        $symbol = 'target';
762        $fixtureCallable1 = $context->program->callable($symbol);
763        self::assertNotNull($fixtureCallable1);
764        $source = $fixtureCallable1->source;
765        $body = new CallableGraph('Base::create', [], [], $source, 'static', className: 'Base');
766        $state = new State();
767        $value = new Term(TermKind::Object, 'instance', attributes: ['class' => 'Child']);
768        $state->lateStaticClass = 'Child';
769        $state->completion = new Completion(CompletionKind::Return, $value);
770        $paths = (new Evaluator($context))->returned($body, $state);
771        self::assertCount(1, $paths);
772        self::assertSame(CompletionKind::Return, $paths[0]->completion->kind);
773        self::assertSame($value, $paths[0]->completion->value);
774    }
775    public function testTerminateJumpRecordsThePredecessorAndUsesTheFirstTarget(): void
776    {
777        $source = '<?php function target(){return 1;}';
778        $budget = new Budget();
779        $configuration = new DeriverConfig();
780        $contributions = new Contributions();
781        $input = new Sources([new Source('fixture.php', $source)]);
782        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
783        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
784        $contributions1 = $providers->contributions;
785        $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);
786        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
787        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
788        $snapshotId = $identity->id;
789        $input1 = $providers->input;
790        $parser = new Parser(new ParserConfig($target->version));
791        $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));
792        $program1 = new Index($snapshotId, $trees);
793        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
794        $snapshotId1 = 'test';
795        $input2 = new Sources([new Source('fixture.php', $source)]);
796        $profile = $execution->configuration->target;
797        $parser1 = new Parser(new ParserConfig($profile->version));
798        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
799        $program = new Index($snapshotId1, $trees1);
800        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
801        $symbol = 'target';
802        $body = $context->program->callable($symbol);
803        self::assertNotNull($body);
804        $state = new State();
805        $state->block = 7;
806        $state->previous = 3;
807        $paths = (new Evaluator($context))->terminate($body, new Terminator(TerminatorKind::Jump, targets: [12, 99]), $state);
808        self::assertSame([$state], $paths);
809        self::assertSame(7, $paths[0]->previous);
810        self::assertSame(12, $paths[0]->block);
811        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
812    }
813    public function testTerminateLeaveTryKeepsOuterHandlersAndResumesAtTheRegionContinuation(): void
814    {
815        $source = '<?php function target(){return 1;}';
816        $budget = new Budget();
817        $configuration = new DeriverConfig();
818        $contributions = new Contributions();
819        $input = new Sources([new Source('fixture.php', $source)]);
820        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
821        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
822        $contributions1 = $providers->contributions;
823        $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);
824        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
825        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
826        $snapshotId = $identity->id;
827        $input1 = $providers->input;
828        $parser = new Parser(new ParserConfig($target->version));
829        $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));
830        $program1 = new Index($snapshotId, $trees);
831        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
832        $snapshotId1 = 'test';
833        $input2 = new Sources([new Source('fixture.php', $source)]);
834        $profile = $execution->configuration->target;
835        $parser1 = new Parser(new ParserConfig($profile->version));
836        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
837        $program = new Index($snapshotId1, $trees1);
838        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
839        $symbol = 'target';
840        $body = $context->program->callable($symbol);
841        self::assertNotNull($body);
842        $state = new State();
843        $state->block = 7;
844        $outer = new Handler(new ExceptionRegion([], 40, 50));
845        $state->handlers = [$outer, new Handler(new ExceptionRegion([], null, 12))];
846        $paths = (new Evaluator($context))->terminate($body, new Terminator(TerminatorKind::LeaveTry), $state);
847        self::assertSame([$state], $paths);
848        self::assertSame([$outer], $paths[0]->handlers);
849        self::assertSame(12, $paths[0]->block);
850        self::assertSame(7, $paths[0]->previous);
851        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
852    }
853    public function testTerminateLeaveTryRunsFinallyBeforeContinuing(): void
854    {
855        $source = '<?php function target(){return 1;}';
856        $budget = new Budget();
857        $configuration = new DeriverConfig();
858        $contributions = new Contributions();
859        $input = new Sources([new Source('fixture.php', $source)]);
860        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
861        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
862        $contributions1 = $providers->contributions;
863        $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);
864        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
865        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
866        $snapshotId = $identity->id;
867        $input1 = $providers->input;
868        $parser = new Parser(new ParserConfig($target->version));
869        $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));
870        $program1 = new Index($snapshotId, $trees);
871        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
872        $snapshotId1 = 'test';
873        $input2 = new Sources([new Source('fixture.php', $source)]);
874        $profile = $execution->configuration->target;
875        $parser1 = new Parser(new ParserConfig($profile->version));
876        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
877        $program = new Index($snapshotId1, $trees1);
878        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
879        $symbol = 'target';
880        $body = $context->program->callable($symbol);
881        self::assertNotNull($body);
882        $state = new State();
883        $state->handlers = [new Handler(new ExceptionRegion([], 8, 12))];
884        $paths = (new Evaluator($context))->terminate($body, new Terminator(TerminatorKind::LeaveTry), $state);
885        self::assertCount(1, $paths);
886        self::assertSame(8, $paths[0]->block);
887        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
888        self::assertSame(HandlerPhase::Finally, $paths[0]->handlers[0]->phase);
889        self::assertSame(CompletionKind::Jump, $paths[0]->handlers[0]->saved?->kind);
890        self::assertSame(12, $paths[0]->handlers[0]->saved->target);
891        self::assertSame(0, $paths[0]->handlers[0]->saved->depth);
892    }
893    public function testTerminateResumeRestoresTheSavedCompletionAndRemovesItsHandler(): void
894    {
895        $source = '<?php function target(){return 1;}';
896        $budget = new Budget();
897        $configuration = new DeriverConfig();
898        $contributions = new Contributions();
899        $input = new Sources([new Source('fixture.php', $source)]);
900        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
901        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
902        $contributions1 = $providers->contributions;
903        $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);
904        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
905        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
906        $snapshotId = $identity->id;
907        $input1 = $providers->input;
908        $parser = new Parser(new ParserConfig($target->version));
909        $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));
910        $program1 = new Index($snapshotId, $trees);
911        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
912        $snapshotId1 = 'test';
913        $input2 = new Sources([new Source('fixture.php', $source)]);
914        $profile = $execution->configuration->target;
915        $parser1 = new Parser(new ParserConfig($profile->version));
916        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
917        $program = new Index($snapshotId1, $trees1);
918        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
919        $symbol = 'target';
920        $body = $context->program->callable($symbol);
921        self::assertNotNull($body);
922        $state = new State();
923        $saved = new Completion(CompletionKind::Return, Term::constant(19));
924        $state->handlers = [new Handler(new ExceptionRegion([], 8, 12), HandlerPhase::Finally, $saved)];
925        $paths = (new Evaluator($context))->terminate($body, new Terminator(TerminatorKind::Resume), $state);
926        self::assertCount(1, $paths);
927        self::assertSame($saved, $paths[0]->completion);
928        self::assertSame([], $paths[0]->handlers);
929    }
930    public function testTerminateResumeWithoutASavedCompletionReturnsNull(): void
931    {
932        $source = '<?php function target(){return 1;}';
933        $budget = new Budget();
934        $configuration = new DeriverConfig();
935        $contributions = new Contributions();
936        $input = new Sources([new Source('fixture.php', $source)]);
937        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
938        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
939        $contributions1 = $providers->contributions;
940        $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);
941        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
942        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
943        $snapshotId = $identity->id;
944        $input1 = $providers->input;
945        $parser = new Parser(new ParserConfig($target->version));
946        $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));
947        $program1 = new Index($snapshotId, $trees);
948        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
949        $snapshotId1 = 'test';
950        $input2 = new Sources([new Source('fixture.php', $source)]);
951        $profile = $execution->configuration->target;
952        $parser1 = new Parser(new ParserConfig($profile->version));
953        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
954        $program = new Index($snapshotId1, $trees1);
955        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
956        $symbol = 'target';
957        $body = $context->program->callable($symbol);
958        self::assertNotNull($body);
959        $state = new State();
960        $paths = (new Evaluator($context))->terminate($body, new Terminator(TerminatorKind::Resume), $state);
961        self::assertCount(1, $paths);
962        self::assertSame(CompletionKind::Return, $paths[0]->completion->kind);
963        self::assertNull($paths[0]->completion->value?->native());
964    }
965    public function testTerminateCompleteJumpUnwindsOnlyTheExitedRegion(): void
966    {
967        $source = '<?php function target(){return 1;}';
968        $budget = new Budget();
969        $configuration = new DeriverConfig();
970        $contributions = new Contributions();
971        $input = new Sources([new Source('fixture.php', $source)]);
972        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
973        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
974        $contributions1 = $providers->contributions;
975        $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);
976        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
977        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
978        $snapshotId = $identity->id;
979        $input1 = $providers->input;
980        $parser = new Parser(new ParserConfig($target->version));
981        $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));
982        $program1 = new Index($snapshotId, $trees);
983        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
984        $snapshotId1 = 'test';
985        $input2 = new Sources([new Source('fixture.php', $source)]);
986        $profile = $execution->configuration->target;
987        $parser1 = new Parser(new ParserConfig($profile->version));
988        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
989        $program = new Index($snapshotId1, $trees1);
990        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
991        $symbol = 'target';
992        $body = $context->program->callable($symbol);
993        self::assertNotNull($body);
994        $state = new State();
995        $outer = new Handler(new ExceptionRegion([], 40, 50));
996        $state->handlers = [$outer, new Handler(new ExceptionRegion([], 8, 12))];
997        $paths = (new Evaluator($context))->terminate($body, new Terminator(TerminatorKind::CompleteJump, targets: [29], handlerDepth: 1), $state);
998        self::assertCount(1, $paths);
999        self::assertSame(8, $paths[0]->block);
1000        self::assertSame($outer, $paths[0]->handlers[0]);
1001        self::assertSame(29, $paths[0]->handlers[1]->saved?->target);
1002        self::assertSame(1, $paths[0]->handlers[1]->saved->depth);
1003    }
1004    /**
1005     * @param bool $byReference Return reference mode
1006     * @param string $kind Expected value category
1007     * @param mixed $literal Expected value payload
1008     */
1009    #[DataProvider('providerReturnReferences')]
1010    public function testTerminateDistinguishesReferenceAndValueReturns(bool $byReference, string $kind, mixed $literal): void
1011    {
1012        $source1 = '<?php function target(){return 1;}';
1013        $budget = new Budget();
1014        $configuration = new DeriverConfig();
1015        $contributions = new Contributions();
1016        $input = new Sources([new Source('fixture.php', $source1)]);
1017        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1018        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1019        $contributions1 = $providers->contributions;
1020        $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);
1021        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1022        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1023        $snapshotId = $identity->id;
1024        $input1 = $providers->input;
1025        $parser = new Parser(new ParserConfig($target->version));
1026        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
1027        $program1 = new Index($snapshotId, $trees);
1028        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1029        $snapshotId1 = 'test';
1030        $input2 = new Sources([new Source('fixture.php', $source1)]);
1031        $profile = $execution->configuration->target;
1032        $parser1 = new Parser(new ParserConfig($profile->version));
1033        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
1034        $program = new Index($snapshotId1, $trees1);
1035        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1036        $symbol = 'target';
1037        $fixtureCallable1 = $context->program->callable($symbol);
1038        self::assertNotNull($fixtureCallable1);
1039        $source = $fixtureCallable1->source;
1040        $body = new CallableGraph('target', [], [], $source, byReference: $byReference);
1041        $state = new State();
1042        $state->memory->cells['shared'] = Term::constant(23);
1043        $state->registers['value'] = new Term(TermKind::Cell, 'shared');
1044        $paths = (new Evaluator($context))->terminate($body, new Terminator(TerminatorKind::Return, 'value'), $state);
1045        self::assertCount(1, $paths);
1046        self::assertSame(CompletionKind::Return, $paths[0]->completion->kind);
1047        self::assertSame($kind, $paths[0]->completion->value?->kind->value);
1048        self::assertSame($literal, $paths[0]->completion->value->literal);
1049    }
1050    /**
1051     * @return iterable<string,array{bool,string,mixed}>
1052     */
1053    public static function providerReturnReferences(): iterable
1054    {
1055        yield 'reference' => [true, 'cell', 'shared'];
1056        yield 'value' => [false, 'constant', 23];
1057    }
1058    public function testBranchForksMemoryAndRetainsUniqueControllingEvidence(): void
1059    {
1060        $source = '<?php function target(){return 1;}';
1061        $budget = new Budget();
1062        $configuration = new DeriverConfig();
1063        $contributions = new Contributions();
1064        $input = new Sources([new Source('fixture.php', $source)]);
1065        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1066        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1067        $contributions1 = $providers->contributions;
1068        $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);
1069        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1070        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1071        $snapshotId = $identity->id;
1072        $input1 = $providers->input;
1073        $parser = new Parser(new ParserConfig($target->version));
1074        $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));
1075        $program1 = new Index($snapshotId, $trees);
1076        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1077        $snapshotId1 = 'test';
1078        $input2 = new Sources([new Source('fixture.php', $source)]);
1079        $profile = $execution->configuration->target;
1080        $parser1 = new Parser(new ParserConfig($profile->version));
1081        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1082        $program = new Index($snapshotId1, $trees1);
1083        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1084        $state = new State();
1085        $state->block = 7;
1086        $state->registers['condition'] = Term::parameter('condition', 'bool');
1087        $state->producers['condition'] = 'predicate';
1088        $state->controls = ['outer', 'predicate'];
1089        $state->memory->write($state->local('value'), Term::constant(4));
1090        $paths = (new Evaluator($context))->branch(new Terminator(TerminatorKind::Branch, 'condition', [12, 19]), $state);
1091        self::assertCount(2, $paths);
1092        self::assertSame([12, 19], array_column($paths, 'block'));
1093        self::assertSame([7, 7], array_column($paths, 'previous'));
1094        self::assertSame(['outer', 'predicate'], $paths[0]->controls);
1095        self::assertSame(['outer', 'predicate'], $paths[1]->controls);
1096        self::assertNotSame($paths[0]->guard, $paths[1]->guard);
1097        self::assertNull($paths[0]->stableHeader);
1098        self::assertNull($paths[1]->stableHeader);
1099        $paths[0]->memory->write($paths[0]->local('value'), Term::constant(8));
1100        self::assertSame(4, $paths[1]->snapshot()['value']->native());
1101        self::assertSame(4, $state->snapshot()['value']->native());
1102    }
1103    public function testBranchAtAStableLoopHeaderKeepsOnlyTheExitPath(): void
1104    {
1105        $source = '<?php function target(){return 1;}';
1106        $budget = new Budget();
1107        $configuration = new DeriverConfig();
1108        $contributions = new Contributions();
1109        $input = new Sources([new Source('fixture.php', $source)]);
1110        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1111        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1112        $contributions1 = $providers->contributions;
1113        $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);
1114        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1115        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1116        $snapshotId = $identity->id;
1117        $input1 = $providers->input;
1118        $parser = new Parser(new ParserConfig($target->version));
1119        $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));
1120        $program1 = new Index($snapshotId, $trees);
1121        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1122        $snapshotId1 = 'test';
1123        $input2 = new Sources([new Source('fixture.php', $source)]);
1124        $profile = $execution->configuration->target;
1125        $parser1 = new Parser(new ParserConfig($profile->version));
1126        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1127        $program = new Index($snapshotId1, $trees1);
1128        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1129        $state = new State();
1130        $state->block = 7;
1131        $state->stableHeader = 7;
1132        $state->registers['condition'] = Term::parameter('condition', 'bool');
1133        $paths = (new Evaluator($context))->branch(new Terminator(TerminatorKind::Branch, 'condition', [12, 19]), $state);
1134        self::assertCount(1, $paths);
1135        self::assertSame(19, $paths[0]->block);
1136        self::assertNull($paths[0]->stableHeader);
1137    }
1138    public function testBlockDropsAStableHeaderWithoutALoopTestAsSubsumed(): void
1139    {
1140        $source1 = '<?php function target(){return 1;}';
1141        $budget = new Budget();
1142        $configuration = new DeriverConfig();
1143        $contributions = new Contributions();
1144        $input = new Sources([new Source('fixture.php', $source1)]);
1145        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1146        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1147        $contributions1 = $providers->contributions;
1148        $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);
1149        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1150        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1151        $snapshotId = $identity->id;
1152        $input1 = $providers->input;
1153        $parser = new Parser(new ParserConfig($target->version));
1154        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
1155        $program1 = new Index($snapshotId, $trees);
1156        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1157        $snapshotId1 = 'test';
1158        $input2 = new Sources([new Source('fixture.php', $source1)]);
1159        $profile = $execution->configuration->target;
1160        $parser1 = new Parser(new ParserConfig($profile->version));
1161        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
1162        $program = new Index($snapshotId1, $trees1);
1163        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1164        $symbol = 'target';
1165        $fixtureCallable1 = $context->program->callable($symbol);
1166        self::assertNotNull($fixtureCallable1);
1167        $source = $fixtureCallable1->source;
1168        $body = new CallableGraph('target', [], [new BasicBlock(0, [], new Terminator(TerminatorKind::Jump, targets: [0]), true)], $source);
1169        $context->demands[$body] = [];
1170        $state = new State();
1171        $state->stableHeader = 0;
1172        self::assertSame([], (new Evaluator($context))->block($body, $state));
1173    }
1174    public function testTerminateResidualCompletesWithTheBoundaryValueAndAnUnknownThrowable(): void
1175    {
1176        $source = '<?php function target(){return 1;}';
1177        $budget = new Budget();
1178        $configuration = new DeriverConfig();
1179        $contributions = new Contributions();
1180        $input = new Sources([new Source('fixture.php', $source)]);
1181        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1182        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1183        $contributions1 = $providers->contributions;
1184        $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);
1185        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1186        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1187        $snapshotId = $identity->id;
1188        $input1 = $providers->input;
1189        $parser = new Parser(new ParserConfig($target->version));
1190        $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));
1191        $program1 = new Index($snapshotId, $trees);
1192        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1193        $snapshotId1 = 'test';
1194        $input2 = new Sources([new Source('fixture.php', $source)]);
1195        $profile = $execution->configuration->target;
1196        $parser1 = new Parser(new ParserConfig($profile->version));
1197        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1198        $program = new Index($snapshotId1, $trees1);
1199        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1200        $symbol = 'target';
1201        $body = $context->program->callable($symbol);
1202        self::assertNotNull($body);
1203        $state = new State();
1204        $state->registers['boundary'] = Term::opaque('UNSUPPORTED_LANGUAGE_FEATURE');
1205        $paths = (new Evaluator($context))->terminate($body, new Terminator(TerminatorKind::Residual, 'boundary'), $state);
1206        self::assertSame([CompletionKind::Return, CompletionKind::Throw], array_column(array_column($paths, 'completion'), 'kind'));
1207        self::assertSame('UNSUPPORTED_LANGUAGE_FEATURE', $paths[0]->completion->value?->literal);
1208        self::assertTrue($paths[1]->completion->value?->attributes['uncertain']);
1209    }
1210    public function testBlockSkipsUndemandedInstructions(): void
1211    {
1212        $source1 = '<?php function target(){return 1;}';
1213        $budget = new Budget();
1214        $configuration = new DeriverConfig();
1215        $contributions = new Contributions();
1216        $input = new Sources([new Source('fixture.php', $source1)]);
1217        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1218        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1219        $contributions1 = $providers->contributions;
1220        $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);
1221        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1222        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1223        $snapshotId = $identity->id;
1224        $input1 = $providers->input;
1225        $parser = new Parser(new ParserConfig($target->version));
1226        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
1227        $program1 = new Index($snapshotId, $trees);
1228        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1229        $snapshotId1 = 'test';
1230        $input2 = new Sources([new Source('fixture.php', $source1)]);
1231        $profile = $execution->configuration->target;
1232        $parser1 = new Parser(new ParserConfig($profile->version));
1233        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
1234        $program = new Index($snapshotId1, $trees1);
1235        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1236        $symbol = 'target';
1237        $fixtureCallable1 = $context->program->callable($symbol);
1238        self::assertNotNull($fixtureCallable1);
1239        $source = $fixtureCallable1->source;
1240        $needed = new BoundInstruction('needed', 'constant', $source, 'result', constant: Term::constant(19));
1241        $irrelevant = new BoundInstruction('irrelevant', 'constant', $source, 'unused', constant: Term::constant(7));
1242        $body = new CallableGraph('target', [], [new BasicBlock(0, [$irrelevant, $needed], new Terminator(TerminatorKind::Return, 'result'))], $source);
1243        $context->demands[$body] = ['needed' => true];
1244        $paths = (new Evaluator($context))->block($body, new State());
1245        self::assertSame(19, $paths[0]->completion->value?->native());
1246        self::assertArrayNotHasKey('unused', $paths[0]->registers);
1247        self::assertSame(['needed'], $paths[0]->evidence);
1248    }
1249    public function testBlockPreservesNormalAndExceptionalResidualsWhenWorkIsExhausted(): void
1250    {
1251        $source1 = '<?php function target(){return 1;}';
1252        $budget = new Budget(transfers: 1);
1253        $configuration = new DeriverConfig();
1254        $contributions = new Contributions();
1255        $input = new Sources([new Source('fixture.php', $source1)]);
1256        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1257        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1258        $contributions1 = $providers->contributions;
1259        $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);
1260        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1261        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1262        $snapshotId = $identity->id;
1263        $input1 = $providers->input;
1264        $parser = new Parser(new ParserConfig($target->version));
1265        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
1266        $program1 = new Index($snapshotId, $trees);
1267        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1268        $snapshotId1 = 'test';
1269        $input2 = new Sources([new Source('fixture.php', $source1)]);
1270        $profile = $execution->configuration->target;
1271        $parser1 = new Parser(new ParserConfig($profile->version));
1272        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
1273        $program = new Index($snapshotId1, $trees1);
1274        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1275        $symbol = 'target';
1276        $fixtureCallable1 = $context->program->callable($symbol);
1277        self::assertNotNull($fixtureCallable1);
1278        $source = $fixtureCallable1->source;
1279        $first = new BoundInstruction('first', 'constant', $source, 'one', constant: Term::constant(1));
1280        $last = new BoundInstruction('last', 'constant', $source, 'two', constant: Term::constant(2));
1281        $body = new CallableGraph('target', [], [new BasicBlock(0, [$first, $last], new Terminator(TerminatorKind::Return, 'two'))], $source);
1282        $context->demands[$body] = ['first' => true, 'last' => true];
1283        $paths = (new Evaluator($context))->block($body, new State());
1284        self::assertCount(2, $paths);
1285        self::assertSame([CompletionKind::Return, CompletionKind::Throw], array_column(array_column($paths, 'completion'), 'kind'));
1286        self::assertSame('BUDGET_EXCEEDED', $paths[0]->completion->value?->literal);
1287        self::assertSame('Throwable', $paths[1]->completion->value?->literal);
1288        self::assertArrayNotHasKey('two', $paths[0]->registers);
1289        self::assertContains('logical-work', array_column($context->frontiers, 'operation'));
1290    }
1291    public function testStepTurnsThrowableResultsIntoExceptionalCompletionsAndRecordsEvidence(): void
1292    {
1293        $source = '<?php function target(){return 1;}';
1294        $budget = new Budget();
1295        $configuration = new DeriverConfig();
1296        $contributions = new Contributions();
1297        $input = new Sources([new Source('fixture.php', $source)]);
1298        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1299        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1300        $contributions1 = $providers->contributions;
1301        $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);
1302        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1303        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1304        $snapshotId = $identity->id;
1305        $input1 = $providers->input;
1306        $parser = new Parser(new ParserConfig($target->version));
1307        $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));
1308        $program1 = new Index($snapshotId, $trees);
1309        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1310        $snapshotId1 = 'test';
1311        $input2 = new Sources([new Source('fixture.php', $source)]);
1312        $profile = $execution->configuration->target;
1313        $parser1 = new Parser(new ParserConfig($profile->version));
1314        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1315        $program = new Index($snapshotId1, $trees1);
1316        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1317        $symbol = 'target';
1318        $body = $context->program->callable($symbol);
1319        self::assertNotNull($body);
1320        $value = new Term(TermKind::Throwable, 'TypeError');
1321        $instruction = new BoundInstruction('failure', 'constant', $body->source, 'result', constant: $value);
1322        $paths = (new Evaluator($context))->step($body, $instruction, new State());
1323        self::assertCount(1, $paths);
1324        self::assertSame(CompletionKind::Throw, $paths[0]->completion->kind);
1325        self::assertSame($value, $paths[0]->completion->value);
1326        self::assertSame(['failure'], $paths[0]->evidence);
1327        self::assertArrayHasKey('failure', $context->evidence);
1328    }
1329    public function testExecuteDoesNotReexecuteAnAlreadyCompletedEntry(): void
1330    {
1331        $source = '<?php function target(){return 1;}';
1332        $budget = new Budget();
1333        $configuration = new DeriverConfig();
1334        $contributions = new Contributions();
1335        $input = new Sources([new Source('fixture.php', $source)]);
1336        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1337        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1338        $contributions1 = $providers->contributions;
1339        $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);
1340        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1341        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1342        $snapshotId = $identity->id;
1343        $input1 = $providers->input;
1344        $parser = new Parser(new ParserConfig($target->version));
1345        $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));
1346        $program1 = new Index($snapshotId, $trees);
1347        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1348        $snapshotId1 = 'test';
1349        $input2 = new Sources([new Source('fixture.php', $source)]);
1350        $profile = $execution->configuration->target;
1351        $parser1 = new Parser(new ParserConfig($profile->version));
1352        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1353        $program = new Index($snapshotId1, $trees1);
1354        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1355        $symbol = 'target';
1356        $body = $context->program->callable($symbol);
1357        self::assertNotNull($body);
1358        $state = new State();
1359        $value = Term::constant(19);
1360        $state->completion = new Completion(CompletionKind::Return, $value);
1361        $paths = (new Evaluator($context))->execute($body, $state);
1362        self::assertSame([$state], $paths);
1363        self::assertSame($value, $paths[0]->completion->value);
1364        self::assertSame([], $paths[0]->registers);
1365        self::assertSame([], $paths[0]->evidence);
1366    }
1367    public function testRunRestoresActiveDepthAndKeepsTheLastPermittedRecursiveEntry(): void
1368    {
1369        $source = '<?php function target(){return 1;}';
1370        $budget = new Budget(recursion: 2);
1371        $configuration = new DeriverConfig();
1372        $contributions = new Contributions();
1373        $input = new Sources([new Source('fixture.php', $source)]);
1374        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1375        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1376        $contributions1 = $providers->contributions;
1377        $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);
1378        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1379        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1380        $snapshotId = $identity->id;
1381        $input1 = $providers->input;
1382        $parser = new Parser(new ParserConfig($target->version));
1383        $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));
1384        $program1 = new Index($snapshotId, $trees);
1385        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1386        $snapshotId1 = 'test';
1387        $input2 = new Sources([new Source('fixture.php', $source)]);
1388        $profile = $execution->configuration->target;
1389        $parser1 = new Parser(new ParserConfig($profile->version));
1390        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1391        $program = new Index($snapshotId1, $trees1);
1392        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1393        $symbol = 'target';
1394        $body = $context->program->callable($symbol);
1395        self::assertNotNull($body);
1396        $context->active['target'] = 1;
1397        $paths = (new Evaluator($context))->run($body, new State());
1398        self::assertCount(1, $paths);
1399        self::assertSame(1, $paths[0]->completion->value?->native());
1400        self::assertSame(1, $context->active['target']);
1401        self::assertSame(['target' => true], $context->graphs);
1402    }
1403    public function testRunRetainsBothResidualExitsAfterTheRecursionLimit(): void
1404    {
1405        $source = '<?php function target(){return 1;}';
1406        $budget = new Budget(recursion: 2);
1407        $configuration = new DeriverConfig();
1408        $contributions = new Contributions();
1409        $input = new Sources([new Source('fixture.php', $source)]);
1410        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1411        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1412        $contributions1 = $providers->contributions;
1413        $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);
1414        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1415        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1416        $snapshotId = $identity->id;
1417        $input1 = $providers->input;
1418        $parser = new Parser(new ParserConfig($target->version));
1419        $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));
1420        $program1 = new Index($snapshotId, $trees);
1421        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1422        $snapshotId1 = 'test';
1423        $input2 = new Sources([new Source('fixture.php', $source)]);
1424        $profile = $execution->configuration->target;
1425        $parser1 = new Parser(new ParserConfig($profile->version));
1426        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1427        $program = new Index($snapshotId1, $trees1);
1428        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1429        $symbol = 'target';
1430        $body = $context->program->callable($symbol);
1431        self::assertNotNull($body);
1432        $context->active['target'] = 2;
1433        $paths = (new Evaluator($context))->run($body, new State());
1434        self::assertCount(2, $paths);
1435        self::assertSame([CompletionKind::Return, CompletionKind::Throw], array_column(array_column($paths, 'completion'), 'kind'));
1436        self::assertSame('BUDGET_EXCEEDED', $paths[0]->completion->value?->literal);
1437        self::assertSame(2, $context->active['target']);
1438        self::assertSame([], $context->graphs);
1439        self::assertContains('recursive-specialization', array_column($context->frontiers, 'operation'));
1440    }
1441}
1442