packages/deriver/tests/Unit/Deriver/Evaluator/Execution/Operation/ConversionsTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution\Operation;
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\BoundInstruction;
14use Deriver\Deriver\Evaluator\Call\Evaluators;
15use Deriver\Deriver\Evaluator\Call\Extensions;
16use Deriver\Deriver\Evaluator\Call\Registrations;
17use Deriver\Deriver\Evaluator\Call\StateRegistry;
18use Deriver\Deriver\Evaluator\EvaluatorConfig;
19use Deriver\Deriver\Evaluator\Evidence\Frontier;
20use Deriver\Deriver\Evaluator\Execution\Call\Allocation;
21use Deriver\Deriver\Evaluator\Execution\Call\ArgumentBinding;
22use Deriver\Deriver\Evaluator\Execution\Call\ArgumentOrder;
23use Deriver\Deriver\Evaluator\Execution\Call\CallableCheck;
24use Deriver\Deriver\Evaluator\Execution\Call\CallExecutor;
25use Deriver\Deriver\Evaluator\Execution\Call\CallResolution;
26use Deriver\Deriver\Evaluator\Execution\Call\Creation\Access;
27use Deriver\Deriver\Evaluator\Execution\Call\Member\Constants;
28use Deriver\Deriver\Evaluator\Execution\Call\Member\Invocation;
29use Deriver\Deriver\Evaluator\Execution\Call\Native\Invocation as NativeInvocation;
30use Deriver\Deriver\Evaluator\Execution\Call\Native\Properties;
31use Deriver\Deriver\Evaluator\Execution\Call\Native\Signatures;
32use Deriver\Deriver\Evaluator\Execution\Call\ParameterBinding;
33use Deriver\Deriver\Evaluator\Execution\Call\PassedArgument;
34use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Arguments;
35use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Creation;
36use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Modes;
37use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Resolution;
38use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target;
39use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Transfer;
40use Deriver\Deriver\Evaluator\Execution\Call\TypeBinding;
41use Deriver\Deriver\Evaluator\Execution\Call\TypeCheck;
42use Deriver\Deriver\Evaluator\Execution\Completion\Alternative;
43use Deriver\Deriver\Evaluator\Execution\Completion\Assessment;
44use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
45use Deriver\Deriver\Evaluator\Execution\Completion\CompletionKind;
46use Deriver\Deriver\Evaluator\Execution\Completion\StorageSnapshot;
47use Deriver\Deriver\Evaluator\Execution\Constant\ClassNames;
48use Deriver\Deriver\Evaluator\Execution\Context;
49use Deriver\Deriver\Evaluator\Execution\Control\ExceptionChain;
50use Deriver\Deriver\Evaluator\Execution\Control\ExceptionMatch;
51use Deriver\Deriver\Evaluator\Execution\Control\Handler;
52use Deriver\Deriver\Evaluator\Execution\Control\ObservationLimit;
53use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
54use Deriver\Deriver\Evaluator\Execution\Control\Unwinding;
55use Deriver\Deriver\Evaluator\Execution\Demand\Cell;
56use Deriver\Deriver\Evaluator\Execution\Demand\Components;
57use Deriver\Deriver\Evaluator\Execution\Demand\Discovery;
58use Deriver\Deriver\Evaluator\Execution\Demand\Key;
59use Deriver\Deriver\Evaluator\Execution\Demand\Table;
60use Deriver\Deriver\Evaluator\Execution\Dependencies;
61use Deriver\Deriver\Evaluator\Execution\Evaluator;
62use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
63use Deriver\Deriver\Evaluator\Execution\Havoc;
64use Deriver\Deriver\Evaluator\Execution\InstructionTransfer;
65use Deriver\Deriver\Evaluator\Execution\Memory\Location;
66use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
67use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
68use Deriver\Deriver\Evaluator\Execution\Memory\ReferenceConstraint;
69use Deriver\Deriver\Evaluator\Execution\Memory\Slots\SlotReference;
70use Deriver\Deriver\Evaluator\Execution\Memory\Slots\StateStorage;
71use Deriver\Deriver\Evaluator\Execution\Memory\StorageCapture;
72use Deriver\Deriver\Evaluator\Execution\ObservationCollector;
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 as SummaryInvocation;
79use Deriver\Deriver\Evaluator\Execution\Summary\Isolation;
80use Deriver\Deriver\Evaluator\Execution\Trace\Derivation;
81use Deriver\Deriver\Evaluator\Execution\Transfer\CallableTransfer;
82use Deriver\Deriver\Evaluator\Execution\Transfer\ConstantTransfer;
83use Deriver\Deriver\Evaluator\Execution\Transfer\MemoryStep;
84use Deriver\Deriver\Evaluator\Execution\Transfer\ObjectAccess;
85use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyAccessCheck;
86use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyLookup;
87use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyReference;
88use Deriver\Deriver\Evaluator\Execution\Transfer\PropertySlot;
89use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyTransfer;
90use Deriver\Deriver\Evaluator\Execution\Transfer\PureStep;
91use Deriver\Deriver\Evaluator\Execution\Transfer\ReferenceAssignment;
92use Deriver\Deriver\Evaluator\Request\Budget;
93use Deriver\Deriver\Evaluator\Request\EntryPoint;
94use Deriver\Deriver\Evaluator\Request\QueryScope;
95use Deriver\Deriver\Evaluator\Request\ReturnQuery;
96use Deriver\Deriver\Evaluator\Scalar\Arithmetic;
97use Deriver\Deriver\Evaluator\Scalar\Arrays;
98use Deriver\Deriver\Evaluator\Scalar\Identity;
99use Deriver\Deriver\Evaluator\Scalar\Increment;
100use Deriver\Deriver\Evaluator\Scalar\Operations;
101use Deriver\Deriver\Evaluator\TargetProfile;
102use Deriver\Deriver\Evaluator\Term;
103use Deriver\Deriver\Evaluator\TermKind;
104use Deriver\Deriver\Execution\DerivationResult;
105use Deriver\Deriver\Execution\QueryExecution;
106use Deriver\Deriver\Execution\ResultAssessment;
107use Deriver\Deriver\Identity\QueryEncoding;
108use Deriver\Deriver\Identity\ValueGraph;
109use Deriver\Deriver\Lowerer\AggregateLowering;
110use Deriver\Deriver\Lowerer\AssignmentLowering;
111use Deriver\Deriver\Lowerer\CallLowering;
112use Deriver\Deriver\Lowerer\Control\DestructuringLowering;
113use Deriver\Deriver\Lowerer\Control\ExceptionLowering;
114use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
115use Deriver\Deriver\Lowerer\Declaration\CallableSource;
116use Deriver\Deriver\Lowerer\Declaration\ConstantSignatures;
117use Deriver\Deriver\Lowerer\Declaration\Index;
118use Deriver\Deriver\Lowerer\Declaration\Indexer;
119use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassConstant;
120use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassDeclaration;
121use Deriver\Deriver\Lowerer\Declaration\Metadata\PropertyDeclaration;
122use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
123use Deriver\Deriver\Lowerer\EffectInspection;
124use Deriver\Deriver\Lowerer\ExpressionLowering;
125use Deriver\Deriver\Lowerer\Graph\Argument;
126use Deriver\Deriver\Lowerer\Graph\BasicBlock;
127use Deriver\Deriver\Lowerer\Graph\CallableGraph;
128use Deriver\Deriver\Lowerer\Graph\CatchTarget;
129use Deriver\Deriver\Lowerer\Graph\ExceptionRegion;
130use Deriver\Deriver\Lowerer\Graph\Instruction;
131use Deriver\Deriver\Lowerer\Graph\Parameter;
132use Deriver\Deriver\Lowerer\Graph\Terminator;
133use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
134use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
135use Deriver\Deriver\Lowerer\InstructionEmitter;
136use Deriver\Deriver\Lowerer\Lowerer;
137use Deriver\Deriver\Lowerer\LoweringScope;
138use Deriver\Deriver\Lowerer\StatementLowering;
139use Deriver\Deriver\MicroEvaluatorConversion;
140use Deriver\Deriver\Parser\Node\File;
141use Deriver\Deriver\Parser\Parser;
142use Deriver\Deriver\Parser\ParserConfig;
143use Deriver\Deriver\Parser\TargetSyntax;
144use Deriver\Deriver\Providers\ProviderExpansion;
145use Deriver\Deriver\Resolver\AssignmentPatterns;
146use Deriver\Deriver\Resolver\ClassScope;
147use Deriver\Deriver\Resolver\ExpressionValidation;
148use Deriver\Deriver\Resolver\MagicContext;
149use Deriver\Deriver\Resolver\Resolver;
150use Deriver\Deriver\Selection\QueryValidation;
151use Deriver\Deriver\SnapshotIdentity;
152use Deriver\Deriver\Sources\LineMap;
153use Deriver\Deriver\Sources\Path;
154use Deriver\Deriver\Sources\Source;
155use Deriver\Deriver\Sources\SourceRef;
156use Deriver\Deriver\Sources\Sources;
157use Deriver\Exception\ParserException;
158use Deriver\Exception\ResolutionException;
159use PHPUnit\Framework\Attributes\CoversClass;
160use PHPUnit\Framework\Attributes\DataProvider;
161use PHPUnit\Framework\Attributes\Small;
162use PHPUnit\Framework\Attributes\UsesClass;
163use PHPUnit\Framework\TestCase;
164
165/**
166 * @covers \Deriver\Deriver\Evaluator\Execution\Operation\Conversions
167 */
168#[UsesClass(Resolver::class)]
169#[UsesClass(ExpressionValidation::class)]
170#[UsesClass(ResolutionException::class)]
171#[UsesClass(ParserConfig::class)]
172#[UsesClass(ParserException::class)]
173#[CoversClass(Conversions::class)]
174#[UsesClass(QueryExecution::class)]
175#[UsesClass(QueryValidation::class)]
176#[UsesClass(ResultAssessment::class)]
177#[UsesClass(Deriver::class)]
178#[UsesClass(Argument::class)]
179#[UsesClass(BasicBlock::class)]
180#[UsesClass(CallableGraph::class)]
181#[UsesClass(CallableIdentity::class)]
182#[UsesClass(CatchTarget::class)]
183#[UsesClass(ClassConstant::class)]
184#[UsesClass(ClassDeclaration::class)]
185#[UsesClass(ExceptionRegion::class)]
186#[UsesClass(Instruction::class)]
187#[UsesClass(Parameter::class)]
188#[UsesClass(PropertyDeclaration::class)]
189#[UsesClass(Terminator::class)]
190#[UsesClass(Allocation::class)]
191#[UsesClass(ArgumentBinding::class)]
192#[UsesClass(ArgumentOrder::class)]
193#[UsesClass(CallExecutor::class)]
194#[UsesClass(CallResolution::class)]
195#[UsesClass(CallableCheck::class)]
196#[UsesClass(Access::class)]
197#[UsesClass(Builtins::class)]
198#[UsesClass(Dispatch::class)]
199#[UsesClass(Constants::class)]
200#[UsesClass(Invocation::class)]
201#[UsesClass(NativeInvocation::class)]
202#[UsesClass(Properties::class)]
203#[UsesClass(Signatures::class)]
204#[UsesClass(ParameterBinding::class)]
205#[UsesClass(PassedArgument::class)]
206#[UsesClass(Arguments::class)]
207#[UsesClass(Creation::class)]
208#[UsesClass(Modes::class)]
209#[UsesClass(Resolution::class)]
210#[UsesClass(Target::class)]
211#[UsesClass(Transfer::class)]
212#[UsesClass(TypeBinding::class)]
213#[UsesClass(TypeCheck::class)]
214#[UsesClass(Completion::class)]
215#[UsesClass(ClassNames::class)]
216#[UsesClass(Context::class)]
217#[UsesClass(ExceptionChain::class)]
218#[UsesClass(ExceptionMatch::class)]
219#[UsesClass(Handler::class)]
220#[UsesClass(ObservationLimit::class)]
221#[UsesClass(StateJoin::class)]
222#[UsesClass(Unwinding::class)]
223#[UsesClass(Cell::class)]
224#[UsesClass(Components::class)]
225#[UsesClass(Discovery::class)]
226#[UsesClass(Key::class)]
227#[UsesClass(Table::class)]
228#[UsesClass(Dependencies::class)]
229#[UsesClass(Havoc::class)]
230#[UsesClass(InstructionTransfer::class)]
231#[UsesClass(Evaluator::class)]
232#[UsesClass(SlotReference::class)]
233#[UsesClass(StateStorage::class)]
234#[UsesClass(ObservationCollector::class)]
235#[UsesClass(ScalarErrors::class)]
236#[UsesClass(State::class)]
237#[UsesClass(CompletionRecord::class)]
238#[UsesClass(Evaluation::class)]
239#[UsesClass(SummaryInvocation::class)]
240#[UsesClass(Isolation::class)]
241#[UsesClass(CallableTransfer::class)]
242#[UsesClass(ConstantTransfer::class)]
243#[UsesClass(MemoryStep::class)]
244#[UsesClass(ObjectAccess::class)]
245#[UsesClass(PropertyAccessCheck::class)]
246#[UsesClass(PropertyLookup::class)]
247#[UsesClass(PropertyReference::class)]
248#[UsesClass(PropertySlot::class)]
249#[UsesClass(PropertyTransfer::class)]
250#[UsesClass(PureStep::class)]
251#[UsesClass(ReferenceAssignment::class)]
252#[UsesClass(Location::class)]
253#[UsesClass(Materialization::class)]
254#[UsesClass(Memory::class)]
255#[UsesClass(ReferenceConstraint::class)]
256#[UsesClass(StorageCapture::class)]
257#[UsesClass(Extensions::class)]
258#[UsesClass(ProviderExpansion::class)]
259#[UsesClass(Evaluators::class)]
260#[UsesClass(StateRegistry::class)]
261#[UsesClass(DeriverConfig::class)]
262#[UsesClass(EntryPoint::class)]
263#[UsesClass(Sources::class)]
264#[UsesClass(Path::class)]
265#[UsesClass(Snapshot::class)]
266#[UsesClass(Source::class)]
267#[UsesClass(TargetProfile::class)]
268#[UsesClass(Budget::class)]
269#[UsesClass(QueryScope::class)]
270#[UsesClass(ReturnQuery::class)]
271#[UsesClass(ResultRef::class)]
272#[UsesClass(SourceRef::class)]
273#[UsesClass(Alternative::class)]
274#[UsesClass(Assessment::class)]
275#[UsesClass(Derivation::class)]
276#[UsesClass(DerivationResult::class)]
277#[UsesClass(Frontier::class)]
278#[UsesClass(QueryEncoding::class)]
279#[UsesClass(ValueGraph::class)]
280#[UsesClass(Statistics::class)]
281#[UsesClass(StorageSnapshot::class)]
282#[UsesClass(Parser::class)]
283#[UsesClass(File::class)]
284#[UsesClass(AggregateLowering::class)]
285#[UsesClass(AssignmentLowering::class)]
286#[UsesClass(CallLowering::class)]
287#[UsesClass(Lowerer::class)]
288#[UsesClass(DestructuringLowering::class)]
289#[UsesClass(ExceptionLowering::class)]
290#[UsesClass(EffectInspection::class)]
291#[UsesClass(ExpressionLowering::class)]
292#[UsesClass(InstructionEmitter::class)]
293#[UsesClass(LoweringScope::class)]
294#[UsesClass(StatementLowering::class)]
295#[UsesClass(ConstantSignatures::class)]
296#[UsesClass(CallableSource::class)]
297#[UsesClass(Indexer::class)]
298#[UsesClass(Index::class)]
299#[UsesClass(Composition::class)]
300#[UsesClass(LineMap::class)]
301#[UsesClass(MagicContext::class)]
302#[UsesClass(AssignmentPatterns::class)]
303#[UsesClass(ClassScope::class)]
304#[UsesClass(TargetSyntax::class)]
305#[UsesClass(Arithmetic::class)]
306#[UsesClass(Arrays::class)]
307#[UsesClass(Identity::class)]
308#[UsesClass(Increment::class)]
309#[UsesClass(Operations::class)]
310#[UsesClass(Term::class)]
311#[Small]
312final class ConversionsTest extends TestCase
313{
314    public function testApplyLeavesNonconversionInstructionsToOrdinaryTransfer(): void
315    {
316        $source = '<?php function target(){return 1;}';
317        $budget = new Budget();
318        $configuration = new DeriverConfig();
319        $contributions = new Contributions();
320        $input = new Sources([new Source('fixture.php', $source)]);
321        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
322        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
323        $contributions1 = $providers->contributions;
324        $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);
325        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
326        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
327        $snapshotId = $identity->id;
328        $input1 = $providers->input;
329        $parser = new Parser(new ParserConfig($target->version));
330        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
331        $program1 = new Index($snapshotId, $trees);
332        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
333        $snapshotId1 = 'test';
334        $input2 = new Sources([new Source('fixture.php', $source)]);
335        $profile = $execution->configuration->target;
336        $parser1 = new Parser(new ParserConfig($profile->version));
337        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
338        $program = new Index($snapshotId1, $trees1);
339        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
340        $body = $context->program->callable('target');
341        self::assertNotNull($body);
342        $operation = new Conversions(new Evaluator($context));
343        self::assertNull($operation->apply($body, new BoundInstruction('x', 'constant', $body->source), new State()));
344    }
345    public function testStringInvokesSourceEffectsOnce(): void
346    {
347        $source = '<?php class B{public int $n=0; function __toString():string{$this->n++;return "ok";}} function target(){$b=new B; $s=(string)$b; return [$s,$b->n];}';
348        $symbol = 'target';
349        $input = new Sources([new Source('fixture.php', $source)]);
350        $configuration = new DeriverConfig();
351        $contributions = new Contributions();
352        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
353        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
354        $contributions1 = $providers->contributions;
355        $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);
356        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
357        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
358        $snapshotId = $identity->id;
359        $input1 = $providers->input;
360        $parser = new Parser(new ParserConfig($target->version));
361        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
362        $program = new Index($snapshotId, $trees);
363        $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
364        self::assertSame(['ok', 1], $result->normalOutcomes[0]->values['return']->native());
365    }
366    public function testConcatKeepsLeftStringBeforeTheRightMethodMutatesItsReceiver(): void
367    {
368        $source = '<?php class B{public int $n=0; function __toString():string{$this->n++;return (string)$this->n;}} function target(){$b=new B; return $b.$b;}';
369        $symbol = 'target';
370        $input = new Sources([new Source('fixture.php', $source)]);
371        $configuration = new DeriverConfig();
372        $contributions = new Contributions();
373        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
374        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
375        $contributions1 = $providers->contributions;
376        $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
377        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
378        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
379        $snapshotId = $identity->id;
380        $input1 = $providers->input;
381        $parser = new Parser(new ParserConfig($target->version));
382        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
383        $program = new Index($snapshotId, $trees);
384        $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
385        self::assertSame('12', $result->normalOutcomes[0]->values['return']->native());
386    }
387    public function testObjectDistinguishesProvenScalarTypesFromPossibleObjects(): void
388    {
389        $source = '<?php function target(){return 1;}';
390        $budget = new Budget();
391        $configuration = new DeriverConfig();
392        $contributions = new Contributions();
393        $input = new Sources([new Source('fixture.php', $source)]);
394        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
395        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
396        $contributions1 = $providers->contributions;
397        $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);
398        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
399        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
400        $snapshotId = $identity->id;
401        $input1 = $providers->input;
402        $parser = new Parser(new ParserConfig($target->version));
403        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
404        $program1 = new Index($snapshotId, $trees);
405        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
406        $snapshotId1 = 'test';
407        $input2 = new Sources([new Source('fixture.php', $source)]);
408        $profile = $execution->configuration->target;
409        $parser1 = new Parser(new ParserConfig($profile->version));
410        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
411        $program = new Index($snapshotId1, $trees1);
412        $operation = new Conversions(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)));
413        self::assertFalse($operation->object(Term::parameter('x', 'int|string')));
414        self::assertTrue($operation->object(Term::parameter('x', 'mixed')));
415    }
416    public function testBoundaryIncludesBothCompletionKindsAndEffects(): void
417    {
418        $source = '<?php function target(){return 1;}';
419        $budget = new Budget();
420        $configuration = new DeriverConfig();
421        $contributions = new Contributions();
422        $input = new Sources([new Source('fixture.php', $source)]);
423        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
424        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
425        $contributions1 = $providers->contributions;
426        $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);
427        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
428        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
429        $snapshotId = $identity->id;
430        $input1 = $providers->input;
431        $parser = new Parser(new ParserConfig($target->version));
432        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
433        $program1 = new Index($snapshotId, $trees);
434        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
435        $snapshotId1 = 'test';
436        $input2 = new Sources([new Source('fixture.php', $source)]);
437        $profile = $execution->configuration->target;
438        $parser1 = new Parser(new ParserConfig($profile->version));
439        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
440        $program = new Index($snapshotId1, $trees1);
441        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
442        $body = $context->program->callable('target');
443        self::assertNotNull($body);
444        $state = new State();
445        $state->memory->write($state->local('x'), Term::constant(1));
446        $paths = (new Conversions(new Evaluator($context)))->boundary($body, new BoundInstruction('x', 'cast', $body->source, 'result'), $state, 'object-cast');
447        self::assertSame(1, $paths[0]->snapshot()['x']->native());
448        self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
449    }
450    public function testReturnedEnforcesTheImplicitStringContract(): void
451    {
452        $source = '<?php class B{function __toString(){return [];}} function target(){try{return (string)new B;}catch(TypeError $e){return "caught";}}';
453        $symbol = 'target';
454        $input = new Sources([new Source('fixture.php', $source)]);
455        $configuration = new DeriverConfig();
456        $contributions = new Contributions();
457        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
458        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
459        $contributions1 = $providers->contributions;
460        $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);
461        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
462        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
463        $snapshotId = $identity->id;
464        $input1 = $providers->input;
465        $parser = new Parser(new ParserConfig($target->version));
466        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
467        $program = new Index($snapshotId, $trees);
468        $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
469        self::assertSame('caught', $result->normalOutcomes[0]->values['return']->native());
470    }
471    #[DataProvider('providerSourceStringConversions')]
472    public function testStringPreservesOracleCheckedSourceEffectsAndCompletion(string $source, Term $expected): void
473    {
474        $symbol = 'target';
475        $input = new Sources([new Source('fixture.php', $source)]);
476        $configuration = new DeriverConfig();
477        $contributions = new Contributions();
478        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
479        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
480        $contributions1 = $providers->contributions;
481        $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);
482        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
483        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
484        $snapshotId = $identity->id;
485        $input1 = $providers->input;
486        $parser = new Parser(new ParserConfig($target->version));
487        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
488        $program = new Index($snapshotId, $trees);
489        $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
490        self::assertSame([], $result->frontiers);
491        self::assertSame([], $result->exceptionalOutcomes);
492        self::assertCount(1, $result->normalOutcomes);
493        self::assertSame($expected->native(), $result->normalOutcomes[0]->values['return']->native());
494    }
495    /**
496     * @return iterable<string,array{string,Term}>
497     */
498    public static function providerSourceStringConversions(): iterable
499    {
500        return array_intersect_key(['unpack stops on append overflow' => ['<?php function target(){try{return [PHP_INT_MAX=>1,...[2,3]];}catch(Error $e){return "error";}}', Term::constant('error')], 'unpack scalar rejects input' => ['<?php function target(){$value=7;try{return [...$value];}catch(Error $e){return get_class($e);}}', Term::constant('Error')], 'unpack closure rejects input' => ['<?php function target(){try{return [...(fn()=>1)];}catch(TypeError $e){return "error";}}', Term::constant('error')], 'count array rejects invalid mode' => ['<?php function target(){try{return count([],2);}catch(ValueError $e){return "error";}}', Term::constant('error')], 'implode rejects two arrays' => ['<?php function target(){try{return implode(["a"],["b"]);}catch(TypeError $e){return "error";}}', Term::constant('error')], 'implode rejects single string' => ['<?php function target(){try{return implode(",");}catch(TypeError $e){return "error";}}', Term::constant('error')], 'implode explicit null overload' => ['<?php function target(){return implode(["a",2],null);}', Term::constant('a2')], 'explode empty separator' => ['<?php function target(){try{return explode("","abc");}catch(ValueError $e){return "error";}}', Term::constant('error')], 'array callback method' => ['<?php class B{public int $n=0;function twice(int $v):int{$this->n++;return 2*$v;}}function target(){$b=new B;return [array_map([$b,"twice"],[2,3]),$b->n];}', Term::fromNative([[4, 6], 2])], 'public method callable predicate' => ['<?php class B{function run(){}}function target(){$b=new B;return [is_callable([$b,"run"]),is_callable("B::run")];}', Term::fromNative([true, false])], 'countable method effects' => ['<?php class B implements Countable{public int $n=0;function count():int{$this->n++;return 4;}}function target(){$b=new B;return [count($b),$b->n];}', Term::fromNative([4, 1])], 'countable method throws' => ['<?php class B implements Countable{public int $n=0;function count():int{$this->n=8;throw new Error;}}function target(){$b=new B;try{count($b);}catch(Error $e){return $b->n;}}', Term::constant(8)], 'countable rejects mode before effects' => ['<?php class B implements Countable{public int $n=0;function count():int{$this->n++;return 4;}}function target(){$b=new B;try{count($b,99);}catch(ValueError $e){return $b->n;}}', Term::constant(0)], 'strict internal null' => ['<?php declare(strict_types=1);function target(){try{return strlen(null);}catch(TypeError $e){return "error";}}', Term::constant('error')], 'invalid array key exists key' => ['<?php function target(){try{return array_key_exists([],[]);}catch(TypeError $e){return "error";}}', Term::constant('error')], 'invalid array callback' => ['<?php function target(){try{return array_map(7,[1]);}catch(TypeError $e){return "error";}}', Term::constant('error')], 'automatic Stringable interface' => ['<?php class B{function __toString():string{return "ok";}}function pass(Stringable $b){return (string)$b;}function target(){return pass(new B);}', Term::constant('ok')], 'source string method effects' => ['<?php class B{public int $n=0; function __toString():string{$this->n++;return "ok";}} function target(){$b=new B; $s=(string)$b; return [$s,$b->n];}', Term::fromNative(['ok', 1])], 'concatenation invokes both operands' => ['<?php class B{public int $n=0; function __toString():string{$this->n++;return (string)$this->n;}} function target(){$b=new B; $s=$b.$b; return [$s,$b->n];}', Term::fromNative(['12', 2])], 'source string method throws after writing' => ['<?php class B{public int $n=0; function __toString():string{$this->n=3;throw new RuntimeException;} } function target(){$b=new B; try{$s=(string)$b;}catch(RuntimeException $e){return $b->n;}}', Term::constant(3)], 'implicit string return declaration' => ['<?php class B{function __toString(){return 42;}} function target(){return (string)new B;}', Term::constant('42')], 'strict implicit string return' => ['<?php declare(strict_types=1); class B{function __toString(){return 42;}} function target(){try{return (string)new B;}catch(TypeError $e){return "error";}}', Term::constant('error')], 'invalid implicit string return' => ['<?php class B{function __toString(){return [];}} function target(){try{return (string)new B;}catch(TypeError $e){return "error";}}', Term::constant('error')], 'object without string method' => ['<?php class B{} function target(){try{return (string)new B;}catch(Error $e){return "error";}}', Term::constant('error')], 'closure string cast' => ['<?php function target(){try{return (string)(fn()=>1);}catch(Error $e){return "error";}}', Term::constant('error')], 'enum string cast' => ['<?php enum B:string{case A="a";} function target(){try{return (string)B::A;}catch(Error $e){return "error";}}', Term::constant('error')], 'zero base power keeps the infinity sign' => ['<?php function target(){$z=0.0;return [0 ** -1 > PHP_INT_MAX, (-$z) ** -1 < 0, (-$z) ** -2 > 0, (-0.0) ** -2.5 > 0, $z ** -0.5 > 0];}', Term::fromNative([true, true, true, true, true])], 'not a number is true' => ['<?php function target(){$n=1e308 * 10 - 1e308 * 10;return [$n ? 1 : 2, $n xor false, !$n];}', Term::fromNative([1, true, false])], 'unary signs keep negative zero' => ['<?php function target(){$z=0.0;return [(-$z) ** -1 < 0, (+"-0.0") ** -1 < 0, (-(-$z)) ** -1 > 0];}', Term::fromNative([true, true, true])]], array_fill_keys(['automatic Stringable interface', 'source string method effects', 'concatenation invokes both operands', 'source string method throws after writing', 'implicit string return declaration', 'strict implicit string return', 'invalid implicit string return', 'object without string method', 'closure string cast', 'enum string cast'], true));
501    }
502    #[DataProvider('providerScalarBounds')]
503    public function testObjectRecognizesEveryScalarTypeAndConservativeObjectBound(Term $value, bool $expected): void
504    {
505        $source = '<?php function target(){return 1;}';
506        $budget = new Budget();
507        $configuration = new DeriverConfig();
508        $contributions = new Contributions();
509        $input = new Sources([new Source('fixture.php', $source)]);
510        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
511        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
512        $contributions1 = $providers->contributions;
513        $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);
514        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
515        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
516        $snapshotId = $identity->id;
517        $input1 = $providers->input;
518        $parser = new Parser(new ParserConfig($target->version));
519        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
520        $program1 = new Index($snapshotId, $trees);
521        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
522        $snapshotId1 = 'test';
523        $input2 = new Sources([new Source('fixture.php', $source)]);
524        $profile = $execution->configuration->target;
525        $parser1 = new Parser(new ParserConfig($profile->version));
526        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
527        $program = new Index($snapshotId1, $trees1);
528        $operation = new Conversions(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)));
529        self::assertSame($expected, $operation->object($value));
530    }
531    /**
532     * @return iterable<string,array{Term,bool}>
533     */
534    public static function providerScalarBounds(): iterable
535    {
536        yield 'constant' => [Term::constant(1), false];
537        yield 'array' => [Term::array([]), false];
538        yield 'uninitialized' => [new Term(TermKind::Uninitialized), false];
539        yield 'integer' => [Term::parameter('x', 'int'), false];
540        yield 'float' => [Term::parameter('x', 'float'), false];
541        yield 'string' => [Term::parameter('x', 'string'), false];
542        yield 'boolean' => [Term::parameter('x', 'bool'), false];
543        yield 'true' => [Term::parameter('x', 'true'), false];
544        yield 'false' => [Term::parameter('x', 'false'), false];
545        yield 'null' => [Term::parameter('x', 'null'), false];
546        yield 'array bound' => [Term::parameter('x', 'array'), false];
547        yield 'scalar union' => [Term::parameter('x', 'int|float|string|bool|true|false|null|array'), false];
548        yield 'unknown' => [new Term(TermKind::External, 'x'), true];
549        yield 'mixed union' => [Term::parameter('x', 'string|Box'), true];
550        yield 'mixed' => [Term::parameter('x'), true];
551        yield 'object' => [new Term(TermKind::Object, 'one', attributes: ['class' => 'Box']), true];
552        yield 'closure' => [new Term(TermKind::Closure, 'closure:a.php:1'), true];
553        yield 'enum' => [new Term(TermKind::Enum, 'E::A'), true];
554    }
555    #[DataProvider('providerOrdinaryOperations')]
556    public function testApplyDefersScalarAndStrictComparisonOperations(string $operation, string $name, Term $left, Term $right): void
557    {
558        $source = '<?php function target(){return 1;}';
559        $budget = new Budget();
560        $configuration = new DeriverConfig();
561        $contributions = new Contributions();
562        $input = new Sources([new Source('fixture.php', $source)]);
563        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
564        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
565        $contributions1 = $providers->contributions;
566        $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
567        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
568        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
569        $snapshotId = $identity->id;
570        $input1 = $providers->input;
571        $parser = new Parser(new ParserConfig($target->version));
572        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
573        $program1 = new Index($snapshotId, $trees);
574        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
575        $snapshotId1 = 'test';
576        $input2 = new Sources([new Source('fixture.php', $source)]);
577        $profile = $execution->configuration->target;
578        $parser1 = new Parser(new ParserConfig($profile->version));
579        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
580        $program = new Index($snapshotId1, $trees1);
581        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
582        $body = $context->program->callable('target');
583        self::assertNotNull($body);
584        $state = new State();
585        $state->registers = ['left' => $left, 'right' => $right];
586        $instruction = new BoundInstruction('i', $operation, $body->source, 'result', ['left', 'right'], $name);
587        $result = (new Conversions(new Evaluator($context)))->apply($body, $instruction, $state);
588        self::assertNull($result);
589        self::assertSame(CompletionKind::Normal, $state->completion->kind);
590        self::assertArrayNotHasKey('result', $state->registers);
591        self::assertSame([], $context->frontiers);
592    }
593    /**
594     * @return iterable<string,array{string,string,Term,Term}>
595     */
596    public static function providerOrdinaryOperations(): iterable
597    {
598        yield 'scalar int cast' => ['cast', 'int', Term::constant('12'), Term::constant(null)];
599        yield 'object bool cast' => ['cast', 'bool', new Term(TermKind::Object, 'one'), Term::constant(null)];
600        yield 'scalar equal' => ['binary', '==', Term::constant(1), Term::constant(2)];
601        yield 'strict identity' => ['binary', '===', new Term(TermKind::Object, 'one'), new Term(TermKind::Object, 'two')];
602        yield 'strict nonidentity' => ['binary', '!==', new Term(TermKind::Object, 'one'), new Term(TermKind::Object, 'two')];
603        yield 'arithmetic' => ['binary', '+', Term::constant(1), Term::constant(2)];
604    }
605    #[DataProvider('providerOpaqueConversions')]
606    public function testApplyKeepsUnsupportedObjectProtocolsExplicit(string $operation, string $name, Term $left, Term $right, string $frontier): void
607    {
608        $source = '<?php function target(){return 1;}';
609        $budget = new Budget();
610        $configuration = new DeriverConfig();
611        $contributions = new Contributions();
612        $input = new Sources([new Source('fixture.php', $source)]);
613        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
614        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
615        $contributions1 = $providers->contributions;
616        $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);
617        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
618        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
619        $snapshotId = $identity->id;
620        $input1 = $providers->input;
621        $parser = new Parser(new ParserConfig($target->version));
622        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
623        $program1 = new Index($snapshotId, $trees);
624        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
625        $snapshotId1 = 'test';
626        $input2 = new Sources([new Source('fixture.php', $source)]);
627        $profile = $execution->configuration->target;
628        $parser1 = new Parser(new ParserConfig($profile->version));
629        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
630        $program = new Index($snapshotId1, $trees1);
631        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
632        $body = $context->program->callable('target');
633        self::assertNotNull($body);
634        $state = new State();
635        $state->registers = ['left' => $left, 'right' => $right];
636        $instruction = new BoundInstruction('i', $operation, $body->source, 'result', ['left', 'right'], $name);
637        $paths = (new Conversions(new Evaluator($context)))->apply($body, $instruction, $state);
638        self::assertNotNull($paths);
639        self::assertCount(2, $paths);
640        self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
641        self::assertSame(TermKind::Opaque, $paths[0]->value('result')->kind);
642        self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
643        $frontiers = array_values($context->frontiers);
644        self::assertSame('UNSUPPORTED_LANGUAGE_FEATURE', $frontiers[0]->code);
645        self::assertSame($frontier, $frontiers[0]->operation);
646        self::assertSame($body->source, $frontiers[0]->at);
647    }
648    /**
649     * @return iterable<string,array{string,string,Term,Term,string}>
650     */
651    public static function providerOpaqueConversions(): iterable
652    {
653        $object = new Term(TermKind::Object, 'one', attributes: ['class' => 'Box']);
654        $scalar = Term::constant(1);
655        yield 'object integer' => ['cast', 'int', $object, $scalar, 'object-cast'];
656        yield 'object float' => ['cast', 'float', $object, $scalar, 'object-cast'];
657        yield 'object array' => ['cast', 'array', $object, $scalar, 'object-cast'];
658        yield 'symbolic string' => ['cast', 'string', Term::parameter('x'), $scalar, 'dynamic-string-conversion'];
659        yield 'object equality' => ['binary', '==', $object, $scalar, 'object-comparison'];
660        yield 'object inequality' => ['binary', '!=', $scalar, $object, 'object-comparison'];
661        yield 'object less' => ['binary', '<', $object, $scalar, 'object-comparison'];
662        yield 'object less equal' => ['binary', '<=', $object, $scalar, 'object-comparison'];
663        yield 'object greater' => ['binary', '>', $object, $scalar, 'object-comparison'];
664        yield 'object greater equal' => ['binary', '>=', $object, $scalar, 'object-comparison'];
665        yield 'object ordering' => ['binary', '<=>', $object, $scalar, 'object-comparison'];
666    }
667    public function testStringRecordsArrayWarnings(): void
668    {
669        $source = '<?php function target(){return 1;}';
670        $budget = new Budget();
671        $configuration = new DeriverConfig();
672        $contributions = new Contributions();
673        $input = new Sources([new Source('fixture.php', $source)]);
674        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
675        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
676        $contributions1 = $providers->contributions;
677        $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);
678        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
679        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
680        $snapshotId = $identity->id;
681        $input1 = $providers->input;
682        $parser = new Parser(new ParserConfig($target->version));
683        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
684        $program1 = new Index($snapshotId, $trees);
685        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
686        $snapshotId1 = 'test';
687        $input2 = new Sources([new Source('fixture.php', $source)]);
688        $profile = $execution->configuration->target;
689        $parser1 = new Parser(new ParserConfig($profile->version));
690        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
691        $program = new Index($snapshotId1, $trees1);
692        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
693        $body = $context->program->callable('target');
694        self::assertNotNull($body);
695        $instruction = new BoundInstruction('i', 'cast', $body->source, 'result');
696        $paths = (new Conversions(new Evaluator($context)))->string($body, $instruction, new State(), Term::array([Term::constant('private')]));
697        self::assertCount(1, $paths);
698        self::assertSame('Array', $paths[0]->value('result')->literal);
699        $frontiers = array_values($context->frontiers);
700        self::assertSame('PHP_WARNING', $frontiers[0]->code);
701        self::assertSame('array-to-string', $frontiers[0]->operation);
702    }
703    public function testReturnedPreservesThrownCompletionsWithoutCheckingTheirReturnRegister(): void
704    {
705        $source = '<?php function target(){return 1;}';
706        $budget = new Budget();
707        $configuration = new DeriverConfig();
708        $contributions = new Contributions();
709        $input = new Sources([new Source('fixture.php', $source)]);
710        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
711        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
712        $contributions1 = $providers->contributions;
713        $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);
714        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
715        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
716        $snapshotId = $identity->id;
717        $input1 = $providers->input;
718        $parser = new Parser(new ParserConfig($target->version));
719        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
720        $program1 = new Index($snapshotId, $trees);
721        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
722        $snapshotId1 = 'test';
723        $input2 = new Sources([new Source('fixture.php', $source)]);
724        $profile = $execution->configuration->target;
725        $parser1 = new Parser(new ParserConfig($profile->version));
726        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
727        $program = new Index($snapshotId1, $trees1);
728        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
729        $body = $context->program->callable('target');
730        self::assertNotNull($body);
731        $state = new State();
732        $state->completion = new Completion(CompletionKind::Throw, new Term(TermKind::Throwable, 'RuntimeException'));
733        $paths = (new Conversions(new Evaluator($context)))->returned([$state], new BoundInstruction('i', 'cast', $body->source, 'result'), 'target');
734        self::assertSame([$state], $paths);
735        self::assertSame('RuntimeException', $paths[0]->completion->value?->literal);
736        self::assertArrayNotHasKey('result', $paths[0]->registers);
737    }
738    public function testReturnedPartitionsAnUnknownReturnIntoValidStringsAndTypeErrors(): void
739    {
740        $source = '<?php function target(){return 1;}';
741        $budget = new Budget();
742        $configuration = new DeriverConfig();
743        $contributions = new Contributions();
744        $input = new Sources([new Source('fixture.php', $source)]);
745        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
746        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
747        $contributions1 = $providers->contributions;
748        $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);
749        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
750        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
751        $snapshotId = $identity->id;
752        $input1 = $providers->input;
753        $parser = new Parser(new ParserConfig($target->version));
754        $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
755        $program1 = new Index($snapshotId, $trees);
756        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
757        $snapshotId1 = 'test';
758        $input2 = new Sources([new Source('fixture.php', $source)]);
759        $profile = $execution->configuration->target;
760        $parser1 = new Parser(new ParserConfig($profile->version));
761        $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
762        $program = new Index($snapshotId1, $trees1);
763        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
764        $body = $context->program->callable('target');
765        self::assertNotNull($body);
766        $state = new State();
767        $state->registers['result'] = Term::parameter('result');
768        $paths = (new Conversions(new Evaluator($context)))->returned([$state], new BoundInstruction('i', 'cast', $body->source, 'result'), 'target');
769        self::assertCount(2, $paths);
770        self::assertSame(CompletionKind::Throw, $paths[0]->completion->kind);
771        self::assertSame('TypeError', $paths[0]->completion->value?->literal);
772        self::assertSame(CompletionKind::Normal, $paths[1]->completion->kind);
773        self::assertSame('string', $paths[1]->value('result')->attributes['type']);
774    }
775}
776