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