packages/deriver/tests/Unit/Deriver/Evaluator/Execution/Call/ParameterBindingTest.php
1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution\Call;
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\Invocation;
28use Deriver\Deriver\Evaluator\Execution\Call\Native\Invocation as NativeInvocation;
29use Deriver\Deriver\Evaluator\Execution\Call\Native\Properties;
30use Deriver\Deriver\Evaluator\Execution\Call\Native\Signatures;
31use Deriver\Deriver\Evaluator\Execution\Call\ParameterBinding;
32use Deriver\Deriver\Evaluator\Execution\Call\PassedArgument;
33use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Arguments;
34use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Creation;
35use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Modes;
36use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Resolution;
37use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target;
38use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Transfer;
39use Deriver\Deriver\Evaluator\Execution\Call\TypeBinding;
40use Deriver\Deriver\Evaluator\Execution\Call\TypeCheck;
41use Deriver\Deriver\Evaluator\Execution\Completion\Alternative;
42use Deriver\Deriver\Evaluator\Execution\Completion\Assessment;
43use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
44use Deriver\Deriver\Evaluator\Execution\Completion\CompletionKind;
45use Deriver\Deriver\Evaluator\Execution\Completion\StorageSnapshot;
46use Deriver\Deriver\Evaluator\Execution\Constraints;
47use Deriver\Deriver\Evaluator\Execution\Context;
48use Deriver\Deriver\Evaluator\Execution\Control\ExceptionChain;
49use Deriver\Deriver\Evaluator\Execution\Control\ExceptionMatch;
50use Deriver\Deriver\Evaluator\Execution\Control\Handler;
51use Deriver\Deriver\Evaluator\Execution\Control\ObservationLimit;
52use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
53use Deriver\Deriver\Evaluator\Execution\Control\Unwinding;
54use Deriver\Deriver\Evaluator\Execution\Demand\Cell;
55use Deriver\Deriver\Evaluator\Execution\Demand\Components;
56use Deriver\Deriver\Evaluator\Execution\Demand\Discovery;
57use Deriver\Deriver\Evaluator\Execution\Demand\Key;
58use Deriver\Deriver\Evaluator\Execution\Demand\Table;
59use Deriver\Deriver\Evaluator\Execution\Dependencies;
60use Deriver\Deriver\Evaluator\Execution\Evaluator;
61use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
62use Deriver\Deriver\Evaluator\Execution\Havoc;
63use Deriver\Deriver\Evaluator\Execution\InstructionTransfer;
64use Deriver\Deriver\Evaluator\Execution\Memory\Location;
65use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
66use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
67use Deriver\Deriver\Evaluator\Execution\Memory\ReferenceConstraint;
68use Deriver\Deriver\Evaluator\Execution\Memory\Slots\SlotReference;
69use Deriver\Deriver\Evaluator\Execution\Memory\Slots\StateStorage;
70use Deriver\Deriver\Evaluator\Execution\Memory\StorageCapture;
71use Deriver\Deriver\Evaluator\Execution\ObservationCollector;
72use Deriver\Deriver\Evaluator\Execution\Operation\Conversions;
73use Deriver\Deriver\Evaluator\Execution\Operation\ScalarErrors;
74use Deriver\Deriver\Evaluator\Execution\State;
75use Deriver\Deriver\Evaluator\Execution\Summary\CompletionRecord;
76use Deriver\Deriver\Evaluator\Execution\Summary\Evaluation;
77use Deriver\Deriver\Evaluator\Execution\Summary\Invocation as SummaryInvocation;
78use Deriver\Deriver\Evaluator\Execution\Summary\Isolation;
79use Deriver\Deriver\Evaluator\Execution\Trace\Derivation;
80use Deriver\Deriver\Evaluator\Execution\Transfer\MemoryStep;
81use Deriver\Deriver\Evaluator\Execution\Transfer\ObjectAccess;
82use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyAccessCheck;
83use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyLookup;
84use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyReference;
85use Deriver\Deriver\Evaluator\Execution\Transfer\PropertySlot;
86use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyTransfer;
87use Deriver\Deriver\Evaluator\Execution\Transfer\PureStep;
88use Deriver\Deriver\Evaluator\Execution\Transfer\ReferenceAssignment;
89use Deriver\Deriver\Evaluator\Request\Budget;
90use Deriver\Deriver\Evaluator\Request\EntryPoint;
91use Deriver\Deriver\Evaluator\Request\QueryScope;
92use Deriver\Deriver\Evaluator\Request\ReturnQuery;
93use Deriver\Deriver\Evaluator\Scalar\Arrays;
94use Deriver\Deriver\Evaluator\Scalar\Identity;
95use Deriver\Deriver\Evaluator\Scalar\IntegerConversion;
96use Deriver\Deriver\Evaluator\Scalar\NumericString;
97use Deriver\Deriver\Evaluator\Scalar\Operations;
98use Deriver\Deriver\Evaluator\TargetProfile;
99use Deriver\Deriver\Evaluator\Term;
100use Deriver\Deriver\Evaluator\TermKind;
101use Deriver\Deriver\Execution\DerivationResult;
102use Deriver\Deriver\Execution\QueryExecution;
103use Deriver\Deriver\Execution\ResultAssessment;
104use Deriver\Deriver\Identity\QueryEncoding;
105use Deriver\Deriver\Identity\ValueGraph;
106use Deriver\Deriver\Lowerer\AggregateLowering;
107use Deriver\Deriver\Lowerer\AssignmentLowering;
108use Deriver\Deriver\Lowerer\CallLowering;
109use Deriver\Deriver\Lowerer\Control\DestructuringLowering;
110use Deriver\Deriver\Lowerer\Control\ExceptionLowering;
111use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
112use Deriver\Deriver\Lowerer\Declaration\CallableSource;
113use Deriver\Deriver\Lowerer\Declaration\ConstantSignatures;
114use Deriver\Deriver\Lowerer\Declaration\Index;
115use Deriver\Deriver\Lowerer\Declaration\Indexer;
116use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassDeclaration;
117use Deriver\Deriver\Lowerer\Declaration\Metadata\PropertyDeclaration;
118use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
119use Deriver\Deriver\Lowerer\ExpressionLowering;
120use Deriver\Deriver\Lowerer\Graph\Argument;
121use Deriver\Deriver\Lowerer\Graph\BasicBlock;
122use Deriver\Deriver\Lowerer\Graph\CallableGraph;
123use Deriver\Deriver\Lowerer\Graph\CatchTarget;
124use Deriver\Deriver\Lowerer\Graph\ExceptionRegion;
125use Deriver\Deriver\Lowerer\Graph\Instruction;
126use Deriver\Deriver\Lowerer\Graph\Parameter;
127use Deriver\Deriver\Lowerer\Graph\Terminator;
128use Deriver\Deriver\Lowerer\Graph\TerminatorKind;
129use Deriver\Deriver\Lowerer\Hierarchy\Access as HierarchyAccess;
130use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
131use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
132use Deriver\Deriver\Lowerer\InstructionEmitter;
133use Deriver\Deriver\Lowerer\Lowerer;
134use Deriver\Deriver\Lowerer\LoweringScope;
135use Deriver\Deriver\Lowerer\StatementLowering;
136use Deriver\Deriver\MicroEvaluatorConversion;
137use Deriver\Deriver\Parser\Node\File;
138use Deriver\Deriver\Parser\Parser;
139use Deriver\Deriver\Parser\ParserConfig;
140use Deriver\Deriver\Parser\TargetSyntax;
141use Deriver\Deriver\Providers\ProviderExpansion;
142use Deriver\Deriver\Resolver\AssignmentPatterns;
143use Deriver\Deriver\Resolver\ClassScope;
144use Deriver\Deriver\Resolver\ExpressionValidation;
145use Deriver\Deriver\Resolver\MagicContext;
146use Deriver\Deriver\Resolver\Resolver;
147use Deriver\Deriver\Selection\QueryValidation;
148use Deriver\Deriver\SnapshotIdentity;
149use Deriver\Deriver\Sources\LineMap;
150use Deriver\Deriver\Sources\Path;
151use Deriver\Deriver\Sources\Source;
152use Deriver\Deriver\Sources\SourceRef;
153use Deriver\Deriver\Sources\Sources;
154use Deriver\Exception\ParserException;
155use Deriver\Exception\ResolutionException;
156use PHPUnit\Framework\Attributes\CoversClass;
157use PHPUnit\Framework\Attributes\DataProvider;
158use PHPUnit\Framework\Attributes\Small;
159use PHPUnit\Framework\Attributes\UsesClass;
160use PHPUnit\Framework\TestCase;
161
162#[UsesClass(Resolver::class)]
163#[UsesClass(ExpressionValidation::class)]
164#[UsesClass(ResolutionException::class)]
165#[UsesClass(ParserConfig::class)]
166#[UsesClass(ParserException::class)]
167#[CoversClass(ParameterBinding::class)]
168#[UsesClass(QueryExecution::class)]
169#[UsesClass(QueryValidation::class)]
170#[UsesClass(ResultAssessment::class)]
171#[UsesClass(Deriver::class)]
172#[UsesClass(Constraints::class)]
173#[UsesClass(Argument::class)]
174#[UsesClass(BasicBlock::class)]
175#[UsesClass(CallableGraph::class)]
176#[UsesClass(CallableIdentity::class)]
177#[UsesClass(CatchTarget::class)]
178#[UsesClass(ClassDeclaration::class)]
179#[UsesClass(ExceptionRegion::class)]
180#[UsesClass(Instruction::class)]
181#[UsesClass(Parameter::class)]
182#[UsesClass(PropertyDeclaration::class)]
183#[UsesClass(Terminator::class)]
184#[UsesClass(Allocation::class)]
185#[UsesClass(ArgumentBinding::class)]
186#[UsesClass(ArgumentOrder::class)]
187#[UsesClass(CallExecutor::class)]
188#[UsesClass(CallResolution::class)]
189#[UsesClass(CallableCheck::class)]
190#[UsesClass(Access::class)]
191#[UsesClass(Builtins::class)]
192#[UsesClass(Dispatch::class)]
193#[UsesClass(HierarchyAccess::class)]
194#[UsesClass(Invocation::class)]
195#[UsesClass(NativeInvocation::class)]
196#[UsesClass(Properties::class)]
197#[UsesClass(Signatures::class)]
198#[UsesClass(PassedArgument::class)]
199#[UsesClass(Arguments::class)]
200#[UsesClass(Creation::class)]
201#[UsesClass(Modes::class)]
202#[UsesClass(Resolution::class)]
203#[UsesClass(Target::class)]
204#[UsesClass(Transfer::class)]
205#[UsesClass(TypeBinding::class)]
206#[UsesClass(TypeCheck::class)]
207#[UsesClass(Completion::class)]
208#[UsesClass(Context::class)]
209#[UsesClass(ExceptionChain::class)]
210#[UsesClass(ExceptionMatch::class)]
211#[UsesClass(Handler::class)]
212#[UsesClass(ObservationLimit::class)]
213#[UsesClass(StateJoin::class)]
214#[UsesClass(Unwinding::class)]
215#[UsesClass(Cell::class)]
216#[UsesClass(Components::class)]
217#[UsesClass(Discovery::class)]
218#[UsesClass(Key::class)]
219#[UsesClass(Table::class)]
220#[UsesClass(Dependencies::class)]
221#[UsesClass(Havoc::class)]
222#[UsesClass(InstructionTransfer::class)]
223#[UsesClass(Evaluator::class)]
224#[UsesClass(SlotReference::class)]
225#[UsesClass(StateStorage::class)]
226#[UsesClass(ObservationCollector::class)]
227#[UsesClass(Conversions::class)]
228#[UsesClass(ScalarErrors::class)]
229#[UsesClass(State::class)]
230#[UsesClass(CompletionRecord::class)]
231#[UsesClass(Evaluation::class)]
232#[UsesClass(SummaryInvocation::class)]
233#[UsesClass(Isolation::class)]
234#[UsesClass(MemoryStep::class)]
235#[UsesClass(ObjectAccess::class)]
236#[UsesClass(PropertyAccessCheck::class)]
237#[UsesClass(PropertyLookup::class)]
238#[UsesClass(PropertyReference::class)]
239#[UsesClass(PropertySlot::class)]
240#[UsesClass(PropertyTransfer::class)]
241#[UsesClass(PureStep::class)]
242#[UsesClass(ReferenceAssignment::class)]
243#[UsesClass(Location::class)]
244#[UsesClass(Materialization::class)]
245#[UsesClass(Memory::class)]
246#[UsesClass(ReferenceConstraint::class)]
247#[UsesClass(StorageCapture::class)]
248#[UsesClass(Extensions::class)]
249#[UsesClass(ProviderExpansion::class)]
250#[UsesClass(Evaluators::class)]
251#[UsesClass(StateRegistry::class)]
252#[UsesClass(DeriverConfig::class)]
253#[UsesClass(EntryPoint::class)]
254#[UsesClass(Sources::class)]
255#[UsesClass(Path::class)]
256#[UsesClass(Snapshot::class)]
257#[UsesClass(Source::class)]
258#[UsesClass(TargetProfile::class)]
259#[UsesClass(Budget::class)]
260#[UsesClass(QueryScope::class)]
261#[UsesClass(ReturnQuery::class)]
262#[UsesClass(ResultRef::class)]
263#[UsesClass(SourceRef::class)]
264#[UsesClass(Alternative::class)]
265#[UsesClass(Assessment::class)]
266#[UsesClass(Derivation::class)]
267#[UsesClass(DerivationResult::class)]
268#[UsesClass(Frontier::class)]
269#[UsesClass(QueryEncoding::class)]
270#[UsesClass(ValueGraph::class)]
271#[UsesClass(Statistics::class)]
272#[UsesClass(StorageSnapshot::class)]
273#[UsesClass(Parser::class)]
274#[UsesClass(File::class)]
275#[UsesClass(AggregateLowering::class)]
276#[UsesClass(AssignmentLowering::class)]
277#[UsesClass(CallLowering::class)]
278#[UsesClass(Lowerer::class)]
279#[UsesClass(DestructuringLowering::class)]
280#[UsesClass(ExceptionLowering::class)]
281#[UsesClass(ExpressionLowering::class)]
282#[UsesClass(InstructionEmitter::class)]
283#[UsesClass(LoweringScope::class)]
284#[UsesClass(StatementLowering::class)]
285#[UsesClass(ConstantSignatures::class)]
286#[UsesClass(CallableSource::class)]
287#[UsesClass(Indexer::class)]
288#[UsesClass(Index::class)]
289#[UsesClass(Composition::class)]
290#[UsesClass(LineMap::class)]
291#[UsesClass(MagicContext::class)]
292#[UsesClass(AssignmentPatterns::class)]
293#[UsesClass(ClassScope::class)]
294#[UsesClass(TargetSyntax::class)]
295#[UsesClass(Arrays::class)]
296#[UsesClass(Identity::class)]
297#[UsesClass(IntegerConversion::class)]
298#[UsesClass(NumericString::class)]
299#[UsesClass(Operations::class)]
300#[UsesClass(Term::class)]
301#[Small]
302final class ParameterBindingTest extends TestCase
303{
304 public function testBindPreservesTheSemanticContract(): void
305 {
306 $source = '<?php class Box{function __construct(public string $value){}}function target(){return (new Box("yes"))->value;}';
307 $symbol = 'target';
308 $input = new Sources([new Source('fixture.php', $source)]);
309 $configuration = new DeriverConfig();
310 $contributions = new Contributions();
311 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
312 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
313 $contributions1 = $providers->contributions;
314 $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);
315 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
316 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
317 $snapshotId = $identity->id;
318 $input1 = $providers->input;
319 $parser = new Parser(new ParserConfig($target->version));
320 $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));
321 $program = new Index($snapshotId, $trees);
322 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
323 self::assertSame('yes', $result->normalOutcomes[0]->values['return']->native());
324 self::assertSame([], $result->frontiers);
325 self::assertSame([], $result->exceptionalOutcomes);
326 }
327 public function testVariadicPreservesNamedKeysAndChecksEveryElement(): void
328 {
329 $source = '<?php function f(int ...$items){return $items;}function target(){return f(1,second:2);}';
330 $symbol = 'target';
331 $input = new Sources([new Source('fixture.php', $source)]);
332 $configuration = new DeriverConfig();
333 $contributions = new Contributions();
334 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
335 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
336 $contributions1 = $providers->contributions;
337 $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);
338 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
339 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
340 $snapshotId = $identity->id;
341 $input1 = $providers->input;
342 $parser = new Parser(new ParserConfig($target->version));
343 $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));
344 $program = new Index($snapshotId, $trees);
345 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
346 self::assertSame([], $result->frontiers);
347 self::assertSame([], $result->exceptionalOutcomes);
348 self::assertCount(1, $result->normalOutcomes);
349 self::assertSame([0 => 1, 'second' => 2], $result->normalOutcomes[0]->values['return']->native());
350 }
351 public function testDefaultArgumentRunsOnlyWhenTheCallerOmitsIt(): void
352 {
353 $source = '<?php function f($x=3){return $x;}function target(){return [f(),f(9)];}';
354 $symbol = 'target';
355 $input = new Sources([new Source('fixture.php', $source)]);
356 $configuration = new DeriverConfig();
357 $contributions = new Contributions();
358 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
359 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
360 $contributions1 = $providers->contributions;
361 $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);
362 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
363 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
364 $snapshotId = $identity->id;
365 $input1 = $providers->input;
366 $parser = new Parser(new ParserConfig($target->version));
367 $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));
368 $program = new Index($snapshotId, $trees);
369 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
370 self::assertSame([], $result->frontiers);
371 self::assertSame([], $result->exceptionalOutcomes);
372 self::assertCount(1, $result->normalOutcomes);
373 self::assertSame([3, 9], $result->normalOutcomes[0]->values['return']->native());
374 }
375 public function testVariadicPreservesTypedPropertyConstraintsBeforeCreatingAReference(): void
376 {
377 $source = '<?php class B{public int $x=1;}function change(float &...$values){$values[0]=2.5;}function target(){$b=new B;try{change($b->x);}catch(TypeError $e){return $b->x;}return 999;}';
378 $symbol = 'target';
379 $input = new Sources([new Source('fixture.php', $source)]);
380 $configuration = new DeriverConfig();
381 $contributions = new Contributions();
382 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
383 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
384 $contributions1 = $providers->contributions;
385 $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);
386 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
387 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
388 $snapshotId = $identity->id;
389 $input1 = $providers->input;
390 $parser = new Parser(new ParserConfig($target->version));
391 $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));
392 $program = new Index($snapshotId, $trees);
393 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
394 self::assertSame(1, $result->normalOutcomes[0]->values['return']->native());
395 self::assertSame([], $result->frontiers);
396 }
397 public function testValueReadsTheCurrentReferenceCellAfterEarlierParameterCoercions(): void
398 {
399 $state = new State();
400 $location = $state->memory->allocate(Term::constant(1));
401 $actual = new PassedArgument(Term::constant('1.5'), location: $location);
402 $source = '<?php function target(){return 1;}';
403 $budget = new Budget();
404 $configuration = new DeriverConfig();
405 $contributions = new Contributions();
406 $input = new Sources([new Source('fixture.php', $source)]);
407 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
408 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
409 $contributions1 = $providers->contributions;
410 $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);
411 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
412 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
413 $snapshotId = $identity->id;
414 $input1 = $providers->input;
415 $parser = new Parser(new ParserConfig($target->version));
416 $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));
417 $program1 = new Index($snapshotId, $trees);
418 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
419 $snapshotId1 = 'test';
420 $input2 = new Sources([new Source('fixture.php', $source)]);
421 $profile = $execution->configuration->target;
422 $parser1 = new Parser(new ParserConfig($profile->version));
423 $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));
424 $program = new Index($snapshotId1, $trees1);
425 $binding = new ParameterBinding(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)));
426 self::assertSame(1, $binding->value(new Parameter('x', byReference: true), $actual, $state)->native());
427 self::assertSame('1.5', $binding->value(new Parameter('x'), $actual, $state)->native());
428 }
429 /**
430 * @param Parameter $parameter Symbolic signature
431 * @param string $expectedType Resolved valid-input type
432 */
433 #[DataProvider('providerSymbolicParameters')]
434 public function testBindKeepsSymbolicInputsIndependentOfOmission(Parameter $parameter, string $expectedType): void
435 {
436 $source = '<?php class Box{function target(self $value){}}';
437 $budget = new Budget();
438 $configuration = new DeriverConfig();
439 $contributions = new Contributions();
440 $input = new Sources([new Source('fixture.php', $source)]);
441 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
442 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
443 $contributions1 = $providers->contributions;
444 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
445 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
446 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
447 $snapshotId = $identity->id;
448 $input1 = $providers->input;
449 $parser = new Parser(new ParserConfig($target->version));
450 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
451 $program1 = new Index($snapshotId, $trees);
452 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
453 $snapshotId1 = 'test';
454 $input2 = new Sources([new Source('fixture.php', $source)]);
455 $profile = $execution->configuration->target;
456 $parser1 = new Parser(new ParserConfig($profile->version));
457 $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));
458 $program = new Index($snapshotId1, $trees1);
459 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
460 $callable = $context->program->callable('Box::target');
461 self::assertNotNull($callable);
462 $state = new State();
463 $paths = (new ParameterBinding(new Evaluator($context)))->bind($callable, $parameter, null, $state, true);
464 self::assertSame([$state], $paths);
465 self::assertSame(CompletionKind::Normal, $state->completion->kind);
466 $value = $state->memory->read($state->local($parameter->name));
467 self::assertSame(TermKind::Parameter, $value->kind);
468 self::assertSame($parameter->name, $value->literal);
469 self::assertSame($expectedType, $value->attributes['type']);
470 }
471 /**
472 * @return array<string, array{Parameter,string}>
473 */
474 public static function providerSymbolicParameters(): array
475 {
476 return ['scalar' => [new Parameter('value', 'int'), 'int'], 'reference' => [new Parameter('value', 'int', byReference: true), 'int'], 'variadic collection' => [new Parameter('items', 'int', variadic: true), 'array'], 'lexical class' => [new Parameter('value', 'self'), 'Box']];
477 }
478 /**
479 * @param Parameter $parameter Selected signature
480 * @param PassedArgument|null $actual Omitted or invalid actual
481 * @param string $exception Expected target error
482 */
483 #[DataProvider('providerInvalidArguments')]
484 public function testBindRejectsInvalidArgumentsWithoutInitializingTheLocal(Parameter $parameter, ?PassedArgument $actual, string $exception): void
485 {
486 $source = '<?php function target(){return 1;}';
487 $budget = new Budget();
488 $configuration = new DeriverConfig();
489 $contributions = new Contributions();
490 $input = new Sources([new Source('fixture.php', $source)]);
491 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
492 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
493 $contributions1 = $providers->contributions;
494 $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);
495 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
496 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
497 $snapshotId = $identity->id;
498 $input1 = $providers->input;
499 $parser = new Parser(new ParserConfig($target->version));
500 $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));
501 $program1 = new Index($snapshotId, $trees);
502 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
503 $snapshotId1 = 'test';
504 $input2 = new Sources([new Source('fixture.php', $source)]);
505 $profile = $execution->configuration->target;
506 $parser1 = new Parser(new ParserConfig($profile->version));
507 $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));
508 $program = new Index($snapshotId1, $trees1);
509 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
510 $callable = $context->program->callable('target');
511 self::assertNotNull($callable);
512 $state = new State();
513 $paths = (new ParameterBinding(new Evaluator($context)))->bind($callable, $parameter, $actual, $state, false, true);
514 self::assertSame([$state], $paths);
515 self::assertSame(CompletionKind::Throw, $state->completion->kind);
516 self::assertSame($exception, $state->completion->value?->literal);
517 self::assertSame([], $state->locals);
518 }
519 /**
520 * @return array<string, array{Parameter,PassedArgument|null,string}>
521 */
522 public static function providerInvalidArguments(): array
523 {
524 return ['missing required' => [new Parameter('x'), null, 'ArgumentCountError'], 'invalid scalar type' => [new Parameter('x', 'int'), new PassedArgument(Term::constant('1')), 'TypeError'], 'reference requires address' => [new Parameter('x', byReference: true), new PassedArgument(Term::constant(1)), 'Error'], 'reference requires writable address' => [new Parameter('x', byReference: true), new PassedArgument(Term::constant(1), location: new Location('readonly'), writable: false), 'Error']];
525 }
526 public function testBindPreservesBothOutcomesWhenAnActualMayViolateItsType(): void
527 {
528 $source = '<?php function target(){return 1;}';
529 $budget = new Budget();
530 $configuration = new DeriverConfig();
531 $contributions = new Contributions();
532 $input = new Sources([new Source('fixture.php', $source)]);
533 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
534 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
535 $contributions1 = $providers->contributions;
536 $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);
537 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
538 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
539 $snapshotId = $identity->id;
540 $input1 = $providers->input;
541 $parser = new Parser(new ParserConfig($target->version));
542 $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));
543 $program1 = new Index($snapshotId, $trees);
544 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
545 $snapshotId1 = 'test';
546 $input2 = new Sources([new Source('fixture.php', $source)]);
547 $profile = $execution->configuration->target;
548 $parser1 = new Parser(new ParserConfig($profile->version));
549 $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));
550 $program = new Index($snapshotId1, $trees1);
551 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
552 $callable = $context->program->callable('target');
553 self::assertNotNull($callable);
554 $state = new State();
555 $actual = new PassedArgument(Term::parameter('input'));
556 $paths = (new ParameterBinding(new Evaluator($context)))->bind($callable, new Parameter('x', 'int'), $actual, $state, false, true);
557 self::assertCount(2, $paths);
558 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
559 self::assertSame('int', $paths[0]->memory->read($paths[0]->local('x'))->attributes['type']);
560 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
561 self::assertSame('TypeError', $paths[1]->completion->value?->literal);
562 self::assertSame([], $paths[1]->locals);
563 self::assertNotSame($paths[0]->memory, $paths[1]->memory);
564 }
565 public function testBindUpdatesACallerReferenceAfterWeakCoercion(): void
566 {
567 $source = '<?php function target(){return 1;}';
568 $budget = new Budget();
569 $configuration = new DeriverConfig();
570 $contributions = new Contributions();
571 $input = new Sources([new Source('fixture.php', $source)]);
572 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
573 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
574 $contributions1 = $providers->contributions;
575 $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);
576 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
577 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
578 $snapshotId = $identity->id;
579 $input1 = $providers->input;
580 $parser = new Parser(new ParserConfig($target->version));
581 $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));
582 $program1 = new Index($snapshotId, $trees);
583 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
584 $snapshotId1 = 'test';
585 $input2 = new Sources([new Source('fixture.php', $source)]);
586 $profile = $execution->configuration->target;
587 $parser1 = new Parser(new ParserConfig($profile->version));
588 $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));
589 $program = new Index($snapshotId1, $trees1);
590 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
591 $callable = $context->program->callable('target');
592 self::assertNotNull($callable);
593 $state = new State();
594 $location = $state->memory->allocate(Term::constant('2'));
595 $actual = new PassedArgument(Term::constant('2'), location: $location);
596 $paths = (new ParameterBinding(new Evaluator($context)))->bind($callable, new Parameter('x', 'int', byReference: true), $actual, $state, false);
597 self::assertSame([$state], $paths);
598 self::assertSame(CompletionKind::Normal, $state->completion->kind);
599 self::assertSame(2, $state->memory->read($location)->native());
600 $state->memory->write($state->local('x'), Term::constant(3));
601 self::assertSame(3, $state->memory->read($location)->native());
602 }
603 public function testVariadicRetainsNamedAliasesAndAppliesCoercionsToEveryCallerCell(): void
604 {
605 $state = new State();
606 $first = $state->memory->allocate(Term::constant('2'));
607 $second = $state->memory->allocate(Term::constant('3'));
608 $actual = new PassedArgument(Term::array([]), elements: [new PassedArgument(Term::constant('2'), location: $first), 'named' => new PassedArgument(Term::constant('3'), location: $second)]);
609 $source = '<?php function target(){return 1;}';
610 $budget = new Budget();
611 $configuration = new DeriverConfig();
612 $contributions = new Contributions();
613 $input = new Sources([new Source('fixture.php', $source)]);
614 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
615 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
616 $contributions1 = $providers->contributions;
617 $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);
618 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
619 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
620 $snapshotId = $identity->id;
621 $input1 = $providers->input;
622 $parser = new Parser(new ParserConfig($target->version));
623 $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));
624 $program1 = new Index($snapshotId, $trees);
625 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
626 $snapshotId1 = 'test';
627 $input2 = new Sources([new Source('fixture.php', $source)]);
628 $profile = $execution->configuration->target;
629 $parser1 = new Parser(new ParserConfig($profile->version));
630 $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));
631 $program = new Index($snapshotId1, $trees1);
632 $binding = new ParameterBinding(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)));
633 self::assertSame([$state], $binding->variadic(new Parameter('items', 'int', byReference: true, variadic: true), $actual, $state, false));
634 self::assertSame(2, $state->memory->read($first)->native());
635 self::assertSame(3, $state->memory->read($second)->native());
636 $items = $state->memory->read($state->local('items'));
637 self::assertSame([0, 'named'], array_keys($items->operands));
638 self::assertSame(TermKind::Cell, $items->operands['named']->kind);
639 $state->memory->write(new Location($state->local('items')->root, ['named']), Term::constant(4));
640 self::assertSame(4, $state->memory->read($second)->native());
641 }
642 /**
643 * @param bool $byReference Whether an address is required
644 * @param PassedArgument $element Invalid variadic element
645 * @param string $exception \Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target error class
646 */
647 #[DataProvider('providerInvalidVariadics')]
648 public function testVariadicRejectsAnInvalidElementBeforePublishingTheCollection(bool $byReference, PassedArgument $element, string $exception): void
649 {
650 $state = new State();
651 $source = '<?php function target(){return 1;}';
652 $budget = new Budget();
653 $configuration = new DeriverConfig();
654 $contributions = new Contributions();
655 $input = new Sources([new Source('fixture.php', $source)]);
656 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
657 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
658 $contributions1 = $providers->contributions;
659 $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);
660 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
661 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
662 $snapshotId = $identity->id;
663 $input1 = $providers->input;
664 $parser = new Parser(new ParserConfig($target->version));
665 $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));
666 $program1 = new Index($snapshotId, $trees);
667 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
668 $snapshotId1 = 'test';
669 $input2 = new Sources([new Source('fixture.php', $source)]);
670 $profile = $execution->configuration->target;
671 $parser1 = new Parser(new ParserConfig($profile->version));
672 $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));
673 $program = new Index($snapshotId1, $trees1);
674 $binding = new ParameterBinding(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)));
675 $paths = $binding->variadic(new Parameter('items', 'int', byReference: $byReference, variadic: true), new PassedArgument(Term::array([]), elements: [$element]), $state, true);
676 self::assertSame([$state], $paths);
677 self::assertSame(CompletionKind::Throw, $state->completion->kind);
678 self::assertSame($exception, $state->completion->value?->literal);
679 self::assertSame([], $state->locals);
680 }
681 /**
682 * @return array<string,array{bool,PassedArgument,string}>
683 */
684 public static function providerInvalidVariadics(): array
685 {
686 return ['type error' => [false, new PassedArgument(Term::constant('2')), 'TypeError'], 'missing address' => [true, new PassedArgument(Term::constant(2)), 'Error'], 'readonly address' => [true, new PassedArgument(Term::constant(2), location: new Location('readonly'), writable: false), 'Error']];
687 }
688 public function testVariadicPreservesPossibleTypeFailureAlongsideTheNormalCollection(): void
689 {
690 $state = new State();
691 $source = '<?php function target(){return 1;}';
692 $budget = new Budget();
693 $configuration = new DeriverConfig();
694 $contributions = new Contributions();
695 $input = new Sources([new Source('fixture.php', $source)]);
696 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
697 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
698 $contributions1 = $providers->contributions;
699 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
700 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
701 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
702 $snapshotId = $identity->id;
703 $input1 = $providers->input;
704 $parser = new Parser(new ParserConfig($target->version));
705 $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));
706 $program1 = new Index($snapshotId, $trees);
707 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
708 $snapshotId1 = 'test';
709 $input2 = new Sources([new Source('fixture.php', $source)]);
710 $profile = $execution->configuration->target;
711 $parser1 = new Parser(new ParserConfig($profile->version));
712 $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));
713 $program = new Index($snapshotId1, $trees1);
714 $binding = new ParameterBinding(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)));
715 $paths = $binding->variadic(new Parameter('items', 'self', variadic: true), new PassedArgument(Term::array([]), elements: [new PassedArgument(Term::parameter('unknown')), new PassedArgument(Term::constant(1))]), $state, true, 'int');
716 self::assertCount(2, $paths);
717 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
718 $items = $paths[0]->memory->read($paths[0]->local('items'));
719 self::assertSame('int', $items->operands[0]->attributes['type']);
720 self::assertSame(1, $items->operands[1]->native());
721 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
722 self::assertSame('TypeError', $paths[1]->completion->value?->literal);
723 self::assertNotSame($paths[0]->memory, $paths[1]->memory);
724 }
725 public function testDefaultArgumentRetainsAnOmittedMarkerWithoutEnforcingAnAbsentType(): void
726 {
727 $source = '<?php function target(){return 1;}';
728 $budget = new Budget();
729 $configuration = new DeriverConfig();
730 $contributions = new Contributions();
731 $input = new Sources([new Source('fixture.php', $source)]);
732 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
733 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
734 $contributions1 = $providers->contributions;
735 $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);
736 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
737 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
738 $snapshotId = $identity->id;
739 $input1 = $providers->input;
740 $parser = new Parser(new ParserConfig($target->version));
741 $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));
742 $program1 = new Index($snapshotId, $trees);
743 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
744 $snapshotId1 = 'test';
745 $input2 = new Sources([new Source('fixture.php', $source)]);
746 $profile = $execution->configuration->target;
747 $parser1 = new Parser(new ParserConfig($profile->version));
748 $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));
749 $program = new Index($snapshotId1, $trees1);
750 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
751 $callable = $context->program->callable('target');
752 self::assertNotNull($callable);
753 $default = new CallableGraph('default', [], [new BasicBlock(0, [new BoundInstruction('default', 'constant', $callable->source, 'r', constant: new Term(TermKind::Omitted))], new Terminator(TerminatorKind::Return, 'r'))], $callable->source);
754 $state = new State();
755 $paths = (new ParameterBinding(new Evaluator($context)))->defaultArgument($callable, new Parameter('optional', 'int'), $default, $state, true);
756 self::assertCount(1, $paths);
757 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
758 self::assertSame(TermKind::Omitted, $paths[0]->memory->read($paths[0]->local('optional'))->kind);
759 self::assertSame([], $state->locals);
760 self::assertNotSame($state->memory, $paths[0]->memory);
761 }
762 public function testDefaultArgumentPreservesInitializerEffectsAndEveryCompletion(): void
763 {
764 $source = '<?php class Initializer{function __construct(){global $flag,$count;$count=7;if($flag){throw new Exception;}}}function target($x=new Initializer){}';
765 $budget = new Budget();
766 $configuration = new DeriverConfig();
767 $contributions = new Contributions();
768 $input = new Sources([new Source('fixture.php', $source)]);
769 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
770 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
771 $contributions1 = $providers->contributions;
772 $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);
773 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
774 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
775 $snapshotId = $identity->id;
776 $input1 = $providers->input;
777 $parser = new Parser(new ParserConfig($target->version));
778 $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));
779 $program1 = new Index($snapshotId, $trees);
780 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
781 $snapshotId1 = 'test';
782 $input2 = new Sources([new Source('fixture.php', $source)]);
783 $profile = $execution->configuration->target;
784 $parser1 = new Parser(new ParserConfig($profile->version));
785 $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));
786 $program = new Index($snapshotId1, $trees1);
787 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
788 $callable = $context->program->callable('target');
789 self::assertNotNull($callable);
790 $default = $callable->parameters[0]->default;
791 self::assertNotNull($default);
792 $state = new State();
793 $paths = (new ParameterBinding(new Evaluator($context)))->defaultArgument($callable, $callable->parameters[0], $default, $state, true);
794 self::assertCount(2, $paths);
795 self::assertEqualsCanonicalizing([CompletionKind::Normal, CompletionKind::Throw], array_column(array_column($paths, 'completion'), 'kind'));
796 self::assertSame(7, $paths[0]->memory->read(new Location('global:count'))->native());
797 self::assertSame(7, $paths[1]->memory->read(new Location('global:count'))->native());
798 self::assertSame(TermKind::Uninitialized, $state->memory->read(new Location('global:count'))->kind);
799 self::assertNotSame($paths[0]->memory, $paths[1]->memory);
800 }
801 public function testVariadicReportsWeakScalarWarningsAtTheBindingSource(): void
802 {
803 $source = '<?php function target(){return 1;}';
804 $budget = new Budget();
805 $configuration = new DeriverConfig();
806 $contributions = new Contributions();
807 $input = new Sources([new Source('fixture.php', $source)]);
808 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
809 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
810 $contributions1 = $providers->contributions;
811 $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);
812 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
813 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
814 $snapshotId = $identity->id;
815 $input1 = $providers->input;
816 $parser = new Parser(new ParserConfig($target->version));
817 $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));
818 $program1 = new Index($snapshotId, $trees);
819 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
820 $snapshotId1 = 'test';
821 $input2 = new Sources([new Source('fixture.php', $source)]);
822 $profile = $execution->configuration->target;
823 $parser1 = new Parser(new ParserConfig($profile->version));
824 $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));
825 $program = new Index($snapshotId1, $trees1);
826 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
827 $callable = $context->program->callable('target');
828 self::assertNotNull($callable);
829 $state = new State();
830 $binding = new ParameterBinding(new Evaluator($context));
831 $paths = $binding->variadic(new Parameter('items', 'int', variadic: true), new PassedArgument(Term::array([]), elements: [new PassedArgument(Term::constant(1.5))]), $state, false, source: $callable->source);
832 self::assertSame([$state], $paths);
833 self::assertSame([1], $state->memory->read($state->local('items'))->native());
834 self::assertSame(['PHP_WARNING'], array_column($context->frontiers, 'code'));
835 }
836 public function testVariadicRetainsArbitraryThrowableFromAnUnresolvedObjectCoercion(): void
837 {
838 $source = '<?php class Text{function __toString(){return "text";}}function target(){}';
839 $budget = new Budget();
840 $configuration = new DeriverConfig();
841 $contributions = new Contributions();
842 $input = new Sources([new Source('fixture.php', $source)]);
843 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
844 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
845 $contributions1 = $providers->contributions;
846 $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);
847 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
848 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
849 $snapshotId = $identity->id;
850 $input1 = $providers->input;
851 $parser = new Parser(new ParserConfig($target->version));
852 $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));
853 $program1 = new Index($snapshotId, $trees);
854 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
855 $snapshotId1 = 'test';
856 $input2 = new Sources([new Source('fixture.php', $source)]);
857 $profile = $execution->configuration->target;
858 $parser1 = new Parser(new ParserConfig($profile->version));
859 $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));
860 $program = new Index($snapshotId1, $trees1);
861 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
862 $callable = $context->program->callable('target');
863 self::assertNotNull($callable);
864 $state = new State();
865 $binding = new ParameterBinding(new Evaluator($context));
866 $actual = new PassedArgument(Term::array([]), elements: [new PassedArgument(new Term(TermKind::Object, 'text', attributes: ['class' => 'Text'])), new PassedArgument(Term::constant('last'))]);
867 $paths = $binding->variadic(new Parameter('items', 'string', variadic: true), $actual, $state, false, source: $callable->source);
868 self::assertCount(2, $paths);
869 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
870 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
871 self::assertSame('Throwable', $paths[1]->completion->value?->literal);
872 self::assertTrue($paths[1]->completion->value->attributes['uncertain']);
873 self::assertSame(['UNSUPPORTED_LANGUAGE_FEATURE'], array_column($context->frontiers, 'code'));
874 }
875}
876