packages/deriver/tests/Unit/Deriver/Evaluator/Execution/Offset/TransferTest.php
1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution\Offset;
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\Call\ArgumentBindings;
17use Deriver\Deriver\Evaluator\Call\BoundArgument;
18use Deriver\Deriver\Evaluator\Call\BoundInstruction;
19use Deriver\Deriver\Evaluator\Call\CallDecision;
20use Deriver\Deriver\Evaluator\Call\CallDescription;
21use Deriver\Deriver\Evaluator\Call\CallDescriptor;
22use Deriver\Deriver\Evaluator\Call\CallEvaluator;
23use Deriver\Deriver\Evaluator\Call\Evaluators;
24use Deriver\Deriver\Evaluator\Call\Extensions;
25use Deriver\Deriver\Evaluator\Call\Plan\Action;
26use Deriver\Deriver\Evaluator\Call\Plan\Expression;
27use Deriver\Deriver\Evaluator\Call\Plan\PlanActions;
28use Deriver\Deriver\Evaluator\Call\Plan\PlanCompiler;
29use Deriver\Deriver\Evaluator\Call\Plan\PlanFootprints;
30use Deriver\Deriver\Evaluator\Call\Plan\PlanValidation;
31use Deriver\Deriver\Evaluator\Call\Plan\SemanticPlan;
32use Deriver\Deriver\Evaluator\Call\Provider\DispatchDecision;
33use Deriver\Deriver\Evaluator\Call\Provider\DispatchRequest;
34use Deriver\Deriver\Evaluator\Call\Registrations;
35use Deriver\Deriver\Evaluator\Call\StateRegistry;
36use Deriver\Deriver\Evaluator\Declaration\Parameter as DeclarationParameter;
37use Deriver\Deriver\Evaluator\Declaration\Signature;
38use Deriver\Deriver\Evaluator\EvaluatorConfig;
39use Deriver\Deriver\Evaluator\Evidence\Frontier;
40use Deriver\Deriver\Evaluator\Execution\Call\Allocation;
41use Deriver\Deriver\Evaluator\Execution\Call\ArgumentBinding;
42use Deriver\Deriver\Evaluator\Execution\Call\ArgumentOrder;
43use Deriver\Deriver\Evaluator\Execution\Call\CallableCheck;
44use Deriver\Deriver\Evaluator\Execution\Call\CallExecutor;
45use Deriver\Deriver\Evaluator\Execution\Call\CallResolution;
46use Deriver\Deriver\Evaluator\Execution\Call\Contract\Inputs;
47use Deriver\Deriver\Evaluator\Execution\Call\Contract\NativeArguments;
48use Deriver\Deriver\Evaluator\Execution\Call\Creation\Access;
49use Deriver\Deriver\Evaluator\Execution\Call\Member\Invocation;
50use Deriver\Deriver\Evaluator\Execution\Call\MethodInvocation;
51use Deriver\Deriver\Evaluator\Execution\Call\Native\Invocation as NativeInvocation;
52use Deriver\Deriver\Evaluator\Execution\Call\Native\Properties;
53use Deriver\Deriver\Evaluator\Execution\Call\Native\Signatures;
54use Deriver\Deriver\Evaluator\Execution\Call\ParameterBinding;
55use Deriver\Deriver\Evaluator\Execution\Call\PassedArgument;
56use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Arguments;
57use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Creation;
58use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Methods;
59use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Modes;
60use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Resolution;
61use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target;
62use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Transfer as PreparationTransfer;
63use Deriver\Deriver\Evaluator\Execution\Call\ProviderDispatch;
64use Deriver\Deriver\Evaluator\Execution\Call\TypeBinding;
65use Deriver\Deriver\Evaluator\Execution\Call\TypeCheck;
66use Deriver\Deriver\Evaluator\Execution\Completion\Alternative;
67use Deriver\Deriver\Evaluator\Execution\Completion\Assessment;
68use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
69use Deriver\Deriver\Evaluator\Execution\Completion\CompletionKind;
70use Deriver\Deriver\Evaluator\Execution\Completion\Exceptional;
71use Deriver\Deriver\Evaluator\Execution\Completion\StorageSnapshot;
72use Deriver\Deriver\Evaluator\Execution\Constant\ClassNames;
73use Deriver\Deriver\Evaluator\Execution\Constraints;
74use Deriver\Deriver\Evaluator\Execution\Context;
75use Deriver\Deriver\Evaluator\Execution\Control\ExceptionChain;
76use Deriver\Deriver\Evaluator\Execution\Control\ExceptionMatch;
77use Deriver\Deriver\Evaluator\Execution\Control\Handler;
78use Deriver\Deriver\Evaluator\Execution\Control\LoopConvergence;
79use Deriver\Deriver\Evaluator\Execution\Control\ObservationLimit;
80use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
81use Deriver\Deriver\Evaluator\Execution\Control\Unwinding;
82use Deriver\Deriver\Evaluator\Execution\Demand\Cell;
83use Deriver\Deriver\Evaluator\Execution\Demand\Components;
84use Deriver\Deriver\Evaluator\Execution\Demand\Discovery;
85use Deriver\Deriver\Evaluator\Execution\Demand\Key;
86use Deriver\Deriver\Evaluator\Execution\Demand\Table;
87use Deriver\Deriver\Evaluator\Execution\Dependencies;
88use Deriver\Deriver\Evaluator\Execution\Evaluator;
89use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
90use Deriver\Deriver\Evaluator\Execution\Havoc;
91use Deriver\Deriver\Evaluator\Execution\InstructionTransfer;
92use Deriver\Deriver\Evaluator\Execution\Memory\Location;
93use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
94use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
95use Deriver\Deriver\Evaluator\Execution\Memory\ReferenceConstraint;
96use Deriver\Deriver\Evaluator\Execution\Memory\Slots\SlotReference;
97use Deriver\Deriver\Evaluator\Execution\Memory\Slots\StateStorage;
98use Deriver\Deriver\Evaluator\Execution\Memory\StorageCapture;
99use Deriver\Deriver\Evaluator\Execution\ObservationCollector;
100use Deriver\Deriver\Evaluator\Execution\Offset\Address;
101use Deriver\Deriver\Evaluator\Execution\Offset\Path;
102use Deriver\Deriver\Evaluator\Execution\Offset\Reader;
103use Deriver\Deriver\Evaluator\Execution\Offset\StringAccess;
104use Deriver\Deriver\Evaluator\Execution\Offset\Strings;
105use Deriver\Deriver\Evaluator\Execution\Offset\Transfer;
106use Deriver\Deriver\Evaluator\Execution\Operation\Conversions;
107use Deriver\Deriver\Evaluator\Execution\Operation\ScalarErrors;
108use Deriver\Deriver\Evaluator\Execution\State;
109use Deriver\Deriver\Evaluator\Execution\Summary\CompletionRecord;
110use Deriver\Deriver\Evaluator\Execution\Summary\Evaluation;
111use Deriver\Deriver\Evaluator\Execution\Summary\Invocation as SummaryInvocation;
112use Deriver\Deriver\Evaluator\Execution\Summary\Isolation;
113use Deriver\Deriver\Evaluator\Execution\Trace\Derivation;
114use Deriver\Deriver\Evaluator\Execution\Transfer\CallableTransfer;
115use Deriver\Deriver\Evaluator\Execution\Transfer\CompoundAssignment;
116use Deriver\Deriver\Evaluator\Execution\Transfer\ConstantTransfer;
117use Deriver\Deriver\Evaluator\Execution\Transfer\IntrinsicTransfer;
118use Deriver\Deriver\Evaluator\Execution\Transfer\MemoryStep;
119use Deriver\Deriver\Evaluator\Execution\Transfer\ObjectAccess;
120use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyAccessCheck;
121use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyLookup;
122use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyMagic;
123use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyReference;
124use Deriver\Deriver\Evaluator\Execution\Transfer\PropertySlot;
125use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyTransfer;
126use Deriver\Deriver\Evaluator\Execution\Transfer\PureStep;
127use Deriver\Deriver\Evaluator\Execution\Transfer\ReferenceAssignment;
128use Deriver\Deriver\Evaluator\Request\Budget;
129use Deriver\Deriver\Evaluator\Request\EntryPoint;
130use Deriver\Deriver\Evaluator\Request\QueryScope;
131use Deriver\Deriver\Evaluator\Request\ReturnQuery;
132use Deriver\Deriver\Evaluator\Scalar\Arithmetic;
133use Deriver\Deriver\Evaluator\Scalar\Arrays;
134use Deriver\Deriver\Evaluator\Scalar\Comparison;
135use Deriver\Deriver\Evaluator\Scalar\Identity;
136use Deriver\Deriver\Evaluator\Scalar\Increment;
137use Deriver\Deriver\Evaluator\Scalar\IntegerConversion;
138use Deriver\Deriver\Evaluator\Scalar\Operations;
139use Deriver\Deriver\Evaluator\TargetProfile;
140use Deriver\Deriver\Evaluator\Term;
141use Deriver\Deriver\Evaluator\TermKind;
142use Deriver\Deriver\Execution\DerivationResult;
143use Deriver\Deriver\Execution\QueryExecution;
144use Deriver\Deriver\Execution\ResultAssessment;
145use Deriver\Deriver\Identity\QueryEncoding;
146use Deriver\Deriver\Identity\ValueGraph;
147use Deriver\Deriver\Lowerer\AggregateLowering;
148use Deriver\Deriver\Lowerer\AssignmentLowering;
149use Deriver\Deriver\Lowerer\CallLowering;
150use Deriver\Deriver\Lowerer\Control\ConditionalLowering;
151use Deriver\Deriver\Lowerer\Control\DestructuringLowering;
152use Deriver\Deriver\Lowerer\Control\ExceptionLowering;
153use Deriver\Deriver\Lowerer\Control\LoopLowering;
154use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
155use Deriver\Deriver\Lowerer\Declaration\CallableSource;
156use Deriver\Deriver\Lowerer\Declaration\ConstantSignatures;
157use Deriver\Deriver\Lowerer\Declaration\Index;
158use Deriver\Deriver\Lowerer\Declaration\Indexer;
159use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassDeclaration;
160use Deriver\Deriver\Lowerer\Declaration\Metadata\PropertyDeclaration;
161use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
162use Deriver\Deriver\Lowerer\EffectInspection;
163use Deriver\Deriver\Lowerer\ExpressionLowering;
164use Deriver\Deriver\Lowerer\Graph\Argument;
165use Deriver\Deriver\Lowerer\Graph\BasicBlock;
166use Deriver\Deriver\Lowerer\Graph\CallableGraph;
167use Deriver\Deriver\Lowerer\Graph\CatchTarget;
168use Deriver\Deriver\Lowerer\Graph\ExceptionRegion;
169use Deriver\Deriver\Lowerer\Graph\Instruction;
170use Deriver\Deriver\Lowerer\Graph\Parameter;
171use Deriver\Deriver\Lowerer\Graph\Terminator;
172use Deriver\Deriver\Lowerer\Hierarchy\Access as HierarchyAccess;
173use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
174use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
175use Deriver\Deriver\Lowerer\InstructionEmitter;
176use Deriver\Deriver\Lowerer\Lowerer;
177use Deriver\Deriver\Lowerer\LoweringScope;
178use Deriver\Deriver\Lowerer\StatementLowering;
179use Deriver\Deriver\MicroEvaluatorConversion;
180use Deriver\Deriver\Parser\Node\File;
181use Deriver\Deriver\Parser\Parser;
182use Deriver\Deriver\Parser\ParserConfig;
183use Deriver\Deriver\Parser\TargetSyntax;
184use Deriver\Deriver\Providers\ProviderExpansion;
185use Deriver\Deriver\Resolver\AssignmentPatterns;
186use Deriver\Deriver\Resolver\ClassScope;
187use Deriver\Deriver\Resolver\ExpressionValidation;
188use Deriver\Deriver\Resolver\MagicContext;
189use Deriver\Deriver\Resolver\Resolver;
190use Deriver\Deriver\Selection\QueryValidation;
191use Deriver\Deriver\SnapshotIdentity;
192use Deriver\Deriver\Sources\LineMap;
193use Deriver\Deriver\Sources\Path as SourcesPath;
194use Deriver\Deriver\Sources\Source;
195use Deriver\Deriver\Sources\SourceRef;
196use Deriver\Deriver\Sources\Sources;
197use Deriver\Exception\ParserException;
198use Deriver\Exception\ResolutionException;
199use JsonException;
200use PHPUnit\Framework\Attributes\CoversClass;
201use PHPUnit\Framework\Attributes\DataProvider;
202use PHPUnit\Framework\Attributes\Small;
203use PHPUnit\Framework\Attributes\UsesClass;
204use PHPUnit\Framework\TestCase;
205
206/**
207 * @covers \Deriver\Deriver\Evaluator\Execution\Offset\Transfer
208 */
209#[UsesClass(Resolver::class)]
210#[UsesClass(ExpressionValidation::class)]
211#[UsesClass(ResolutionException::class)]
212#[UsesClass(ParserConfig::class)]
213#[UsesClass(ParserException::class)]
214#[CoversClass(Transfer::class)]
215#[UsesClass(QueryExecution::class)]
216#[UsesClass(QueryValidation::class)]
217#[UsesClass(ResultAssessment::class)]
218#[UsesClass(Deriver::class)]
219#[UsesClass(Constraints::class)]
220#[UsesClass(Argument::class)]
221#[UsesClass(BasicBlock::class)]
222#[UsesClass(CallableGraph::class)]
223#[UsesClass(CallableIdentity::class)]
224#[UsesClass(CatchTarget::class)]
225#[UsesClass(ClassDeclaration::class)]
226#[UsesClass(ExceptionRegion::class)]
227#[UsesClass(Instruction::class)]
228#[UsesClass(Parameter::class)]
229#[UsesClass(PropertyDeclaration::class)]
230#[UsesClass(Terminator::class)]
231#[UsesClass(Allocation::class)]
232#[UsesClass(ArgumentBinding::class)]
233#[UsesClass(ArgumentOrder::class)]
234#[UsesClass(CallExecutor::class)]
235#[UsesClass(CallResolution::class)]
236#[UsesClass(CallableCheck::class)]
237#[UsesClass(Access::class)]
238#[UsesClass(Builtins::class)]
239#[UsesClass(Dispatch::class)]
240#[UsesClass(HierarchyAccess::class)]
241#[UsesClass(Invocation::class)]
242#[UsesClass(MethodInvocation::class)]
243#[UsesClass(Inputs::class)]
244#[UsesClass(NativeArguments::class)]
245#[UsesClass(NativeInvocation::class)]
246#[UsesClass(Properties::class)]
247#[UsesClass(Signatures::class)]
248#[UsesClass(ParameterBinding::class)]
249#[UsesClass(PassedArgument::class)]
250#[UsesClass(Arguments::class)]
251#[UsesClass(Creation::class)]
252#[UsesClass(Methods::class)]
253#[UsesClass(Modes::class)]
254#[UsesClass(Resolution::class)]
255#[UsesClass(Target::class)]
256#[UsesClass(PreparationTransfer::class)]
257#[UsesClass(ProviderDispatch::class)]
258#[UsesClass(TypeBinding::class)]
259#[UsesClass(TypeCheck::class)]
260#[UsesClass(Completion::class)]
261#[UsesClass(ClassNames::class)]
262#[UsesClass(Context::class)]
263#[UsesClass(ExceptionChain::class)]
264#[UsesClass(ExceptionMatch::class)]
265#[UsesClass(Handler::class)]
266#[UsesClass(LoopConvergence::class)]
267#[UsesClass(ObservationLimit::class)]
268#[UsesClass(StateJoin::class)]
269#[UsesClass(Unwinding::class)]
270#[UsesClass(Cell::class)]
271#[UsesClass(Components::class)]
272#[UsesClass(Discovery::class)]
273#[UsesClass(Key::class)]
274#[UsesClass(Table::class)]
275#[UsesClass(Dependencies::class)]
276#[UsesClass(Havoc::class)]
277#[UsesClass(InstructionTransfer::class)]
278#[UsesClass(Evaluator::class)]
279#[UsesClass(SlotReference::class)]
280#[UsesClass(StateStorage::class)]
281#[UsesClass(ObservationCollector::class)]
282#[UsesClass(Address::class)]
283#[UsesClass(Path::class)]
284#[UsesClass(Reader::class)]
285#[UsesClass(StringAccess::class)]
286#[UsesClass(Strings::class)]
287#[UsesClass(Conversions::class)]
288#[UsesClass(ScalarErrors::class)]
289#[UsesClass(State::class)]
290#[UsesClass(CompletionRecord::class)]
291#[UsesClass(Evaluation::class)]
292#[UsesClass(SummaryInvocation::class)]
293#[UsesClass(Isolation::class)]
294#[UsesClass(CallableTransfer::class)]
295#[UsesClass(CompoundAssignment::class)]
296#[UsesClass(ConstantTransfer::class)]
297#[UsesClass(IntrinsicTransfer::class)]
298#[UsesClass(MemoryStep::class)]
299#[UsesClass(ObjectAccess::class)]
300#[UsesClass(PropertyAccessCheck::class)]
301#[UsesClass(PropertyLookup::class)]
302#[UsesClass(PropertyMagic::class)]
303#[UsesClass(PropertyReference::class)]
304#[UsesClass(PropertySlot::class)]
305#[UsesClass(PropertyTransfer::class)]
306#[UsesClass(PureStep::class)]
307#[UsesClass(ReferenceAssignment::class)]
308#[UsesClass(Location::class)]
309#[UsesClass(Materialization::class)]
310#[UsesClass(Memory::class)]
311#[UsesClass(ReferenceConstraint::class)]
312#[UsesClass(StorageCapture::class)]
313#[UsesClass(ArgumentBindings::class)]
314#[UsesClass(BoundArgument::class)]
315#[UsesClass(FunctionEvaluator::class)]
316#[UsesClass(Library::class)]
317#[UsesClass(ScalarFunctions::class)]
318#[UsesClass(CallDescription::class)]
319#[UsesClass(CallEvaluator::class)]
320#[UsesClass(PlanActions::class)]
321#[UsesClass(PlanCompiler::class)]
322#[UsesClass(PlanFootprints::class)]
323#[UsesClass(PlanValidation::class)]
324#[UsesClass(CallDecision::class)]
325#[UsesClass(CallDescriptor::class)]
326#[UsesClass(Action::class)]
327#[UsesClass(Expression::class)]
328#[UsesClass(SemanticPlan::class)]
329#[UsesClass(DispatchDecision::class)]
330#[UsesClass(DispatchRequest::class)]
331#[UsesClass(Extensions::class)]
332#[UsesClass(ProviderExpansion::class)]
333#[UsesClass(Evaluators::class)]
334#[UsesClass(StateRegistry::class)]
335#[UsesClass(DeclarationParameter::class)]
336#[UsesClass(Signature::class)]
337#[UsesClass(DeriverConfig::class)]
338#[UsesClass(EntryPoint::class)]
339#[UsesClass(Sources::class)]
340#[UsesClass(SourcesPath::class)]
341#[UsesClass(Snapshot::class)]
342#[UsesClass(Source::class)]
343#[UsesClass(TargetProfile::class)]
344#[UsesClass(Budget::class)]
345#[UsesClass(QueryScope::class)]
346#[UsesClass(ReturnQuery::class)]
347#[UsesClass(ResultRef::class)]
348#[UsesClass(SourceRef::class)]
349#[UsesClass(Alternative::class)]
350#[UsesClass(Assessment::class)]
351#[UsesClass(Derivation::class)]
352#[UsesClass(DerivationResult::class)]
353#[UsesClass(Exceptional::class)]
354#[UsesClass(Frontier::class)]
355#[UsesClass(QueryEncoding::class)]
356#[UsesClass(ValueGraph::class)]
357#[UsesClass(Statistics::class)]
358#[UsesClass(StorageSnapshot::class)]
359#[UsesClass(Parser::class)]
360#[UsesClass(File::class)]
361#[UsesClass(AggregateLowering::class)]
362#[UsesClass(AssignmentLowering::class)]
363#[UsesClass(CallLowering::class)]
364#[UsesClass(Lowerer::class)]
365#[UsesClass(ConditionalLowering::class)]
366#[UsesClass(DestructuringLowering::class)]
367#[UsesClass(ExceptionLowering::class)]
368#[UsesClass(LoopLowering::class)]
369#[UsesClass(EffectInspection::class)]
370#[UsesClass(ExpressionLowering::class)]
371#[UsesClass(InstructionEmitter::class)]
372#[UsesClass(LoweringScope::class)]
373#[UsesClass(StatementLowering::class)]
374#[UsesClass(ConstantSignatures::class)]
375#[UsesClass(CallableSource::class)]
376#[UsesClass(Indexer::class)]
377#[UsesClass(Index::class)]
378#[UsesClass(Composition::class)]
379#[UsesClass(LineMap::class)]
380#[UsesClass(MagicContext::class)]
381#[UsesClass(AssignmentPatterns::class)]
382#[UsesClass(ClassScope::class)]
383#[UsesClass(TargetSyntax::class)]
384#[UsesClass(Arithmetic::class)]
385#[UsesClass(Arrays::class)]
386#[UsesClass(Comparison::class)]
387#[UsesClass(Identity::class)]
388#[UsesClass(Increment::class)]
389#[UsesClass(IntegerConversion::class)]
390#[UsesClass(Operations::class)]
391#[UsesClass(Term::class)]
392#[Small]
393final class TransferTest extends TestCase
394{
395 public function testApplyCreatesAReferenceCellForAnAbsentArrayElement(): void
396 {
397 $source = '<?php function target(){return 1;}';
398 $budget = new Budget();
399 $configuration = new DeriverConfig();
400 $contributions = new Contributions();
401 $input = new Sources([new Source('fixture.php', $source)]);
402 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
403 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
404 $contributions1 = $providers->contributions;
405 $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);
406 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
407 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
408 $snapshotId = $identity->id;
409 $input1 = $providers->input;
410 $parser = new Parser(new ParserConfig($target->version));
411 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
412 $program1 = new Index($snapshotId, $trees);
413 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
414 $snapshotId1 = 'test';
415 $input2 = new Sources([new Source('fixture.php', $source)]);
416 $profile = $execution->configuration->target;
417 $parser1 = new Parser(new ParserConfig($profile->version));
418 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
419 $program = new Index($snapshotId1, $trees1);
420 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
421 $body = $context->program->callable('target');
422 self::assertNotNull($body);
423 $instruction = new BoundInstruction('offset', 'write', $body->source, 'result', ['slot', 'rhs']);
424 $state = new State();
425 $base = $state->memory->allocate(Term::constant(null));
426 $state->addresses['base'] = $base;
427 $state->offsets['slot'] = new Address('base', Term::constant('x'));
428 $path = new Path($context);
429 $read = new BoundInstruction('reference', 'reference', $body->source, 'result', ['slot']);
430 $paths = (new Transfer(new Evaluator($context)))->apply($body, $read, $state);
431 self::assertCount(1, $paths);
432 self::assertSame(TermKind::Cell, $paths[0]->registers['result']->kind);
433 self::assertSame(['x' => null], $paths[0]->memory->materialize($paths[0]->memory->read($base))->native());
434 }
435 public function testStringRejectsReferencesWithoutChangingTheContainingString(): void
436 {
437 $source = '<?php function target(){return 1;}';
438 $budget = new Budget();
439 $configuration = new DeriverConfig();
440 $contributions = new Contributions();
441 $input = new Sources([new Source('fixture.php', $source)]);
442 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
443 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
444 $contributions1 = $providers->contributions;
445 $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);
446 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
447 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
448 $snapshotId = $identity->id;
449 $input1 = $providers->input;
450 $parser = new Parser(new ParserConfig($target->version));
451 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
452 $program1 = new Index($snapshotId, $trees);
453 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
454 $snapshotId1 = 'test';
455 $input2 = new Sources([new Source('fixture.php', $source)]);
456 $profile = $execution->configuration->target;
457 $parser1 = new Parser(new ParserConfig($profile->version));
458 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
459 $program = new Index($snapshotId1, $trees1);
460 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
461 $body = $context->program->callable('target');
462 self::assertNotNull($body);
463 $instruction = new BoundInstruction('offset', 'write', $body->source, 'result', ['slot', 'rhs']);
464 $state = new State();
465 $location = $state->memory->allocate(Term::constant('abc'));
466 $access = new StringAccess($location, Term::constant(1));
467 $reference = new BoundInstruction('reference', 'reference', $body->source, 'result', ['slot']);
468 $paths = (new Transfer(new Evaluator($context)))->string($reference, $state, $access);
469 self::assertSame('Error', $paths[0]->completion->value?->literal);
470 self::assertSame('abc', $paths[0]->memory->read($location)->native());
471 }
472 public function testResultMarksConcreteErrorsAsExceptional(): void
473 {
474 $source = '<?php function target(){return 1;}';
475 $budget = new Budget();
476 $configuration = new DeriverConfig();
477 $contributions = new Contributions();
478 $input = new Sources([new Source('fixture.php', $source)]);
479 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
480 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
481 $contributions1 = $providers->contributions;
482 $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);
483 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
484 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
485 $snapshotId = $identity->id;
486 $input1 = $providers->input;
487 $parser = new Parser(new ParserConfig($target->version));
488 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
489 $program1 = new Index($snapshotId, $trees);
490 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
491 $snapshotId1 = 'test';
492 $input2 = new Sources([new Source('fixture.php', $source)]);
493 $profile = $execution->configuration->target;
494 $parser1 = new Parser(new ParserConfig($profile->version));
495 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
496 $program = new Index($snapshotId1, $trees1);
497 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
498 $body = $context->program->callable('target');
499 self::assertNotNull($body);
500 $instruction = new BoundInstruction('offset', 'write', $body->source, 'result', ['slot', 'rhs']);
501 $paths = (new Transfer(new Evaluator($context)))->result(new State(), $instruction, new Term(TermKind::Throwable, 'TypeError'));
502 self::assertSame(CompletionKind::Throw, $paths[0]->completion->kind);
503 self::assertSame('TypeError', $paths[0]->completion->value?->literal);
504 }
505 public function testBoundaryPreservesBothExitKindsAndInvalidatesAffectedState(): void
506 {
507 $source = '<?php function target(){return 1;}';
508 $budget = new Budget();
509 $configuration = new DeriverConfig();
510 $contributions = new Contributions();
511 $input = new Sources([new Source('fixture.php', $source)]);
512 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
513 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
514 $contributions1 = $providers->contributions;
515 $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);
516 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
517 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
518 $snapshotId = $identity->id;
519 $input1 = $providers->input;
520 $parser = new Parser(new ParserConfig($target->version));
521 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
522 $program1 = new Index($snapshotId, $trees);
523 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
524 $snapshotId1 = 'test';
525 $input2 = new Sources([new Source('fixture.php', $source)]);
526 $profile = $execution->configuration->target;
527 $parser1 = new Parser(new ParserConfig($profile->version));
528 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
529 $program = new Index($snapshotId1, $trees1);
530 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
531 $body = $context->program->callable('target');
532 self::assertNotNull($body);
533 $instruction = new BoundInstruction('offset', 'write', $body->source, 'result', ['slot', 'rhs']);
534 $state = new State();
535 $location = $state->local('value');
536 $state->memory->write($location, Term::constant(1));
537 $paths = (new Transfer(new Evaluator($context)))->boundary($state, $instruction, Term::opaque('OFFSET_OPERATION'));
538 self::assertCount(2, $paths);
539 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
540 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
541 self::assertSame(1, $paths[0]->memory->read($location)->native());
542 self::assertSame(['UNSUPPORTED_LANGUAGE_FEATURE'], array_column(array_values($context->frontiers), 'code'));
543 }
544 public function testCompoundRejectsStringOffsetsAfterCheckingTheOriginalKey(): void
545 {
546 $source = '<?php function target(){$a="123";$b=0;try{$a[0]+=++$b;}catch(Error $e){return [$a,$b];}return 999;}';
547 $symbol = 'target';
548 $input = new Sources([new Source('fixture.php', $source)]);
549 $configuration = new DeriverConfig();
550 $contributions = new Contributions();
551 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
552 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
553 $contributions1 = $providers->contributions;
554 $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);
555 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
556 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
557 $snapshotId = $identity->id;
558 $input1 = $providers->input;
559 $parser = new Parser(new ParserConfig($target->version));
560 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
561 $program = new Index($snapshotId, $trees);
562 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
563 self::assertSame(['123', 1], $result->normalOutcomes[0]->values['return']->native());
564 self::assertSame([], $result->frontiers);
565 }
566 /**
567 * @throws JsonException If recorded engine observations cannot be decoded
568 */
569 #[DataProvider('providerOffsetSemantics')]
570 public function testApplyPreservesRecordedOffsetValuesEffectsAndExceptions(string $source, string $normalJson, string $exception, bool $diagnostic): void
571 {
572 $expected = json_decode($normalJson, true, 512, JSON_THROW_ON_ERROR);
573 $symbol = 'target';
574 $input = new Sources([new Source('fixture.php', $source)]);
575 $configuration = new DeriverConfig();
576 $contributions = new Contributions();
577 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
578 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
579 $contributions1 = $providers->contributions;
580 $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);
581 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
582 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
583 $snapshotId = $identity->id;
584 $input1 = $providers->input;
585 $parser = new Parser(new ParserConfig($target->version));
586 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
587 $program = new Index($snapshotId, $trees);
588 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
589 self::assertSame($expected, array_map(static fn (Alternative $outcome) => $outcome->values['return']->native(), $result->normalOutcomes));
590 self::assertSame($exception === '' ? [] : [$exception], array_map(static fn (Exceptional $outcome): int|float|string|bool|null => $outcome->exception->literal, $result->exceptionalOutcomes));
591 self::assertSame($diagnostic, in_array('PHP_WARNING', array_column($result->frontiers, 'code'), true));
592 self::assertSame([], array_diff(array_column($result->frontiers, 'code'), ['PHP_WARNING']));
593 }
594 /**
595 * @return iterable<string,array{string,string,string,bool}>
596 */
597 public static function providerOffsetSemantics(): iterable
598 {
599 foreach (['intWrite' => ['<?php function target(){$a=4;try{$a[0]=2;}catch(Error $e){return $a;}return 999;}', '[4]', '', false], 'nullWrite' => ['<?php function target(){$a=null;$a[0]=2;return $a;}', '[[2]]', '', false], 'falseWrite' => ['<?php function target(){$a=false;$a[0]=2;return $a;}', '[[2]]', '', true], 'arrayKey' => ['<?php function target(){$a=[];try{$a[[]]=2;}catch(TypeError $e){return $a;}return 999;}', '[[]]', '', false], 'nullInvalidKey' => ['<?php function target(){$a=null;try{$a[[]]=2;}catch(TypeError $e){return $a;}return 999;}', '[[]]', '', false], 'nestedInvalidKey' => ['<?php function target(){$a=[];try{$a["x"][[]]=2;}catch(TypeError $e){return $a;}return 999;}', '[{"x":[]}]', '', false], 'nullInvalidRead' => ['<?php function target(){$a=null;try{return $a[[]];}catch(TypeError $e){return "error";}}', '[null]', '', true], 'arrayInvalidRead' => ['<?php function target(){$a=[];try{return $a[[]];}catch(TypeError $e){return "error";}}', '["error"]', '', false], 'nullInvalidIsset' => ['<?php function target(){$a=null;try{return isset($a[[]]);}catch(TypeError $e){return "error";}}', '[false]', '', false], 'arrayInvalidIsset' => ['<?php function target(){$a=[];try{return isset($a[[]]);}catch(TypeError $e){return "error";}}', '["error"]', '', false], 'intRead' => ['<?php function target(){$a=4;return $a[0];}', '[null]', '', true], 'stringRead' => ['<?php function target(){$a="abc";return [$a[0],$a[-1],$a["1"]];}', '[["a","c","b"]]', '', false], 'stringWrite' => ['<?php function target(){$a="abc";$r=($a[1]="xy");return [$r,$a];}', '[["x","axc"]]', '', true], 'stringNegativeWrite' => ['<?php function target(){$a="abc";$a[-1]="X";return $a;}', '["abX"]', '', false], 'stringPad' => ['<?php function target(){$a="a";$a[3]="Z";return $a;}', '["a Z"]', '', false], 'stringInvalidKey' => ['<?php function target(){$a="abc";try{$a["x"]="Z";}catch(TypeError $e){return $a;}return 999;}', '["abc"]', '', false], 'stringAppend' => ['<?php function target(){$a="abc";try{$a[]="Z";}catch(Error $e){return $a;}return 999;}', '["abc"]', '', false], 'stringEmptyWrite' => ['<?php function target(){$a="abc";try{$a[1]="";}catch(Error $e){return $a;}return 999;}', '["abc"]', '', false], 'stringArrayWrite' => ['<?php function target(){$a="abc";try{$a[1]=[];}catch(TypeError $e){return $a;}return $a;}', '["aAc"]', '', true], 'appendMax' => ['<?php function target(){$a=[9223372036854775807=>1];try{$a[]=2;}catch(Error $e){return $a;}return 999;}', '[{"9223372036854775807":1}]', '', false], 'appendMaxAfterUnset' => ['<?php function target(){$a=[9223372036854775807=>1];unset($a[9223372036854775807]);$a[]=2;return $a;}', '[{"9223372036854775807":2}]', '', false], 'string-"1.0"-read' => ['<?php function target(){$s="abc";return $s["1.0"];}', '[]', 'TypeError', false], 'string-"1.0"-isset' => ['<?php function target(){$s="abc";return isset($s["1.0"]);}', '[false]', '', false], 'string-"1.0"-write' => ['<?php function target(){$s="abc";$r=($s["1.0"]="X");return [$r,$s];}', '[]', 'TypeError', false], 'string-"1x"-read' => ['<?php function target(){$s="abc";return $s["1x"];}', '["b"]', '', true], 'string-"1x"-isset' => ['<?php function target(){$s="abc";return isset($s["1x"]);}', '[false]', '', false], 'string-"1x"-write' => ['<?php function target(){$s="abc";$r=($s["1x"]="X");return [$r,$s];}', '[["X","aXc"]]', '', true], 'string-" 1 "-read' => ['<?php function target(){$s="abc";return $s[" 1 "];}', '["b"]', '', false], 'string-" 1 "-isset' => ['<?php function target(){$s="abc";return isset($s[" 1 "]);}', '[true]', '', false], 'string-" 1 "-write' => ['<?php function target(){$s="abc";$r=($s[" 1 "]="X");return [$r,$s];}', '[["X","aXc"]]', '', false], 'string-"+1"-read' => ['<?php function target(){$s="abc";return $s["+1"];}', '["b"]', '', false], 'string-"+1"-isset' => ['<?php function target(){$s="abc";return isset($s["+1"]);}', '[true]', '', false], 'string-"+1"-write' => ['<?php function target(){$s="abc";$r=($s["+1"]="X");return [$r,$s];}', '[["X","aXc"]]', '', false], 'string-"01"-read' => ['<?php function target(){$s="abc";return $s["01"];}', '["b"]', '', false], 'string-"01"-isset' => ['<?php function target(){$s="abc";return isset($s["01"]);}', '[true]', '', false], 'string-"01"-write' => ['<?php function target(){$s="abc";$r=($s["01"]="X");return [$r,$s];}', '[["X","aXc"]]', '', false], 'string-1.5-read' => ['<?php function target(){$s="abc";return $s[1.5];}', '["b"]', '', true], 'string-1.5-isset' => ['<?php function target(){$s="abc";return isset($s[1.5]);}', '[true]', '', true], 'string-1.5-write' => ['<?php function target(){$s="abc";$r=($s[1.5]="X");return [$r,$s];}', '[["X","aXc"]]', '', true], 'string-true-read' => ['<?php function target(){$s="abc";return $s[true];}', '["b"]', '', true], 'string-true-isset' => ['<?php function target(){$s="abc";return isset($s[true]);}', '[true]', '', false], 'string-true-write' => ['<?php function target(){$s="abc";$r=($s[true]="X");return [$r,$s];}', '[["X","aXc"]]', '', true], 'string-null-read' => ['<?php function target(){$s="abc";return $s[null];}', '["a"]', '', true], 'string-null-isset' => ['<?php function target(){$s="abc";return isset($s[null]);}', '[true]', '', false], 'string-null-write' => ['<?php function target(){$s="abc";$r=($s[null]="X");return [$r,$s];}', '[["X","Xbc"]]', '', true], 'string-[]-read' => ['<?php function target(){$s="abc";return $s[[]];}', '[]', 'TypeError', false], 'string-[]-isset' => ['<?php function target(){$s="abc";return isset($s[[]]);}', '[false]', '', false], 'string-[]-write' => ['<?php function target(){$s="abc";$r=($s[[]]="X");return [$r,$s];}', '[]', 'TypeError', false], 'string-"9223372036854775807"-read' => ['<?php function target(){$s="abc";return $s["9223372036854775807"];}', '[""]', '', true], 'string-"9223372036854775807"-isset' => ['<?php function target(){$s="abc";return isset($s["9223372036854775807"]);}', '[false]', '', false], 'string-"9223372036854775808"-read' => ['<?php function target(){$s="abc";return $s["9223372036854775808"];}', '[]', 'TypeError', false], 'string-"9223372036854775808"-isset' => ['<?php function target(){$s="abc";return isset($s["9223372036854775808"]);}', '[false]', '', false], 'string-bounds--5' => ['<?php function target(){$s="abc";$r=($s[-5]="X");return [$r,$s];}', '[[null,"abc"]]', '', true], 'string-bounds--4' => ['<?php function target(){$s="abc";$r=($s[-4]="X");return [$r,$s];}', '[[null,"abc"]]', '', true], 'string-bounds--3' => ['<?php function target(){$s="abc";$r=($s[-3]="X");return [$r,$s];}', '[["X","Xbc"]]', '', false], 'string-bounds-3' => ['<?php function target(){$s="abc";$r=($s[3]="X");return [$r,$s];}', '[["X","abcX"]]', '', false], 'string-bounds-4' => ['<?php function target(){$s="abc";$r=($s[4]="X");return [$r,$s];}', '[["X","abc X"]]', '', false], 'literalString' => ['<?php function target(){return "abc"[1];}', '["b"]', '', false], 'literalArrayInvalid' => ['<?php function target(){return [1][[]];}', '[]', 'TypeError', false], 'literalScalar' => ['<?php function target(){return (4)[0];}', '[null]', '', true], 'nestedStringRead' => ['<?php function target(){$a=["abc"];return $a[0][-1][0];}', '["c"]', '', false], 'nestedStringWrite' => ['<?php function target(){$a=["abc"];$a[0][-1]="X";return $a;}', '[["abX"]]', '', false], 'privateArray' => ['<?php class B{private array $x=[];}function target(){$b=new B;try{$b->x[0]=1;}catch(Error $e){return "private";}return 999;}', '["private"]', '', false], 'privateArrayIsset' => ['<?php class B{private array $x=[1];}function target(){$b=new B;return isset($b->x[0]);}', '[false]', '', false], 'typedArrayCreate' => ['<?php class B{public array $x;}function target(){$b=new B;$b->x[0]=1;return $b->x;}', '[[1]]', '', false], 'typedNullableInt' => ['<?php class B{public ?int $x;}function target(){$b=new B;try{$b->x[0]=1;}catch(TypeError $e){return isset($b->x);}return 999;}', '[false]', '', false], 'typedNullableIntNull' => ['<?php class B{public ?int $x=null;}function target(){$b=new B;try{$b->x[0]=1;}catch(TypeError $e){return $b->x;}return 999;}', '[null]', '', false], 'staticArray' => ['<?php class B{public static array $x=[1];}function target(){B::$x[]=2;return B::$x;}', '[[1,2]]', '', false], 'readonlyArray' => ['<?php class B{public readonly array $x;function __construct(){$this->x=[1];}}function target(){$b=new B;try{$b->x[0]=2;}catch(Error $e){return $b->x;}return 999;}', '[[1]]', '', false], 'arrayReference' => ['<?php function target(){$a=[];$r=&$a["x"];$r=2;return $a;}', '[{"x":2}]', '', false], 'arrayReferenceInitial' => ['<?php function target(){$a=[];$r=&$a["x"];return [$a,$r];}', '[[{"x":null},null]]', '', false], 'arrayAliasDestination' => ['<?php function target(){$a=null;$b=2;$a["x"]=&$b;$b=3;return $a;}', '[{"x":3}]', '', false], 'stringReference' => ['<?php function target(){$a="abc";try{$r=&$a[0];}catch(Error $e){return $a;}return 999;}', '["abc"]', '', false], 'stringUnset' => ['<?php function target(){$a="abc";try{unset($a[0]);}catch(Error $e){return $a;}return 999;}', '["abc"]', '', false], 'scalarUnset' => ['<?php function target(){$a=4;unset($a[0]);return $a;}', '[]', 'Error', false], 'stringIncrement' => ['<?php function target(){$a="abc";try{$a[0]++;}catch(Error $e){return $a;}return 999;}', '["abc"]', '', false], 'arrayMissingIncrement' => ['<?php function target(){$a=[];$a[0]++;return $a;}', '[[1]]', '', true], 'plainObject' => ['<?php function target(){$a=new stdClass;try{$a[0]=1;}catch(Error $e){return "object";}return 999;}', '["object"]', '', false], 'arrayFractionalKey' => ['<?php function target(){$a=[];$a[1.5]=2;return $a;}', '[{"1":2}]', '', true], 'arrayaccess:read-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=$b["n"];return [$r,$b->items,$b->log];}', '[[2,{"x":[1],"n":2},["get:n"]]]', '', false], 'arrayaccess:write-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=($b["n"]=3);return [$r,$b->items,$b->log];}', '[[3,{"x":[1],"n":3},["set:n"]]]', '', false], 'arrayaccess:isset-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=isset($b["n"]);return [$r,$b->items,$b->log];}', '[[true,{"x":[1],"n":2},["exists:n"]]]', '', false], 'arrayaccess:coalesce-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=$b["n"]??9;return [$r,$b->items,$b->log];}', '[[2,{"x":[1],"n":2},["exists:n","get:n"]]]', '', false], 'arrayaccess:empty-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=empty($b["n"]);return [$r,$b->items,$b->log];}', '[[false,{"x":[1],"n":2},["exists:n","get:n"]]]', '', false], 'arrayaccess:unset-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;unset($b["n"]);$r=1;return [$r,$b->items,$b->log];}', '[[1,{"x":[1]},["unset:n"]]]', '', false], 'arrayaccess:increment-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=$b["n"]++;return [$r,$b->items,$b->log];}', '[[2,{"x":[1],"n":2},["get:n"]]]', '', true], 'arrayaccess:compound-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=($b["n"]+=3);return [$r,$b->items,$b->log];}', '[[5,{"x":[1],"n":5},["get:n","set:n"]]]', '', false], 'arrayaccess:nestedwrite-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$b["x"][0]=3;$r=1;return [$r,$b->items,$b->log];}', '[[1,{"x":[1],"n":2},["get:x"]]]', '', true], 'arrayaccess:nestedread-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=$b["x"][0];return [$r,$b->items,$b->log];}', '[[1,{"x":[1],"n":2},["get:x"]]]', '', false], 'arrayaccess:nestedisset-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=isset($b["x"][0]);return [$r,$b->items,$b->log];}', '[[true,{"x":[1],"n":2},["exists:x","get:x"]]]', '', false], 'arrayaccess:reference-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=&$b["n"];$r=9;return [$r,$b->items,$b->log];}', '[[9,{"x":[1],"n":2},["get:n"]]]', '', true], 'arrayaccess:alias-value' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=9;$b["n"]=&$r;return [$r,$b->items,$b->log];}', '[]', 'Error', true], 'arrayaccess:read-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=$b["n"];return [$r,$b->items,$b->log];}', '[[2,{"x":[1],"n":2},["get:n"]]]', '', false], 'arrayaccess:write-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=($b["n"]=3);return [$r,$b->items,$b->log];}', '[[3,{"x":[1],"n":3},["set:n"]]]', '', false], 'arrayaccess:isset-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=isset($b["n"]);return [$r,$b->items,$b->log];}', '[[true,{"x":[1],"n":2},["exists:n"]]]', '', false], 'arrayaccess:coalesce-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=$b["n"]??9;return [$r,$b->items,$b->log];}', '[[2,{"x":[1],"n":2},["exists:n","get:n"]]]', '', false], 'arrayaccess:empty-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=empty($b["n"]);return [$r,$b->items,$b->log];}', '[[false,{"x":[1],"n":2},["exists:n","get:n"]]]', '', false], 'arrayaccess:unset-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;unset($b["n"]);$r=1;return [$r,$b->items,$b->log];}', '[[1,{"x":[1]},["unset:n"]]]', '', false], 'arrayaccess:increment-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=$b["n"]++;return [$r,$b->items,$b->log];}', '[[2,{"x":[1],"n":3},["get:n"]]]', '', false], 'arrayaccess:compound-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=($b["n"]+=3);return [$r,$b->items,$b->log];}', '[[5,{"x":[1],"n":5},["get:n","set:n"]]]', '', false], 'arrayaccess:nestedwrite-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$b["x"][0]=3;$r=1;return [$r,$b->items,$b->log];}', '[[1,{"x":[3],"n":2},["get:x"]]]', '', false], 'arrayaccess:nestedread-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=$b["x"][0];return [$r,$b->items,$b->log];}', '[[1,{"x":[1],"n":2},["get:x"]]]', '', false], 'arrayaccess:nestedisset-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=isset($b["x"][0]);return [$r,$b->items,$b->log];}', '[[true,{"x":[1],"n":2},["exists:x","get:x"]]]', '', false], 'arrayaccess:reference-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=&$b["n"];$r=9;return [$r,$b->items,$b->log];}', '[[9,{"x":[1],"n":9},["get:n"]]]', '', false], 'arrayaccess:alias-ref' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function &offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function target(){$b=new Box;$r=9;$b["n"]=&$r;return [$r,$b->items,$b->log];}', '[]', 'Error', false], 'arrayaccess:unset-null' => ['<?php function target(){$a=null;unset($a[0]);return $a;}', '[null]', '', false], 'arrayaccess:unset-false' => ['<?php function target(){$a=false;unset($a[0]);return $a;}', '[false]', '', true], 'arrayaccess:unset-true' => ['<?php function target(){$a=true;unset($a[0]);return $a;}', '[]', 'Error', false], 'arrayaccess:unset-4' => ['<?php function target(){$a=4;unset($a[0]);return $a;}', '[]', 'Error', false], 'arrayaccess:unset-4.5' => ['<?php function target(){$a=4.5;unset($a[0]);return $a;}', '[]', 'Error', false], 'arrayaccess:readonly-object' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}class Holder{public readonly Box $box;function __construct(){$this->box=new Box;}}function target(){$h=new Holder;$h->box["n"]=8;return [$h->box->items,$h->box->log];}', '[[{"x":[1],"n":8},["set:n"]]]', '', false], 'arrayaccess:computed-object' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function make(){return new Box;}function target(){return make()["n"];}', '[2]', '', false], 'arrayaccess:computed-coalesce' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}function make(){return new Box;}function target(){return make()["absent"]??9;}', '[9]', '', false], 'arrayaccess:literal-coalesce' => ['<?php function target(){return []["absent"]??9;}', '[9]', '', false], 'arrayaccess:literal-empty' => ['<?php function target(){return empty([]["absent"]);}', '[true]', '', false], 'arrayaccess:literal-nested-coalesce' => ['<?php function target(){return []["absent"]["nested"]??9;}', '[9]', '', false], 'compound:local-order' => ['<?php function target(){$a=1;$a+=($a=2);return $a;}', '[4]', '', false], 'compound:array-order' => ['<?php function target(){$a=[1];$a[0]+=($a[0]=2);return $a;}', '[[4]]', '', false], 'compound:string-compound' => ['<?php function target(){$a="abc";try{$a[0].="x";}catch(Throwable $e){return get_class($e);}return $a;}', '["Error"]', '', false], 'compound:string-numeric' => ['<?php function target(){$a="123";try{$a[0]+=2;}catch(Throwable $e){return get_class($e);}return $a;}', '["Error"]', '', false], 'compound:string-rhs' => ['<?php function target(){$a="abc";$b=0;try{$a[0]+=++$b;}catch(Throwable $e){return [$b,get_class($e)];}return $a;}', '[[1,"Error"]]', '', false], 'compound:null-failed' => ['<?php function target(){$a=null;try{$a[0]+=new stdClass;}catch(Throwable $e){return [$a,get_class($e)];}return $a;}', '[[[null],"TypeError"]]', '', true], 'compound:array-failed' => ['<?php function target(){$a=[];try{$a[0]+=new stdClass;}catch(Throwable $e){return [$a,get_class($e)];}return $a;}', '[[[null],"TypeError"]]', '', true], 'compound:string-invalid-key' => ['<?php function target(){$a="123";try{$a[[]]+=2;}catch(Throwable $e){return get_class($e);}return $a;}', '["TypeError"]', '', false], 'compound:key-1.2x' => ['<?php function target(){$s="abc";return $s[\'1.2x\'];}', '[]', 'TypeError', false], 'compound:key-1e2x' => ['<?php function target(){$s="abc";return $s[\'1e2x\'];}', '[]', 'TypeError', false], 'compound:key-1e' => ['<?php function target(){$s="abc";return $s[\'1e\'];}', '["b"]', '', true], 'compound:key-1.x' => ['<?php function target(){$s="abc";return $s[\'1.x\'];}', '[]', 'TypeError', false], 'compound:key- +001x' => ['<?php function target(){$s="abc";return $s[\' +001x\'];}', '["b"]', '', true], 'compound:key-9223372036854775807x' => ['<?php function target(){$s="abc";return $s[\'9223372036854775807x\'];}', '[""]', '', true], 'compound:caught-inspection' => ['<?php function target(){try{1/0;}catch(DivisionByZeroError $e){$same=$e;return [get_class($e),$same===$e,$e->getCode(),$e->getPrevious(),$e instanceof Error];}return 999;}', '[["DivisionByZeroError",true,0,null,true]]', '', false], 'compound:caught-rethrow' => ['<?php function target(){try{try{1/0;}catch(DivisionByZeroError $e){throw $e;}}catch(Error $other){return get_class($other);}return 999;}', '["DivisionByZeroError"]', '', false], 'rejected:reference:[]' => ['<?php function target(){$s="abc";$r=&$s[[]];return $s;}', '[]', 'TypeError', false], 'rejected:increment:[]' => ['<?php function target(){$s="abc";$s[[]]++;return $s;}', '[]', 'TypeError', false], 'rejected:unset:[]' => ['<?php function target(){$s="abc";unset($s[[]]);return $s;}', '[]', 'Error', false], 'rejected:nested:[]' => ['<?php function target(){$s="abc";$s[[]][0]="X";return $s;}', '[]', 'TypeError', false], 'rejected:compound:[]' => ['<?php function target(){$s="abc";$s[[]].="X";return $s;}', '[]', 'TypeError', false], 'rejected:reference:"1x"' => ['<?php function target(){$s="abc";$r=&$s["1x"];return $s;}', '[]', 'Error', true], 'rejected:increment:"1x"' => ['<?php function target(){$s="abc";$s["1x"]++;return $s;}', '[]', 'Error', true], 'rejected:unset:"1x"' => ['<?php function target(){$s="abc";unset($s["1x"]);return $s;}', '[]', 'Error', false], 'rejected:nested:"1x"' => ['<?php function target(){$s="abc";$s["1x"][0]="X";return $s;}', '[]', 'Error', true], 'rejected:compound:"1x"' => ['<?php function target(){$s="abc";$s["1x"].="X";return $s;}', '[]', 'Error', true], 'rejected:reference:1.5' => ['<?php function target(){$s="abc";$r=&$s[1.5];return $s;}', '[]', 'Error', true], 'rejected:increment:1.5' => ['<?php function target(){$s="abc";$s[1.5]++;return $s;}', '[]', 'Error', true], 'rejected:unset:1.5' => ['<?php function target(){$s="abc";unset($s[1.5]);return $s;}', '[]', 'Error', false], 'rejected:nested:1.5' => ['<?php function target(){$s="abc";$s[1.5][0]="X";return $s;}', '[]', 'Error', true], 'rejected:compound:1.5' => ['<?php function target(){$s="abc";$s[1.5].="X";return $s;}', '[]', 'Error', true], 'rejected:readonly-array-object' => ['<?php class Box implements ArrayAccess {public array $items=["x"=>[1],"n"=>2];public array $log=[];public function offsetExists(mixed $key):bool{$this->log[]="exists:".$key;return isset($this->items[$key]);}public function offsetGet(mixed $key):mixed{$this->log[]="get:".$key;return $this->items[$key];}public function offsetSet(mixed $key,mixed $value):void{$this->log[]="set:".$key;$this->items[$key]=$value;}public function offsetUnset(mixed $key):void{$this->log[]="unset:".$key;unset($this->items[$key]);}}class Holder{public readonly array $boxes;function __construct(){$this->boxes=[new Box];}}function target(){$h=new Holder;$h->boxes[0]["n"]=8;return [$h->boxes[0]->items,$h->boxes[0]->log];}', '[]', 'Error', false], 'caught:fresh-identities' => ['<?php function caught(){try{1/0;}catch(Error $e){return $e;}}function target(){$objects=[];for($i=0;$i<2;$i++){$objects[]=caught();}return $objects[0]===$objects[1];}', '[false]', '', false]] as $name => $case) {
600 if (!str_contains($case[0], 'ArrayAccess')) {
601 yield $name => $case;
602 }
603 }
604 }
605}
606