packages/deriver/tests/Unit/Deriver/Evaluator/Execution/Call/CallExecutorTest.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\Builtin\FunctionEvaluator;
14use Deriver\Deriver\Evaluator\Builtin\Library;
15use Deriver\Deriver\Evaluator\Builtin\ScalarFunctions;
16use Deriver\Deriver\Evaluator\Builtin\StringFunctions;
17use Deriver\Deriver\Evaluator\Builtin\TypePredicates;
18use Deriver\Deriver\Evaluator\Call\ArgumentBindings;
19use Deriver\Deriver\Evaluator\Call\BoundArgument;
20use Deriver\Deriver\Evaluator\Call\BoundInstruction;
21use Deriver\Deriver\Evaluator\Call\CallDecision;
22use Deriver\Deriver\Evaluator\Call\CallDescription;
23use Deriver\Deriver\Evaluator\Call\CallDescriptor;
24use Deriver\Deriver\Evaluator\Call\Evaluators;
25use Deriver\Deriver\Evaluator\Call\Extensions;
26use Deriver\Deriver\Evaluator\Call\Plan\Action;
27use Deriver\Deriver\Evaluator\Call\Plan\Expression;
28use Deriver\Deriver\Evaluator\Call\Plan\Location\LocationRef;
29use Deriver\Deriver\Evaluator\Call\Plan\PlanActions;
30use Deriver\Deriver\Evaluator\Call\Plan\PlanCompiler;
31use Deriver\Deriver\Evaluator\Call\Plan\PlanFootprints;
32use Deriver\Deriver\Evaluator\Call\Plan\PlanValidation;
33use Deriver\Deriver\Evaluator\Call\Plan\SemanticPlan;
34use Deriver\Deriver\Evaluator\Call\Provider\DispatchDecision;
35use Deriver\Deriver\Evaluator\Call\Provider\DispatchRequest;
36use Deriver\Deriver\Evaluator\Call\Registrations;
37use Deriver\Deriver\Evaluator\Call\StateRegistry;
38use Deriver\Deriver\Evaluator\Declaration\Parameter as DeclarationParameter;
39use Deriver\Deriver\Evaluator\Declaration\Signature;
40use Deriver\Deriver\Evaluator\EvaluatorConfig;
41use Deriver\Deriver\Evaluator\Evidence\Frontier;
42use Deriver\Deriver\Evaluator\Execution\Call\Allocation;
43use Deriver\Deriver\Evaluator\Execution\Call\ArgumentBinding;
44use Deriver\Deriver\Evaluator\Execution\Call\ArgumentOrder;
45use Deriver\Deriver\Evaluator\Execution\Call\CallableCheck;
46use Deriver\Deriver\Evaluator\Execution\Call\CallExecutor;
47use Deriver\Deriver\Evaluator\Execution\Call\CallResolution;
48use Deriver\Deriver\Evaluator\Execution\Call\Closure\Binding;
49use Deriver\Deriver\Evaluator\Execution\Call\Closure\Capture;
50use Deriver\Deriver\Evaluator\Execution\Call\Contract\Inputs;
51use Deriver\Deriver\Evaluator\Execution\Call\Contract\NativeArguments;
52use Deriver\Deriver\Evaluator\Execution\Call\Creation\Access;
53use Deriver\Deriver\Evaluator\Execution\Call\Member\Invocation;
54use Deriver\Deriver\Evaluator\Execution\Call\MethodInvocation;
55use Deriver\Deriver\Evaluator\Execution\Call\Native\Invocation as NativeInvocation;
56use Deriver\Deriver\Evaluator\Execution\Call\Native\Properties;
57use Deriver\Deriver\Evaluator\Execution\Call\Native\Signatures;
58use Deriver\Deriver\Evaluator\Execution\Call\ParameterBinding;
59use Deriver\Deriver\Evaluator\Execution\Call\PassedArgument;
60use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Arguments;
61use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Creation;
62use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Methods;
63use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Modes;
64use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Resolution;
65use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target;
66use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Transfer;
67use Deriver\Deriver\Evaluator\Execution\Call\ProviderDispatch;
68use Deriver\Deriver\Evaluator\Execution\Call\TypeBinding;
69use Deriver\Deriver\Evaluator\Execution\Call\TypeCheck;
70use Deriver\Deriver\Evaluator\Execution\Call\UnknownCall;
71use Deriver\Deriver\Evaluator\Execution\Completion\Alternative;
72use Deriver\Deriver\Evaluator\Execution\Completion\Assessment;
73use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
74use Deriver\Deriver\Evaluator\Execution\Completion\CompletionKind;
75use Deriver\Deriver\Evaluator\Execution\Completion\StorageSnapshot;
76use Deriver\Deriver\Evaluator\Execution\Constant\ClassNames;
77use Deriver\Deriver\Evaluator\Execution\Constraints;
78use Deriver\Deriver\Evaluator\Execution\Context;
79use Deriver\Deriver\Evaluator\Execution\Control\ExceptionChain;
80use Deriver\Deriver\Evaluator\Execution\Control\ExceptionMatch;
81use Deriver\Deriver\Evaluator\Execution\Control\Handler;
82use Deriver\Deriver\Evaluator\Execution\Control\ObservationLimit;
83use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
84use Deriver\Deriver\Evaluator\Execution\Control\Unwinding;
85use Deriver\Deriver\Evaluator\Execution\Demand\Cell;
86use Deriver\Deriver\Evaluator\Execution\Demand\Components;
87use Deriver\Deriver\Evaluator\Execution\Demand\Discovery;
88use Deriver\Deriver\Evaluator\Execution\Demand\Key;
89use Deriver\Deriver\Evaluator\Execution\Demand\Table;
90use Deriver\Deriver\Evaluator\Execution\Dependencies;
91use Deriver\Deriver\Evaluator\Execution\Evaluator;
92use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
93use Deriver\Deriver\Evaluator\Execution\Havoc;
94use Deriver\Deriver\Evaluator\Execution\InstructionTransfer;
95use Deriver\Deriver\Evaluator\Execution\Memory\Location;
96use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
97use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
98use Deriver\Deriver\Evaluator\Execution\Memory\ReferenceConstraint;
99use Deriver\Deriver\Evaluator\Execution\Memory\Slots\SlotReference;
100use Deriver\Deriver\Evaluator\Execution\Memory\Slots\StateStorage;
101use Deriver\Deriver\Evaluator\Execution\Memory\StorageCapture;
102use Deriver\Deriver\Evaluator\Execution\ObservationCollector;
103use Deriver\Deriver\Evaluator\Execution\Operation\CallablePredicate;
104use Deriver\Deriver\Evaluator\Execution\Operation\Conversions;
105use Deriver\Deriver\Evaluator\Execution\Operation\ScalarErrors;
106use Deriver\Deriver\Evaluator\Execution\State;
107use Deriver\Deriver\Evaluator\Execution\Summary\CompletionRecord;
108use Deriver\Deriver\Evaluator\Execution\Summary\Evaluation;
109use Deriver\Deriver\Evaluator\Execution\Summary\Invocation as SummaryInvocation;
110use Deriver\Deriver\Evaluator\Execution\Summary\Isolation;
111use Deriver\Deriver\Evaluator\Execution\Trace\Derivation;
112use Deriver\Deriver\Evaluator\Execution\Transfer\CallableTransfer;
113use Deriver\Deriver\Evaluator\Execution\Transfer\ConstantTransfer;
114use Deriver\Deriver\Evaluator\Execution\Transfer\IntrinsicTransfer;
115use Deriver\Deriver\Evaluator\Execution\Transfer\MemoryStep;
116use Deriver\Deriver\Evaluator\Execution\Transfer\ObjectAccess;
117use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyAccessCheck;
118use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyLookup;
119use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyReference;
120use Deriver\Deriver\Evaluator\Execution\Transfer\PropertySlot;
121use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyTransfer;
122use Deriver\Deriver\Evaluator\Execution\Transfer\PureStep;
123use Deriver\Deriver\Evaluator\Execution\Transfer\ReferenceAssignment;
124use Deriver\Deriver\Evaluator\Request\Budget;
125use Deriver\Deriver\Evaluator\Request\EntryPoint;
126use Deriver\Deriver\Evaluator\Request\QueryScope;
127use Deriver\Deriver\Evaluator\Request\ReturnQuery;
128use Deriver\Deriver\Evaluator\Scalar\Arithmetic;
129use Deriver\Deriver\Evaluator\Scalar\Arrays;
130use Deriver\Deriver\Evaluator\Scalar\Comparison;
131use Deriver\Deriver\Evaluator\Scalar\Identity;
132use Deriver\Deriver\Evaluator\Scalar\Increment;
133use Deriver\Deriver\Evaluator\Scalar\NumericString;
134use Deriver\Deriver\Evaluator\Scalar\Operations;
135use Deriver\Deriver\Evaluator\TargetProfile;
136use Deriver\Deriver\Evaluator\Term;
137use Deriver\Deriver\Evaluator\TermKind;
138use Deriver\Deriver\Execution\DerivationResult;
139use Deriver\Deriver\Execution\QueryExecution;
140use Deriver\Deriver\Execution\ResultAssessment;
141use Deriver\Deriver\Identity\QueryEncoding;
142use Deriver\Deriver\Identity\ValueGraph;
143use Deriver\Deriver\Lowerer\AggregateLowering;
144use Deriver\Deriver\Lowerer\AssignmentLowering;
145use Deriver\Deriver\Lowerer\CallLowering;
146use Deriver\Deriver\Lowerer\Control\ConditionalLowering;
147use Deriver\Deriver\Lowerer\Control\DestructuringLowering;
148use Deriver\Deriver\Lowerer\Control\ExceptionLowering;
149use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
150use Deriver\Deriver\Lowerer\Declaration\CallableSource;
151use Deriver\Deriver\Lowerer\Declaration\ConstantSignatures;
152use Deriver\Deriver\Lowerer\Declaration\Index;
153use Deriver\Deriver\Lowerer\Declaration\Indexer;
154use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassDeclaration;
155use Deriver\Deriver\Lowerer\Declaration\Metadata\PropertyDeclaration;
156use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
157use Deriver\Deriver\Lowerer\EffectInspection;
158use Deriver\Deriver\Lowerer\ExpressionLowering;
159use Deriver\Deriver\Lowerer\Graph\Argument;
160use Deriver\Deriver\Lowerer\Graph\BasicBlock;
161use Deriver\Deriver\Lowerer\Graph\CallableGraph;
162use Deriver\Deriver\Lowerer\Graph\CatchTarget;
163use Deriver\Deriver\Lowerer\Graph\ExceptionRegion;
164use Deriver\Deriver\Lowerer\Graph\Instruction;
165use Deriver\Deriver\Lowerer\Graph\Parameter;
166use Deriver\Deriver\Lowerer\Graph\Terminator;
167use Deriver\Deriver\Lowerer\Hierarchy\Access as HierarchyAccess;
168use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
169use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
170use Deriver\Deriver\Lowerer\InstructionEmitter;
171use Deriver\Deriver\Lowerer\Lowerer;
172use Deriver\Deriver\Lowerer\LoweringScope;
173use Deriver\Deriver\Lowerer\StatementLowering;
174use Deriver\Deriver\MicroEvaluatorConversion;
175use Deriver\Deriver\Parser\Node\File;
176use Deriver\Deriver\Parser\Parser;
177use Deriver\Deriver\Parser\ParserConfig;
178use Deriver\Deriver\Parser\TargetSyntax;
179use Deriver\Deriver\Providers\ProviderExpansion;
180use Deriver\Deriver\Resolver\AssignmentPatterns;
181use Deriver\Deriver\Resolver\ClassScope;
182use Deriver\Deriver\Resolver\ExpressionValidation;
183use Deriver\Deriver\Resolver\MagicContext;
184use Deriver\Deriver\Resolver\Resolver;
185use Deriver\Deriver\Selection\QueryValidation;
186use Deriver\Deriver\SnapshotIdentity;
187use Deriver\Deriver\Sources\LineMap;
188use Deriver\Deriver\Sources\Path;
189use Deriver\Deriver\Sources\Source;
190use Deriver\Deriver\Sources\SourceRef;
191use Deriver\Deriver\Sources\Sources;
192use Deriver\Exception\ParserException;
193use Deriver\Exception\ResolutionException;
194use JsonException;
195use PHPUnit\Framework\Attributes\CoversClass;
196use PHPUnit\Framework\Attributes\DataProvider;
197use PHPUnit\Framework\Attributes\Small;
198use PHPUnit\Framework\Attributes\UsesClass;
199use PHPUnit\Framework\TestCase;
200
201#[UsesClass(Resolver::class)]
202#[UsesClass(ExpressionValidation::class)]
203#[UsesClass(ResolutionException::class)]
204#[UsesClass(ParserConfig::class)]
205#[UsesClass(ParserException::class)]
206#[CoversClass(CallExecutor::class)]
207#[UsesClass(QueryExecution::class)]
208#[UsesClass(QueryValidation::class)]
209#[UsesClass(ResultAssessment::class)]
210#[UsesClass(Deriver::class)]
211#[UsesClass(Constraints::class)]
212#[UsesClass(Argument::class)]
213#[UsesClass(BasicBlock::class)]
214#[UsesClass(CallableGraph::class)]
215#[UsesClass(CallableIdentity::class)]
216#[UsesClass(CatchTarget::class)]
217#[UsesClass(ClassDeclaration::class)]
218#[UsesClass(ExceptionRegion::class)]
219#[UsesClass(Instruction::class)]
220#[UsesClass(Parameter::class)]
221#[UsesClass(PropertyDeclaration::class)]
222#[UsesClass(Terminator::class)]
223#[UsesClass(Allocation::class)]
224#[UsesClass(ArgumentBinding::class)]
225#[UsesClass(ArgumentOrder::class)]
226#[UsesClass(CallResolution::class)]
227#[UsesClass(CallableCheck::class)]
228#[UsesClass(Binding::class)]
229#[UsesClass(Capture::class)]
230#[UsesClass(Access::class)]
231#[UsesClass(Builtins::class)]
232#[UsesClass(Dispatch::class)]
233#[UsesClass(HierarchyAccess::class)]
234#[UsesClass(Invocation::class)]
235#[UsesClass(MethodInvocation::class)]
236#[UsesClass(Inputs::class)]
237#[UsesClass(NativeArguments::class)]
238#[UsesClass(NativeInvocation::class)]
239#[UsesClass(Properties::class)]
240#[UsesClass(Signatures::class)]
241#[UsesClass(ParameterBinding::class)]
242#[UsesClass(PassedArgument::class)]
243#[UsesClass(Arguments::class)]
244#[UsesClass(Creation::class)]
245#[UsesClass(Methods::class)]
246#[UsesClass(Modes::class)]
247#[UsesClass(Resolution::class)]
248#[UsesClass(Target::class)]
249#[UsesClass(Transfer::class)]
250#[UsesClass(ProviderDispatch::class)]
251#[UsesClass(TypeBinding::class)]
252#[UsesClass(TypeCheck::class)]
253#[UsesClass(UnknownCall::class)]
254#[UsesClass(Completion::class)]
255#[UsesClass(ClassNames::class)]
256#[UsesClass(Context::class)]
257#[UsesClass(ExceptionChain::class)]
258#[UsesClass(ExceptionMatch::class)]
259#[UsesClass(Handler::class)]
260#[UsesClass(ObservationLimit::class)]
261#[UsesClass(StateJoin::class)]
262#[UsesClass(Unwinding::class)]
263#[UsesClass(Cell::class)]
264#[UsesClass(Components::class)]
265#[UsesClass(Discovery::class)]
266#[UsesClass(Key::class)]
267#[UsesClass(Table::class)]
268#[UsesClass(Dependencies::class)]
269#[UsesClass(Havoc::class)]
270#[UsesClass(InstructionTransfer::class)]
271#[UsesClass(Evaluator::class)]
272#[UsesClass(SlotReference::class)]
273#[UsesClass(StateStorage::class)]
274#[UsesClass(ObservationCollector::class)]
275#[UsesClass(CallablePredicate::class)]
276#[UsesClass(Conversions::class)]
277#[UsesClass(ScalarErrors::class)]
278#[UsesClass(State::class)]
279#[UsesClass(CompletionRecord::class)]
280#[UsesClass(Evaluation::class)]
281#[UsesClass(SummaryInvocation::class)]
282#[UsesClass(Isolation::class)]
283#[UsesClass(CallableTransfer::class)]
284#[UsesClass(ConstantTransfer::class)]
285#[UsesClass(IntrinsicTransfer::class)]
286#[UsesClass(MemoryStep::class)]
287#[UsesClass(ObjectAccess::class)]
288#[UsesClass(PropertyAccessCheck::class)]
289#[UsesClass(PropertyLookup::class)]
290#[UsesClass(PropertyReference::class)]
291#[UsesClass(PropertySlot::class)]
292#[UsesClass(PropertyTransfer::class)]
293#[UsesClass(PureStep::class)]
294#[UsesClass(ReferenceAssignment::class)]
295#[UsesClass(Location::class)]
296#[UsesClass(Materialization::class)]
297#[UsesClass(Memory::class)]
298#[UsesClass(ReferenceConstraint::class)]
299#[UsesClass(StorageCapture::class)]
300#[UsesClass(ArgumentBindings::class)]
301#[UsesClass(BoundArgument::class)]
302#[UsesClass(LocationRef::class)]
303#[UsesClass(FunctionEvaluator::class)]
304#[UsesClass(Library::class)]
305#[UsesClass(ScalarFunctions::class)]
306#[UsesClass(StringFunctions::class)]
307#[UsesClass(TypePredicates::class)]
308#[UsesClass(CallDescription::class)]
309#[UsesClass(PlanActions::class)]
310#[UsesClass(PlanCompiler::class)]
311#[UsesClass(PlanFootprints::class)]
312#[UsesClass(PlanValidation::class)]
313#[UsesClass(CallDecision::class)]
314#[UsesClass(CallDescriptor::class)]
315#[UsesClass(Action::class)]
316#[UsesClass(Expression::class)]
317#[UsesClass(SemanticPlan::class)]
318#[UsesClass(DispatchDecision::class)]
319#[UsesClass(DispatchRequest::class)]
320#[UsesClass(Extensions::class)]
321#[UsesClass(ProviderExpansion::class)]
322#[UsesClass(Evaluators::class)]
323#[UsesClass(StateRegistry::class)]
324#[UsesClass(DeclarationParameter::class)]
325#[UsesClass(Signature::class)]
326#[UsesClass(DeriverConfig::class)]
327#[UsesClass(EntryPoint::class)]
328#[UsesClass(Sources::class)]
329#[UsesClass(Path::class)]
330#[UsesClass(Snapshot::class)]
331#[UsesClass(Source::class)]
332#[UsesClass(TargetProfile::class)]
333#[UsesClass(Budget::class)]
334#[UsesClass(QueryScope::class)]
335#[UsesClass(ReturnQuery::class)]
336#[UsesClass(ResultRef::class)]
337#[UsesClass(SourceRef::class)]
338#[UsesClass(Alternative::class)]
339#[UsesClass(Assessment::class)]
340#[UsesClass(Derivation::class)]
341#[UsesClass(DerivationResult::class)]
342#[UsesClass(Frontier::class)]
343#[UsesClass(QueryEncoding::class)]
344#[UsesClass(ValueGraph::class)]
345#[UsesClass(Statistics::class)]
346#[UsesClass(StorageSnapshot::class)]
347#[UsesClass(Parser::class)]
348#[UsesClass(File::class)]
349#[UsesClass(AggregateLowering::class)]
350#[UsesClass(AssignmentLowering::class)]
351#[UsesClass(CallLowering::class)]
352#[UsesClass(Lowerer::class)]
353#[UsesClass(ConditionalLowering::class)]
354#[UsesClass(DestructuringLowering::class)]
355#[UsesClass(ExceptionLowering::class)]
356#[UsesClass(EffectInspection::class)]
357#[UsesClass(ExpressionLowering::class)]
358#[UsesClass(InstructionEmitter::class)]
359#[UsesClass(LoweringScope::class)]
360#[UsesClass(StatementLowering::class)]
361#[UsesClass(ConstantSignatures::class)]
362#[UsesClass(CallableSource::class)]
363#[UsesClass(Indexer::class)]
364#[UsesClass(Index::class)]
365#[UsesClass(Composition::class)]
366#[UsesClass(LineMap::class)]
367#[UsesClass(MagicContext::class)]
368#[UsesClass(AssignmentPatterns::class)]
369#[UsesClass(ClassScope::class)]
370#[UsesClass(TargetSyntax::class)]
371#[UsesClass(Arithmetic::class)]
372#[UsesClass(Arrays::class)]
373#[UsesClass(Comparison::class)]
374#[UsesClass(Identity::class)]
375#[UsesClass(Increment::class)]
376#[UsesClass(NumericString::class)]
377#[UsesClass(Operations::class)]
378#[UsesClass(Term::class)]
379#[Small]
380final class CallExecutorTest extends TestCase
381{
382 public function testInstructionPreservesTheSemanticContract(): void
383 {
384 $source = '<?php function tagged(string $tag,int $id) { return $tag . ":" . $id; } function target() { return [tagged("user",1),tagged("item",2)]; }';
385 $symbol = 'target';
386 $input = new Sources([new Source('fixture.php', $source)]);
387 $configuration = new DeriverConfig();
388 $contributions = new Contributions();
389 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
390 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
391 $contributions1 = $providers->contributions;
392 $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);
393 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
394 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
395 $snapshotId = $identity->id;
396 $input1 = $providers->input;
397 $parser = new Parser(new ParserConfig($target->version));
398 $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));
399 $program = new Index($snapshotId, $trees);
400 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
401 self::assertSame(['user:1', 'item:2'], $result->normalOutcomes[0]->values['return']->native());
402 self::assertSame([], $result->frontiers);
403 self::assertSame([], $result->exceptionalOutcomes);
404 }
405 public function testInvokeBindsCapturedClosureValues(): void
406 {
407 $source = '<?php function target(){$x=2;$f=fn($n)=>$x+$n;$x=9;return $f(3);}';
408 $symbol = 'target';
409 $input = new Sources([new Source('fixture.php', $source)]);
410 $configuration = new DeriverConfig();
411 $contributions = new Contributions();
412 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
413 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
414 $contributions1 = $providers->contributions;
415 $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);
416 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
417 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
418 $snapshotId = $identity->id;
419 $input1 = $providers->input;
420 $parser = new Parser(new ParserConfig($target->version));
421 $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));
422 $program = new Index($snapshotId, $trees);
423 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
424 self::assertSame([], $result->frontiers);
425 self::assertSame([], $result->exceptionalOutcomes);
426 self::assertCount(1, $result->normalOutcomes);
427 self::assertSame(5, $result->normalOutcomes[0]->values['return']->native());
428 }
429 public function testSymbolPropagatesACompletedReferenceWriteBackToTheCaller(): void
430 {
431 $source = '<?php function f(&$x){$x=2;return 3;}function target(){$x=1;$r=f($x);return [$x,$r];}';
432 $symbol = 'target';
433 $input = new Sources([new Source('fixture.php', $source)]);
434 $configuration = new DeriverConfig();
435 $contributions = new Contributions();
436 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
437 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
438 $contributions1 = $providers->contributions;
439 $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);
440 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
441 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
442 $snapshotId = $identity->id;
443 $input1 = $providers->input;
444 $parser = new Parser(new ParserConfig($target->version));
445 $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));
446 $program = new Index($snapshotId, $trees);
447 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
448 self::assertSame([], $result->frontiers);
449 self::assertSame([], $result->exceptionalOutcomes);
450 self::assertCount(1, $result->normalOutcomes);
451 self::assertSame([2, 3], $result->normalOutcomes[0]->values['return']->native());
452 }
453 public function testObjectInvokesAnObjectCallable(): void
454 {
455 $source = '<?php class B{function __invoke($x){return $x+1;}}function target(){$f=new B;return $f(3);}';
456 $symbol = 'target';
457 $input = new Sources([new Source('fixture.php', $source)]);
458 $configuration = new DeriverConfig();
459 $contributions = new Contributions();
460 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
461 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
462 $contributions1 = $providers->contributions;
463 $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);
464 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
465 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
466 $snapshotId = $identity->id;
467 $input1 = $providers->input;
468 $parser = new Parser(new ParserConfig($target->version));
469 $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));
470 $program = new Index($snapshotId, $trees);
471 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
472 self::assertSame([], $result->frontiers);
473 self::assertSame([], $result->exceptionalOutcomes);
474 self::assertCount(1, $result->normalOutcomes);
475 self::assertSame(4, $result->normalOutcomes[0]->values['return']->native());
476 }
477 /**
478 * @param string $source Independently verified callable fixture
479 * @param string $expectedJson Complete normal return observations
480 * @throws JsonException If fixture observations cannot be decoded
481 */
482 #[DataProvider('providerCallableForms')]
483 public function testInvokePreservesCallableBindingIdentityAndCallingContext(string $source, string $expectedJson): void
484 {
485 $symbol = 'target';
486 $input = new Sources([new Source('fixture.php', $source)]);
487 $configuration = new DeriverConfig();
488 $contributions = new Contributions();
489 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
490 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
491 $contributions1 = $providers->contributions;
492 $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);
493 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
494 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
495 $snapshotId = $identity->id;
496 $input1 = $providers->input;
497 $parser = new Parser(new ParserConfig($target->version));
498 $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));
499 $program = new Index($snapshotId, $trees);
500 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
501 self::assertSame([], $result->frontiers);
502 self::assertSame([], $result->exceptionalOutcomes);
503 $expected = json_decode($expectedJson, true, 512, JSON_THROW_ON_ERROR);
504 self::assertIsArray($expected);
505 self::assertCount(1, $result->normalOutcomes);
506 self::assertSame($expected[0], $result->normalOutcomes[0]->values['return']->native());
507 }
508 /**
509 * @return array<string,array{string,string}>
510 */
511 public static function providerCallableForms(): array
512 {
513 return array_map(static fn (array $fixture): array => [$fixture[0], $fixture[1]], ['magic-namespaced-function' => ['<?php namespace N{function names($a=__FUNCTION__,$b=__METHOD__){return [$a,$b,__FUNCTION__,__METHOD__,__NAMESPACE__,__CLASS__];}}namespace{function target(){return N\names();}}', '[["N\\\\names","N\\\\names","N\\\\names","N\\\\names","N",""]]', '', false], 'magic-method' => ['<?php namespace N{class Box{function names($a=__FUNCTION__,$b=__METHOD__){return [$a,$b,__FUNCTION__,__METHOD__,__NAMESPACE__,__CLASS__];}}}namespace{function target(){return (new N\Box)->names();}}', '[["names","N\\\\Box::names","names","N\\\\Box::names","N","N\\\\Box"]]', '', false], 'magic-closure-method' => ['<?php namespace N{class Box{function names(){return (fn()=>[__FUNCTION__,__METHOD__,__NAMESPACE__,__CLASS__])();}}}namespace{function target(){return (new N\Box)->names();}}', '[["N\\\\{closure}","N\\\\{closure}","N","N\\\\Box"]]', '', false], 'magic-closure-function' => ['<?php namespace N{function names(){return (function(){return [__FUNCTION__,__METHOD__,__NAMESPACE__,__CLASS__];})();}}namespace{function target(){return N\names();}}', '[["N\\\\{closure}","N\\\\{closure}","N",""]]', '', false], 'magic-global-closure' => ['<?php function target(){return (fn()=>[__FUNCTION__,__METHOD__,__NAMESPACE__,__CLASS__])();}', '[["{closure}","{closure}","",""]]', '', false], 'array-call-extra-entry' => ['<?php class Box{static function run(){return 1;}}function target(){$f=["Box","run",2];try{return $f();}catch(Error $e){return 3;}}', '[3]', '', false], 'array-call-extra-entry-capture' => ['<?php class Box{static function run(){return 1;}}function target(){$f=["Box","run",2];try{$g=$f(...);return $g();}catch(Error $e){return 3;}}', '[3]', '', false], 'array-call-wrong-keys' => ['<?php class Box{static function run(){return 1;}}function target(){$f=[1=>"Box",2=>"run"];try{return $f();}catch(Error $e){return 3;}}', '[3]', '', false], 'array-call-key-order' => ['<?php class Box{static function run(){return 1;}}function target(){$f=[1=>"run",0=>"Box"];return $f();}', '[1]', '', false], 'array-call-reference-elements' => ['<?php class Box{static function run(&$x){$x=2;return 1;}}function target(){$c="Box";$m="run";$f=[&$c,&$m];$x=0;$r=$f($x);return [$r,$x];}', '[[1,2]]', '', false], 'array-capture-reference-elements' => ['<?php class Box{static function run(&$x){$x=2;return 1;}}function target(){$c="Box";$m="run";$f=[&$c,&$m];$g=$f(...);$m="missing";$x=0;$r=$g($x);return [$r,$x];}', '[[1,2]]', '', false], 'private-escaped' => ['<?php class Box{private function hidden(&$x){$x=3;return 2;}function callback(){return $this->hidden(...);}}function target(){$f=(new Box)->callback();$a=1;return [$f($a),$a];}', '[[2,3]]', '', false], 'private-creation-error' => ['<?php class Box{private function hidden(){return 1;}}function target(){$b=new Box;try{$f=$b->hidden(...);}catch(Error $e){return 2;}return 3;}', '[2]', '', false], 'private-array-creation' => ['<?php class Box{private function hidden(){return 1;}function callback(){return [$this,"hidden"](...);}}function target(){$f=(new Box)->callback();return $f();}', '[1]', '', false], 'private-string-creation' => ['<?php class Box{private static function hidden(){return 1;}static function callback(){return "Box::hidden"(...);}}function target(){$f=Box::callback();return $f();}', '[1]', '', false], 'private-static-creation' => ['<?php class Box{private static function hidden(){return 1;}static function callback(){return self::hidden(...);}}function target(){$f=Box::callback();return $f();}', '[1]', '', false], 'inherited-self' => ['<?php class Base{static function name(){return static::class;}static function callback(){return self::name(...);}}class Child extends Base{}function target(){$f=Child::callback();return $f();}', '["Child"]', '', false], 'inherited-parent' => ['<?php class Base{static function name(){return static::class;}}class Child extends Base{static function callback(){return parent::name(...);}}function target(){$f=Child::callback();return $f();}', '["Child"]', '', false], 'inherited-instance-parent' => ['<?php class Base{function name(){return [static::class,$this->x];}}class Child extends Base{public $x=2;function callback(){return parent::name(...);}}function target(){$f=(new Child)->callback();return $f();}', '[["Child",2]]', '', false], 'explicit-base' => ['<?php class Base{static function name(){return static::class;}static function callback(){return Base::name(...);}}class Child extends Base{}function target(){$f=Child::callback();return $f();}', '["Base"]', '', false], 'ordinary-forward-static' => ['<?php class Base{static function name(){return static::class;}static function call(){return self::name();}}class Child extends Base{}function target(){return Child::call();}', '["Child"]', '', false], 'ordinary-call-does-not-rebind' => ['<?php class Other{static function name(){return 1;}}class Base{static function call(){Other::name();return static::class;}}class Child extends Base{}function target(){return Child::call();}', '["Child"]', '', false], 'closure-called-class' => ['<?php class Base{static function callback(){return static fn()=>static::class;}}class Child extends Base{}function target(){$f=Child::callback();return $f();}', '["Child"]', '', false], 'closure-static-this' => ['<?php class Box{function callback(){return static function(){return isset($this);};}}function target(){$f=(new Box)->callback();return $f();}', '[false]', '', false], 'magic-escaped' => ['<?php class Box{public $x=1;function __call($name,$args){$this->x++;return [$name,$args,$this->x];}}function target(){$b=new Box;$f=$b->missing(...);return [$f(2),$b->x];}', '[[["missing",[2],2],2]]', '', false], 'magic-static-escaped' => ['<?php class Box{static function __callStatic($name,$args){return [$name,$args,static::class];}static function callback(){return self::missing(...);}}class Child extends Box{}function target(){$f=Child::callback();return $f(x:2);}', '[["missing",{"x":2},"Child"]]', '', false], 'invoke-object' => ['<?php class Box{function __invoke(&$x){$x=2;return 3;}}function target(){$b=new Box;$a=1;$f=$b(...);return [$f($a),$a];}', '[[3,2]]', '', false], 'closure-first-class-identity' => ['<?php function target(){$f=fn()=>1;$g=$f(...);return $f===$g;}', '[true]', '', false], 'first-class-identity' => ['<?php function f(){}function target(){$a=f(...);$b=f(...);return [$a===$a,$a===$b];}', '[[true,false]]', '', false], 'ordinary-array-private-error' => ['<?php class Box{private function hidden(){return 1;}function callback(){return [$this,"hidden"];}}function target(){$f=(new Box)->callback();try{return $f();}catch(Error $e){return 2;}}', '[2]', '', false], 'ordinary-string-inherited' => ['<?php class Base{static function name(){return static::class;}}class Child extends Base{}function target(){$f="Child::name";return $f();}', '["Child"]', '', false], 'parent-target-frozen' => ['<?php class Base{function foo(){return 1;}}class Child extends Base{function foo(){return 2;}function callback(){return parent::foo(...);}}function target(){$f=(new Child)->callback();return $f();}', '[1]', '', false], 'private-target-frozen' => ['<?php class Base{private function foo(){return 1;}function callback(){return $this->foo(...);}}class Child extends Base{function foo(){return 2;}}function target(){$f=(new Child)->callback();return $f();}', '[1]', '', false], 'caller-this-restored' => ['<?php class Base{public $x=1;function foo(){return $this->x;}function callback(){return self::foo(...);}}class Other{public $x=2;function run($f){return [$f(),$this->x];}}function target(){$f=(new Base)->callback();return (new Other)->run($f);}', '[[1,2]]', '', false], 'callable-original-this-live' => ['<?php class Box{public $x=1;function foo(){return $this->x;}}function target(){$b=new Box;$f=$b->foo(...);$b->x=2;return $f();}', '[2]', '', false], 'closure-fresh-same-site' => ['<?php function make(){return fn()=>1;}function target(){$a=make();$b=make();return [$a===$a,$a===$b];}', '[[true,false]]', '', false], 'callable-fresh-same-site' => ['<?php function f(){return 1;}function make(){return f(...);}function target(){$a=make();$b=make();return [$a===$a,$a===$b];}', '[[true,false]]', '', false], 'callable-closure-class' => ['<?php function f(){return 1;}function target(){$a=f(...);return [get_class($a),$a instanceof Closure,is_object($a),is_callable($a)];}', '[["Closure",true,true,true]]', '', false], 'namespace-fallback' => ['<?php namespace N{function build(){$f=strlen(...);return [$f("abc"),strlen("abcd")];}}namespace{function target(){return \N\build();}}', '[[3,4]]', '', false], 'captured-strict-invocation' => ['<?php declare(strict_types=1);function f(int $x){return $x;}function target(){$g=f(...);try{return $g("2");}catch(\TypeError $e){return 3;}}', '[3]', '', false], 'captured-weak-invocation' => ['<?php function f(int $x){return $x;}function target(){$g=f(...);return $g("2");}', '[2]', '', false], 'forward-after-nested-call' => ['<?php class Other{static function name(){return 1;}}class Base{static function name(){return static::class;}static function callback(){Other::name();return self::name(...);}}class Child extends Base{}function target(){$f=Child::callback();return $f();}', '["Child"]', '', false], 'closure-called-class-interleaved' => ['<?php class Base{static function callback(){return static fn()=>static::class;}}class Child extends Base{}class Other extends Base{}function target(){$a=Child::callback();$b=Other::callback();return [$a(),$b()];}', '[["Child","Other"]]', '', false]]);
514 }
515 public function testInvokeUnwrapsAResolvedCallableAndPropagatesStrictArgumentErrors(): void
516 {
517 $source = '<?php declare(strict_types=1);function helper(int $x){return $x;}function target(){}';
518 $budget = new Budget();
519 $configuration = new DeriverConfig();
520 $contributions = new Contributions();
521 $input = new Sources([new Source('fixture.php', $source)]);
522 $target1 = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
523 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
524 $contributions1 = $providers->contributions;
525 $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);
526 $settings = new EvaluatorConfig($target1, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
527 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
528 $snapshotId = $identity->id;
529 $input1 = $providers->input;
530 $parser = new Parser(new ParserConfig($target1->version));
531 $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));
532 $program1 = new Index($snapshotId, $trees);
533 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target1, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
534 $snapshotId1 = 'test';
535 $input2 = new Sources([new Source('fixture.php', $source)]);
536 $profile = $execution->configuration->target;
537 $parser1 = new Parser(new ParserConfig($profile->version));
538 $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));
539 $program = new Index($snapshotId1, $trees1);
540 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
541 $caller = $context->program->callable('target');
542 self::assertNotNull($caller);
543 $site = new BoundInstruction('call', 'invoke', $caller->source, 'result');
544 $target = new Term(TermKind::Callable, operands: [Term::constant('helper')]);
545 $paths = (new CallExecutor(new Evaluator($context)))->invoke($target, [new PassedArgument(Term::constant('2'))], new State(), $site, $caller);
546 self::assertCount(1, $paths);
547 self::assertSame(CompletionKind::Throw, $paths[0]->completion->kind);
548 self::assertSame('TypeError', $paths[0]->completion->value?->literal);
549 }
550 public function testSymbolRestoresCallerLocalsAndKeepsEveryExitEffectAndExplanation(): void
551 {
552 $source = '<?php function helper(&$x,$flag){$x=2;if($flag){return 3;}$x=4;throw new Exception;}function target(){}';
553 $budget = new Budget();
554 $configuration = new DeriverConfig();
555 $contributions = new Contributions();
556 $input = new Sources([new Source('fixture.php', $source)]);
557 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
558 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
559 $contributions1 = $providers->contributions;
560 $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);
561 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
562 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
563 $snapshotId = $identity->id;
564 $input1 = $providers->input;
565 $parser = new Parser(new ParserConfig($target->version));
566 $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));
567 $program1 = new Index($snapshotId, $trees);
568 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
569 $snapshotId1 = 'test';
570 $input2 = new Sources([new Source('fixture.php', $source)]);
571 $profile = $execution->configuration->target;
572 $parser1 = new Parser(new ParserConfig($profile->version));
573 $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));
574 $program = new Index($snapshotId1, $trees1);
575 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
576 $caller = $context->program->callable('target');
577 self::assertNotNull($caller);
578 $state = new State();
579 $state->memory->write($state->local('callerValue'), Term::constant(1));
580 $state->registers['old'] = Term::constant('preserved');
581 $state->evidence = ['caller:first', 'caller:second'];
582 $state->controls = ['control:first', 'control:second'];
583 $state->guard = ['outer' => true];
584 $state->constraints = ['input' => ['min' => 0, 'max' => 10, 'equal' => null, 'excluded' => []]];
585 $state->observed = true;
586 $context->summaries->history = ['outer-call'];
587 $site = new BoundInstruction('call', 'invoke', $caller->source, 'result');
588 $paths = (new CallExecutor(new Evaluator($context)))->symbol('helper', [new PassedArgument(Term::constant(1), location: $state->local('callerValue')), new PassedArgument(Term::parameter('flag', 'bool'))], $state, $site);
589 self::assertCount(2, $paths);
590 self::assertEqualsCanonicalizing([CompletionKind::Normal, CompletionKind::Throw], array_column(array_column($paths, 'completion'), 'kind'));
591 self::assertEqualsCanonicalizing([2, 4], array_map(static fn (State $path): int|float|string|bool|null => $path->memory->read($path->local('callerValue'))->literal, $paths));
592 self::assertSame(['callerValue'], array_keys($paths[0]->locals));
593 self::assertSame('preserved', $paths[0]->registers['old']->native());
594 self::assertTrue($paths[0]->observed);
595 self::assertTrue($paths[1]->observed);
596 self::assertTrue($paths[0]->guard['outer']);
597 self::assertNotSame($paths[0]->guard, $paths[1]->guard);
598 self::assertSame($state->constraints['input'], $paths[0]->constraints['input']);
599 self::assertContains('caller:first', $paths[0]->evidence);
600 self::assertContains('caller:second', $paths[0]->evidence);
601 self::assertGreaterThan(2, count($paths[0]->evidence));
602 self::assertSame(array_values(array_unique($paths[0]->evidence)), $paths[0]->evidence);
603 self::assertContains('control:first', $paths[0]->controls);
604 self::assertContains('control:second', $paths[0]->controls);
605 self::assertSame(array_values(array_unique($paths[0]->controls)), $paths[0]->controls);
606 self::assertSame(['outer-call'], $context->summaries->history);
607 self::assertSame(1, $state->memory->read($state->local('callerValue'))->native());
608 self::assertSame(CompletionKind::Normal, $state->completion->kind);
609 self::assertNotSame($paths[0]->memory, $paths[1]->memory);
610 }
611 public function testSymbolUsesWeakCoercionByDefaultAndOverridesOnlyCalleeLateStaticBinding(): void
612 {
613 $source = '<?php class Base{static function helper(int $x){return [$x,static::class,isset($this)];}}function target(){}';
614 $budget = new Budget();
615 $configuration = new DeriverConfig();
616 $contributions = new Contributions();
617 $input = new Sources([new Source('fixture.php', $source)]);
618 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
619 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
620 $contributions1 = $providers->contributions;
621 $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);
622 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
623 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
624 $snapshotId = $identity->id;
625 $input1 = $providers->input;
626 $parser = new Parser(new ParserConfig($target->version));
627 $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));
628 $program1 = new Index($snapshotId, $trees);
629 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
630 $snapshotId1 = 'test';
631 $input2 = new Sources([new Source('fixture.php', $source)]);
632 $profile = $execution->configuration->target;
633 $parser1 = new Parser(new ParserConfig($profile->version));
634 $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));
635 $program = new Index($snapshotId1, $trees1);
636 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
637 $caller = $context->program->callable('target');
638 self::assertNotNull($caller);
639 $state = new State();
640 $state->lateStaticClass = 'Caller';
641 $receiver = new Term(TermKind::Object, 'box', attributes: ['class' => 'Base']);
642 $paths = (new CallExecutor(new Evaluator($context)))->symbol('Base::helper', [new PassedArgument(Term::constant('2'))], $state, new BoundInstruction('call', 'invoke', $caller->source, 'result'), $receiver, calledClass: 'Child');
643 self::assertCount(1, $paths);
644 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
645 self::assertSame([2, 'Child', false], $paths[0]->registers['result']->native());
646 self::assertSame('Caller', $paths[0]->lateStaticClass);
647 self::assertSame('Caller', $state->lateStaticClass);
648 }
649 public function testObjectDispatchesReferencedArrayCallableElements(): void
650 {
651 $source = '<?php class Box{static function run($x){return $x+1;}}function target(){}';
652 $budget = new Budget();
653 $configuration = new DeriverConfig();
654 $contributions = new Contributions();
655 $input = new Sources([new Source('fixture.php', $source)]);
656 $target1 = 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($target1, $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($target1->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($target1, $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 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
675 $caller = $context->program->callable('target');
676 self::assertNotNull($caller);
677 $state = new State();
678 $class = $state->memory->allocate(Term::constant('Box'));
679 $method = $state->memory->allocate(Term::constant('run'));
680 $target = Term::array([new Term(TermKind::Cell, $class->root), new Term(TermKind::Cell, $method->root)]);
681 $paths = (new CallExecutor(new Evaluator($context)))->object($target, [new PassedArgument(Term::constant(2))], $state, new BoundInstruction('call', 'invoke', $caller->source, 'result'), $caller);
682 self::assertNotNull($paths);
683 self::assertCount(1, $paths);
684 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
685 self::assertSame(3, $paths[0]->registers['result']->native());
686 }
687 /**
688 * @param Term $target Value without a resolved object callable
689 */
690 #[DataProvider('providerNonObjectTargets')]
691 public function testObjectLeavesOtherCallableFormsToTheirOwnResolution(Term $target): void
692 {
693 $source = '<?php class Box{}function target(){}';
694 $budget = new Budget();
695 $configuration = new DeriverConfig();
696 $contributions = new Contributions();
697 $input = new Sources([new Source('fixture.php', $source)]);
698 $target1 = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
699 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
700 $contributions1 = $providers->contributions;
701 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
702 $settings = new EvaluatorConfig($target1, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
703 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
704 $snapshotId = $identity->id;
705 $input1 = $providers->input;
706 $parser = new Parser(new ParserConfig($target1->version));
707 $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));
708 $program1 = new Index($snapshotId, $trees);
709 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target1, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
710 $snapshotId1 = 'test';
711 $input2 = new Sources([new Source('fixture.php', $source)]);
712 $profile = $execution->configuration->target;
713 $parser1 = new Parser(new ParserConfig($profile->version));
714 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
715 $program = new Index($snapshotId1, $trees1);
716 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
717 $caller = $context->program->callable('target');
718 self::assertNotNull($caller);
719 self::assertNull((new CallExecutor(new Evaluator($context)))->object($target, [], new State(), new BoundInstruction('call', 'invoke', $caller->source, 'result'), $caller));
720 self::assertSame([], $context->frontiers);
721 }
722 /**
723 * @return array<string,array{Term}>
724 */
725 public static function providerNonObjectTargets(): array
726 {
727 return ['plain function' => [Term::constant('target')], 'number' => [Term::constant(1)], 'symbolic spelling' => [Term::parameter('Box::run')], 'object without invoke' => [new Term(TermKind::Object, 'box', attributes: ['class' => 'Box'])], 'opaque' => [Term::opaque('unknown')]];
728 }
729 public function testInvokeRetainsAnOpenDispatchBoundaryForAnUnresolvedTarget(): void
730 {
731 $source = '<?php function target(){return 1;}';
732 $budget = new Budget();
733 $configuration = new DeriverConfig();
734 $contributions = new Contributions();
735 $input = new Sources([new Source('fixture.php', $source)]);
736 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
737 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
738 $contributions1 = $providers->contributions;
739 $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);
740 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
741 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
742 $snapshotId = $identity->id;
743 $input1 = $providers->input;
744 $parser = new Parser(new ParserConfig($target->version));
745 $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));
746 $program1 = new Index($snapshotId, $trees);
747 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
748 $snapshotId1 = 'test';
749 $input2 = new Sources([new Source('fixture.php', $source)]);
750 $profile = $execution->configuration->target;
751 $parser1 = new Parser(new ParserConfig($profile->version));
752 $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));
753 $program = new Index($snapshotId1, $trees1);
754 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
755 $caller = $context->program->callable('target');
756 self::assertNotNull($caller);
757 $paths = (new CallExecutor(new Evaluator($context)))->invoke(Term::parameter('callback'), [], new State(), new BoundInstruction('call', 'invoke', $caller->source, 'result'), $caller);
758 self::assertCount(2, $paths);
759 self::assertSame(['OPEN_DISPATCH'], array_column($context->frontiers, 'code'));
760 self::assertSame('OPEN_DISPATCH', $paths[0]->registers['result']->literal);
761 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
762 }
763}
764