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