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

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution;
6
7use Deriver\Deriver;
8use Deriver\Deriver\Candidate\ResultRef;
9use Deriver\Deriver\Candidate\Snapshot;
10use Deriver\Deriver\Candidate\Statistics;
11use Deriver\Deriver\Contributions;
12use Deriver\Deriver\DeriverConfig;
13use Deriver\Deriver\Evaluator\Builtin\TypePredicates;
14use Deriver\Deriver\Evaluator\Call\BoundInstruction;
15use Deriver\Deriver\Evaluator\Call\Evaluators;
16use Deriver\Deriver\Evaluator\Call\Extensions;
17use Deriver\Deriver\Evaluator\Call\Provider\DispatchDecision;
18use Deriver\Deriver\Evaluator\Call\Registrations;
19use Deriver\Deriver\Evaluator\Call\State\StateSlot;
20use Deriver\Deriver\Evaluator\Call\StateRegistry;
21use Deriver\Deriver\Evaluator\EvaluatorConfig;
22use Deriver\Deriver\Evaluator\Evidence\Frontier;
23use Deriver\Deriver\Evaluator\Execution\Call\Allocation;
24use Deriver\Deriver\Evaluator\Execution\Call\ArgumentBinding;
25use Deriver\Deriver\Evaluator\Execution\Call\ArgumentOrder;
26use Deriver\Deriver\Evaluator\Execution\Call\CallableCheck;
27use Deriver\Deriver\Evaluator\Execution\Call\CallExecutor;
28use Deriver\Deriver\Evaluator\Execution\Call\CallResolution;
29use Deriver\Deriver\Evaluator\Execution\Call\Closure\Binding;
30use Deriver\Deriver\Evaluator\Execution\Call\Closure\Capture;
31use Deriver\Deriver\Evaluator\Execution\Call\Contract\Effects;
32use Deriver\Deriver\Evaluator\Execution\Call\Creation\Access;
33use Deriver\Deriver\Evaluator\Execution\Call\Member\Constants;
34use Deriver\Deriver\Evaluator\Execution\Call\Member\Invocation;
35use Deriver\Deriver\Evaluator\Execution\Call\MethodInvocation;
36use Deriver\Deriver\Evaluator\Execution\Call\Native\Invocation as NativeInvocation;
37use Deriver\Deriver\Evaluator\Execution\Call\Native\Properties;
38use Deriver\Deriver\Evaluator\Execution\Call\Native\Signatures;
39use Deriver\Deriver\Evaluator\Execution\Call\ParameterBinding;
40use Deriver\Deriver\Evaluator\Execution\Call\PassedArgument;
41use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Arguments;
42use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Creation;
43use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Methods;
44use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Modes;
45use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Resolution;
46use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target;
47use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Transfer;
48use Deriver\Deriver\Evaluator\Execution\Call\TypeBinding;
49use Deriver\Deriver\Evaluator\Execution\Call\TypeCheck;
50use Deriver\Deriver\Evaluator\Execution\Call\UnknownCall;
51use Deriver\Deriver\Evaluator\Execution\Completion\Alternative;
52use Deriver\Deriver\Evaluator\Execution\Completion\Assessment;
53use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
54use Deriver\Deriver\Evaluator\Execution\Completion\CompletionKind;
55use Deriver\Deriver\Evaluator\Execution\Completion\StorageSnapshot;
56use Deriver\Deriver\Evaluator\Execution\Constant\ClassNames;
57use Deriver\Deriver\Evaluator\Execution\Constraints;
58use Deriver\Deriver\Evaluator\Execution\Context;
59use Deriver\Deriver\Evaluator\Execution\Control\ExceptionChain;
60use Deriver\Deriver\Evaluator\Execution\Control\ExceptionMatch;
61use Deriver\Deriver\Evaluator\Execution\Control\Handler;
62use Deriver\Deriver\Evaluator\Execution\Control\HandlerPhase;
63use Deriver\Deriver\Evaluator\Execution\Control\IterationStep;
64use Deriver\Deriver\Evaluator\Execution\Control\IteratorCursor;
65use Deriver\Deriver\Evaluator\Execution\Control\LoopConvergence;
66use Deriver\Deriver\Evaluator\Execution\Control\ObservationLimit;
67use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
68use Deriver\Deriver\Evaluator\Execution\Control\Unwinding;
69use Deriver\Deriver\Evaluator\Execution\Demand\Cell;
70use Deriver\Deriver\Evaluator\Execution\Demand\Components;
71use Deriver\Deriver\Evaluator\Execution\Demand\Discovery;
72use Deriver\Deriver\Evaluator\Execution\Demand\Key;
73use Deriver\Deriver\Evaluator\Execution\Demand\Table;
74use Deriver\Deriver\Evaluator\Execution\Dependencies;
75use Deriver\Deriver\Evaluator\Execution\Evaluator;
76use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
77use Deriver\Deriver\Evaluator\Execution\Havoc;
78use Deriver\Deriver\Evaluator\Execution\InstructionTransfer;
79use Deriver\Deriver\Evaluator\Execution\Memory\LiveArray;
80use Deriver\Deriver\Evaluator\Execution\Memory\Location;
81use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
82use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
83use Deriver\Deriver\Evaluator\Execution\Memory\ReferenceConstraint;
84use Deriver\Deriver\Evaluator\Execution\Memory\Slots\SlotReference;
85use Deriver\Deriver\Evaluator\Execution\Memory\Slots\StateStorage;
86use Deriver\Deriver\Evaluator\Execution\Memory\StorageCapture;
87use Deriver\Deriver\Evaluator\Execution\ObservationCollector;
88use Deriver\Deriver\Evaluator\Execution\Offset\Address;
89use Deriver\Deriver\Evaluator\Execution\Offset\Path;
90use Deriver\Deriver\Evaluator\Execution\Offset\Protocol;
91use Deriver\Deriver\Evaluator\Execution\Offset\ProtocolAccess;
92use Deriver\Deriver\Evaluator\Execution\Offset\Reader;
93use Deriver\Deriver\Evaluator\Execution\Offset\Strings;
94use Deriver\Deriver\Evaluator\Execution\Offset\Transfer as OffsetTransfer;
95use Deriver\Deriver\Evaluator\Execution\Operation\Conversions;
96use Deriver\Deriver\Evaluator\Execution\Operation\ScalarErrors;
97use Deriver\Deriver\Evaluator\Execution\State;
98use Deriver\Deriver\Evaluator\Execution\Summary\CompletionRecord;
99use Deriver\Deriver\Evaluator\Execution\Summary\Evaluation;
100use Deriver\Deriver\Evaluator\Execution\Summary\Invocation as SummaryInvocation;
101use Deriver\Deriver\Evaluator\Execution\Summary\Isolation;
102use Deriver\Deriver\Evaluator\Execution\Trace\Derivation;
103use Deriver\Deriver\Evaluator\Execution\Transfer\CallableTransfer;
104use Deriver\Deriver\Evaluator\Execution\Transfer\CompoundAssignment;
105use Deriver\Deriver\Evaluator\Execution\Transfer\ConstantTransfer;
106use Deriver\Deriver\Evaluator\Execution\Transfer\MemoryStep;
107use Deriver\Deriver\Evaluator\Execution\Transfer\ObjectAccess;
108use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyAccessCheck;
109use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyLookup;
110use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyReference;
111use Deriver\Deriver\Evaluator\Execution\Transfer\PropertySlot;
112use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyTransfer;
113use Deriver\Deriver\Evaluator\Execution\Transfer\PureStep;
114use Deriver\Deriver\Evaluator\Execution\Transfer\ReferenceAssignment;
115use Deriver\Deriver\Evaluator\Request\Budget;
116use Deriver\Deriver\Evaluator\Request\EntryPoint;
117use Deriver\Deriver\Evaluator\Request\QueryScope;
118use Deriver\Deriver\Evaluator\Request\ReturnQuery;
119use Deriver\Deriver\Evaluator\Scalar\Arithmetic;
120use Deriver\Deriver\Evaluator\Scalar\Arrays;
121use Deriver\Deriver\Evaluator\Scalar\Identity;
122use Deriver\Deriver\Evaluator\Scalar\Increment;
123use Deriver\Deriver\Evaluator\Scalar\NumericString;
124use Deriver\Deriver\Evaluator\Scalar\Operations;
125use Deriver\Deriver\Evaluator\TargetProfile;
126use Deriver\Deriver\Evaluator\Term;
127use Deriver\Deriver\Evaluator\TermKind;
128use Deriver\Deriver\Execution\DerivationResult;
129use Deriver\Deriver\Execution\QueryExecution;
130use Deriver\Deriver\Execution\ResultAssessment;
131use Deriver\Deriver\Identity\QueryEncoding;
132use Deriver\Deriver\Identity\ValueGraph;
133use Deriver\Deriver\Lowerer\AggregateLowering;
134use Deriver\Deriver\Lowerer\AssignmentLowering;
135use Deriver\Deriver\Lowerer\CallLowering;
136use Deriver\Deriver\Lowerer\Control\ConditionalLowering;
137use Deriver\Deriver\Lowerer\Control\DestructuringLowering;
138use Deriver\Deriver\Lowerer\Control\ExceptionLowering;
139use Deriver\Deriver\Lowerer\Control\LoopLowering;
140use Deriver\Deriver\Lowerer\Control\StaticLowering;
141use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
142use Deriver\Deriver\Lowerer\Declaration\CallableSource;
143use Deriver\Deriver\Lowerer\Declaration\ConstantSignatures;
144use Deriver\Deriver\Lowerer\Declaration\Index;
145use Deriver\Deriver\Lowerer\Declaration\Indexer;
146use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassConstant;
147use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassDeclaration;
148use Deriver\Deriver\Lowerer\Declaration\Metadata\PropertyDeclaration;
149use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
150use Deriver\Deriver\Lowerer\EffectInspection;
151use Deriver\Deriver\Lowerer\ExpressionLowering;
152use Deriver\Deriver\Lowerer\Graph\Argument;
153use Deriver\Deriver\Lowerer\Graph\BasicBlock;
154use Deriver\Deriver\Lowerer\Graph\CallableGraph;
155use Deriver\Deriver\Lowerer\Graph\CatchTarget;
156use Deriver\Deriver\Lowerer\Graph\ExceptionRegion;
157use Deriver\Deriver\Lowerer\Graph\Instruction;
158use Deriver\Deriver\Lowerer\Graph\Parameter;
159use Deriver\Deriver\Lowerer\Graph\Terminator;
160use Deriver\Deriver\Lowerer\Hierarchy\Access as HierarchyAccess;
161use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
162use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
163use Deriver\Deriver\Lowerer\InstructionEmitter;
164use Deriver\Deriver\Lowerer\Lowerer;
165use Deriver\Deriver\Lowerer\LoweringScope;
166use Deriver\Deriver\Lowerer\StatementLowering;
167use Deriver\Deriver\MicroEvaluatorConversion;
168use Deriver\Deriver\Parser\Node\File;
169use Deriver\Deriver\Parser\Parser;
170use Deriver\Deriver\Parser\ParserConfig;
171use Deriver\Deriver\Parser\TargetSyntax;
172use Deriver\Deriver\Providers\ProviderExpansion;
173use Deriver\Deriver\Resolver\AssignmentPatterns;
174use Deriver\Deriver\Resolver\ClassScope;
175use Deriver\Deriver\Resolver\ExpressionValidation;
176use Deriver\Deriver\Resolver\MagicContext;
177use Deriver\Deriver\Resolver\Resolver;
178use Deriver\Deriver\Selection\QueryValidation;
179use Deriver\Deriver\SnapshotIdentity;
180use Deriver\Deriver\Sources\LineMap;
181use Deriver\Deriver\Sources\Path as SourcesPath;
182use Deriver\Deriver\Sources\Source;
183use Deriver\Deriver\Sources\SourceRef;
184use Deriver\Deriver\Sources\Sources;
185use Deriver\Exception\ParserException;
186use Deriver\Exception\ResolutionException;
187use PHPUnit\Framework\Attributes\CoversClass;
188use PHPUnit\Framework\Attributes\DataProvider;
189use PHPUnit\Framework\Attributes\Small;
190use PHPUnit\Framework\Attributes\UsesClass;
191use PHPUnit\Framework\TestCase;
192
193#[UsesClass(Resolver::class)]
194#[UsesClass(ExpressionValidation::class)]
195#[UsesClass(ResolutionException::class)]
196#[UsesClass(ParserConfig::class)]
197#[UsesClass(ParserException::class)]
198#[CoversClass(InstructionTransfer::class)]
199#[UsesClass(QueryExecution::class)]
200#[UsesClass(QueryValidation::class)]
201#[UsesClass(ResultAssessment::class)]
202#[UsesClass(Deriver::class)]
203#[UsesClass(Constraints::class)]
204#[UsesClass(Argument::class)]
205#[UsesClass(BasicBlock::class)]
206#[UsesClass(CallableGraph::class)]
207#[UsesClass(CallableIdentity::class)]
208#[UsesClass(CatchTarget::class)]
209#[UsesClass(ClassConstant::class)]
210#[UsesClass(ClassDeclaration::class)]
211#[UsesClass(ExceptionRegion::class)]
212#[UsesClass(Instruction::class)]
213#[UsesClass(Parameter::class)]
214#[UsesClass(PropertyDeclaration::class)]
215#[UsesClass(Terminator::class)]
216#[UsesClass(Allocation::class)]
217#[UsesClass(ArgumentBinding::class)]
218#[UsesClass(ArgumentOrder::class)]
219#[UsesClass(CallExecutor::class)]
220#[UsesClass(CallResolution::class)]
221#[UsesClass(CallableCheck::class)]
222#[UsesClass(Binding::class)]
223#[UsesClass(Capture::class)]
224#[UsesClass(Access::class)]
225#[UsesClass(Builtins::class)]
226#[UsesClass(Dispatch::class)]
227#[UsesClass(HierarchyAccess::class)]
228#[UsesClass(Constants::class)]
229#[UsesClass(Invocation::class)]
230#[UsesClass(MethodInvocation::class)]
231#[UsesClass(NativeInvocation::class)]
232#[UsesClass(Properties::class)]
233#[UsesClass(Signatures::class)]
234#[UsesClass(ParameterBinding::class)]
235#[UsesClass(PassedArgument::class)]
236#[UsesClass(Arguments::class)]
237#[UsesClass(Creation::class)]
238#[UsesClass(Methods::class)]
239#[UsesClass(Modes::class)]
240#[UsesClass(Resolution::class)]
241#[UsesClass(Target::class)]
242#[UsesClass(Transfer::class)]
243#[UsesClass(TypeBinding::class)]
244#[UsesClass(TypeCheck::class)]
245#[UsesClass(UnknownCall::class)]
246#[UsesClass(Completion::class)]
247#[UsesClass(ClassNames::class)]
248#[UsesClass(Context::class)]
249#[UsesClass(ExceptionChain::class)]
250#[UsesClass(ExceptionMatch::class)]
251#[UsesClass(Handler::class)]
252#[UsesClass(IterationStep::class)]
253#[UsesClass(IteratorCursor::class)]
254#[UsesClass(LoopConvergence::class)]
255#[UsesClass(ObservationLimit::class)]
256#[UsesClass(StateJoin::class)]
257#[UsesClass(Unwinding::class)]
258#[UsesClass(Cell::class)]
259#[UsesClass(Components::class)]
260#[UsesClass(Discovery::class)]
261#[UsesClass(Key::class)]
262#[UsesClass(Table::class)]
263#[UsesClass(Dependencies::class)]
264#[UsesClass(Havoc::class)]
265#[UsesClass(Evaluator::class)]
266#[UsesClass(Effects::class)]
267#[UsesClass(SlotReference::class)]
268#[UsesClass(StateStorage::class)]
269#[UsesClass(ObservationCollector::class)]
270#[UsesClass(Address::class)]
271#[UsesClass(Path::class)]
272#[UsesClass(Protocol::class)]
273#[UsesClass(ProtocolAccess::class)]
274#[UsesClass(Reader::class)]
275#[UsesClass(Strings::class)]
276#[UsesClass(OffsetTransfer::class)]
277#[UsesClass(Conversions::class)]
278#[UsesClass(ScalarErrors::class)]
279#[UsesClass(State::class)]
280#[UsesClass(CompletionRecord::class)]
281#[UsesClass(Evaluation::class)]
282#[UsesClass(SummaryInvocation::class)]
283#[UsesClass(Isolation::class)]
284#[UsesClass(CallableTransfer::class)]
285#[UsesClass(CompoundAssignment::class)]
286#[UsesClass(ConstantTransfer::class)]
287#[UsesClass(MemoryStep::class)]
288#[UsesClass(ObjectAccess::class)]
289#[UsesClass(PropertyAccessCheck::class)]
290#[UsesClass(PropertyLookup::class)]
291#[UsesClass(PropertyReference::class)]
292#[UsesClass(PropertySlot::class)]
293#[UsesClass(PropertyTransfer::class)]
294#[UsesClass(PureStep::class)]
295#[UsesClass(ReferenceAssignment::class)]
296#[UsesClass(LiveArray::class)]
297#[UsesClass(Location::class)]
298#[UsesClass(Materialization::class)]
299#[UsesClass(Memory::class)]
300#[UsesClass(ReferenceConstraint::class)]
301#[UsesClass(StorageCapture::class)]
302#[UsesClass(TypePredicates::class)]
303#[UsesClass(DispatchDecision::class)]
304#[UsesClass(Extensions::class)]
305#[UsesClass(ProviderExpansion::class)]
306#[UsesClass(Evaluators::class)]
307#[UsesClass(StateRegistry::class)]
308#[UsesClass(StateSlot::class)]
309#[UsesClass(DeriverConfig::class)]
310#[UsesClass(EntryPoint::class)]
311#[UsesClass(Sources::class)]
312#[UsesClass(SourcesPath::class)]
313#[UsesClass(Snapshot::class)]
314#[UsesClass(Source::class)]
315#[UsesClass(TargetProfile::class)]
316#[UsesClass(Budget::class)]
317#[UsesClass(QueryScope::class)]
318#[UsesClass(ReturnQuery::class)]
319#[UsesClass(ResultRef::class)]
320#[UsesClass(SourceRef::class)]
321#[UsesClass(Alternative::class)]
322#[UsesClass(Assessment::class)]
323#[UsesClass(Derivation::class)]
324#[UsesClass(DerivationResult::class)]
325#[UsesClass(Frontier::class)]
326#[UsesClass(QueryEncoding::class)]
327#[UsesClass(ValueGraph::class)]
328#[UsesClass(Statistics::class)]
329#[UsesClass(StorageSnapshot::class)]
330#[UsesClass(Parser::class)]
331#[UsesClass(File::class)]
332#[UsesClass(AggregateLowering::class)]
333#[UsesClass(AssignmentLowering::class)]
334#[UsesClass(CallLowering::class)]
335#[UsesClass(Lowerer::class)]
336#[UsesClass(ConditionalLowering::class)]
337#[UsesClass(DestructuringLowering::class)]
338#[UsesClass(ExceptionLowering::class)]
339#[UsesClass(LoopLowering::class)]
340#[UsesClass(StaticLowering::class)]
341#[UsesClass(EffectInspection::class)]
342#[UsesClass(ExpressionLowering::class)]
343#[UsesClass(InstructionEmitter::class)]
344#[UsesClass(LoweringScope::class)]
345#[UsesClass(StatementLowering::class)]
346#[UsesClass(ConstantSignatures::class)]
347#[UsesClass(CallableSource::class)]
348#[UsesClass(Indexer::class)]
349#[UsesClass(Index::class)]
350#[UsesClass(Composition::class)]
351#[UsesClass(LineMap::class)]
352#[UsesClass(MagicContext::class)]
353#[UsesClass(AssignmentPatterns::class)]
354#[UsesClass(ClassScope::class)]
355#[UsesClass(TargetSyntax::class)]
356#[UsesClass(Arithmetic::class)]
357#[UsesClass(Arrays::class)]
358#[UsesClass(Identity::class)]
359#[UsesClass(Increment::class)]
360#[UsesClass(NumericString::class)]
361#[UsesClass(Operations::class)]
362#[UsesClass(Term::class)]
363#[Small]
364final class InstructionTransferTest extends TestCase
365{
366    public function testApplyPreservesTheSemanticContract(): void
367    {
368        $source = '<?php function target() { $x="before"; $x="after"; return $x . ":done"; }';
369        $symbol = 'target';
370        $input = new Sources([new Source('fixture.php', $source)]);
371        $configuration = new DeriverConfig();
372        $contributions = new Contributions();
373        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
374        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
375        $contributions1 = $providers->contributions;
376        $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);
377        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
378        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
379        $snapshotId = $identity->id;
380        $input1 = $providers->input;
381        $parser = new Parser(new ParserConfig($target->version));
382        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
383        $program = new Index($snapshotId, $trees);
384        $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
385        self::assertSame('after:done', $result->normalOutcomes[0]->values['return']->native());
386        self::assertSame([], $result->frontiers);
387        self::assertSame([], $result->exceptionalOutcomes);
388    }
389    public function testOtherConvertsEvalIntoAnExplicitStateBoundary(): void
390    {
391        $source = '<?php function target(){return 1;}';
392        $budget = new Budget();
393        $configuration = new DeriverConfig();
394        $contributions = new Contributions();
395        $input = new Sources([new Source('fixture.php', $source)]);
396        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
397        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
398        $contributions1 = $providers->contributions;
399        $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);
400        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
401        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
402        $snapshotId = $identity->id;
403        $input1 = $providers->input;
404        $parser = new Parser(new ParserConfig($target->version));
405        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
406        $program1 = new Index($snapshotId, $trees);
407        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
408        $snapshotId1 = 'test';
409        $input2 = new Sources([new Source('fixture.php', $source)]);
410        $profile = $execution->configuration->target;
411        $parser1 = new Parser(new ParserConfig($profile->version));
412        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
413        $program = new Index($snapshotId1, $trees1);
414        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
415        $machine = new Evaluator($context);
416        $body = $context->program->callable('target');
417        self::assertNotNull($body);
418        $state = new State();
419        $state->memory->write($state->local('x'), Term::constant(1));
420        $value = (new InstructionTransfer($machine))->other($body, new BoundInstruction('eval', 'symbol-table-boundary', $body->source, 'result', name: 'UNSUPPORTED_LANGUAGE_FEATURE'), $state);
421        self::assertSame(TermKind::Opaque, $value->kind);
422        self::assertSame(TermKind::Opaque, $state->snapshot()['x']->kind);
423    }
424    public function testBoundaryKeepsBothCaughtAndNormallyCompletedUnknownCode(): void
425    {
426        $source = '<?php function target($code){try{eval($code);}catch(Throwable $e){return "caught";}return "normal";}';
427        $symbol = 'target';
428        $input = new Sources([new Source('fixture.php', $source)]);
429        $configuration = new DeriverConfig();
430        $contributions = new Contributions();
431        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
432        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
433        $contributions1 = $providers->contributions;
434        $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);
435        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
436        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
437        $snapshotId = $identity->id;
438        $input1 = $providers->input;
439        $parser = new Parser(new ParserConfig($target->version));
440        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
441        $program = new Index($snapshotId, $trees);
442        $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
443        $values = array_column(array_column(array_column($result->normalOutcomes, 'values'), 'return'), 'literal');
444        sort($values);
445        self::assertSame(['caught', 'normal'], $values);
446        self::assertSame([], $result->exceptionalOutcomes);
447        self::assertNotEmpty($result->frontiers);
448    }
449    public function testStorageRoutesOffsetReadsThroughContainerSemantics(): void
450    {
451        $source = '<?php function target(){return 1;}';
452        $budget = new Budget();
453        $configuration = new DeriverConfig();
454        $contributions = new Contributions();
455        $input = new Sources([new Source('fixture.php', $source)]);
456        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
457        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
458        $contributions1 = $providers->contributions;
459        $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);
460        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
461        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
462        $snapshotId = $identity->id;
463        $input1 = $providers->input;
464        $parser = new Parser(new ParserConfig($target->version));
465        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
466        $program1 = new Index($snapshotId, $trees);
467        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
468        $snapshotId1 = 'test';
469        $input2 = new Sources([new Source('fixture.php', $source)]);
470        $profile = $execution->configuration->target;
471        $parser1 = new Parser(new ParserConfig($profile->version));
472        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
473        $program = new Index($snapshotId1, $trees1);
474        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
475        $body = $context->program->callable('target');
476        self::assertNotNull($body);
477        $instruction = new BoundInstruction('offset', 'write', $body->source, 'result', ['slot', 'rhs']);
478        $state = new State();
479        $state->addresses['base'] = $state->memory->allocate(Term::constant('abc'));
480        $state->offsets['slot'] = new Address('base', Term::constant(-1));
481        $read = new BoundInstruction('read', 'read', $body->source, 'result', ['slot']);
482        $paths = (new InstructionTransfer(new Evaluator($context)))->storage($body, $read, $state);
483        self::assertNotNull($paths);
484        self::assertSame('c', $paths[0]->value('result')->native());
485    }
486    public function testOffsetReadPreservesSilentAbsenceOnComputedArrays(): void
487    {
488        $source = '<?php class Box implements ArrayAccess {public mixed $value=0;function offsetGet($key){return $key;}function offsetExists($key){return $key === "yes";}function offsetSet($key,$value){$this->value=$value;}function offsetUnset($key){$this->value=null;}}function target(){}';
489        $budget = new Budget();
490        $configuration = new DeriverConfig();
491        $contributions = new Contributions();
492        $input = new Sources([new Source('fixture.php', $source)]);
493        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
494        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
495        $contributions1 = $providers->contributions;
496        $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);
497        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
498        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
499        $snapshotId = $identity->id;
500        $input1 = $providers->input;
501        $parser = new Parser(new ParserConfig($target->version));
502        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
503        $program1 = new Index($snapshotId, $trees);
504        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
505        $snapshotId1 = 'test';
506        $input2 = new Sources([new Source('fixture.php', $source)]);
507        $profile = $execution->configuration->target;
508        $parser1 = new Parser(new ParserConfig($profile->version));
509        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
510        $program = new Index($snapshotId1, $trees1);
511        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
512        $body = $context->program->callable('target');
513        self::assertNotNull($body);
514        $state = new State();
515        $state->memory->cells['box'] = Term::array(['value' => Term::constant(0)]);
516        $state->memory->classes['box'] = 'Box';
517        $receiver = new Term(TermKind::Object, 'box', attributes: ['class' => 'Box']);
518        $access = new ProtocolAccess($receiver, Term::constant('yes'));
519        $protocol = new Protocol(new Evaluator($context));
520        $state->registers['array'] = Term::array([]);
521        $state->registers['key'] = Term::constant('absent');
522        $instruction = new BoundInstruction('read', 'array-read', $body->source, 'result', ['array', 'key'], attributes: ['silent' => true]);
523        $paths = (new InstructionTransfer(new Evaluator($context)))->offsetRead($body, $instruction, $state);
524        self::assertSame(TermKind::Uninitialized, $paths[0]->value('result')->kind);
525        self::assertSame([], $context->frontiers);
526    }
527    public function testStorageRetainsUnknownReferenceEffectsAndExceptions(): void
528    {
529        $source = '<?php function target(){return 1;}';
530        $budget = new Budget();
531        $configuration = new DeriverConfig();
532        $contributions = new Contributions();
533        $input = new Sources([new Source('fixture.php', $source)]);
534        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
535        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
536        $contributions1 = $providers->contributions;
537        $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);
538        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
539        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
540        $snapshotId = $identity->id;
541        $input1 = $providers->input;
542        $parser = new Parser(new ParserConfig($target->version));
543        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
544        $program1 = new Index($snapshotId, $trees);
545        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
546        $snapshotId1 = 'test';
547        $input2 = new Sources([new Source('fixture.php', $source)]);
548        $profile = $execution->configuration->target;
549        $parser1 = new Parser(new ParserConfig($profile->version));
550        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
551        $program = new Index($snapshotId1, $trees1);
552        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
553        $body = $context->program->callable('target');
554        self::assertNotNull($body);
555        $machine = new Evaluator($context);
556        $state = new State();
557        $instruction = new BoundInstruction('arg', 'argument', $body->source, 'result', ['prepared', 'input']);
558        $state->addresses['unknown'] = new Location('unresolved', unknown: true);
559        $state->memory->write($state->local('value'), Term::constant(1));
560        $reference = new BoundInstruction('ref', 'reference', $body->source, 'result', ['unknown']);
561        $paths = (new InstructionTransfer($machine))->storage($body, $reference, $state);
562        self::assertNotNull($paths);
563        self::assertCount(2, $paths);
564        self::assertSame(TermKind::Opaque, $paths[0]->registers['result']->kind);
565        self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
566    }
567    public function testPreparationLeavesOrdinaryOperationsToValueTransfer(): void
568    {
569        $source = '<?php function target(){return 1;}';
570        $budget = new Budget();
571        $configuration = new DeriverConfig();
572        $contributions = new Contributions();
573        $input = new Sources([new Source('fixture.php', $source)]);
574        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
575        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
576        $contributions1 = $providers->contributions;
577        $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);
578        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
579        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
580        $snapshotId = $identity->id;
581        $input1 = $providers->input;
582        $parser = new Parser(new ParserConfig($target->version));
583        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
584        $program1 = new Index($snapshotId, $trees);
585        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
586        $snapshotId1 = 'test';
587        $input2 = new Sources([new Source('fixture.php', $source)]);
588        $profile = $execution->configuration->target;
589        $parser1 = new Parser(new ParserConfig($profile->version));
590        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
591        $program = new Index($snapshotId1, $trees1);
592        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
593        $body = $context->program->callable('target');
594        self::assertNotNull($body);
595        $instruction = new BoundInstruction('value', 'constant', $body->source, constant: Term::constant(1));
596        self::assertNull((new InstructionTransfer(new Evaluator($context)))->preparation($body, $instruction, new State()));
597    }
598    /**
599     * @param Term $item Unpack operand
600     */
601    #[DataProvider('providerInvalidUnpacks')]
602    public function testUnpackRejectsNonIterableValues(Term $item, string $exception): void
603    {
604        $state = new State();
605        $state->registers = ['array' => Term::array([]), 'item' => $item];
606        $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
607        $source = '<?php function target(){return 1;}';
608        $budget = new Budget();
609        $configuration = new DeriverConfig();
610        $contributions = new Contributions();
611        $input = new Sources([new Source('fixture.php', $source)]);
612        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
613        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
614        $contributions1 = $providers->contributions;
615        $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);
616        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
617        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
618        $snapshotId = $identity->id;
619        $input1 = $providers->input;
620        $parser = new Parser(new ParserConfig($target->version));
621        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
622        $program1 = new Index($snapshotId, $trees);
623        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
624        $snapshotId1 = 'test';
625        $input2 = new Sources([new Source('fixture.php', $source)]);
626        $profile = $execution->configuration->target;
627        $parser1 = new Parser(new ParserConfig($profile->version));
628        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
629        $program = new Index($snapshotId1, $trees1);
630        $paths = (new InstructionTransfer(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators))))->unpack($instruction, $state);
631        self::assertCount(1, $paths);
632        self::assertSame(CompletionKind::Throw, $paths[0]->completion->kind);
633        self::assertNotNull($paths[0]->completion->value);
634        self::assertSame($exception, $paths[0]->completion->value->literal);
635    }
636    /**
637     * @return array<string,array{Term,string}>
638     */
639    public static function providerInvalidUnpacks(): array
640    {
641        return ['null' => [Term::constant(null), 'Error'], 'int' => [Term::constant(3), 'Error'], 'string' => [Term::constant('abc'), 'Error'], 'bool' => [Term::constant(false), 'Error'], 'closure' => [new Term(TermKind::Closure, 'body'), 'TypeError']];
642    }
643    public function testUnpackRetainsUnknownIterableEffectsAndBothExits(): void
644    {
645        $source = '<?php function target(){return 1;}';
646        $budget = new Budget();
647        $configuration = new DeriverConfig();
648        $contributions = new Contributions();
649        $input = new Sources([new Source('fixture.php', $source)]);
650        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
651        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
652        $contributions1 = $providers->contributions;
653        $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);
654        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
655        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
656        $snapshotId = $identity->id;
657        $input1 = $providers->input;
658        $parser = new Parser(new ParserConfig($target->version));
659        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
660        $program1 = new Index($snapshotId, $trees);
661        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
662        $snapshotId1 = 'test';
663        $input2 = new Sources([new Source('fixture.php', $source)]);
664        $profile = $execution->configuration->target;
665        $parser1 = new Parser(new ParserConfig($profile->version));
666        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
667        $program = new Index($snapshotId1, $trees1);
668        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
669        $state = new State();
670        $state->memory->cells['global:x'] = Term::constant(4);
671        $state->registers = ['array' => Term::array([]), 'item' => Term::parameter('input', 'iterable')];
672        $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
673        $paths = (new InstructionTransfer(new Evaluator($context)))->unpack($instruction, $state);
674        self::assertCount(2, $paths);
675        self::assertSame(TermKind::Opaque, $paths[0]->registers['result']->kind);
676        self::assertSame(TermKind::Opaque, $paths[0]->memory->cells['global:x']->kind);
677        self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
678        self::assertSame(['UNSUPPORTED_LANGUAGE_FEATURE'], array_column(array_values($context->frontiers), 'code'));
679    }
680    public function testUnpackMergesClosedArraysInInsertionOrder(): void
681    {
682        $state = new State();
683        $state->registers = ['array' => Term::fromNative(['a' => 1, 0 => 'first']), 'item' => Term::fromNative(['a' => 2, 7 => 'last'])];
684        $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
685        $source = '<?php function target(){return 1;}';
686        $budget = new Budget();
687        $configuration = new DeriverConfig();
688        $contributions = new Contributions();
689        $input = new Sources([new Source('fixture.php', $source)]);
690        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
691        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
692        $contributions1 = $providers->contributions;
693        $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);
694        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
695        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
696        $snapshotId = $identity->id;
697        $input1 = $providers->input;
698        $parser = new Parser(new ParserConfig($target->version));
699        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
700        $program1 = new Index($snapshotId, $trees);
701        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
702        $snapshotId1 = 'test';
703        $input2 = new Sources([new Source('fixture.php', $source)]);
704        $profile = $execution->configuration->target;
705        $parser1 = new Parser(new ParserConfig($profile->version));
706        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
707        $program = new Index($snapshotId1, $trees1);
708        $paths = (new InstructionTransfer(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators))))->unpack($instruction, $state);
709        self::assertCount(1, $paths);
710        self::assertSame(['a' => 2, 0 => 'first', 1 => 'last'], $paths[0]->registers['result']->native());
711        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
712    }
713    /**
714     * @param string $source Captured PHP source
715     * @param mixed $expected Concrete returned state
716     */
717    #[DataProvider('providerInstructionPrograms')]
718    public function testApplyDispatchesEffectsAndReturnsTheFinalState(string $source, mixed $expected): void
719    {
720        $symbol = 'target';
721        $input = new Sources([new Source('fixture.php', $source)]);
722        $configuration = new DeriverConfig();
723        $contributions = new Contributions();
724        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
725        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
726        $contributions1 = $providers->contributions;
727        $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);
728        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
729        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
730        $snapshotId = $identity->id;
731        $input1 = $providers->input;
732        $parser = new Parser(new ParserConfig($target->version));
733        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
734        $program = new Index($snapshotId, $trees);
735        $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
736        self::assertCount(1, $result->normalOutcomes);
737        self::assertSame($expected, $result->normalOutcomes[0]->values['return']->native());
738        self::assertSame([], $result->exceptionalOutcomes);
739        self::assertSame([], $result->frontiers);
740    }
741    /**
742     * @return iterable<string, array{string, mixed}>
743     */
744    public static function providerInstructionPrograms(): iterable
745    {
746        yield 'array explicit and append' => ['<?php function target(){$a=[4=>"a"]; $a["key"]="b"; $a[]="c";return $a;}', [4 => 'a', 'key' => 'b', 5 => 'c']];
747        yield 'array unpack' => ['<?php function target(){return [1,...[9=>2,"x"=>3],4];}', [1, 2, 'x' => 3, 4]];
748        yield 'computed offset' => ['<?php function target(){return (["x"=>12])["x"];} ', 12];
749        yield 'unary conversion' => ['<?php function target(){return [(int)"17",(bool)[],!1,-4];}', [17, false, false, -4]];
750        yield 'string conversion method' => ['<?php class Text{function __toString(){return "value";}} function target(){return (string)new Text;}', 'value'];
751        yield 'compound assignment' => ['<?php function target(){$a=["x"=>4];$v=($a["x"]+=3);return [$a,$v];}', [['x' => 7], 7]];
752        yield 'constant fetch' => ['<?php const X=12;function target(){return X;}', 12];
753        yield 'class constant' => ['<?php class A{const X=13;}function target(){return A::X;}', 13];
754        yield 'instanceof' => ['<?php class A{}function target(){return (new A) instanceof A;}', true];
755        yield 'static call' => ['<?php class A{static function value($x){return $x+1;}}function target(){return A::value(4);}', 5];
756        yield 'method call' => ['<?php class A{public $x=4;function value(){return $this->x;}}function target(){return (new A)->value();}', 4];
757        yield 'clone' => ['<?php class A{public $x=4;}function target(){$a=new A;$b=clone $a;$b->x=7;return [$a->x,$b->x];}', [4, 7]];
758        yield 'closure' => ['<?php function target(){$x=4;$f=function($v)use($x){return $x+$v;};return $f(3);}', 7];
759        yield 'first class function' => ['<?php function value($v){return $v+2;}function target(){$f=value(...);return $f(3);}', 5];
760        yield 'first class method' => ['<?php class A{function value($v){return $v+2;}}function target(){$a=new A;$f=$a->value(...);return $f(3);}', 5];
761        yield 'reference array source' => ['<?php function target(){$a=["x"=>1];$r=&$a["x"];$r=7;return $a;}', ['x' => 7]];
762        yield 'reference property source' => ['<?php class A{public int $x=1;}function target(){$a=new A;$r=&$a->x;$r=7;return $a->x;}', 7];
763        yield 'property write' => ['<?php class A{public int $x=1;}function target(){$a=new A;$a->x=4;return $a->x;}', 4];
764        yield 'property increment' => ['<?php class A{public int $x=1;}function target(){$a=new A;$v=$a->x++;return [$v,$a->x];}', [1, 2]];
765        yield 'property unset' => ['<?php class A{public int $x=1;}function target(){$a=new A;unset($a->x);return isset($a->x);}', false];
766        yield 'typed reference assignment' => ['<?php class A{public int $x=1;}function target(){$a=new A;$r=&$a->x;$r="12";return [$r,$a->x];}', [12, 12]];
767        yield 'typed reference error preserves state' => ['<?php class A{public int $x=1;}function target(){$a=new A;$r=&$a->x;try{$r=[];}catch(TypeError $e){}return [$r,$a->x];}', [1, 1]];
768        yield 'array offset increment' => ['<?php function target(){$a=[4];$v=$a[0]++;return [$v,$a];}', [4, [5]]];
769        yield 'array offset unset' => ['<?php function target(){$a=["x"=>4,"y"=>5];unset($a["x"]);return $a;}', ['y' => 5]];
770        yield 'by reference loop' => ['<?php function target(){$a=[1,2];foreach($a as &$v){$v+=2;}unset($v);return $a;}', [3, 4]];
771        yield 'by value loop' => ['<?php function target(){$a=[];foreach(["x"=>3,"y"=>4] as $k=>$v){$a[$k]=$v+1;}return $a;}', ['x' => 4, 'y' => 5]];
772        yield 'static local' => ['<?php function counter(){static $x=0;return ++$x;}function target(){return [counter(),counter()];}', [1, 2]];
773        yield 'global alias' => ['<?php function target(){global $x;$x=4;$r=&$x;$r=8;return $x;}', 8];
774        yield 'throw instruction' => ['<?php function target(){try{throw new RuntimeException;}catch(RuntimeException $e){return 9;}}', 9];
775        yield 'ArrayAccess computed read' => ['<?php class Bag implements ArrayAccess{function offsetGet($k):mixed{return $k."!";}function offsetExists($k):bool{return true;}function offsetSet($k,$v):void{}function offsetUnset($k):void{}}function target(){return (new Bag)["x"];}', 'x!'];
776        yield 'ArrayAccess silent read' => ['<?php class Bag implements ArrayAccess{function offsetExists($k):bool{return false;}function offsetGet($k):mixed{throw new Error;}function offsetSet($k,$v):void{}function offsetUnset($k):void{}}function target(){return (new Bag)["x"]??8;}', 8];
777    }
778    public function testApplyArrayConstructionPreservesSharedReferenceCells(): void
779    {
780        $source = '<?php function target(){return 1;}';
781        $budget = new Budget();
782        $configuration = new DeriverConfig();
783        $contributions = new Contributions();
784        $input = new Sources([new Source('fixture.php', $source)]);
785        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
786        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
787        $contributions1 = $providers->contributions;
788        $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);
789        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
790        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
791        $snapshotId = $identity->id;
792        $input1 = $providers->input;
793        $parser = new Parser(new ParserConfig($target->version));
794        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
795        $program1 = new Index($snapshotId, $trees);
796        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
797        $snapshotId1 = 'test';
798        $input2 = new Sources([new Source('fixture.php', $source)]);
799        $profile = $execution->configuration->target;
800        $parser1 = new Parser(new ParserConfig($profile->version));
801        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
802        $program = new Index($snapshotId1, $trees1);
803        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
804        $body = $context->program->callable('target');
805        self::assertNotNull($body);
806        $state = new State();
807        $cell = $state->memory->allocate(Term::constant(4));
808        $reference = new Term(TermKind::Cell, $cell->root);
809        $state->registers = ['array' => Term::array([]), 'key' => Term::constant('shared'), 'value' => $reference];
810        $paths = (new InstructionTransfer(new Evaluator($context)))->apply($body, new BoundInstruction('set', 'array-set', $body->source, 'result', ['array', 'key', 'value']), $state);
811        self::assertCount(1, $paths);
812        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
813        self::assertSame($reference, $paths[0]->registers['result']->operands['shared']);
814        $paths[0]->memory->write($cell, Term::constant(9));
815        self::assertSame(9, $paths[0]->memory->element($paths[0]->registers['result'], 'shared')->native());
816    }
817    public function testApplyArrayConstructionRetainsAnUncomputedElement(): void
818    {
819        $source = '<?php function target(){return 1;}';
820        $budget = new Budget();
821        $configuration = new DeriverConfig();
822        $contributions = new Contributions();
823        $input = new Sources([new Source('fixture.php', $source)]);
824        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
825        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
826        $contributions1 = $providers->contributions;
827        $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);
828        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
829        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
830        $snapshotId = $identity->id;
831        $input1 = $providers->input;
832        $parser = new Parser(new ParserConfig($target->version));
833        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
834        $program1 = new Index($snapshotId, $trees);
835        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
836        $snapshotId1 = 'test';
837        $input2 = new Sources([new Source('fixture.php', $source)]);
838        $profile = $execution->configuration->target;
839        $parser1 = new Parser(new ParserConfig($profile->version));
840        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
841        $program = new Index($snapshotId1, $trees1);
842        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
843        $body = $context->program->callable('target');
844        self::assertNotNull($body);
845        $state = new State();
846        $state->registers['array'] = Term::array([]);
847        $paths = (new InstructionTransfer(new Evaluator($context)))->apply($body, new BoundInstruction('set', 'array-set', $body->source, 'result', ['array', '', 'absent']), $state);
848        self::assertCount(1, $paths);
849        self::assertSame('UNCOMPUTED_REGISTER', $paths[0]->registers['result']->operands[0]->literal);
850        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
851    }
852    public function testUnpackOpenArraysKeepsBothContinuationsAndKnownDependencies(): void
853    {
854        $source = '<?php function target(){return 1;}';
855        $budget = new Budget();
856        $configuration = new DeriverConfig();
857        $contributions = new Contributions();
858        $input = new Sources([new Source('fixture.php', $source)]);
859        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
860        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
861        $contributions1 = $providers->contributions;
862        $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);
863        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
864        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
865        $snapshotId = $identity->id;
866        $input1 = $providers->input;
867        $parser = new Parser(new ParserConfig($target->version));
868        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
869        $program1 = new Index($snapshotId, $trees);
870        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
871        $snapshotId1 = 'test';
872        $input2 = new Sources([new Source('fixture.php', $source)]);
873        $profile = $execution->configuration->target;
874        $parser1 = new Parser(new ParserConfig($profile->version));
875        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
876        $program = new Index($snapshotId1, $trees1);
877        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
878        $state = new State();
879        $array = Term::array(['known' => Term::constant(4), PHP_INT_MAX - 1 => Term::constant(5)]);
880        $item = Term::array(['item' => Term::constant(8)], open: true);
881        $state->registers = ['array' => $array, 'item' => $item];
882        $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
883        $paths = (new InstructionTransfer(new Evaluator($context)))->unpack($instruction, $state);
884        self::assertCount(2, $paths);
885        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
886        self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
887        self::assertSame('Error', $paths[1]->completion->value?->literal);
888        self::assertSame(TermKind::ArrayMerge, $paths[0]->registers['result']->kind);
889        $frontier = array_values($context->frontiers)[0];
890        self::assertSame('WIDENED', $frontier->code);
891        self::assertSame('symbolic-unpack-append', $frontier->operation);
892        self::assertSame([$array, $item], $frontier->residual?->operands);
893    }
894    public function testUnpackIntoAnAppendableArrayCannotReachTheOccupiedMaximumIndex(): void
895    {
896        $source = '<?php function target(){return 1;}';
897        $budget = new Budget();
898        $configuration = new DeriverConfig();
899        $contributions = new Contributions();
900        $input = new Sources([new Source('fixture.php', $source)]);
901        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
902        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
903        $contributions1 = $providers->contributions;
904        $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);
905        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
906        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
907        $snapshotId = $identity->id;
908        $input1 = $providers->input;
909        $parser = new Parser(new ParserConfig($target->version));
910        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
911        $program1 = new Index($snapshotId, $trees);
912        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
913        $snapshotId1 = 'test';
914        $input2 = new Sources([new Source('fixture.php', $source)]);
915        $profile = $execution->configuration->target;
916        $parser1 = new Parser(new ParserConfig($profile->version));
917        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
918        $program = new Index($snapshotId1, $trees1);
919        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
920        $state = new State();
921        $state->registers = ['array' => Term::array(['known' => Term::constant(4), 7 => Term::constant(5)]), 'item' => Term::parameter('input', 'array')];
922        $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
923        $paths = (new InstructionTransfer(new Evaluator($context)))->unpack($instruction, $state);
924        self::assertCount(1, $paths);
925        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
926        self::assertSame(TermKind::ArrayMerge, $paths[0]->registers['result']->kind);
927        self::assertSame([], $context->frontiers);
928    }
929    /**
930     * @param string $operation Boundary instruction
931     * @param string $name Descriptive or diagnostic name
932     * @param string $reason Expected frontier
933     */
934    #[DataProvider('providerBoundaries')]
935    public function testBoundaryInvalidatesStateAndKeepsIndependentExitStates(string $operation, string $name, string $reason): void
936    {
937        $source = '<?php function target(){return 1;}';
938        $budget = new Budget();
939        $configuration = new DeriverConfig();
940        $contributions = new Contributions();
941        $input = new Sources([new Source('fixture.php', $source)]);
942        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
943        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
944        $contributions1 = $providers->contributions;
945        $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);
946        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
947        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
948        $snapshotId = $identity->id;
949        $input1 = $providers->input;
950        $parser = new Parser(new ParserConfig($target->version));
951        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
952        $program1 = new Index($snapshotId, $trees);
953        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
954        $snapshotId1 = 'test';
955        $input2 = new Sources([new Source('fixture.php', $source)]);
956        $profile = $execution->configuration->target;
957        $parser1 = new Parser(new ParserConfig($profile->version));
958        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
959        $program = new Index($snapshotId1, $trees1);
960        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
961        $body = $context->program->callable('target');
962        self::assertNotNull($body);
963        $state = new State();
964        $state->memory->write($state->local('value'), Term::constant(7));
965        $instruction = new BoundInstruction('boundary', $operation, $body->source, 'result', name: $name);
966        $paths = (new InstructionTransfer(new Evaluator($context)))->apply($body, $instruction, $state);
967        self::assertCount(2, $paths);
968        self::assertNotSame($paths[0], $paths[1]);
969        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
970        self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
971        self::assertSame('Throwable', $paths[1]->completion->value?->literal);
972        self::assertSame(true, $paths[1]->completion->value->attributes['uncertain']);
973        self::assertSame($reason, $paths[0]->registers['result']->literal);
974        self::assertSame($reason, $paths[0]->snapshot()['value']->literal);
975        self::assertSame([$reason], array_column($context->frontiers, 'code'));
976        self::assertSame([$name], array_column($context->frontiers, 'operation'));
977        $paths[0]->memory->write($paths[0]->local('value'), Term::constant(9));
978        self::assertSame($reason, $paths[1]->snapshot()['value']->literal);
979    }
980    /**
981     * @return iterable<string,array{string,string,string}>
982     */
983    public static function providerBoundaries(): iterable
984    {
985        yield 'unsupported' => ['unsupported', 'unknown-syntax', 'UNSUPPORTED_LANGUAGE_FEATURE'];
986        yield 'symbol table' => ['symbol-table-boundary', 'DYNAMIC_SYMBOL_TABLE', 'DYNAMIC_SYMBOL_TABLE'];
987        yield 'order' => ['uncertain-order', 'multiple-effects', 'UNSPECIFIED_EVALUATION_ORDER'];
988    }
989    public function testOtherPushesTheSelectedExceptionRegionOntoExistingHandlers(): void
990    {
991        $source = '<?php function target(){try{return 1;}finally{$x=2;}}';
992        $budget = new Budget();
993        $configuration = new DeriverConfig();
994        $contributions = new Contributions();
995        $input = new Sources([new Source('fixture.php', $source)]);
996        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
997        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
998        $contributions1 = $providers->contributions;
999        $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);
1000        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1001        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1002        $snapshotId = $identity->id;
1003        $input1 = $providers->input;
1004        $parser = new Parser(new ParserConfig($target->version));
1005        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1006        $program1 = new Index($snapshotId, $trees);
1007        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1008        $snapshotId1 = 'test';
1009        $input2 = new Sources([new Source('fixture.php', $source)]);
1010        $profile = $execution->configuration->target;
1011        $parser1 = new Parser(new ParserConfig($profile->version));
1012        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1013        $program = new Index($snapshotId1, $trees1);
1014        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1015        $body = $context->program->callable('target');
1016        self::assertNotNull($body);
1017        $state = new State();
1018        $outer = new Handler(new ExceptionRegion([], null, 99));
1019        $state->handlers = [$outer];
1020        $state = $state->fork();
1021        $result = (new InstructionTransfer(new Evaluator($context)))->other($body, new BoundInstruction('enter', 'enter-try', $body->source, attributes: ['region' => 0]), $state);
1022        self::assertNull($result->native());
1023        self::assertCount(2, $state->handlers);
1024        $handlers = $state->handlers;
1025        self::assertSame($outer, $handlers[0]);
1026        self::assertSame($body->regions[0], $handlers[1]->region);
1027        self::assertSame(HandlerPhase::Try, $handlers[1]->phase);
1028    }
1029    public function testOtherThrowRetainsTheEvaluatedThrowableIdentity(): void
1030    {
1031        $source = '<?php function target(){return 1;}';
1032        $budget = new Budget();
1033        $configuration = new DeriverConfig();
1034        $contributions = new Contributions();
1035        $input = new Sources([new Source('fixture.php', $source)]);
1036        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1037        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1038        $contributions1 = $providers->contributions;
1039        $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);
1040        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1041        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1042        $snapshotId = $identity->id;
1043        $input1 = $providers->input;
1044        $parser = new Parser(new ParserConfig($target->version));
1045        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1046        $program1 = new Index($snapshotId, $trees);
1047        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1048        $snapshotId1 = 'test';
1049        $input2 = new Sources([new Source('fixture.php', $source)]);
1050        $profile = $execution->configuration->target;
1051        $parser1 = new Parser(new ParserConfig($profile->version));
1052        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1053        $program = new Index($snapshotId1, $trees1);
1054        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1055        $body = $context->program->callable('target');
1056        self::assertNotNull($body);
1057        $state = new State();
1058        $value = new Term(TermKind::Object, 'exception', attributes: ['class' => 'RuntimeException']);
1059        $state->registers['exception'] = $value;
1060        $result = (new InstructionTransfer(new Evaluator($context)))->other($body, new BoundInstruction('throw', 'throw', $body->source, 'result', ['exception']), $state);
1061        self::assertSame($value, $result);
1062        self::assertSame($value, $state->completion->value);
1063        self::assertSame(CompletionKind::Throw, $state->completion->kind);
1064    }
1065    public function testPreparationResolvesRegisteredModelStateStorage(): void
1066    {
1067        $source = '<?php function target(){return 1;}';
1068        $budget = new Budget();
1069        $configuration = new DeriverConfig();
1070        $contributions = new Contributions(stateSlots: [new StateSlot('example.slot', 'int')]);
1071        $input = new Sources([new Source('fixture.php', $source)]);
1072        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1073        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1074        $contributions1 = $providers->contributions;
1075        $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);
1076        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1077        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1078        $snapshotId = $identity->id;
1079        $input1 = $providers->input;
1080        $parser = new Parser(new ParserConfig($target->version));
1081        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1082        $program1 = new Index($snapshotId, $trees);
1083        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1084        $snapshotId1 = 'test';
1085        $input2 = new Sources([new Source('fixture.php', $source)]);
1086        $profile = $execution->configuration->target;
1087        $parser1 = new Parser(new ParserConfig($profile->version));
1088        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1089        $program = new Index($snapshotId1, $trees1);
1090        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1091        $body = $context->program->callable('target');
1092        self::assertNotNull($body);
1093        $state = new State();
1094        $state->registers['receiver'] = new Term(TermKind::Object, 'external');
1095        $paths = (new InstructionTransfer(new Evaluator($context)))->preparation($body, new BoundInstruction('slot', 'evaluator-state-address', $body->source, 'result', ['receiver'], 'example.slot'), $state);
1096        self::assertNotNull($paths);
1097        self::assertCount(1, $paths);
1098        self::assertSame(TermKind::Location, $paths[0]->registers['result']->kind);
1099        self::assertSame('evaluator:external', $paths[0]->addresses['result']->root);
1100        self::assertSame(['example.slot'], $paths[0]->addresses['result']->path);
1101        self::assertSame(TermKind::StateInput, $paths[0]->memory->read($paths[0]->addresses['result'])->kind);
1102        self::assertSame([], $paths[0]->properties);
1103    }
1104    public function testPreparationAppliesExplicitModelWriteFootprintsBeforeContinuing(): void
1105    {
1106        $source = '<?php function target(){return 1;}';
1107        $budget = new Budget();
1108        $configuration = new DeriverConfig();
1109        $contributions = new Contributions();
1110        $input = new Sources([new Source('fixture.php', $source)]);
1111        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1112        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1113        $contributions1 = $providers->contributions;
1114        $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);
1115        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1116        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1117        $snapshotId = $identity->id;
1118        $input1 = $providers->input;
1119        $parser = new Parser(new ParserConfig($target->version));
1120        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1121        $program1 = new Index($snapshotId, $trees);
1122        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1123        $snapshotId1 = 'test';
1124        $input2 = new Sources([new Source('fixture.php', $source)]);
1125        $profile = $execution->configuration->target;
1126        $parser1 = new Parser(new ParserConfig($profile->version));
1127        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1128        $program = new Index($snapshotId1, $trees1);
1129        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1130        $body = $context->program->callable('target');
1131        self::assertNotNull($body);
1132        $state = new State();
1133        $state->addresses['written'] = $state->memory->allocate(Term::constant(7));
1134        $state->memory->write($state->local('unrelated'), Term::constant(9));
1135        $paths = (new InstructionTransfer(new Evaluator($context)))->preparation($body, new BoundInstruction('havoc', 'evaluator-havoc', $body->source, 'result', ['written'], 'external-write'), $state);
1136        self::assertNotNull($paths);
1137        self::assertCount(2, $paths);
1138        self::assertSame('UNSUPPORTED_EVALUATOR_CASE', $paths[0]->registers['result']->literal);
1139        self::assertSame(TermKind::Opaque, $paths[0]->memory->read($paths[0]->addresses['written'])->kind);
1140        self::assertSame(9, $paths[0]->snapshot()['unrelated']->native());
1141        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
1142        self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
1143    }
1144}
1145