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