packages/deriver/tests/Unit/Deriver/Evaluator/Execution/Control/PathJoinTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution\Control;
6
7use Deriver\Deriver\Contributions;
8use Deriver\Deriver\DeriverConfig;
9use Deriver\Deriver\Evaluator\Call\Evaluators;
10use Deriver\Deriver\Evaluator\Call\Extensions;
11use Deriver\Deriver\Evaluator\Call\Registrations;
12use Deriver\Deriver\Evaluator\Call\StateRegistry;
13use Deriver\Deriver\Evaluator\Domain\Lattice;
14use Deriver\Deriver\Evaluator\EvaluatorConfig;
15use Deriver\Deriver\Evaluator\Evidence\Frontier;
16use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
17use Deriver\Deriver\Evaluator\Execution\Completion\CompletionKind;
18use Deriver\Deriver\Evaluator\Execution\Context;
19use Deriver\Deriver\Evaluator\Execution\Control\PathJoin;
20use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
21use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
22use Deriver\Deriver\Evaluator\Execution\Havoc;
23use Deriver\Deriver\Evaluator\Execution\Memory\Location;
24use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
25use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
26use Deriver\Deriver\Evaluator\Execution\Offset\Address;
27use Deriver\Deriver\Evaluator\Execution\State;
28use Deriver\Deriver\Evaluator\Request\Budget;
29use Deriver\Deriver\Evaluator\Request\QueryScope;
30use Deriver\Deriver\Evaluator\Request\ReturnQuery;
31use Deriver\Deriver\Evaluator\Scalar\Identity;
32use Deriver\Deriver\Evaluator\Scalar\StringPrefix;
33use Deriver\Deriver\Evaluator\TargetProfile;
34use Deriver\Deriver\Evaluator\Term;
35use Deriver\Deriver\Evaluator\TermKind;
36use Deriver\Deriver\Execution\QueryExecution;
37use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
38use Deriver\Deriver\Lowerer\Declaration\CallableSource;
39use Deriver\Deriver\Lowerer\Declaration\Index;
40use Deriver\Deriver\Lowerer\Declaration\Indexer;
41use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
42use Deriver\Deriver\MicroEvaluatorConversion;
43use Deriver\Deriver\Parser\Node\File;
44use Deriver\Deriver\Parser\Parser;
45use Deriver\Deriver\Parser\ParserConfig;
46use Deriver\Deriver\Parser\TargetSyntax;
47use Deriver\Deriver\Providers\ProviderExpansion;
48use Deriver\Deriver\Resolver\AssignmentPatterns;
49use Deriver\Deriver\Resolver\ClassScope;
50use Deriver\Deriver\Resolver\ExpressionValidation;
51use Deriver\Deriver\Resolver\MagicContext;
52use Deriver\Deriver\Resolver\Resolver;
53use Deriver\Deriver\SnapshotIdentity;
54use Deriver\Deriver\Sources\LineMap;
55use Deriver\Deriver\Sources\Path;
56use Deriver\Deriver\Sources\Source;
57use Deriver\Deriver\Sources\SourceRef;
58use Deriver\Deriver\Sources\Sources;
59use Deriver\Exception\ParserException;
60use Deriver\Exception\ResolutionException;
61use PHPUnit\Framework\Attributes\CoversClass;
62use PHPUnit\Framework\Attributes\Small;
63use PHPUnit\Framework\Attributes\UsesClass;
64use PHPUnit\Framework\TestCase;
65
66#[UsesClass(Resolver::class)]
67#[UsesClass(ExpressionValidation::class)]
68#[UsesClass(ResolutionException::class)]
69#[UsesClass(ParserConfig::class)]
70#[UsesClass(ParserException::class)]
71#[CoversClass(PathJoin::class)]
72#[UsesClass(Address::class)]
73#[UsesClass(Completion::class)]
74#[UsesClass(State::class)]
75#[UsesClass(Location::class)]
76#[UsesClass(Term::class)]
77#[UsesClass(SourceRef::class)]
78#[UsesClass(State::class)]
79#[UsesClass(CallableIdentity::class)]
80#[UsesClass(Context::class)]
81#[UsesClass(StateJoin::class)]
82#[UsesClass(Havoc::class)]
83#[UsesClass(Materialization::class)]
84#[UsesClass(Memory::class)]
85#[UsesClass(Extensions::class)]
86#[UsesClass(Evaluators::class)]
87#[UsesClass(StateRegistry::class)]
88#[UsesClass(DeriverConfig::class)]
89#[UsesClass(Sources::class)]
90#[UsesClass(Path::class)]
91#[UsesClass(Source::class)]
92#[UsesClass(TargetProfile::class)]
93#[UsesClass(Budget::class)]
94#[UsesClass(QueryScope::class)]
95#[UsesClass(ReturnQuery::class)]
96#[UsesClass(Frontier::class)]
97#[UsesClass(Parser::class)]
98#[UsesClass(File::class)]
99#[UsesClass(CallableSource::class)]
100#[UsesClass(Indexer::class)]
101#[UsesClass(Index::class)]
102#[UsesClass(Composition::class)]
103#[UsesClass(LineMap::class)]
104#[UsesClass(MagicContext::class)]
105#[UsesClass(AssignmentPatterns::class)]
106#[UsesClass(ClassScope::class)]
107#[UsesClass(TargetSyntax::class)]
108#[UsesClass(Identity::class)]
109#[UsesClass(Lattice::class)]
110#[UsesClass(StringPrefix::class)]
111#[Small]
112final class PathJoinTest extends TestCase
113{
114    public function testJoinWidensDifferingPlainValuesAndKeepsSharedFacts(): void
115    {
116        $source = '<?php function target(){return 1;}';
117        $budget = new Budget();
118        $configuration = new DeriverConfig();
119        $contributions = new Contributions();
120        $input = new Sources([new Source('fixture.php', $source)]);
121        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
122        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
123        $contributions1 = $providers->contributions;
124        $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);
125        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
126        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
127        $snapshotId = $identity->id;
128        $input1 = $providers->input;
129        $parser = new Parser(new ParserConfig($target->version));
130        $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));
131        $program1 = new Index($snapshotId, $trees);
132        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
133        $snapshotId1 = 'test';
134        $input2 = new Sources([new Source('fixture.php', $source)]);
135        $profile = $execution->configuration->target;
136        $parser1 = new Parser(new ParserConfig($profile->version));
137        $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));
138        $program = new Index($snapshotId1, $trees1);
139        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
140        $first = Term::constant('SELECT a');
141        $second = Term::constant('SELECT b');
142        $base = new State();
143        $base->block = 2;
144        $base->previous = 1;
145        $base->memory->write($base->local('table'), Term::constant('kept'));
146        $base->local('sql');
147        $a1 = $base->fork();
148        $b1 = $base->fork();
149        $a1->memory->write($a1->local('sql'), $first);
150        $b1->memory->write($b1->local('sql'), $second);
151        [$a, $b] = [$a1, $b1];
152        $a->guard = ['shared' => true, 'left' => true];
153        $b->guard = ['shared' => true, 'left' => false];
154        $a->visits = [3 => 2];
155        $b->visits = [3 => 5];
156        $a->evidence = ['x'];
157        $b->evidence = ['y'];
158        $b->memory->sequence = 9;
159        $joined = $join->join([$a, $b]);
160        self::assertNotNull($joined);
161        $value = $joined->memory->read($joined->locals['sql']);
162        self::assertSame('SELECT ', $value->operands[0]->native());
163        self::assertTrue((new Lattice())->contains($value, Term::constant('SELECT a')));
164        self::assertTrue((new Lattice())->contains($value, Term::constant('SELECT b')));
165        self::assertSame('kept', $joined->memory->read($joined->locals['table'])->native());
166        self::assertSame(['shared' => true], $joined->guard);
167        self::assertSame([3 => 5], $joined->visits);
168        self::assertSame(['x', 'y'], $joined->evidence);
169        self::assertSame(9, $joined->memory->sequence);
170        self::assertSame([], $joined->constraints);
171        self::assertSame(CompletionKind::Normal, $joined->completion->kind);
172        self::assertSame('SELECT a', $a->memory->read($a->locals['sql'])->native());
173    }
174    public function testJoinRejectsDifferentPredecessors(): void
175    {
176        $first = Term::constant(1);
177        $second = Term::constant(2);
178        $base = new State();
179        $base->block = 2;
180        $base->previous = 1;
181        $base->memory->write($base->local('table'), Term::constant('kept'));
182        $base->local('sql');
183        $a1 = $base->fork();
184        $b1 = $base->fork();
185        $a1->memory->write($a1->local('sql'), $first);
186        $b1->memory->write($b1->local('sql'), $second);
187        [$a, $b] = [$a1, $b1];
188        $b->previous = 4;
189        $source = '<?php function target(){return 1;}';
190        $budget = new Budget();
191        $configuration = new DeriverConfig();
192        $contributions = new Contributions();
193        $input = new Sources([new Source('fixture.php', $source)]);
194        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
195        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
196        $contributions1 = $providers->contributions;
197        $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);
198        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
199        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
200        $snapshotId = $identity->id;
201        $input1 = $providers->input;
202        $parser = new Parser(new ParserConfig($target->version));
203        $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));
204        $program1 = new Index($snapshotId, $trees);
205        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
206        $snapshotId1 = 'test';
207        $input2 = new Sources([new Source('fixture.php', $source)]);
208        $profile = $execution->configuration->target;
209        $parser1 = new Parser(new ParserConfig($profile->version));
210        $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));
211        $program = new Index($snapshotId1, $trees1);
212        self::assertNull((new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->join([$a, $b]));
213    }
214    public function testJoinRejectsDifferentAliasesAndObjects(): void
215    {
216        $source = '<?php function target(){return 1;}';
217        $budget = new Budget();
218        $configuration = new DeriverConfig();
219        $contributions = new Contributions();
220        $input = new Sources([new Source('fixture.php', $source)]);
221        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
222        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
223        $contributions1 = $providers->contributions;
224        $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);
225        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
226        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
227        $snapshotId = $identity->id;
228        $input1 = $providers->input;
229        $parser = new Parser(new ParserConfig($target->version));
230        $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));
231        $program1 = new Index($snapshotId, $trees);
232        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
233        $snapshotId1 = 'test';
234        $input2 = new Sources([new Source('fixture.php', $source)]);
235        $profile = $execution->configuration->target;
236        $parser1 = new Parser(new ParserConfig($profile->version));
237        $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));
238        $program = new Index($snapshotId1, $trees1);
239        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
240        $first = Term::constant(1);
241        $second = new Term(TermKind::Cell, 'cell:9');
242        $base = new State();
243        $base->block = 2;
244        $base->previous = 1;
245        $base->memory->write($base->local('table'), Term::constant('kept'));
246        $base->local('sql');
247        $a1 = $base->fork();
248        $b1 = $base->fork();
249        $a1->memory->write($a1->local('sql'), $first);
250        $b1->memory->write($b1->local('sql'), $second);
251        [$a, $b] = [$a1, $b1];
252        self::assertNull($join->join([$a, $b]));
253        $first1 = new Term(TermKind::Object, 'object:1', attributes: ['class' => 'A']);
254        $second1 = new Term(TermKind::Object, 'object:2', attributes: ['class' => 'A']);
255        $base1 = new State();
256        $base1->block = 2;
257        $base1->previous = 1;
258        $base1->memory->write($base1->local('table'), Term::constant('kept'));
259        $base1->local('sql');
260        $a2 = $base1->fork();
261        $b2 = $base1->fork();
262        $a2->memory->write($a2->local('sql'), $first1);
263        $b2->memory->write($b2->local('sql'), $second1);
264        [$a, $b] = [$a2, $b2];
265        self::assertNull($join->join([$a, $b]));
266        $first2 = Term::constant(1);
267        $second2 = Term::constant(2);
268        $base2 = new State();
269        $base2->block = 2;
270        $base2->previous = 1;
271        $base2->memory->write($base2->local('table'), Term::constant('kept'));
272        $base2->local('sql');
273        $a3 = $base2->fork();
274        $b3 = $base2->fork();
275        $a3->memory->write($a3->local('sql'), $first2);
276        $b3->memory->write($b3->local('sql'), $second2);
277        [$a, $b] = [$a3, $b3];
278        $b->locals['other'] = new Location('other');
279        self::assertNull($join->join([$a, $b]));
280    }
281    public function testJoinKeepsRegisterMetadataOnlyWhenEveryPathAgrees(): void
282    {
283        $source = '<?php function target(){return 1;}';
284        $budget = new Budget();
285        $configuration = new DeriverConfig();
286        $contributions = new Contributions();
287        $input = new Sources([new Source('fixture.php', $source)]);
288        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
289        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
290        $contributions1 = $providers->contributions;
291        $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);
292        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
293        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
294        $snapshotId = $identity->id;
295        $input1 = $providers->input;
296        $parser = new Parser(new ParserConfig($target->version));
297        $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));
298        $program1 = new Index($snapshotId, $trees);
299        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
300        $snapshotId1 = 'test';
301        $input2 = new Sources([new Source('fixture.php', $source)]);
302        $profile = $execution->configuration->target;
303        $parser1 = new Parser(new ParserConfig($profile->version));
304        $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));
305        $program = new Index($snapshotId1, $trees1);
306        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
307        $first = Term::constant(1);
308        $second = Term::constant(2);
309        $base = new State();
310        $base->block = 2;
311        $base->previous = 1;
312        $base->memory->write($base->local('table'), Term::constant('kept'));
313        $base->local('sql');
314        $a1 = $base->fork();
315        $b1 = $base->fork();
316        $a1->memory->write($a1->local('sql'), $first);
317        $b1->memory->write($b1->local('sql'), $second);
318        [$a, $b] = [$a1, $b1];
319        $a->registers = ['r1' => Term::constant(1), 'r2' => Term::constant('only a')];
320        $b->registers = ['r1' => Term::constant(1)];
321        $a->addresses = ['r1' => new Location('x'), 'r2' => new Location('y')];
322        $b->addresses = ['r1' => new Location('x')];
323        $joined = $join->join([$a, $b]);
324        self::assertNotNull($joined);
325        self::assertSame(['r1'], array_keys($joined->registers));
326        self::assertSame(['r1'], array_keys($joined->addresses));
327        $b->addresses = ['r1' => new Location('z')];
328        self::assertNull($join->join([$a, $b]));
329    }
330    public function testJoinWidensCompletedReturnValuesWithoutComparingRegisters(): void
331    {
332        $source = '<?php function target(){return 1;}';
333        $budget = new Budget();
334        $configuration = new DeriverConfig();
335        $contributions = new Contributions();
336        $input = new Sources([new Source('fixture.php', $source)]);
337        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
338        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
339        $contributions1 = $providers->contributions;
340        $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);
341        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
342        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
343        $snapshotId = $identity->id;
344        $input1 = $providers->input;
345        $parser = new Parser(new ParserConfig($target->version));
346        $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));
347        $program1 = new Index($snapshotId, $trees);
348        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
349        $snapshotId1 = 'test';
350        $input2 = new Sources([new Source('fixture.php', $source)]);
351        $profile = $execution->configuration->target;
352        $parser1 = new Parser(new ParserConfig($profile->version));
353        $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));
354        $program = new Index($snapshotId1, $trees1);
355        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
356        $first = Term::constant(1);
357        $second = Term::constant(1);
358        $base = new State();
359        $base->block = 2;
360        $base->previous = 1;
361        $base->memory->write($base->local('table'), Term::constant('kept'));
362        $base->local('sql');
363        $a1 = $base->fork();
364        $b1 = $base->fork();
365        $a1->memory->write($a1->local('sql'), $first);
366        $b1->memory->write($b1->local('sql'), $second);
367        [$a, $b] = [$a1, $b1];
368        $a->completion = new Completion(CompletionKind::Return, Term::constant('a'));
369        $b->completion = new Completion(CompletionKind::Return, Term::constant(2));
370        $a->block = 1;
371        $b->block = 2;
372        $a->registers = ['r1' => new Term(TermKind::Cell, 'cell:1')];
373        $b->registers = ['r1' => Term::constant(1)];
374        self::assertNull($join->join([$a, $b]));
375        $joined = $join->join([$a, $b], true);
376        self::assertNotNull($joined);
377        self::assertSame(CompletionKind::Return, $joined->completion->kind);
378        self::assertSame('int|string', (new Lattice())->type($joined->completion->value ?? Term::constant(null)));
379    }
380    public function testMergeAccumulatesBookkeepingConservatively(): void
381    {
382        $source = '<?php function target(){return 1;}';
383        $budget = new Budget();
384        $configuration = new DeriverConfig();
385        $contributions = new Contributions();
386        $input = new Sources([new Source('fixture.php', $source)]);
387        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
388        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
389        $contributions1 = $providers->contributions;
390        $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);
391        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
392        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
393        $snapshotId = $identity->id;
394        $input1 = $providers->input;
395        $parser = new Parser(new ParserConfig($target->version));
396        $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));
397        $program1 = new Index($snapshotId, $trees);
398        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
399        $snapshotId1 = 'test';
400        $input2 = new Sources([new Source('fixture.php', $source)]);
401        $profile = $execution->configuration->target;
402        $parser1 = new Parser(new ParserConfig($profile->version));
403        $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));
404        $program = new Index($snapshotId1, $trees1);
405        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
406        $first = Term::constant(1);
407        $second = Term::constant(2);
408        $base = new State();
409        $base->block = 2;
410        $base->previous = 1;
411        $base->memory->write($base->local('table'), Term::constant('kept'));
412        $base->local('sql');
413        $a1 = $base->fork();
414        $b1 = $base->fork();
415        $a1->memory->write($a1->local('sql'), $first);
416        $b1->memory->write($b1->local('sql'), $second);
417        [$a, $b] = [$a1, $b1];
418        $a->observed = true;
419        $b->observed = false;
420        $a->producers = ['r1' => 'x', 'r2' => 'y'];
421        $b->producers = ['r1' => 'x', 'r2' => 'z'];
422        $a->registers = ['r1' => Term::constant(1), 'r2' => Term::constant(2)];
423        $b->unknownLocals = 'EVAL';
424        $b->memory->unknownShared = 'MISSING_CALL_MODEL';
425        $b->memory->versions = ['cell:9' => 4];
426        $join->merge($a, $b);
427        self::assertFalse($a->observed);
428        self::assertSame(['r1' => 'x'], $a->producers);
429        self::assertSame('EVAL', $a->unknownLocals);
430        self::assertSame('MISSING_CALL_MODEL', $a->memory->unknownShared);
431        self::assertSame(4, $a->memory->versions['cell:9']);
432    }
433    public function testStructureIgnoresValuesButNotPositionsOrAliases(): void
434    {
435        $source = '<?php function target(){return 1;}';
436        $budget = new Budget();
437        $configuration = new DeriverConfig();
438        $contributions = new Contributions();
439        $input = new Sources([new Source('fixture.php', $source)]);
440        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
441        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
442        $contributions1 = $providers->contributions;
443        $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);
444        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
445        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
446        $snapshotId = $identity->id;
447        $input1 = $providers->input;
448        $parser = new Parser(new ParserConfig($target->version));
449        $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));
450        $program1 = new Index($snapshotId, $trees);
451        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
452        $snapshotId1 = 'test';
453        $input2 = new Sources([new Source('fixture.php', $source)]);
454        $profile = $execution->configuration->target;
455        $parser1 = new Parser(new ParserConfig($profile->version));
456        $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));
457        $program = new Index($snapshotId1, $trees1);
458        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
459        $first = Term::constant(1);
460        $second = Term::constant(2);
461        $base = new State();
462        $base->block = 2;
463        $base->previous = 1;
464        $base->memory->write($base->local('table'), Term::constant('kept'));
465        $base->local('sql');
466        $a1 = $base->fork();
467        $b1 = $base->fork();
468        $a1->memory->write($a1->local('sql'), $first);
469        $b1->memory->write($b1->local('sql'), $second);
470        [$a, $b] = [$a1, $b1];
471        $b->guard = ['g' => true];
472        $b->registers = ['r1' => Term::constant(3)];
473        self::assertSame($join->structure($a), $join->structure($b));
474        $b->block = 3;
475        self::assertNotSame($join->structure($a), $join->structure($b));
476        self::assertSame($join->structure($a, true), $join->structure($b, true));
477        $b->memory->classes['object:1'] = 'A';
478        self::assertNotSame($join->structure($a, true), $join->structure($b, true));
479    }
480    public function testCommonKeepsEqualMetadataPresentOnEveryPath(): void
481    {
482        $source = '<?php function target(){return 1;}';
483        $budget = new Budget();
484        $configuration = new DeriverConfig();
485        $contributions = new Contributions();
486        $input = new Sources([new Source('fixture.php', $source)]);
487        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
488        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
489        $contributions1 = $providers->contributions;
490        $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);
491        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
492        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
493        $snapshotId = $identity->id;
494        $input1 = $providers->input;
495        $parser = new Parser(new ParserConfig($target->version));
496        $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));
497        $program1 = new Index($snapshotId, $trees);
498        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
499        $snapshotId1 = 'test';
500        $input2 = new Sources([new Source('fixture.php', $source)]);
501        $profile = $execution->configuration->target;
502        $parser1 = new Parser(new ParserConfig($profile->version));
503        $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));
504        $program = new Index($snapshotId1, $trees1);
505        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
506        self::assertEquals(['r1' => new Location('x')], $join->common([['r1' => new Location('x'), 'r2' => new Location('y')], ['r1' => new Location('x')]]));
507        self::assertNull($join->common([['r1' => new Location('x')], ['r1' => new Location('y')]]));
508    }
509    public function testValuesWidensDifferencesAndRejectsIdentities(): void
510    {
511        $source = '<?php function target(){return 1;}';
512        $budget = new Budget();
513        $configuration = new DeriverConfig();
514        $contributions = new Contributions();
515        $input = new Sources([new Source('fixture.php', $source)]);
516        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
517        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
518        $contributions1 = $providers->contributions;
519        $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);
520        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
521        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
522        $snapshotId = $identity->id;
523        $input1 = $providers->input;
524        $parser = new Parser(new ParserConfig($target->version));
525        $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));
526        $program1 = new Index($snapshotId, $trees);
527        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
528        $snapshotId1 = 'test';
529        $input2 = new Sources([new Source('fixture.php', $source)]);
530        $profile = $execution->configuration->target;
531        $parser1 = new Parser(new ParserConfig($profile->version));
532        $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));
533        $program = new Index($snapshotId1, $trees1);
534        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
535        $values = $join->values([['a' => Term::constant(1), 'b' => Term::constant('x')], ['a' => Term::constant(1), 'b' => Term::constant('y')]], false);
536        self::assertNotNull($values);
537        self::assertSame(1, $values['a']->native());
538        self::assertSame('WIDENED', $values['b']->literal);
539        self::assertSame(['a'], array_keys($join->values([['a' => Term::constant(1), 'b' => Term::constant(2)], ['a' => Term::constant(1)]], false) ?? []));
540        self::assertSame(['a', 'b'], array_keys($join->values([['a' => Term::constant(1), 'b' => Term::constant(2)], ['a' => Term::constant(1)]], true) ?? []));
541        self::assertNull($join->values([['a' => new Term(TermKind::Cell, 'cell:1')], ['a' => new Term(TermKind::Cell, 'cell:2')]], false));
542    }
543    public function testLocationsGeneralizeDifferingAddressesIntoOneRoot(): void
544    {
545        $source = '<?php function target(){return 1;}';
546        $budget = new Budget();
547        $configuration = new DeriverConfig();
548        $contributions = new Contributions();
549        $input = new Sources([new Source('fixture.php', $source)]);
550        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
551        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
552        $contributions1 = $providers->contributions;
553        $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);
554        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
555        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
556        $snapshotId = $identity->id;
557        $input1 = $providers->input;
558        $parser = new Parser(new ParserConfig($target->version));
559        $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));
560        $program1 = new Index($snapshotId, $trees);
561        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
562        $snapshotId1 = 'test';
563        $input2 = new Sources([new Source('fixture.php', $source)]);
564        $profile = $execution->configuration->target;
565        $parser1 = new Parser(new ParserConfig($profile->version));
566        $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));
567        $program = new Index($snapshotId1, $trees1);
568        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
569        $locations = $join->locations([['r1' => new Location('cell:1', [0]), 'r2' => new Location('cell:2')], ['r1' => new Location('cell:1', [1]), 'r2' => new Location('cell:2')]]);
570        self::assertNotNull($locations);
571        self::assertEquals(new Location('cell:1', unknown: true), $locations['r1']);
572        self::assertEquals(new Location('cell:2'), $locations['r2']);
573        self::assertNull($join->locations([['r1' => new Location('cell:1')], ['r1' => new Location('cell:2')]]));
574    }
575    public function testOffsetsWidenKeysBelowOneParent(): void
576    {
577        $source = '<?php function target(){return 1;}';
578        $budget = new Budget();
579        $configuration = new DeriverConfig();
580        $contributions = new Contributions();
581        $input = new Sources([new Source('fixture.php', $source)]);
582        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
583        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
584        $contributions1 = $providers->contributions;
585        $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);
586        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
587        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
588        $snapshotId = $identity->id;
589        $input1 = $providers->input;
590        $parser = new Parser(new ParserConfig($target->version));
591        $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));
592        $program1 = new Index($snapshotId, $trees);
593        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
594        $snapshotId1 = 'test';
595        $input2 = new Sources([new Source('fixture.php', $source)]);
596        $profile = $execution->configuration->target;
597        $parser1 = new Parser(new ParserConfig($profile->version));
598        $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));
599        $program = new Index($snapshotId1, $trees1);
600        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
601        $offsets = $join->offsets([['r1' => new Address('r0', Term::constant(0))], ['r1' => new Address('r0', Term::constant(1))]]);
602        self::assertNotNull($offsets);
603        self::assertSame('r0', $offsets['r1']->parent);
604        self::assertTrue((new Lattice())->contains($offsets['r1']->key ?? Term::constant(null), Term::constant(1)));
605        self::assertNull($join->offsets([['r1' => new Address('r0', null)], ['r1' => new Address('r0', Term::constant(1))]]));
606        self::assertNull($join->offsets([['r1' => new Address('r0', null)], ['r1' => new Address('r9', null)]]));
607    }
608    public function testLoopsKeepOnlyBookkeepingBothPathsShare(): void
609    {
610        $source = '<?php function target(){return 1;}';
611        $budget = new Budget();
612        $configuration = new DeriverConfig();
613        $contributions = new Contributions();
614        $input = new Sources([new Source('fixture.php', $source)]);
615        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
616        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
617        $contributions1 = $providers->contributions;
618        $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);
619        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
620        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
621        $snapshotId = $identity->id;
622        $input1 = $providers->input;
623        $parser = new Parser(new ParserConfig($target->version));
624        $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));
625        $program1 = new Index($snapshotId, $trees);
626        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
627        $snapshotId1 = 'test';
628        $input2 = new Sources([new Source('fixture.php', $source)]);
629        $profile = $execution->configuration->target;
630        $parser1 = new Parser(new ParserConfig($profile->version));
631        $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));
632        $program = new Index($snapshotId1, $trees1);
633        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
634        $first = Term::constant(1);
635        $second = Term::constant(2);
636        $base = new State();
637        $base->block = 2;
638        $base->previous = 1;
639        $base->memory->write($base->local('table'), Term::constant('kept'));
640        $base->local('sql');
641        $a1 = $base->fork();
642        $b1 = $base->fork();
643        $a1->memory->write($a1->local('sql'), $first);
644        $b1->memory->write($b1->local('sql'), $second);
645        [$a, $b] = [$a1, $b1];
646        $a->loopGuards = [5 => ['flag' => true, 'other' => true], 6 => ['x' => true]];
647        $b->loopGuards = [5 => ['flag' => false, 'other' => true]];
648        $a->approximations = [5 => ['cell:1' => Term::constant(1)]];
649        $b->approximations = [5 => ['cell:1' => Term::constant(2)]];
650        $a->stableHeader = 5;
651        $join->loops($a, $b);
652        self::assertSame([5 => ['other' => true]], $a->loopGuards);
653        self::assertSame([], $a->approximations);
654        self::assertNull($a->stableHeader);
655    }
656    public function testValuesKeepObjectRecordsExact(): void
657    {
658        $source = '<?php function target(){return 1;}';
659        $budget = new Budget();
660        $configuration = new DeriverConfig();
661        $contributions = new Contributions();
662        $input = new Sources([new Source('fixture.php', $source)]);
663        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
664        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
665        $contributions1 = $providers->contributions;
666        $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);
667        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
668        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
669        $snapshotId = $identity->id;
670        $input1 = $providers->input;
671        $parser = new Parser(new ParserConfig($target->version));
672        $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));
673        $program1 = new Index($snapshotId, $trees);
674        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
675        $snapshotId1 = 'test';
676        $input2 = new Sources([new Source('fixture.php', $source)]);
677        $profile = $execution->configuration->target;
678        $parser1 = new Parser(new ParserConfig($profile->version));
679        $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));
680        $program = new Index($snapshotId1, $trees1);
681        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
682        self::assertNull($join->values([['object:1' => Term::array([])], ['object:1' => Term::array(['p' => Term::constant(5)])]], true, true));
683        self::assertNotNull($join->values([['cell:1' => Term::array([])], ['cell:1' => Term::array(['p' => Term::constant(5)])]], true, true));
684        self::assertNotNull($join->values([['object:1' => Term::array([])], ['object:1' => Term::array([])]], true, true));
685    }
686    public function testPlainRejectsNestedIdentities(): void
687    {
688        $source = '<?php function target(){return 1;}';
689        $budget = new Budget();
690        $configuration = new DeriverConfig();
691        $contributions = new Contributions();
692        $input = new Sources([new Source('fixture.php', $source)]);
693        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
694        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
695        $contributions1 = $providers->contributions;
696        $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);
697        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
698        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
699        $snapshotId = $identity->id;
700        $input1 = $providers->input;
701        $parser = new Parser(new ParserConfig($target->version));
702        $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));
703        $program1 = new Index($snapshotId, $trees);
704        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
705        $snapshotId1 = 'test';
706        $input2 = new Sources([new Source('fixture.php', $source)]);
707        $profile = $execution->configuration->target;
708        $parser1 = new Parser(new ParserConfig($profile->version));
709        $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));
710        $program = new Index($snapshotId1, $trees1);
711        $join = new PathJoin(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
712        self::assertTrue($join->plain(Term::array([Term::constant(1), Term::parameter('x')])));
713        self::assertFalse($join->plain(Term::array([Term::constant(1), new Term(TermKind::Cell, 'cell:1')])));
714        self::assertTrue($join->plain(new Term(TermKind::Uninitialized)));
715    }
716}
717