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