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