packages/deriver/tests/Unit/Deriver/Evaluator/Execution/Offset/ReaderTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution\Offset;
6
7use Deriver\Deriver\Contributions;
8use Deriver\Deriver\DeriverConfig;
9use Deriver\Deriver\Evaluator\Call\BoundInstruction;
10use Deriver\Deriver\Evaluator\Call\Evaluators;
11use Deriver\Deriver\Evaluator\Call\Extensions;
12use Deriver\Deriver\Evaluator\Call\Registrations;
13use Deriver\Deriver\Evaluator\Call\StateRegistry;
14use Deriver\Deriver\Evaluator\EvaluatorConfig;
15use Deriver\Deriver\Evaluator\Evidence\Frontier;
16use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
17use Deriver\Deriver\Evaluator\Execution\Context;
18use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
19use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
20use Deriver\Deriver\Evaluator\Execution\Offset\Reader;
21use Deriver\Deriver\Evaluator\Execution\Offset\Strings;
22use Deriver\Deriver\Evaluator\Execution\State;
23use Deriver\Deriver\Evaluator\Request\Budget;
24use Deriver\Deriver\Evaluator\Request\QueryScope;
25use Deriver\Deriver\Evaluator\Request\ReturnQuery;
26use Deriver\Deriver\Evaluator\Scalar\Arrays;
27use Deriver\Deriver\Evaluator\Scalar\IntegerConversion;
28use Deriver\Deriver\Evaluator\Scalar\Operations;
29use Deriver\Deriver\Evaluator\TargetProfile;
30use Deriver\Deriver\Evaluator\Term;
31use Deriver\Deriver\Evaluator\TermKind;
32use Deriver\Deriver\Execution\QueryExecution;
33use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
34use Deriver\Deriver\Lowerer\Declaration\CallableSource;
35use Deriver\Deriver\Lowerer\Declaration\ConstantSignatures;
36use Deriver\Deriver\Lowerer\Declaration\Index;
37use Deriver\Deriver\Lowerer\Declaration\Indexer;
38use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassDeclaration;
39use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
40use Deriver\Deriver\Lowerer\ExpressionLowering;
41use Deriver\Deriver\Lowerer\Graph\BasicBlock;
42use Deriver\Deriver\Lowerer\Graph\CallableGraph;
43use Deriver\Deriver\Lowerer\Graph\Instruction;
44use Deriver\Deriver\Lowerer\Graph\Terminator;
45use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
46use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
47use Deriver\Deriver\Lowerer\InstructionEmitter;
48use Deriver\Deriver\Lowerer\Lowerer;
49use Deriver\Deriver\Lowerer\LoweringScope;
50use Deriver\Deriver\Lowerer\StatementLowering;
51use Deriver\Deriver\MicroEvaluatorConversion;
52use Deriver\Deriver\Parser\Node\File;
53use Deriver\Deriver\Parser\Parser;
54use Deriver\Deriver\Parser\ParserConfig;
55use Deriver\Deriver\Parser\TargetSyntax;
56use Deriver\Deriver\Providers\ProviderExpansion;
57use Deriver\Deriver\Resolver\AssignmentPatterns;
58use Deriver\Deriver\Resolver\ClassScope;
59use Deriver\Deriver\Resolver\ExpressionValidation;
60use Deriver\Deriver\Resolver\MagicContext;
61use Deriver\Deriver\Resolver\Resolver;
62use Deriver\Deriver\SnapshotIdentity;
63use Deriver\Deriver\Sources\LineMap;
64use Deriver\Deriver\Sources\Path;
65use Deriver\Deriver\Sources\Source;
66use Deriver\Deriver\Sources\SourceRef;
67use Deriver\Deriver\Sources\Sources;
68use Deriver\Exception\ParserException;
69use Deriver\Exception\ResolutionException;
70use PHPUnit\Framework\Attributes\CoversClass;
71use PHPUnit\Framework\Attributes\DataProvider;
72use PHPUnit\Framework\Attributes\Small;
73use PHPUnit\Framework\Attributes\UsesClass;
74use PHPUnit\Framework\TestCase;
75
76/**
77 * @covers \Deriver\Deriver\Evaluator\Execution\Offset\Reader
78 */
79#[UsesClass(Resolver::class)]
80#[UsesClass(ExpressionValidation::class)]
81#[UsesClass(ResolutionException::class)]
82#[UsesClass(ParserConfig::class)]
83#[UsesClass(ParserException::class)]
84#[CoversClass(Reader::class)]
85#[UsesClass(BasicBlock::class)]
86#[UsesClass(CallableGraph::class)]
87#[UsesClass(CallableIdentity::class)]
88#[UsesClass(ClassDeclaration::class)]
89#[UsesClass(Instruction::class)]
90#[UsesClass(Terminator::class)]
91#[UsesClass(Builtins::class)]
92#[UsesClass(Dispatch::class)]
93#[UsesClass(Completion::class)]
94#[UsesClass(Context::class)]
95#[UsesClass(Strings::class)]
96#[UsesClass(State::class)]
97#[UsesClass(Memory::class)]
98#[UsesClass(Extensions::class)]
99#[UsesClass(Evaluators::class)]
100#[UsesClass(StateRegistry::class)]
101#[UsesClass(DeriverConfig::class)]
102#[UsesClass(Sources::class)]
103#[UsesClass(Path::class)]
104#[UsesClass(Source::class)]
105#[UsesClass(TargetProfile::class)]
106#[UsesClass(Budget::class)]
107#[UsesClass(QueryScope::class)]
108#[UsesClass(ReturnQuery::class)]
109#[UsesClass(SourceRef::class)]
110#[UsesClass(Frontier::class)]
111#[UsesClass(Parser::class)]
112#[UsesClass(File::class)]
113#[UsesClass(Lowerer::class)]
114#[UsesClass(ExpressionLowering::class)]
115#[UsesClass(InstructionEmitter::class)]
116#[UsesClass(LoweringScope::class)]
117#[UsesClass(StatementLowering::class)]
118#[UsesClass(ConstantSignatures::class)]
119#[UsesClass(CallableSource::class)]
120#[UsesClass(Indexer::class)]
121#[UsesClass(Index::class)]
122#[UsesClass(Composition::class)]
123#[UsesClass(LineMap::class)]
124#[UsesClass(MagicContext::class)]
125#[UsesClass(AssignmentPatterns::class)]
126#[UsesClass(ClassScope::class)]
127#[UsesClass(TargetSyntax::class)]
128#[UsesClass(Arrays::class)]
129#[UsesClass(IntegerConversion::class)]
130#[UsesClass(Operations::class)]
131#[UsesClass(Term::class)]
132#[Small]
133final class ReaderTest extends TestCase
134{
135    public function testReadScalarOffsetsReturnNullWithAWarning(): void
136    {
137        $source = '<?php function target(){return 1;}';
138        $budget = new Budget();
139        $configuration = new DeriverConfig();
140        $contributions = new Contributions();
141        $input = new Sources([new Source('fixture.php', $source)]);
142        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
143        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
144        $contributions1 = $providers->contributions;
145        $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);
146        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
147        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
148        $snapshotId = $identity->id;
149        $input1 = $providers->input;
150        $parser = new Parser(new ParserConfig($target->version));
151        $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));
152        $program1 = new Index($snapshotId, $trees);
153        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
154        $snapshotId1 = 'test';
155        $input2 = new Sources([new Source('fixture.php', $source)]);
156        $profile = $execution->configuration->target;
157        $parser1 = new Parser(new ParserConfig($profile->version));
158        $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));
159        $program = new Index($snapshotId1, $trees1);
160        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
161        $body = $context->program->callable('target');
162        self::assertNotNull($body);
163        $instruction = new BoundInstruction('offset', 'write', $body->source, 'result', ['slot', 'rhs']);
164        $reader = new Reader($context);
165        self::assertNull($reader->read(Term::constant(4), Term::array([]), $instruction, new State())->native());
166        self::assertSame(['PHP_WARNING'], array_column(array_values($context->frontiers), 'code'));
167    }
168    public function testArrayRejectsIllegalKeysEvenInExistenceChecks(): void
169    {
170        $source = '<?php function target(){return 1;}';
171        $budget = new Budget();
172        $configuration = new DeriverConfig();
173        $contributions = new Contributions();
174        $input = new Sources([new Source('fixture.php', $source)]);
175        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
176        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
177        $contributions1 = $providers->contributions;
178        $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);
179        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
180        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
181        $snapshotId = $identity->id;
182        $input1 = $providers->input;
183        $parser = new Parser(new ParserConfig($target->version));
184        $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));
185        $program1 = new Index($snapshotId, $trees);
186        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
187        $snapshotId1 = 'test';
188        $input2 = new Sources([new Source('fixture.php', $source)]);
189        $profile = $execution->configuration->target;
190        $parser1 = new Parser(new ParserConfig($profile->version));
191        $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));
192        $program = new Index($snapshotId1, $trees1);
193        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
194        $body = $context->program->callable('target');
195        self::assertNotNull($body);
196        $instruction = new BoundInstruction('offset', 'write', $body->source, 'result', ['slot', 'rhs']);
197        $reader = new Reader($context);
198        self::assertSame('TypeError', $reader->array(Term::array([]), Term::array([]), $instruction, new State(), true)->literal);
199        self::assertSame(TermKind::Uninitialized, $reader->array(Term::array([]), Term::constant('absent'), $instruction, new State(), true)->kind);
200        self::assertSame([], $context->frontiers);
201    }
202    public function testArrayReadsOnlyIntegerHeadKeysOfAnUnknownMergeExactly(): void
203    {
204        $source = '<?php function target(){return 1;}';
205        $budget = new Budget();
206        $configuration = new DeriverConfig();
207        $contributions = new Contributions();
208        $input = new Sources([new Source('fixture.php', $source)]);
209        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
210        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
211        $contributions1 = $providers->contributions;
212        $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);
213        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
214        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
215        $snapshotId = $identity->id;
216        $input1 = $providers->input;
217        $parser = new Parser(new ParserConfig($target->version));
218        $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));
219        $program1 = new Index($snapshotId, $trees);
220        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
221        $snapshotId1 = 'test';
222        $input2 = new Sources([new Source('fixture.php', $source)]);
223        $profile = $execution->configuration->target;
224        $parser1 = new Parser(new ParserConfig($profile->version));
225        $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));
226        $program = new Index($snapshotId1, $trees1);
227        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
228        $body = $context->program->callable('target');
229        self::assertNotNull($body);
230        $instruction = new BoundInstruction('offset', 'read', $body->source, 'result', ['slot']);
231        $reader = new Reader($context);
232        $merge = (new Arrays())->merge(Term::fromNative(['id', 'name']), Term::parameter('x', 'array'));
233        self::assertSame('name', $reader->array($merge, Term::constant('1'), $instruction, new State(), false)->native());
234        self::assertSame(TermKind::ArrayRead, $reader->array($merge, Term::constant(2), $instruction, new State(), false)->kind);
235        $named = (new Arrays())->merge(Term::fromNative(['k' => 'id', 0 => 'name']), Term::parameter('x', 'array'));
236        self::assertSame(TermKind::ArrayRead, $reader->array($named, Term::constant(0), $instruction, new State(), false)->kind);
237        self::assertSame([], $context->frontiers);
238    }
239    public function testKeyTruncatesFloatKeysAndRecordsLossOfPrecision(): void
240    {
241        $source = '<?php function target(){return 1;}';
242        $budget = new Budget();
243        $configuration = new DeriverConfig();
244        $contributions = new Contributions();
245        $input = new Sources([new Source('fixture.php', $source)]);
246        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
247        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
248        $contributions1 = $providers->contributions;
249        $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);
250        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
251        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
252        $snapshotId = $identity->id;
253        $input1 = $providers->input;
254        $parser = new Parser(new ParserConfig($target->version));
255        $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));
256        $program1 = new Index($snapshotId, $trees);
257        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
258        $snapshotId1 = 'test';
259        $input2 = new Sources([new Source('fixture.php', $source)]);
260        $profile = $execution->configuration->target;
261        $parser1 = new Parser(new ParserConfig($profile->version));
262        $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));
263        $program = new Index($snapshotId1, $trees1);
264        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
265        $body = $context->program->callable('target');
266        self::assertNotNull($body);
267        $instruction = new BoundInstruction('offset', 'write', $body->source, 'result', ['slot', 'rhs']);
268        $key = (new Reader($context))->key(Term::constant(1.5), $instruction);
269        self::assertSame(1, $key->native());
270        self::assertSame(['PHP_WARNING'], array_column(array_values($context->frontiers), 'code'));
271    }
272    public function testPlainObjectRecognizesArrayAccessImplementations(): void
273    {
274        $source = '<?php class A implements ArrayAccess{} function target(){}';
275        $budget = new Budget();
276        $configuration = new DeriverConfig();
277        $contributions = new Contributions();
278        $input = new Sources([new Source('fixture.php', $source)]);
279        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
280        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
281        $contributions1 = $providers->contributions;
282        $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);
283        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
284        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
285        $snapshotId = $identity->id;
286        $input1 = $providers->input;
287        $parser = new Parser(new ParserConfig($target->version));
288        $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));
289        $program1 = new Index($snapshotId, $trees);
290        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
291        $snapshotId1 = 'test';
292        $input2 = new Sources([new Source('fixture.php', $source)]);
293        $profile = $execution->configuration->target;
294        $parser1 = new Parser(new ParserConfig($profile->version));
295        $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));
296        $program = new Index($snapshotId1, $trees1);
297        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
298        $reader = new Reader($context);
299        self::assertTrue($reader->plainObject(new Term(TermKind::Object, 'a', attributes: ['class' => 'stdClass'])));
300        self::assertFalse($reader->plainObject(new Term(TermKind::Object, 'b', attributes: ['class' => 'A'])));
301        self::assertTrue($reader->plainObject(new Term(TermKind::Closure, 'c')));
302    }
303    #[DataProvider('providerScalarPatterns')]
304    public function testReadTreatsScalarPatternEntriesAsNullWithoutOffsetDiagnostics(Term $container): void
305    {
306        $source1 = '<?php function target(){return 1;}';
307        $budget = new Budget();
308        $configuration = new DeriverConfig();
309        $contributions = new Contributions();
310        $input = new Sources([new Source('fixture.php', $source1)]);
311        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
312        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
313        $contributions1 = $providers->contributions;
314        $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);
315        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
316        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
317        $snapshotId = $identity->id;
318        $input1 = $providers->input;
319        $parser = new Parser(new ParserConfig($target->version));
320        $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
321        $program1 = new Index($snapshotId, $trees);
322        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
323        $snapshotId1 = 'test';
324        $input2 = new Sources([new Source('fixture.php', $source1)]);
325        $profile = $execution->configuration->target;
326        $parser1 = new Parser(new ParserConfig($profile->version));
327        $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
328        $program = new Index($snapshotId1, $trees1);
329        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
330        $source = new SourceRef('test', 'a.php', 0, 1);
331        $instruction = new BoundInstruction('i', 'array-read', $source, 'result', attributes: ['destructure' => true]);
332        $result = (new Reader($context))->read($container, Term::constant(1), $instruction, new State());
333        self::assertSame(TermKind::Constant, $result->kind);
334        self::assertNull($result->literal);
335        self::assertSame([], $context->frontiers);
336    }
337    /**
338     * @return iterable<string,array{Term}>
339     */
340    public static function providerScalarPatterns(): iterable
341    {
342        yield 'null' => [Term::constant(null)];
343        yield 'integer' => [Term::constant(42)];
344        yield 'float' => [Term::constant(1.5)];
345        yield 'true' => [Term::constant(true)];
346        yield 'false' => [Term::constant(false)];
347        yield 'string' => [Term::constant('abc')];
348        yield 'uninitialized' => [new Term(TermKind::Uninitialized)];
349    }
350}
351