packages/deriver/tests/Unit/Deriver/Evaluator/Execution/Summary/EvaluationTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution\Summary;
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\Call\Evaluators;
14use Deriver\Deriver\Evaluator\Call\Extensions;
15use Deriver\Deriver\Evaluator\Call\Registrations;
16use Deriver\Deriver\Evaluator\Call\StateRegistry;
17use Deriver\Deriver\Evaluator\EvaluatorConfig;
18use Deriver\Deriver\Evaluator\Evidence\Frontier;
19use Deriver\Deriver\Evaluator\Execution\Call\ArgumentBinding;
20use Deriver\Deriver\Evaluator\Execution\Call\ArgumentOrder;
21use Deriver\Deriver\Evaluator\Execution\Call\CallableCheck;
22use Deriver\Deriver\Evaluator\Execution\Call\CallExecutor;
23use Deriver\Deriver\Evaluator\Execution\Call\CallResolution;
24use Deriver\Deriver\Evaluator\Execution\Call\Native\Properties;
25use Deriver\Deriver\Evaluator\Execution\Call\Native\Signatures;
26use Deriver\Deriver\Evaluator\Execution\Call\ParameterBinding;
27use Deriver\Deriver\Evaluator\Execution\Call\PassedArgument;
28use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Arguments;
29use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Modes;
30use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Resolution;
31use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target;
32use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Transfer;
33use Deriver\Deriver\Evaluator\Execution\Call\TypeBinding;
34use Deriver\Deriver\Evaluator\Execution\Call\TypeCheck;
35use Deriver\Deriver\Evaluator\Execution\Completion\Alternative;
36use Deriver\Deriver\Evaluator\Execution\Completion\Assessment;
37use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
38use Deriver\Deriver\Evaluator\Execution\Completion\CompletionKind;
39use Deriver\Deriver\Evaluator\Execution\Completion\Exceptional;
40use Deriver\Deriver\Evaluator\Execution\Completion\StorageSnapshot;
41use Deriver\Deriver\Evaluator\Execution\Constraints;
42use Deriver\Deriver\Evaluator\Execution\Context;
43use Deriver\Deriver\Evaluator\Execution\Control\ExceptionChain;
44use Deriver\Deriver\Evaluator\Execution\Control\ExceptionMatch;
45use Deriver\Deriver\Evaluator\Execution\Control\Handler;
46use Deriver\Deriver\Evaluator\Execution\Control\LoopConvergence;
47use Deriver\Deriver\Evaluator\Execution\Control\ObservationLimit;
48use Deriver\Deriver\Evaluator\Execution\Control\ResidualPaths;
49use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
50use Deriver\Deriver\Evaluator\Execution\Control\Unwinding;
51use Deriver\Deriver\Evaluator\Execution\Demand\Cell;
52use Deriver\Deriver\Evaluator\Execution\Demand\Components;
53use Deriver\Deriver\Evaluator\Execution\Demand\DemandKind;
54use Deriver\Deriver\Evaluator\Execution\Demand\DemandStatus;
55use Deriver\Deriver\Evaluator\Execution\Demand\Discovery;
56use Deriver\Deriver\Evaluator\Execution\Demand\Key;
57use Deriver\Deriver\Evaluator\Execution\Demand\Table;
58use Deriver\Deriver\Evaluator\Execution\Dependencies;
59use Deriver\Deriver\Evaluator\Execution\Evaluator;
60use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
61use Deriver\Deriver\Evaluator\Execution\Havoc;
62use Deriver\Deriver\Evaluator\Execution\InstructionTransfer;
63use Deriver\Deriver\Evaluator\Execution\Memory\Location;
64use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
65use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
66use Deriver\Deriver\Evaluator\Execution\Memory\ReferenceConstraint;
67use Deriver\Deriver\Evaluator\Execution\Memory\Slots\SlotReference;
68use Deriver\Deriver\Evaluator\Execution\Memory\StorageCapture;
69use Deriver\Deriver\Evaluator\Execution\ObservationCollector;
70use Deriver\Deriver\Evaluator\Execution\Offset\Address;
71use Deriver\Deriver\Evaluator\Execution\Offset\Path;
72use Deriver\Deriver\Evaluator\Execution\Offset\Transfer as OffsetTransfer;
73use Deriver\Deriver\Evaluator\Execution\Operation\Conversions;
74use Deriver\Deriver\Evaluator\Execution\Operation\ScalarErrors;
75use Deriver\Deriver\Evaluator\Execution\State;
76use Deriver\Deriver\Evaluator\Execution\Summary\CompletionRecord;
77use Deriver\Deriver\Evaluator\Execution\Summary\Evaluation;
78use Deriver\Deriver\Evaluator\Execution\Summary\Invocation;
79use Deriver\Deriver\Evaluator\Execution\Summary\Isolation;
80use Deriver\Deriver\Evaluator\Execution\Trace\Derivation;
81use Deriver\Deriver\Evaluator\Execution\Transfer\MemoryStep;
82use Deriver\Deriver\Evaluator\Execution\Transfer\PureStep;
83use Deriver\Deriver\Evaluator\Execution\Transfer\ReferenceAssignment;
84use Deriver\Deriver\Evaluator\Request\Budget;
85use Deriver\Deriver\Evaluator\Request\EntryPoint;
86use Deriver\Deriver\Evaluator\Request\Query;
87use Deriver\Deriver\Evaluator\Request\QueryScope;
88use Deriver\Deriver\Evaluator\Request\ReturnQuery;
89use Deriver\Deriver\Evaluator\Request\ValueQuery;
90use Deriver\Deriver\Evaluator\Scalar\Arithmetic;
91use Deriver\Deriver\Evaluator\Scalar\Arrays;
92use Deriver\Deriver\Evaluator\Scalar\Comparison;
93use Deriver\Deriver\Evaluator\Scalar\Identity;
94use Deriver\Deriver\Evaluator\Scalar\Increment;
95use Deriver\Deriver\Evaluator\Scalar\Operations;
96use Deriver\Deriver\Evaluator\TargetProfile;
97use Deriver\Deriver\Evaluator\Term;
98use Deriver\Deriver\Evaluator\TermKind;
99use Deriver\Deriver\Execution\DerivationResult;
100use Deriver\Deriver\Execution\QueryExecution;
101use Deriver\Deriver\Execution\ResultAssessment;
102use Deriver\Deriver\Identity\QueryEncoding;
103use Deriver\Deriver\Identity\ValueGraph;
104use Deriver\Deriver\Lowerer\AggregateLowering;
105use Deriver\Deriver\Lowerer\AssignmentLowering;
106use Deriver\Deriver\Lowerer\CallLowering;
107use Deriver\Deriver\Lowerer\Control\DestructuringLowering;
108use Deriver\Deriver\Lowerer\Control\ExceptionLowering;
109use Deriver\Deriver\Lowerer\Control\LoopLowering;
110use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
111use Deriver\Deriver\Lowerer\Declaration\CallableSource;
112use Deriver\Deriver\Lowerer\Declaration\Index;
113use Deriver\Deriver\Lowerer\Declaration\Indexer;
114use Deriver\Deriver\Lowerer\Declaration\Metadata\PropertyDeclaration;
115use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
116use Deriver\Deriver\Lowerer\EffectInspection;
117use Deriver\Deriver\Lowerer\ExpressionLowering;
118use Deriver\Deriver\Lowerer\Graph\Argument;
119use Deriver\Deriver\Lowerer\Graph\BasicBlock;
120use Deriver\Deriver\Lowerer\Graph\CallableGraph;
121use Deriver\Deriver\Lowerer\Graph\CatchTarget;
122use Deriver\Deriver\Lowerer\Graph\ExceptionRegion;
123use Deriver\Deriver\Lowerer\Graph\ExpressionRef;
124use Deriver\Deriver\Lowerer\Graph\Instruction;
125use Deriver\Deriver\Lowerer\Graph\Parameter;
126use Deriver\Deriver\Lowerer\Graph\Terminator;
127use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
128use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
129use Deriver\Deriver\Lowerer\InstructionEmitter;
130use Deriver\Deriver\Lowerer\Lowerer;
131use Deriver\Deriver\Lowerer\LoweringScope;
132use Deriver\Deriver\Lowerer\StatementLowering;
133use Deriver\Deriver\MicroEvaluatorConversion;
134use Deriver\Deriver\Parser\Node\File;
135use Deriver\Deriver\Parser\Parser;
136use Deriver\Deriver\Parser\ParserConfig;
137use Deriver\Deriver\Parser\TargetSyntax;
138use Deriver\Deriver\Providers\ProviderExpansion;
139use Deriver\Deriver\Resolver\AssignmentPatterns;
140use Deriver\Deriver\Resolver\ClassScope;
141use Deriver\Deriver\Resolver\ExpressionValidation;
142use Deriver\Deriver\Resolver\MagicContext;
143use Deriver\Deriver\Resolver\Resolver;
144use Deriver\Deriver\Selection\QueryValidation;
145use Deriver\Deriver\SnapshotIdentity;
146use Deriver\Deriver\Sources\LineMap;
147use Deriver\Deriver\Sources\Path as SourcesPath;
148use Deriver\Deriver\Sources\Source;
149use Deriver\Deriver\Sources\SourceRef;
150use Deriver\Deriver\Sources\Sources;
151use Deriver\Exception\ParserException;
152use Deriver\Exception\ResolutionException;
153use PHPUnit\Framework\Attributes\CoversClass;
154use PHPUnit\Framework\Attributes\DataProvider;
155use PHPUnit\Framework\Attributes\Small;
156use PHPUnit\Framework\Attributes\UsesClass;
157use PHPUnit\Framework\TestCase;
158
159/**
160 * @covers \Deriver\Deriver\Evaluator\Execution\Summary\Evaluation
161 */
162#[UsesClass(Resolver::class)]
163#[UsesClass(ExpressionValidation::class)]
164#[UsesClass(ResolutionException::class)]
165#[UsesClass(ParserConfig::class)]
166#[UsesClass(ParserException::class)]
167#[CoversClass(Evaluation::class)]
168#[UsesClass(QueryExecution::class)]
169#[UsesClass(QueryValidation::class)]
170#[UsesClass(ResultAssessment::class)]
171#[UsesClass(Deriver::class)]
172#[UsesClass(Constraints::class)]
173#[UsesClass(Argument::class)]
174#[UsesClass(BasicBlock::class)]
175#[UsesClass(CallableGraph::class)]
176#[UsesClass(CallableIdentity::class)]
177#[UsesClass(CatchTarget::class)]
178#[UsesClass(ExceptionRegion::class)]
179#[UsesClass(Instruction::class)]
180#[UsesClass(Parameter::class)]
181#[UsesClass(PropertyDeclaration::class)]
182#[UsesClass(Terminator::class)]
183#[UsesClass(ArgumentBinding::class)]
184#[UsesClass(ArgumentOrder::class)]
185#[UsesClass(CallExecutor::class)]
186#[UsesClass(CallResolution::class)]
187#[UsesClass(CallableCheck::class)]
188#[UsesClass(Builtins::class)]
189#[UsesClass(Dispatch::class)]
190#[UsesClass(Properties::class)]
191#[UsesClass(Signatures::class)]
192#[UsesClass(ParameterBinding::class)]
193#[UsesClass(PassedArgument::class)]
194#[UsesClass(Arguments::class)]
195#[UsesClass(Modes::class)]
196#[UsesClass(Resolution::class)]
197#[UsesClass(Target::class)]
198#[UsesClass(Transfer::class)]
199#[UsesClass(TypeBinding::class)]
200#[UsesClass(TypeCheck::class)]
201#[UsesClass(Completion::class)]
202#[UsesClass(Context::class)]
203#[UsesClass(ExceptionChain::class)]
204#[UsesClass(ExceptionMatch::class)]
205#[UsesClass(Handler::class)]
206#[UsesClass(LoopConvergence::class)]
207#[UsesClass(ObservationLimit::class)]
208#[UsesClass(ResidualPaths::class)]
209#[UsesClass(StateJoin::class)]
210#[UsesClass(Unwinding::class)]
211#[UsesClass(Cell::class)]
212#[UsesClass(Components::class)]
213#[UsesClass(Discovery::class)]
214#[UsesClass(Key::class)]
215#[UsesClass(Table::class)]
216#[UsesClass(Dependencies::class)]
217#[UsesClass(Havoc::class)]
218#[UsesClass(InstructionTransfer::class)]
219#[UsesClass(Evaluator::class)]
220#[UsesClass(SlotReference::class)]
221#[UsesClass(ObservationCollector::class)]
222#[UsesClass(Address::class)]
223#[UsesClass(Path::class)]
224#[UsesClass(OffsetTransfer::class)]
225#[UsesClass(Conversions::class)]
226#[UsesClass(ScalarErrors::class)]
227#[UsesClass(State::class)]
228#[UsesClass(CompletionRecord::class)]
229#[UsesClass(Invocation::class)]
230#[UsesClass(Isolation::class)]
231#[UsesClass(MemoryStep::class)]
232#[UsesClass(PureStep::class)]
233#[UsesClass(ReferenceAssignment::class)]
234#[UsesClass(Location::class)]
235#[UsesClass(Materialization::class)]
236#[UsesClass(Memory::class)]
237#[UsesClass(ReferenceConstraint::class)]
238#[UsesClass(StorageCapture::class)]
239#[UsesClass(Extensions::class)]
240#[UsesClass(ProviderExpansion::class)]
241#[UsesClass(Evaluators::class)]
242#[UsesClass(StateRegistry::class)]
243#[UsesClass(DeriverConfig::class)]
244#[UsesClass(EntryPoint::class)]
245#[UsesClass(Sources::class)]
246#[UsesClass(SourcesPath::class)]
247#[UsesClass(Snapshot::class)]
248#[UsesClass(Source::class)]
249#[UsesClass(TargetProfile::class)]
250#[UsesClass(Budget::class)]
251#[UsesClass(QueryScope::class)]
252#[UsesClass(ReturnQuery::class)]
253#[UsesClass(ValueQuery::class)]
254#[UsesClass(ResultRef::class)]
255#[UsesClass(SourceRef::class)]
256#[UsesClass(Alternative::class)]
257#[UsesClass(Assessment::class)]
258#[UsesClass(Derivation::class)]
259#[UsesClass(DerivationResult::class)]
260#[UsesClass(Exceptional::class)]
261#[UsesClass(Frontier::class)]
262#[UsesClass(QueryEncoding::class)]
263#[UsesClass(ValueGraph::class)]
264#[UsesClass(Statistics::class)]
265#[UsesClass(StorageSnapshot::class)]
266#[UsesClass(Parser::class)]
267#[UsesClass(File::class)]
268#[UsesClass(AggregateLowering::class)]
269#[UsesClass(AssignmentLowering::class)]
270#[UsesClass(CallLowering::class)]
271#[UsesClass(Lowerer::class)]
272#[UsesClass(DestructuringLowering::class)]
273#[UsesClass(ExceptionLowering::class)]
274#[UsesClass(LoopLowering::class)]
275#[UsesClass(EffectInspection::class)]
276#[UsesClass(ExpressionLowering::class)]
277#[UsesClass(InstructionEmitter::class)]
278#[UsesClass(LoweringScope::class)]
279#[UsesClass(StatementLowering::class)]
280#[UsesClass(CallableSource::class)]
281#[UsesClass(Indexer::class)]
282#[UsesClass(Index::class)]
283#[UsesClass(Composition::class)]
284#[UsesClass(LineMap::class)]
285#[UsesClass(MagicContext::class)]
286#[UsesClass(AssignmentPatterns::class)]
287#[UsesClass(ClassScope::class)]
288#[UsesClass(TargetSyntax::class)]
289#[UsesClass(Arithmetic::class)]
290#[UsesClass(Arrays::class)]
291#[UsesClass(Comparison::class)]
292#[UsesClass(Identity::class)]
293#[UsesClass(Increment::class)]
294#[UsesClass(Operations::class)]
295#[UsesClass(Term::class)]
296#[Small]
297final class EvaluationTest extends TestCase
298{
299    public function testRunReusesCallsAtTheSameSiteWithoutMergingDifferentArguments(): void
300    {
301        $source = '<?php function helper($x){return $x+1;}function target(){$r=[];for($i=0;$i<4;$i++){$r[]=helper(2);}return [$r,helper(9)];}';
302        $symbol = 'target';
303        $input = new Sources([new Source('fixture.php', $source)]);
304        $configuration = new DeriverConfig();
305        $contributions = new Contributions();
306        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
307        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
308        $contributions1 = $providers->contributions;
309        $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);
310        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
311        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
312        $snapshotId = $identity->id;
313        $input1 = $providers->input;
314        $parser = new Parser(new ParserConfig($target->version));
315        $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));
316        $program = new Index($snapshotId, $trees);
317        $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
318        self::assertSame([[3, 3, 3, 3], 10], $result->normalOutcomes[0]->values['return']->native());
319        self::assertGreaterThanOrEqual(3, $result->statistics->sharedNodeHits);
320        self::assertSame([], $result->frontiers);
321    }
322    public function testEligibleRejectsHiddenReferencesDespiteAPureBody(): void
323    {
324        $source = '<?php function target($x){$x[0]=2;return $x;}';
325        $budget = new Budget();
326        $configuration = new DeriverConfig();
327        $contributions = new Contributions();
328        $input = new Sources([new Source('fixture.php', $source)]);
329        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
330        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
331        $contributions1 = $providers->contributions;
332        $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);
333        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
334        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
335        $snapshotId = $identity->id;
336        $input1 = $providers->input;
337        $parser = new Parser(new ParserConfig($target->version));
338        $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));
339        $program1 = new Index($snapshotId, $trees);
340        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
341        $snapshotId1 = 'test';
342        $input2 = new Sources([new Source('fixture.php', $source)]);
343        $profile = $execution->configuration->target;
344        $parser1 = new Parser(new ParserConfig($profile->version));
345        $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));
346        $program = new Index($snapshotId1, $trees1);
347        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
348        $entry = new State();
349        $entry->locals['x'] = $entry->memory->allocate(Term::array([new Term(TermKind::Cell, 'shared')]));
350        $evaluation = new Evaluation(new Evaluator($context));
351        $symbol = 'target';
352        $fixtureCallable1 = $context->program->callable($symbol);
353        self::assertNotNull($fixtureCallable1);
354        self::assertFalse($evaluation->eligible($fixtureCallable1, $entry));
355    }
356    public function testEvaluateStoresAnIndependentCompletionAndLogicalCost(): void
357    {
358        $source = '<?php function target(){return 1;}';
359        $budget = new Budget();
360        $configuration = new DeriverConfig();
361        $contributions = new Contributions();
362        $input = new Sources([new Source('fixture.php', $source)]);
363        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
364        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
365        $contributions1 = $providers->contributions;
366        $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);
367        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
368        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
369        $snapshotId = $identity->id;
370        $input1 = $providers->input;
371        $parser = new Parser(new ParserConfig($target->version));
372        $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));
373        $program1 = new Index($snapshotId, $trees);
374        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
375        $snapshotId1 = 'test';
376        $input2 = new Sources([new Source('fixture.php', $source)]);
377        $profile = $execution->configuration->target;
378        $parser1 = new Parser(new ParserConfig($profile->version));
379        $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));
380        $program = new Index($snapshotId1, $trees1);
381        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
382        $symbol = 'target';
383        $body = $context->program->callable($symbol);
384        self::assertNotNull($body);
385        $entry = new State();
386        $key = (new Invocation())->key($body, $entry, []);
387        $table = $context->summaries;
388        $cell = $table->register($key, $body, $entry);
389        $context->demands[$body] = (new Discovery($context))->instructions($body);
390        $evaluation = new Evaluation(new Evaluator($context));
391        self::assertTrue($evaluation->evaluate($cell));
392        self::assertSame(DemandStatus::Stable, $cell->status);
393        self::assertGreaterThan(0, $cell->cost);
394        self::assertSame(1, $evaluation->replay($cell, $entry)[0]->completion->value?->literal);
395    }
396    public function testCloseResolvesCyclicPendingCellsBeforeExport(): void
397    {
398        $source = '<?php function target(){return hop();}function hop(){return target();}';
399        $budget = new Budget();
400        $configuration = new DeriverConfig();
401        $contributions = new Contributions();
402        $input = new Sources([new Source('fixture.php', $source)]);
403        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
404        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
405        $contributions1 = $providers->contributions;
406        $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);
407        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
408        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
409        $snapshotId = $identity->id;
410        $input1 = $providers->input;
411        $parser = new Parser(new ParserConfig($target->version));
412        $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));
413        $program1 = new Index($snapshotId, $trees);
414        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
415        $snapshotId1 = 'test';
416        $input2 = new Sources([new Source('fixture.php', $source)]);
417        $profile = $execution->configuration->target;
418        $parser1 = new Parser(new ParserConfig($profile->version));
419        $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));
420        $program = new Index($snapshotId1, $trees1);
421        $context1 = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
422        $symbol = 'target';
423        $body = $context1->program->callable($symbol);
424        self::assertNotNull($body);
425        $machine = new Evaluator($context1);
426        array_map(static fn (State $entry): array => $machine->run($body, $entry), (new ArgumentBinding($machine))->bind($body, new State(), [], symbolic: true));
427        $context = $context1;
428        $statuses = array_unique(array_map(static fn (Cell $cell): string => $cell->status->value, $context->summaries->cells));
429        self::assertSame(['frontier'], array_values($statuses));
430        self::assertNotEmpty($context->normal);
431        self::assertNotEmpty($context->exceptional);
432        self::assertSame([], $context->summaries->stack);
433    }
434    public function testReplayPreservesOrdinaryExceptions(): void
435    {
436        $source = '<?php function helper(){return 1/0;}function target(){for($i=0;$i<2;$i++){try{helper();}catch(DivisionByZeroError $e){}}return 7;}';
437        $symbol = 'target';
438        $input = new Sources([new Source('fixture.php', $source)]);
439        $configuration = new DeriverConfig();
440        $contributions = new Contributions();
441        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
442        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
443        $contributions1 = $providers->contributions;
444        $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);
445        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
446        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
447        $snapshotId = $identity->id;
448        $input1 = $providers->input;
449        $parser = new Parser(new ParserConfig($target->version));
450        $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));
451        $program = new Index($snapshotId, $trees);
452        $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
453        self::assertSame(7, $result->normalOutcomes[0]->values['return']->native());
454        self::assertSame([], $result->exceptionalOutcomes);
455        self::assertGreaterThan(0, $result->statistics->sharedNodeHits);
456    }
457    public function testSealInvalidatesACompletedDependentAndKeepsResidualExceptions(): void
458    {
459        $source = '<?php function target(){return 1;}';
460        $budget = new Budget();
461        $configuration = new DeriverConfig();
462        $contributions = new Contributions();
463        $input = new Sources([new Source('fixture.php', $source)]);
464        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
465        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
466        $contributions1 = $providers->contributions;
467        $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);
468        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
469        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
470        $snapshotId = $identity->id;
471        $input1 = $providers->input;
472        $parser = new Parser(new ParserConfig($target->version));
473        $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));
474        $program1 = new Index($snapshotId, $trees);
475        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
476        $snapshotId1 = 'test';
477        $input2 = new Sources([new Source('fixture.php', $source)]);
478        $profile = $execution->configuration->target;
479        $parser1 = new Parser(new ParserConfig($profile->version));
480        $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));
481        $program = new Index($snapshotId1, $trees1);
482        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
483        $symbol = 'target';
484        $body = $context->program->callable($symbol);
485        self::assertNotNull($body);
486        $entry = new State();
487        $key = (new Invocation())->key($body, $entry, []);
488        $table = $context->summaries;
489        $cell = $table->register($key, $body, $entry);
490        $table->stack[] = $key->id();
491        $dependency = (new Invocation())->key($body, $entry, ['site']);
492        $child = $table->register($dependency, $body, $entry);
493        $cell->status = DemandStatus::Stable;
494        $evaluation = new Evaluation(new Evaluator($context));
495        $evaluation->seal($child);
496        self::assertSame(DemandStatus::Frontier, $child->status);
497        self::assertSame(DemandStatus::Pending, $cell->status);
498        self::assertSame(['return', 'throw'], array_map(static fn (State $state): string => $state->completion->kind->value, $evaluation->replay($child, $entry)));
499    }
500    #[DataProvider('providerSummaryInputs')]
501    public function testEligibleRequiresAnIsolatedValueForEveryLocal(Term $value, bool $expected): void
502    {
503        $source = '<?php function target(){return 1;}';
504        $budget = new Budget();
505        $configuration = new DeriverConfig();
506        $contributions = new Contributions();
507        $input = new Sources([new Source('fixture.php', $source)]);
508        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
509        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
510        $contributions1 = $providers->contributions;
511        $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);
512        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
513        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
514        $snapshotId = $identity->id;
515        $input1 = $providers->input;
516        $parser = new Parser(new ParserConfig($target->version));
517        $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));
518        $program1 = new Index($snapshotId, $trees);
519        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
520        $snapshotId1 = 'test';
521        $input2 = new Sources([new Source('fixture.php', $source)]);
522        $profile = $execution->configuration->target;
523        $parser1 = new Parser(new ParserConfig($profile->version));
524        $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));
525        $program = new Index($snapshotId1, $trees1);
526        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
527        $entry = new State();
528        $entry->locals['first'] = $entry->memory->allocate(Term::constant(1));
529        $entry->locals['second'] = $entry->memory->allocate($value);
530        $evaluation = new Evaluation(new Evaluator($context));
531        $symbol = 'target';
532        $fixtureCallable1 = $context->program->callable($symbol);
533        self::assertNotNull($fixtureCallable1);
534        self::assertSame($expected, $evaluation->eligible($fixtureCallable1, $entry));
535    }
536    /**
537     * @return iterable<string,array{Term,bool}>
538     */
539    public static function providerSummaryInputs(): iterable
540    {
541        yield 'scalar' => [Term::constant(1), true];
542        yield 'closed array' => [Term::fromNative([1, 2]), true];
543        yield 'open array' => [Term::array([], true), false];
544        yield 'object' => [new Term(TermKind::Object, 'one'), false];
545        yield 'typed scalar' => [Term::parameter('n', 'int'), true];
546        yield 'mixed input' => [Term::parameter('n'), false];
547    }
548    #[DataProvider('providerNonSummaryQuery')]
549    public function testEligibleRejectsNonSymbolicReturnObservations(Query $query): void
550    {
551        $source = '<?php function target(){return 1;}';
552        $budget = new Budget();
553        $configuration = new DeriverConfig();
554        $contributions = new Contributions();
555        $input = new Sources([new Source('fixture.php', $source)]);
556        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
557        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
558        $contributions1 = $providers->contributions;
559        $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);
560        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
561        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
562        $snapshotId = $identity->id;
563        $input1 = $providers->input;
564        $parser = new Parser(new ParserConfig($target->version));
565        $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));
566        $program1 = new Index($snapshotId, $trees);
567        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
568        $snapshotId1 = 'test';
569        $input2 = new Sources([new Source('fixture.php', $source)]);
570        $profile = $execution->configuration->target;
571        $parser1 = new Parser(new ParserConfig($profile->version));
572        $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));
573        $program = new Index($snapshotId1, $trees1);
574        $fixture = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
575        $context = new Context($fixture->program, $query, $fixture->configuration, $fixture->evaluators);
576        $evaluation = new Evaluation(new Evaluator($context));
577        $symbol = 'target';
578        $fixtureCallable1 = $context->program->callable($symbol);
579        self::assertNotNull($fixtureCallable1);
580        self::assertFalse($evaluation->eligible($fixtureCallable1, new State()));
581        self::assertSame([], $context->summaries->isolated);
582    }
583    /**
584     * @return iterable<string,array{Query}>
585     */
586    public static function providerNonSummaryQuery(): iterable
587    {
588        yield 'entrypoint' => [new ReturnQuery('target', QueryScope::fromEntrypoints([new EntryPoint('target')]))];
589        yield 'value' => [new ValueQuery(new ExpressionRef(new SourceRef('test', 'fixture.php', 0, 1), 'target', 'r'))];
590    }
591    #[DataProvider('providerReplayStatus')]
592    public function testRunReplaysAvailableApproximationsWithoutExecutingAgain(DemandStatus $status, bool $solving, int $expectedCost, int $hits): void
593    {
594        $source = '<?php function target(){return 1;}';
595        $budget = new Budget();
596        $configuration = new DeriverConfig();
597        $contributions = new Contributions();
598        $input = new Sources([new Source('fixture.php', $source)]);
599        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
600        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
601        $contributions1 = $providers->contributions;
602        $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);
603        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
604        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
605        $snapshotId = $identity->id;
606        $input1 = $providers->input;
607        $parser = new Parser(new ParserConfig($target->version));
608        $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));
609        $program1 = new Index($snapshotId, $trees);
610        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
611        $snapshotId1 = 'test';
612        $input2 = new Sources([new Source('fixture.php', $source)]);
613        $profile = $execution->configuration->target;
614        $parser1 = new Parser(new ParserConfig($profile->version));
615        $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));
616        $program = new Index($snapshotId1, $trees1);
617        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
618        $symbol = 'target';
619        $body = $context->program->callable($symbol);
620        self::assertNotNull($body);
621        $entry = new State();
622        $key = (new Invocation())->key($body, $entry, []);
623        $cell = $context->summaries->register($key, $body, $entry);
624        $outcome = $entry->fork();
625        $outcome->completion = new Completion(CompletionKind::Return, Term::constant(1));
626        $record = new CompletionRecord($outcome, 0);
627        $cell->outcomes = [$record->id() => $record];
628        $cell->status = $status;
629        $cell->cost = 3;
630        $context->transfers = 5;
631        $context->summaries->solving = $solving;
632        $paths = (new Evaluation(new Evaluator($context)))->run($body, $entry);
633        self::assertCount(1, $paths);
634        self::assertSame(1, $paths[0]->completion->value?->native());
635        self::assertSame($expectedCost, $context->transfers);
636        self::assertSame($hits, $context->summaries->hits);
637        self::assertSame(0, $cell->updates);
638        self::assertSame($status, $cell->status);
639        self::assertNotSame($outcome, $paths[0]);
640        self::assertSame(CompletionKind::Normal, $entry->completion->kind);
641    }
642    /**
643     * @return iterable<string,array{DemandStatus,bool,int,int}>
644     */
645    public static function providerReplayStatus(): iterable
646    {
647        yield 'running' => [DemandStatus::Running, false, 5, 0];
648        yield 'pending during closure' => [DemandStatus::Pending, true, 5, 0];
649        yield 'frontier' => [DemandStatus::Frontier, false, 5, 0];
650        yield 'stable' => [DemandStatus::Stable, false, 8, 1];
651    }
652    public function testRunKeepsAnEmptyRunningApproximationProvisional(): void
653    {
654        $source = '<?php function target(){return 1;}';
655        $budget = new Budget();
656        $configuration = new DeriverConfig();
657        $contributions = new Contributions();
658        $input = new Sources([new Source('fixture.php', $source)]);
659        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
660        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
661        $contributions1 = $providers->contributions;
662        $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);
663        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
664        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
665        $snapshotId = $identity->id;
666        $input1 = $providers->input;
667        $parser = new Parser(new ParserConfig($target->version));
668        $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));
669        $program1 = new Index($snapshotId, $trees);
670        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
671        $snapshotId1 = 'test';
672        $input2 = new Sources([new Source('fixture.php', $source)]);
673        $profile = $execution->configuration->target;
674        $parser1 = new Parser(new ParserConfig($profile->version));
675        $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));
676        $program = new Index($snapshotId1, $trees1);
677        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
678        $symbol = 'target';
679        $body = $context->program->callable($symbol);
680        self::assertNotNull($body);
681        $entry = new State();
682        $key = (new Invocation())->key($body, $entry, []);
683        $cell = $context->summaries->register($key, $body, $entry);
684        $cell->status = DemandStatus::Running;
685        $paths = (new Evaluation(new Evaluator($context)))->run($body, $entry);
686        self::assertSame([], $paths);
687        self::assertSame(DemandStatus::Running, $cell->status);
688        self::assertSame([], $context->frontiers);
689        self::assertSame(0, $context->transfers);
690    }
691    public function testRunSealsAnAdditionalSpecializationAtTheOwnerLimit(): void
692    {
693        $source = '<?php function target(){return 1;}';
694        $budget = new Budget();
695        $configuration = new DeriverConfig();
696        $contributions = new Contributions();
697        $input = new Sources([new Source('fixture.php', $source)]);
698        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
699        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
700        $contributions1 = $providers->contributions;
701        $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);
702        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
703        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
704        $snapshotId = $identity->id;
705        $input1 = $providers->input;
706        $parser = new Parser(new ParserConfig($target->version));
707        $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));
708        $program1 = new Index($snapshotId, $trees);
709        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
710        $snapshotId1 = 'test';
711        $input2 = new Sources([new Source('fixture.php', $source)]);
712        $profile = $execution->configuration->target;
713        $parser1 = new Parser(new ParserConfig($profile->version));
714        $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));
715        $program = new Index($snapshotId1, $trees1);
716        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
717        $symbol = 'target';
718        $body = $context->program->callable($symbol);
719        self::assertNotNull($body);
720        $context->summaries->owners['target'] = 32;
721        $paths = (new Evaluation(new Evaluator($context)))->run($body, new State());
722        self::assertSame([CompletionKind::Return, CompletionKind::Throw], array_map(static fn ($path) => $path->completion->kind, $paths));
723        self::assertSame(['CORRELATION_RELAXED', 'BUDGET_EXCEEDED'], array_column($context->frontiers, 'code'));
724        self::assertSame([], $context->summaries->cells);
725        self::assertSame(32, $context->summaries->owners['target']);
726    }
727    public function testRunAllowsTheLastAvailableSpecialization(): void
728    {
729        $source = '<?php function target(){return 1;}';
730        $budget = new Budget();
731        $configuration = new DeriverConfig();
732        $contributions = new Contributions();
733        $input = new Sources([new Source('fixture.php', $source)]);
734        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
735        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
736        $contributions1 = $providers->contributions;
737        $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);
738        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
739        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
740        $snapshotId = $identity->id;
741        $input1 = $providers->input;
742        $parser = new Parser(new ParserConfig($target->version));
743        $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));
744        $program1 = new Index($snapshotId, $trees);
745        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
746        $snapshotId1 = 'test';
747        $input2 = new Sources([new Source('fixture.php', $source)]);
748        $profile = $execution->configuration->target;
749        $parser1 = new Parser(new ParserConfig($profile->version));
750        $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));
751        $program = new Index($snapshotId1, $trees1);
752        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
753        $symbol = 'target';
754        $body = $context->program->callable($symbol);
755        self::assertNotNull($body);
756        $context->demands[$body] = (new Discovery($context))->instructions($body);
757        $context->summaries->owners['target'] = 31;
758        $paths = (new Evaluation(new Evaluator($context)))->run($body, new State());
759        self::assertCount(1, $paths);
760        self::assertSame(1, $paths[0]->completion->value?->native());
761        self::assertSame([], $context->frontiers);
762        self::assertCount(1, $context->summaries->cells);
763        self::assertSame(32, $context->summaries->owners['target']);
764    }
765    public function testEvaluateRestoresHistoryAndDetectsAnUnchangedApproximation(): void
766    {
767        $source = '<?php function target(){return 1;}';
768        $budget = new Budget();
769        $configuration = new DeriverConfig();
770        $contributions = new Contributions();
771        $input = new Sources([new Source('fixture.php', $source)]);
772        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
773        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
774        $contributions1 = $providers->contributions;
775        $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);
776        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
777        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
778        $snapshotId = $identity->id;
779        $input1 = $providers->input;
780        $parser = new Parser(new ParserConfig($target->version));
781        $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));
782        $program1 = new Index($snapshotId, $trees);
783        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
784        $snapshotId1 = 'test';
785        $input2 = new Sources([new Source('fixture.php', $source)]);
786        $profile = $execution->configuration->target;
787        $parser1 = new Parser(new ParserConfig($profile->version));
788        $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));
789        $program = new Index($snapshotId1, $trees1);
790        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
791        $symbol = 'target';
792        $body = $context->program->callable($symbol);
793        self::assertNotNull($body);
794        $entry = new State();
795        $context->summaries->history = ['registered'];
796        $key = (new Invocation())->key($body, $entry, ['registered']);
797        $cell = $context->summaries->register($key, $body, $entry);
798        $context->summaries->history = ['outer'];
799        $context->demands[$body] = (new Discovery($context))->instructions($body);
800        $evaluation = new Evaluation(new Evaluator($context));
801        $first = $evaluation->evaluate($cell);
802        $second = $evaluation->evaluate($cell);
803        self::assertTrue($first);
804        self::assertFalse($second);
805        self::assertSame(2, $cell->updates);
806        self::assertCount(1, $cell->outcomes);
807        self::assertSame(DemandStatus::Stable, $cell->status);
808        self::assertSame(['outer'], $context->summaries->history);
809        self::assertSame([], $context->summaries->stack);
810        self::assertSame([], $entry->registers);
811    }
812    public function testEvaluateSealsAtTheExactRecursionBudget(): void
813    {
814        $source = '<?php function target(){return 1;}';
815        $budget = new Budget(recursion: 2);
816        $configuration = new DeriverConfig();
817        $contributions = new Contributions();
818        $input = new Sources([new Source('fixture.php', $source)]);
819        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
820        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
821        $contributions1 = $providers->contributions;
822        $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);
823        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
824        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
825        $snapshotId = $identity->id;
826        $input1 = $providers->input;
827        $parser = new Parser(new ParserConfig($target->version));
828        $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));
829        $program1 = new Index($snapshotId, $trees);
830        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
831        $snapshotId1 = 'test';
832        $input2 = new Sources([new Source('fixture.php', $source)]);
833        $profile = $execution->configuration->target;
834        $parser1 = new Parser(new ParserConfig($profile->version));
835        $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));
836        $program = new Index($snapshotId1, $trees1);
837        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
838        $symbol = 'target';
839        $body = $context->program->callable($symbol);
840        self::assertNotNull($body);
841        $entry = new State();
842        $key = (new Invocation())->key($body, $entry, []);
843        $cell = $context->summaries->register($key, $body, $entry);
844        $cell->updates = 2;
845        $evaluation = new Evaluation(new Evaluator($context));
846        self::assertTrue($evaluation->evaluate($cell));
847        self::assertSame(DemandStatus::Frontier, $cell->status);
848        self::assertSame(2, $cell->updates);
849        self::assertSame(0, $context->transfers);
850        self::assertSame([CompletionKind::Return, CompletionKind::Throw], array_map(static fn ($path) => $path->completion->kind, $evaluation->replay($cell, $entry)));
851    }
852    public function testSealRetainsTheBoundedKnownOutcomeBeforeResiduals(): void
853    {
854        $source = '<?php function target(){return 1;}';
855        $budget = new Budget(partitions: 1);
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        $symbol = 'target';
879        $body = $context->program->callable($symbol);
880        self::assertNotNull($body);
881        $entry = new State();
882        $key = (new Invocation())->key($body, $entry, []);
883        $cell = $context->summaries->register($key, $body, $entry);
884        $first = $entry->fork();
885        $first->completion = new Completion(CompletionKind::Return, Term::constant(1));
886        $second = $entry->fork();
887        $second->completion = new Completion(CompletionKind::Return, Term::constant(2));
888        $a = new CompletionRecord($first, 0);
889        $b = new CompletionRecord($second, 0);
890        $cell->outcomes = [$a->id() => $a, $b->id() => $b];
891        $evaluation = new Evaluation(new Evaluator($context));
892        $evaluation->seal($cell);
893        $paths = $evaluation->replay($cell, $entry);
894        self::assertCount(3, $paths);
895        self::assertSame(1, $paths[0]->completion->value?->native());
896        self::assertSame('BUDGET_EXCEEDED', $paths[1]->completion->value?->literal);
897        self::assertSame(CompletionKind::Throw, $paths[2]->completion->kind);
898        self::assertArrayHasKey($a->id(), $cell->outcomes);
899        self::assertArrayNotHasKey($b->id(), $cell->outcomes);
900        self::assertSame(DemandStatus::Frontier, $cell->status);
901    }
902    public function testRunExecutesStatefulBodiesWithoutPublishingReusableOutcomes(): void
903    {
904        $source = '<?php function target(){global $x;$x=2;return $x;}';
905        $budget = new Budget();
906        $configuration = new DeriverConfig();
907        $contributions = new Contributions();
908        $input = new Sources([new Source('fixture.php', $source)]);
909        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
910        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
911        $contributions1 = $providers->contributions;
912        $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);
913        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
914        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
915        $snapshotId = $identity->id;
916        $input1 = $providers->input;
917        $parser = new Parser(new ParserConfig($target->version));
918        $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));
919        $program1 = new Index($snapshotId, $trees);
920        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
921        $snapshotId1 = 'test';
922        $input2 = new Sources([new Source('fixture.php', $source)]);
923        $profile = $execution->configuration->target;
924        $parser1 = new Parser(new ParserConfig($profile->version));
925        $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));
926        $program = new Index($snapshotId1, $trees1);
927        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
928        $symbol = 'target';
929        $body = $context->program->callable($symbol);
930        self::assertNotNull($body);
931        $context->demands[$body] = (new Discovery($context))->instructions($body);
932        $entry = new State();
933        $paths = (new Evaluation(new Evaluator($context)))->run($body, $entry);
934        self::assertCount(1, $paths);
935        self::assertSame(2, $paths[0]->completion->value?->native());
936        self::assertSame(2, $paths[0]->memory->cells['global:x']->native());
937        self::assertSame([], $context->summaries->cells);
938        self::assertSame(['target' => false], $context->summaries->isolated);
939    }
940    public function testRunSealsAtTheSharedSpecializationBudget(): void
941    {
942        $source = '<?php function target(){return 1;}';
943        $budget = new Budget(nodes: 2);
944        $configuration = new DeriverConfig();
945        $contributions = new Contributions();
946        $input = new Sources([new Source('fixture.php', $source)]);
947        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
948        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
949        $contributions1 = $providers->contributions;
950        $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);
951        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
952        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
953        $snapshotId = $identity->id;
954        $input1 = $providers->input;
955        $parser = new Parser(new ParserConfig($target->version));
956        $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));
957        $program1 = new Index($snapshotId, $trees);
958        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
959        $snapshotId1 = 'test';
960        $input2 = new Sources([new Source('fixture.php', $source)]);
961        $profile = $execution->configuration->target;
962        $parser1 = new Parser(new ParserConfig($profile->version));
963        $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));
964        $program = new Index($snapshotId1, $trees1);
965        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
966        $symbol = 'target';
967        $body = $context->program->callable($symbol);
968        self::assertNotNull($body);
969        $other = new Key('test', 'other', 'entry', DemandKind::Value, '', '', '', '');
970        $context->summaries->register($other, $body, new State());
971        $context->summaries->register(new Key('test', 'another', 'entry', DemandKind::Value, '', '', '', ''), $body, new State());
972        $paths = (new Evaluation(new Evaluator($context)))->run($body, new State());
973        self::assertSame([CompletionKind::Return, CompletionKind::Throw], array_map(static fn ($path) => $path->completion->kind, $paths));
974        self::assertSame(['CORRELATION_RELAXED', 'BUDGET_EXCEEDED'], array_column($context->frontiers, 'code'));
975        self::assertCount(2, $context->summaries->cells);
976        self::assertArrayNotHasKey('target', $context->summaries->owners);
977    }
978    #[DataProvider('providerMemoizedBudget')]
979    public function testRunChargesMemoizedWorkOnlyWhenItFitsTheRemainingBudget(int $cost, int $hits, int $updates): void
980    {
981        $source = '<?php function target(){return 1;}';
982        $budget = new Budget(transfers: 10);
983        $configuration = new DeriverConfig();
984        $contributions = new Contributions();
985        $input = new Sources([new Source('fixture.php', $source)]);
986        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
987        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
988        $contributions1 = $providers->contributions;
989        $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);
990        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
991        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
992        $snapshotId = $identity->id;
993        $input1 = $providers->input;
994        $parser = new Parser(new ParserConfig($target->version));
995        $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));
996        $program1 = new Index($snapshotId, $trees);
997        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
998        $snapshotId1 = 'test';
999        $input2 = new Sources([new Source('fixture.php', $source)]);
1000        $profile = $execution->configuration->target;
1001        $parser1 = new Parser(new ParserConfig($profile->version));
1002        $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));
1003        $program = new Index($snapshotId1, $trees1);
1004        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1005        $symbol = 'target';
1006        $body = $context->program->callable($symbol);
1007        self::assertNotNull($body);
1008        $entry = new State();
1009        $key = (new Invocation())->key($body, $entry, []);
1010        $cell = $context->summaries->register($key, $body, $entry);
1011        $cell->status = DemandStatus::Stable;
1012        $cell->cost = $cost;
1013        $state = $entry->fork();
1014        $state->completion = new Completion(CompletionKind::Return, Term::constant(1));
1015        $record = new CompletionRecord($state, 0);
1016        $cell->outcomes[$record->id()] = $record;
1017        $context->transfers = 5;
1018        $context->demands[$body] = (new Discovery($context))->instructions($body);
1019        $paths = (new Evaluation(new Evaluator($context)))->run($body, $entry);
1020        self::assertSame($hits, $context->summaries->hits);
1021        self::assertSame($updates, $cell->updates);
1022        self::assertLessThanOrEqual(10, $context->transfers);
1023        self::assertCount(1, $paths);
1024        self::assertSame(1, $paths[0]->completion->value?->native());
1025        self::assertSame([], $context->frontiers);
1026    }
1027    /**
1028     * @return iterable<string,array{int,int,int}>
1029     */
1030    public static function providerMemoizedBudget(): iterable
1031    {
1032        yield 'exact remaining budget' => [5, 1, 0];
1033        yield 'memoized cost exceeds remaining budget' => [6, 0, 1];
1034    }
1035    public function testRunCannotReplayAClosedSummaryAfterTheContextIsSealed(): void
1036    {
1037        $source = '<?php function target(){return 1;}';
1038        $budget = new Budget();
1039        $configuration = new DeriverConfig();
1040        $contributions = new Contributions();
1041        $input = new Sources([new Source('fixture.php', $source)]);
1042        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1043        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1044        $contributions1 = $providers->contributions;
1045        $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);
1046        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1047        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1048        $snapshotId = $identity->id;
1049        $input1 = $providers->input;
1050        $parser = new Parser(new ParserConfig($target->version));
1051        $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));
1052        $program1 = new Index($snapshotId, $trees);
1053        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1054        $snapshotId1 = 'test';
1055        $input2 = new Sources([new Source('fixture.php', $source)]);
1056        $profile = $execution->configuration->target;
1057        $parser1 = new Parser(new ParserConfig($profile->version));
1058        $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));
1059        $program = new Index($snapshotId1, $trees1);
1060        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1061        $symbol = 'target';
1062        $body = $context->program->callable($symbol);
1063        self::assertNotNull($body);
1064        $entry = new State();
1065        $cell = $context->summaries->register((new Invocation())->key($body, $entry, []), $body, $entry);
1066        $cell->status = DemandStatus::Stable;
1067        $context->sealed = true;
1068        $paths = (new Evaluation(new Evaluator($context)))->run($body, $entry);
1069        self::assertSame(DemandStatus::Frontier, $cell->status);
1070        self::assertSame(0, $cell->updates);
1071        self::assertSame(0, $context->summaries->hits);
1072        self::assertSame([CompletionKind::Return, CompletionKind::Throw], array_map(static fn ($path) => $path->completion->kind, $paths));
1073    }
1074    public function testEvaluateMeasuresOnlyItsOwnTransfersAndAllocationEvents(): void
1075    {
1076        $source = '<?php function target(){return 1;}';
1077        $budget = new Budget(partitions: 1);
1078        $configuration = new DeriverConfig();
1079        $contributions = new Contributions();
1080        $input = new Sources([new Source('fixture.php', $source)]);
1081        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1082        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1083        $contributions1 = $providers->contributions;
1084        $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);
1085        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1086        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1087        $snapshotId = $identity->id;
1088        $input1 = $providers->input;
1089        $parser = new Parser(new ParserConfig($target->version));
1090        $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));
1091        $program1 = new Index($snapshotId, $trees);
1092        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1093        $snapshotId1 = 'test';
1094        $input2 = new Sources([new Source('fixture.php', $source)]);
1095        $profile = $execution->configuration->target;
1096        $parser1 = new Parser(new ParserConfig($profile->version));
1097        $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));
1098        $program = new Index($snapshotId1, $trees1);
1099        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1100        $symbol = 'target';
1101        $body = $context->program->callable($symbol);
1102        self::assertNotNull($body);
1103        $entry = new State();
1104        $entry->memory->sequence = 9;
1105        $cell = $context->summaries->register((new Invocation())->key($body, $entry, []), $body, $entry);
1106        $context->demands[$body] = (new Discovery($context))->instructions($body);
1107        $context->transfers = 7;
1108        $evaluation = new Evaluation(new Evaluator($context));
1109        self::assertTrue($evaluation->evaluate($cell));
1110        self::assertGreaterThan(0, $cell->cost);
1111        self::assertSame($context->transfers - 7, $cell->cost);
1112        self::assertCount(1, $cell->outcomes);
1113        self::assertSame(DemandStatus::Stable, $cell->status);
1114        self::assertSame(0, array_values($cell->outcomes)[0]->allocations);
1115        self::assertFalse(array_values($cell->outcomes)[0]->havoc);
1116        self::assertSame(9, $entry->memory->sequence);
1117        self::assertSame(9, $evaluation->replay($cell, $entry)[0]->memory->sequence);
1118    }
1119    #[DataProvider('providerExistingFrontiers')]
1120    public function testEvaluateSealsOnlyWhenPriorFrontiersInvalidateRemainingEffects(string $code, DemandStatus $status, int $outcomes, bool $havoc): void
1121    {
1122        $source = '<?php function target(){return 1;}';
1123        $budget = new Budget();
1124        $configuration = new DeriverConfig();
1125        $contributions = new Contributions();
1126        $input = new Sources([new Source('fixture.php', $source)]);
1127        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1128        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1129        $contributions1 = $providers->contributions;
1130        $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);
1131        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1132        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1133        $snapshotId = $identity->id;
1134        $input1 = $providers->input;
1135        $parser = new Parser(new ParserConfig($target->version));
1136        $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));
1137        $program1 = new Index($snapshotId, $trees);
1138        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1139        $snapshotId1 = 'test';
1140        $input2 = new Sources([new Source('fixture.php', $source)]);
1141        $profile = $execution->configuration->target;
1142        $parser1 = new Parser(new ParserConfig($profile->version));
1143        $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));
1144        $program = new Index($snapshotId1, $trees1);
1145        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1146        $symbol = 'target';
1147        $body = $context->program->callable($symbol);
1148        self::assertNotNull($body);
1149        $entry = new State();
1150        $cell = $context->summaries->register((new Invocation())->key($body, $entry, []), $body, $entry);
1151        $context->demands[$body] = (new Discovery($context))->instructions($body);
1152        $context->frontier($code, $body->source, 'earlier-operation');
1153        self::assertTrue((new Evaluation(new Evaluator($context)))->evaluate($cell));
1154        self::assertSame($status, $cell->status);
1155        self::assertCount($outcomes, $cell->outcomes);
1156        self::assertSame($havoc, array_values($cell->outcomes)[0]->havoc);
1157    }
1158    /**
1159     * @return iterable<string,array{string,DemandStatus,int,bool}>
1160     */
1161    public static function providerExistingFrontiers(): iterable
1162    {
1163        yield 'interrupted effects' => ['BUDGET_EXCEEDED', DemandStatus::Frontier, 3, true];
1164        yield 'unrelated warning' => ['PHP_WARNING', DemandStatus::Stable, 1, false];
1165    }
1166    public function testCloseEvaluatesPendingComputationsAndClearsTheSolvingFlag(): void
1167    {
1168        $source = '<?php function target(){return 1;}';
1169        $budget = new Budget();
1170        $configuration = new DeriverConfig();
1171        $contributions = new Contributions();
1172        $input = new Sources([new Source('fixture.php', $source)]);
1173        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1174        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1175        $contributions1 = $providers->contributions;
1176        $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);
1177        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1178        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1179        $snapshotId = $identity->id;
1180        $input1 = $providers->input;
1181        $parser = new Parser(new ParserConfig($target->version));
1182        $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));
1183        $program1 = new Index($snapshotId, $trees);
1184        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1185        $snapshotId1 = 'test';
1186        $input2 = new Sources([new Source('fixture.php', $source)]);
1187        $profile = $execution->configuration->target;
1188        $parser1 = new Parser(new ParserConfig($profile->version));
1189        $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));
1190        $program = new Index($snapshotId1, $trees1);
1191        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1192        $symbol = 'target';
1193        $body = $context->program->callable($symbol);
1194        self::assertNotNull($body);
1195        $entry = new State();
1196        $cell = $context->summaries->register((new Invocation())->key($body, $entry, []), $body, $entry);
1197        $context->demands[$body] = (new Discovery($context))->instructions($body);
1198        $evaluation = new Evaluation(new Evaluator($context));
1199        $evaluation->close();
1200        self::assertSame(DemandStatus::Stable, $cell->status);
1201        self::assertSame(1, $cell->updates);
1202        self::assertFalse($context->summaries->solving);
1203        self::assertSame([], $context->summaries->stack);
1204        self::assertSame([], $context->frontiers);
1205        self::assertSame(1, $evaluation->replay($cell, $entry)[0]->completion->value?->native());
1206    }
1207    public function testCloseSealsARecursiveComponentWithNoProductiveOutcome(): void
1208    {
1209        $source = '<?php function target(){return target();}';
1210        $budget = new Budget();
1211        $configuration = new DeriverConfig();
1212        $contributions = new Contributions();
1213        $input = new Sources([new Source('fixture.php', $source)]);
1214        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1215        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1216        $contributions1 = $providers->contributions;
1217        $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);
1218        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1219        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1220        $snapshotId = $identity->id;
1221        $input1 = $providers->input;
1222        $parser = new Parser(new ParserConfig($target->version));
1223        $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));
1224        $program1 = new Index($snapshotId, $trees);
1225        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1226        $snapshotId1 = 'test';
1227        $input2 = new Sources([new Source('fixture.php', $source)]);
1228        $profile = $execution->configuration->target;
1229        $parser1 = new Parser(new ParserConfig($profile->version));
1230        $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));
1231        $program = new Index($snapshotId1, $trees1);
1232        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1233        $symbol = 'target';
1234        $body = $context->program->callable($symbol);
1235        self::assertNotNull($body);
1236        $entry = new State();
1237        $cell = $context->summaries->register((new Invocation())->key($body, $entry, []), $body, $entry);
1238        $context->demands[$body] = (new Discovery($context))->instructions($body);
1239        $evaluation = new Evaluation(new Evaluator($context));
1240        $evaluation->close();
1241        self::assertSame(DemandStatus::Frontier, $cell->status);
1242        self::assertFalse($context->summaries->solving);
1243        self::assertSame([], $context->summaries->stack);
1244        self::assertContains('BUDGET_EXCEEDED', array_column($context->frontiers, 'code'));
1245        self::assertSame([CompletionKind::Return, CompletionKind::Throw], array_map(static fn ($path) => $path->completion->kind, $evaluation->replay($cell, $entry)));
1246    }
1247    public function testEvaluateInvalidatesDependentsOnlyAfterItsApproximationGrows(): void
1248    {
1249        $source = '<?php function target(int $x){return 1;}';
1250        $budget = new Budget();
1251        $configuration = new DeriverConfig();
1252        $contributions = new Contributions();
1253        $input = new Sources([new Source('fixture.php', $source)]);
1254        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1255        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1256        $contributions1 = $providers->contributions;
1257        $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);
1258        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1259        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1260        $snapshotId = $identity->id;
1261        $input1 = $providers->input;
1262        $parser = new Parser(new ParserConfig($target->version));
1263        $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));
1264        $program1 = new Index($snapshotId, $trees);
1265        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1266        $snapshotId1 = 'test';
1267        $input2 = new Sources([new Source('fixture.php', $source)]);
1268        $profile = $execution->configuration->target;
1269        $parser1 = new Parser(new ParserConfig($profile->version));
1270        $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));
1271        $program = new Index($snapshotId1, $trees1);
1272        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1273        $symbol = 'target';
1274        $body = $context->program->callable($symbol);
1275        self::assertNotNull($body);
1276        $outer = new State();
1277        $outer->memory->write($outer->local('x'), Term::constant(2));
1278        $inner = new State();
1279        $inner->memory->write($inner->local('x'), Term::constant(1));
1280        $outerKey = (new Invocation())->key($body, $outer, []);
1281        $parent = $context->summaries->register($outerKey, $body, $outer);
1282        $parent->status = DemandStatus::Stable;
1283        $context->summaries->stack = [$outerKey->id()];
1284        $child = $context->summaries->register((new Invocation())->key($body, $inner, []), $body, $inner);
1285        $context->summaries->stack = [];
1286        $context->demands[$body] = (new Discovery($context))->instructions($body);
1287        $evaluation = new Evaluation(new Evaluator($context));
1288        $changed = $evaluation->evaluate($child);
1289        $invalidated = $parent->status;
1290        $parent->status = DemandStatus::Stable;
1291        $unchanged = $evaluation->evaluate($child);
1292        self::assertTrue($changed);
1293        self::assertSame(DemandStatus::Pending, $invalidated);
1294        self::assertFalse($unchanged);
1295        self::assertSame(DemandStatus::Stable, $parent->status);
1296        self::assertSame(DemandStatus::Stable, $child->status);
1297    }
1298}
1299