packages/deriver/tests/Unit/Deriver/Evaluator/Index/DefinitionIndexTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Index;
6
7use Deriver\Deriver\Contributions;
8use Deriver\Deriver\DeriverConfig;
9use Deriver\Deriver\Evaluator\Call\Evaluators;
10use Deriver\Deriver\Evaluator\Call\Registrations;
11use Deriver\Deriver\Evaluator\Evaluator;
12use Deriver\Deriver\Evaluator\EvaluatorConfig;
13use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
14use Deriver\Deriver\Evaluator\Request\Budget;
15use Deriver\Deriver\Evaluator\Scope\Context;
16use Deriver\Deriver\Evaluator\Scope\Frame;
17use Deriver\Deriver\Evaluator\TargetProfile;
18use Deriver\Deriver\Evaluator\Term;
19use Deriver\Deriver\Lowerer\Declaration\Index;
20use Deriver\Deriver\MicroEvaluatorConversion;
21use Deriver\Deriver\Parser\Node\File;
22use Deriver\Deriver\Parser\Parser;
23use Deriver\Deriver\Parser\ParserConfig;
24use Deriver\Deriver\Providers\ProviderExpansion;
25use Deriver\Deriver\Resolver\Resolver;
26use Deriver\Deriver\Sources\Path;
27use Deriver\Deriver\Sources\Source;
28use Deriver\Deriver\Sources\Sources;
29use PHPUnit\Framework\Attributes\CoversNothing;
30use PHPUnit\Framework\TestCase;
31
32#[CoversNothing]
33final class DefinitionIndexTest extends TestCase
34{
35    public function testGraphDoesNotDeriveACompiledBody(): void
36    {
37        $source = 'function target($x){return $x+2;}';
38        $configuration = new DeriverConfig();
39        $budget = new Budget();
40        $contributions = new Contributions();
41        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
42        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
43        $contributions1 = $providers->contributions;
44        $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);
45        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
46        $snapshotId = 'test';
47        $input = $providers->input;
48        $parser = new Parser(new ParserConfig($target->version));
49        $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($input, false));
50        $program = new Index($snapshotId, $trees);
51        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
52        self::assertNotNull($e->context->index->graph('target'));
53        self::assertSame(0, $e->context->bodyExpansions);
54    }
55    public function testCallersKeepsAllMatchingSites(): void
56    {
57        $source = 'function target($x){return $x;}function caller(){target(1);target(2);}';
58        $configuration = new DeriverConfig();
59        $budget = new Budget();
60        $contributions = new Contributions();
61        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
62        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
63        $contributions1 = $providers->contributions;
64        $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);
65        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
66        $snapshotId = 'test';
67        $input = $providers->input;
68        $parser = new Parser(new ParserConfig($target->version));
69        $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($input, false));
70        $program = new Index($snapshotId, $trees);
71        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
72        self::assertCount(2, $e->context->index->callers('target'));
73    }
74    public function testTargetResolvesAnInheritedDeclaration(): void
75    {
76        $source = 'class A{function f(){return 1;}}class B extends A{function target(){return $this->f();}}';
77        $configuration = new DeriverConfig();
78        $budget = new Budget();
79        $contributions = new Contributions();
80        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
81        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
82        $contributions1 = $providers->contributions;
83        $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);
84        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
85        $snapshotId = 'test';
86        $input = $providers->input;
87        $parser = new Parser(new ParserConfig($target->version));
88        $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($input, false));
89        $program = new Index($snapshotId, $trees);
90        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
91        $symbol = 'B::target';
92        $graph = $e->context->index->graph($symbol);
93        self::assertNotNull($graph);
94        $f = new Frame($graph, 'source:' . $symbol);
95        $operation = 'invoke-method';
96        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
97        self::assertNotNull($instruction);
98        self::assertSame('A::f', $e->context->index->target($f->graph, $instruction));
99    }
100    public function testLiteralKeepsAStringName(): void
101    {
102        $source = 'function target(){return "name";}';
103        $configuration = new DeriverConfig();
104        $budget = new Budget();
105        $contributions = new Contributions();
106        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
107        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
108        $contributions1 = $providers->contributions;
109        $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);
110        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
111        $snapshotId = 'test';
112        $input = $providers->input;
113        $parser = new Parser(new ParserConfig($target->version));
114        $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($input, false));
115        $program = new Index($snapshotId, $trees);
116        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
117        $symbol = 'target';
118        $graph = $e->context->index->graph($symbol);
119        self::assertNotNull($graph);
120        $f = new Frame($graph, 'source:' . $symbol);
121        $operation = 'constant';
122        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
123        self::assertNotNull($instruction);
124        self::assertSame('name', $e->context->index->literal($f->graph, $instruction->result));
125    }
126    public function testClassNameResolvesTheKnownParent(): void
127    {
128        $source = 'class A{}class B extends A{}';
129        $configuration = new DeriverConfig();
130        $budget = new Budget();
131        $contributions = new Contributions();
132        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
133        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
134        $contributions1 = $providers->contributions;
135        $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);
136        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
137        $snapshotId = 'test';
138        $input = $providers->input;
139        $parser = new Parser(new ParserConfig($target->version));
140        $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($input, false));
141        $program = new Index($snapshotId, $trees);
142        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
143        self::assertSame('A', $e->context->index->className('parent', 'B'));
144    }
145    public function testMethodDoesNotTreatAMissingAncestorAsAKnownMethod(): void
146    {
147        $source = 'class B extends Missing{}';
148        $configuration = new DeriverConfig();
149        $budget = new Budget();
150        $contributions = new Contributions();
151        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
152        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
153        $contributions1 = $providers->contributions;
154        $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);
155        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
156        $snapshotId = 'test';
157        $input = $providers->input;
158        $parser = new Parser(new ParserConfig($target->version));
159        $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($input, false));
160        $program = new Index($snapshotId, $trees);
161        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
162        self::assertSame('B::unknown', $e->context->index->method('B', 'unknown'));
163    }
164    public function testPropertyDoesNotMixUnrelatedDeclarations(): void
165    {
166        $source = 'class A{public $x=1;}class B{public $x=2;}';
167        $configuration = new DeriverConfig();
168        $budget = new Budget();
169        $contributions = new Contributions();
170        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
171        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
172        $contributions1 = $providers->contributions;
173        $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);
174        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
175        $snapshotId = 'test';
176        $input = $providers->input;
177        $parser = new Parser(new ParserConfig($target->version));
178        $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($input, false));
179        $program = new Index($snapshotId, $trees);
180        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
181        $a = $e->context->index->property('A', 'x');
182        self::assertNotNull($a);
183        self::assertSame('A', $a->className);
184        $b = $e->context->index->property('B', 'x');
185        self::assertNotNull($b);
186        self::assertSame('B', $b->className);
187    }
188    public function testTypeRetainsADeclaredReceiver(): void
189    {
190        $source = 'function target(PDO $db){return $db;}';
191        $configuration = new DeriverConfig();
192        $budget = new Budget();
193        $contributions = new Contributions();
194        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
195        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), 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        $snapshotId = 'test';
200        $input = $providers->input;
201        $parser = new Parser(new ParserConfig($target->version));
202        $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($input, false));
203        $program = new Index($snapshotId, $trees);
204        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
205        $symbol = 'target';
206        $graph = $e->context->index->graph($symbol);
207        self::assertNotNull($graph);
208        $f = new Frame($graph, 'source:' . $symbol);
209        $operation = 'read';
210        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
211        self::assertNotNull($instruction);
212        self::assertSame('PDO', $e->context->index->type($f->graph, $instruction->result));
213    }
214    public function testDeclaredPropertyResolvesItsDeclaringClass(): void
215    {
216        $source = 'class A{public $x=1;function target(){return $this->x;}}';
217        $configuration = new DeriverConfig();
218        $budget = new Budget();
219        $contributions = new Contributions();
220        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
221        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
222        $contributions1 = $providers->contributions;
223        $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);
224        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
225        $snapshotId = 'test';
226        $input = $providers->input;
227        $parser = new Parser(new ParserConfig($target->version));
228        $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($input, false));
229        $program = new Index($snapshotId, $trees);
230        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
231        $symbol = 'A::target';
232        $graph = $e->context->index->graph($symbol);
233        self::assertNotNull($graph);
234        $f = new Frame($graph, 'source:' . $symbol);
235        $operation = 'field-address';
236        $instruction = array_values(array_filter($f->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
237        self::assertNotNull($instruction);
238        $property = $e->context->index->declaredProperty($f->graph, $instruction);
239        self::assertNotNull($property);
240        self::assertSame('A', $property->className);
241    }
242    public function testWritesCollectsOnlyTheRequestedDeclaration(): void
243    {
244        $source = 'class A{public $x=1;function set(){$this->x=2;}}class B{public $x=3;function set(){$this->x=4;}}';
245        $configuration = new DeriverConfig();
246        $budget = new Budget();
247        $contributions = new Contributions();
248        $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
249        $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
250        $contributions1 = $providers->contributions;
251        $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);
252        $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
253        $snapshotId = 'test';
254        $input = $providers->input;
255        $parser = new Parser(new ParserConfig($target->version));
256        $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($input, false));
257        $program = new Index($snapshotId, $trees);
258        $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
259        $p = $e->context->index->property('A', 'x');
260        self::assertNotNull($p);
261        $writes = $e->context->index->writes($p);
262        self::assertCount(1, $writes);
263        self::assertSame('A::set', $writes[0][0]->body->symbol);
264    }
265}
266