packages/deriver/tests/Unit/Deriver/Evaluator/Execution/Constant/ClassNamesTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution\Constant;
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\Constant\ClassNames;
18use Deriver\Deriver\Evaluator\Execution\Context;
19use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
20use Deriver\Deriver\Evaluator\Execution\State;
21use Deriver\Deriver\Evaluator\Request\Budget;
22use Deriver\Deriver\Evaluator\Request\QueryScope;
23use Deriver\Deriver\Evaluator\Request\ReturnQuery;
24use Deriver\Deriver\Evaluator\TargetProfile;
25use Deriver\Deriver\Evaluator\Term;
26use Deriver\Deriver\Evaluator\TermKind;
27use Deriver\Deriver\Execution\QueryExecution;
28use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
29use Deriver\Deriver\Lowerer\Declaration\CallableSource;
30use Deriver\Deriver\Lowerer\Declaration\ConstantSignatures;
31use Deriver\Deriver\Lowerer\Declaration\Index;
32use Deriver\Deriver\Lowerer\Declaration\Indexer;
33use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassDeclaration;
34use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
35use Deriver\Deriver\Lowerer\ExpressionLowering;
36use Deriver\Deriver\Lowerer\Graph\BasicBlock;
37use Deriver\Deriver\Lowerer\Graph\CallableGraph;
38use Deriver\Deriver\Lowerer\Graph\Instruction;
39use Deriver\Deriver\Lowerer\Graph\Terminator;
40use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
41use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
42use Deriver\Deriver\Lowerer\InstructionEmitter;
43use Deriver\Deriver\Lowerer\Lowerer;
44use Deriver\Deriver\Lowerer\LoweringScope;
45use Deriver\Deriver\Lowerer\StatementLowering;
46use Deriver\Deriver\MicroEvaluatorConversion;
47use Deriver\Deriver\Parser\Node\File;
48use Deriver\Deriver\Parser\Parser;
49use Deriver\Deriver\Parser\ParserConfig;
50use Deriver\Deriver\Parser\TargetSyntax;
51use Deriver\Deriver\Providers\ProviderExpansion;
52use Deriver\Deriver\Resolver\AssignmentPatterns;
53use Deriver\Deriver\Resolver\ClassScope;
54use Deriver\Deriver\Resolver\ExpressionValidation;
55use Deriver\Deriver\Resolver\MagicContext;
56use Deriver\Deriver\Resolver\Resolver;
57use Deriver\Deriver\SnapshotIdentity;
58use Deriver\Deriver\Sources\LineMap;
59use Deriver\Deriver\Sources\Path;
60use Deriver\Deriver\Sources\Source;
61use Deriver\Deriver\Sources\SourceRef;
62use Deriver\Deriver\Sources\Sources;
63use Deriver\Exception\ParserException;
64use Deriver\Exception\ResolutionException;
65use PHPUnit\Framework\Attributes\CoversClass;
66use PHPUnit\Framework\Attributes\DataProvider;
67use PHPUnit\Framework\Attributes\Small;
68use PHPUnit\Framework\Attributes\UsesClass;
69use PHPUnit\Framework\TestCase;
70
71#[UsesClass(Resolver::class)]
72#[UsesClass(ExpressionValidation::class)]
73#[UsesClass(ResolutionException::class)]
74#[UsesClass(ParserConfig::class)]
75#[UsesClass(ParserException::class)]
76#[CoversClass(ClassNames::class)]
77#[UsesClass(BasicBlock::class)]
78#[UsesClass(CallableGraph::class)]
79#[UsesClass(CallableIdentity::class)]
80#[UsesClass(ClassDeclaration::class)]
81#[UsesClass(Instruction::class)]
82#[UsesClass(Terminator::class)]
83#[UsesClass(Builtins::class)]
84#[UsesClass(Dispatch::class)]
85#[UsesClass(Completion::class)]
86#[UsesClass(Context::class)]
87#[UsesClass(State::class)]
88#[UsesClass(Extensions::class)]
89#[UsesClass(Evaluators::class)]
90#[UsesClass(StateRegistry::class)]
91#[UsesClass(DeriverConfig::class)]
92#[UsesClass(Sources::class)]
93#[UsesClass(Path::class)]
94#[UsesClass(Source::class)]
95#[UsesClass(TargetProfile::class)]
96#[UsesClass(Budget::class)]
97#[UsesClass(QueryScope::class)]
98#[UsesClass(ReturnQuery::class)]
99#[UsesClass(SourceRef::class)]
100#[UsesClass(Parser::class)]
101#[UsesClass(File::class)]
102#[UsesClass(Lowerer::class)]
103#[UsesClass(ExpressionLowering::class)]
104#[UsesClass(InstructionEmitter::class)]
105#[UsesClass(LoweringScope::class)]
106#[UsesClass(StatementLowering::class)]
107#[UsesClass(ConstantSignatures::class)]
108#[UsesClass(CallableSource::class)]
109#[UsesClass(Indexer::class)]
110#[UsesClass(Index::class)]
111#[UsesClass(Composition::class)]
112#[UsesClass(LineMap::class)]
113#[UsesClass(MagicContext::class)]
114#[UsesClass(AssignmentPatterns::class)]
115#[UsesClass(ClassScope::class)]
116#[UsesClass(TargetSyntax::class)]
117#[UsesClass(Term::class)]
118#[Small]
119final class ClassNamesTest extends TestCase
120{
121    #[DataProvider('providerRuntimeValues')]
122    public function testRuntimeUsesOnlyEstablishedObjectClasses(Term $value, string $kind, string $literal): void
123    {
124        $source = '<?php function target(){return 1;}';
125        $budget = new Budget();
126        $configuration = new DeriverConfig();
127        $contributions = new Contributions();
128        $input = new Sources([new Source('fixture.php', $source)]);
129        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
130        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
131        $contributions1 = $providers->contributions;
132        $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);
133        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
134        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
135        $snapshotId = $identity->id;
136        $input1 = $providers->input;
137        $parser = new Parser(new ParserConfig($target->version));
138        $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));
139        $program1 = new Index($snapshotId, $trees);
140        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
141        $snapshotId1 = 'test';
142        $input2 = new Sources([new Source('fixture.php', $source)]);
143        $profile = $execution->configuration->target;
144        $parser1 = new Parser(new ParserConfig($profile->version));
145        $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));
146        $program = new Index($snapshotId1, $trees1);
147        $result = (new ClassNames(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->runtime($value);
148        self::assertSame($kind, $result->kind->value);
149        self::assertSame($literal, $result->literal);
150    }
151    /**
152     * @return iterable<string,array{Term,string,string}>
153     */
154    public static function providerRuntimeValues(): iterable
155    {
156        yield 'object' => [new Term(TermKind::Object, 'one', attributes: ['class' => 'Box']), 'constant', 'Box'];
157        yield 'enum' => [new Term(TermKind::Enum, 'E::A', attributes: ['class' => 'E']), 'constant', 'E'];
158        yield 'closure' => [new Term(TermKind::Closure, 'closure:a.php:1'), 'constant', 'Closure'];
159        yield 'string' => [Term::constant('Box'), 'throwable', 'TypeError'];
160        yield 'integer' => [Term::constant(1), 'throwable', 'TypeError'];
161        yield 'float' => [Term::constant(1.5), 'throwable', 'TypeError'];
162        yield 'boolean' => [Term::constant(true), 'throwable', 'TypeError'];
163        yield 'null' => [Term::constant(null), 'throwable', 'TypeError'];
164        yield 'array' => [Term::array([]), 'throwable', 'TypeError'];
165        yield 'uninitialized' => [new Term(TermKind::Uninitialized), 'throwable', 'TypeError'];
166        yield 'declared subtype' => [Term::parameter('x', 'Box'), 'opaque', 'UNSUPPORTED_LANGUAGE_FEATURE'];
167        yield 'unknown object' => [new Term(TermKind::Object, 'one'), 'opaque', 'UNSUPPORTED_LANGUAGE_FEATURE'];
168    }
169    #[DataProvider('providerClassReferences')]
170    public function testResolveDistinguishesLexicalNamesFromRuntimeStrings(Term $value, bool $literal, string $kind, string $expected): void
171    {
172        $source = '<?php class Base{}class Box extends Base{function value(){}}class Child extends Box{}function target(){}';
173        $budget = new Budget();
174        $configuration = new DeriverConfig();
175        $contributions = new Contributions();
176        $input = new Sources([new Source('fixture.php', $source)]);
177        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
178        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
179        $contributions1 = $providers->contributions;
180        $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);
181        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
182        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
183        $snapshotId = $identity->id;
184        $input1 = $providers->input;
185        $parser = new Parser(new ParserConfig($target->version));
186        $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));
187        $program1 = new Index($snapshotId, $trees);
188        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
189        $snapshotId1 = 'test';
190        $input2 = new Sources([new Source('fixture.php', $source)]);
191        $profile = $execution->configuration->target;
192        $parser1 = new Parser(new ParserConfig($profile->version));
193        $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));
194        $program = new Index($snapshotId1, $trees1);
195        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
196        $caller = $context->program->callable('Box::value');
197        self::assertNotNull($caller);
198        $state = new State();
199        $state->lateStaticClass = 'Child';
200        $resolved = (new ClassNames($context))->resolve($value, $literal, $caller, $state);
201        self::assertSame($kind, $resolved->kind->value);
202        self::assertSame($expected, $resolved->literal);
203    }
204    /**
205     * @return iterable<string,array{Term,bool,string,string}>
206     */
207    public static function providerClassReferences(): iterable
208    {
209        yield 'literal self' => [Term::constant('self'), true, 'constant', 'Box'];
210        yield 'literal parent' => [Term::constant('parent'), true, 'constant', 'Base'];
211        yield 'literal static' => [Term::constant('static'), true, 'constant', 'Child'];
212        yield 'literal unknown' => [Term::constant('Missing'), true, 'constant', 'Missing'];
213        yield 'runtime string' => [Term::constant('bOx'), false, 'constant', 'bOx'];
214        yield 'qualified runtime string' => [Term::constant('\Box'), false, 'constant', 'Box'];
215        yield 'runtime self' => [Term::constant('self'), false, 'throwable', 'Error'];
216        yield 'runtime parent' => [Term::constant('parent'), false, 'throwable', 'Error'];
217        yield 'runtime static' => [Term::constant('static'), false, 'throwable', 'Error'];
218        yield 'empty runtime name' => [Term::constant(''), false, 'throwable', 'Error'];
219        yield 'object' => [new Term(TermKind::Object, 'one', attributes: ['class' => 'Box']), false, 'constant', 'Box'];
220        yield 'enum' => [new Term(TermKind::Enum, 'E::A', attributes: ['class' => 'E']), false, 'constant', 'E'];
221        yield 'closure' => [new Term(TermKind::Closure, 'closure:a.php:1'), false, 'constant', 'Closure'];
222        yield 'array' => [Term::array([]), false, 'throwable', 'Error'];
223        yield 'integer' => [Term::constant(1), false, 'throwable', 'Error'];
224        yield 'uninitialized' => [new Term(TermKind::Uninitialized), false, 'throwable', 'Error'];
225        yield 'symbolic' => [Term::parameter('x'), false, 'opaque', 'UNSUPPORTED_LANGUAGE_FEATURE'];
226    }
227    public function testResolveRejectsRelativeNamesWithoutARuntimeScope(): void
228    {
229        $source = '<?php function target(){return 1;}';
230        $budget = new Budget();
231        $configuration = new DeriverConfig();
232        $contributions = new Contributions();
233        $input = new Sources([new Source('fixture.php', $source)]);
234        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
235        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
236        $contributions1 = $providers->contributions;
237        $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);
238        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
239        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
240        $snapshotId = $identity->id;
241        $input1 = $providers->input;
242        $parser = new Parser(new ParserConfig($target->version));
243        $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));
244        $program1 = new Index($snapshotId, $trees);
245        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
246        $snapshotId1 = 'test';
247        $input2 = new Sources([new Source('fixture.php', $source)]);
248        $profile = $execution->configuration->target;
249        $parser1 = new Parser(new ParserConfig($profile->version));
250        $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));
251        $program = new Index($snapshotId1, $trees1);
252        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
253        $caller = $context->program->callable('target');
254        self::assertNotNull($caller);
255        $result = (new ClassNames($context))->resolve(Term::constant('self'), true, $caller, new State());
256        self::assertSame(TermKind::Throwable, $result->kind);
257        self::assertSame('Error', $result->literal);
258    }
259    public function testCanonicalUsesCapturedAndSupportedNativeDeclarationSpelling(): void
260    {
261        $source = '<?php namespace N;class Box{}';
262        $budget = new Budget();
263        $configuration = new DeriverConfig();
264        $contributions = new Contributions();
265        $input = new Sources([new Source('fixture.php', $source)]);
266        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
267        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
268        $contributions1 = $providers->contributions;
269        $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);
270        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
271        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
272        $snapshotId = $identity->id;
273        $input1 = $providers->input;
274        $parser = new Parser(new ParserConfig($target->version));
275        $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));
276        $program1 = new Index($snapshotId, $trees);
277        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
278        $snapshotId1 = 'test';
279        $input2 = new Sources([new Source('fixture.php', $source)]);
280        $profile = $execution->configuration->target;
281        $parser1 = new Parser(new ParserConfig($profile->version));
282        $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));
283        $program = new Index($snapshotId1, $trees1);
284        $names = new ClassNames(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
285        self::assertSame('N\Box', $names->canonical('n\bOx'));
286        self::assertSame('ErrorException', $names->canonical('errorexception'));
287        self::assertSame('Closure', $names->canonical('cLoSuRe'));
288        self::assertNull($names->canonical('Missing'));
289    }
290    public function testObjectDoesNotTreatASubtypeBoundAsAnExactRuntimeClass(): void
291    {
292        $source = '<?php function target(){return 1;}';
293        $budget = new Budget();
294        $configuration = new DeriverConfig();
295        $contributions = new Contributions();
296        $input = new Sources([new Source('fixture.php', $source)]);
297        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
298        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
299        $contributions1 = $providers->contributions;
300        $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);
301        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
302        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
303        $snapshotId = $identity->id;
304        $input1 = $providers->input;
305        $parser = new Parser(new ParserConfig($target->version));
306        $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));
307        $program1 = new Index($snapshotId, $trees);
308        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
309        $snapshotId1 = 'test';
310        $input2 = new Sources([new Source('fixture.php', $source)]);
311        $profile = $execution->configuration->target;
312        $parser1 = new Parser(new ParserConfig($profile->version));
313        $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));
314        $program = new Index($snapshotId1, $trees1);
315        $names = new ClassNames(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
316        self::assertNull($names->object(Term::parameter('x', 'Box')));
317        self::assertNull($names->object(Term::constant('Box')));
318        self::assertSame('Closure', $names->object(new Term(TermKind::Closure, 'c')));
319        self::assertSame('Box', $names->object(new Term(TermKind::Object, 'one', attributes: ['class' => 'Box'])));
320    }
321    public function testRuntimeResolvesTheObjectClass(): void
322    {
323        $source = '<?php function target(){return 1;}';
324        $budget = new Budget();
325        $configuration = new DeriverConfig();
326        $contributions = new Contributions();
327        $input = new Sources([new Source('fixture.php', $source)]);
328        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
329        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
330        $contributions1 = $providers->contributions;
331        $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);
332        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
333        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
334        $snapshotId = $identity->id;
335        $input1 = $providers->input;
336        $parser = new Parser(new ParserConfig($target->version));
337        $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));
338        $program1 = new Index($snapshotId, $trees);
339        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
340        $snapshotId1 = 'test';
341        $input2 = new Sources([new Source('fixture.php', $source)]);
342        $profile = $execution->configuration->target;
343        $parser1 = new Parser(new ParserConfig($profile->version));
344        $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));
345        $program = new Index($snapshotId1, $trees1);
346        $names = new ClassNames(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
347        $value = new Term(TermKind::Object, 'one', attributes: ['class' => 'Box']);
348        $result = $names->runtime($value);
349        self::assertSame('Box', $result->literal);
350        self::assertSame('Box', $names->runtime(new Term(TermKind::Object, 'two', attributes: ['class' => 'Box']))->literal);
351    }
352    #[DataProvider('providerReceiverClasses')]
353    public function testReceiverPreservesInstanceBoundsAndStaticClassSyntax(Term $value, bool $static, bool $literal, string $kind, string $expected): void
354    {
355        $source = '<?php class Base{}class Box extends Base{function value(){}}class Child extends Box{}function target(){}';
356        $budget = new Budget();
357        $configuration = new DeriverConfig();
358        $contributions = new Contributions();
359        $input = new Sources([new Source('fixture.php', $source)]);
360        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
361        $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
362        $contributions1 = $providers->contributions;
363        $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);
364        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
365        $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
366        $snapshotId = $identity->id;
367        $input1 = $providers->input;
368        $parser = new Parser(new ParserConfig($target->version));
369        $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));
370        $program1 = new Index($snapshotId, $trees);
371        $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
372        $snapshotId1 = 'test';
373        $input2 = new Sources([new Source('fixture.php', $source)]);
374        $profile = $execution->configuration->target;
375        $parser1 = new Parser(new ParserConfig($profile->version));
376        $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));
377        $program = new Index($snapshotId1, $trees1);
378        $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
379        $caller = $context->program->callable('Box::value');
380        self::assertNotNull($caller);
381        $state = new State();
382        $state->lateStaticClass = 'Child';
383        $instruction = new BoundInstruction('call', 'invoke-static', $caller->source, 'result', attributes: ['literal-class' => $literal]);
384        $resolved = (new ClassNames($context))->receiver($caller, $instruction, $state, $value, $static);
385        self::assertSame($kind, $resolved->kind->value);
386        self::assertSame($expected, $resolved->literal);
387    }
388    /**
389     * @return iterable<string,array{Term,bool,bool,string,string}>
390     */
391    public static function providerReceiverClasses(): iterable
392    {
393        yield 'instance class' => [new Term(TermKind::Object, 'one', attributes: ['class' => 'Box']), false, false, 'constant', 'Box'];
394        yield 'instance subtype bound' => [Term::parameter('x', 'Box'), false, false, 'constant', 'Box'];
395        yield 'instance relative type' => [Term::parameter('x', 'self'), false, false, 'constant', 'Box'];
396        yield 'unknown instance' => [new Term(TermKind::Object, 'one'), false, false, 'constant', ''];
397        yield 'static object' => [new Term(TermKind::Object, 'one', attributes: ['class' => 'Box']), true, false, 'constant', 'Box'];
398        yield 'static runtime string' => [Term::constant('\bOx'), true, false, 'constant', 'bOx'];
399        yield 'static literal self' => [Term::constant('self'), true, true, 'constant', 'Box'];
400        yield 'static literal parent' => [Term::constant('parent'), true, true, 'constant', 'Base'];
401        yield 'static late bound' => [Term::constant('static'), true, true, 'constant', 'Child'];
402        yield 'static dynamic relative' => [Term::constant('self'), true, false, 'throwable', 'Error'];
403        yield 'invalid static type' => [Term::constant(1), true, false, 'throwable', 'Error'];
404        yield 'unknown static type' => [Term::parameter('x'), true, false, 'opaque', 'UNSUPPORTED_LANGUAGE_FEATURE'];
405    }
406}
407