packages/deriver/tests/Unit/Deriver/Evaluator/DemandTest.php

1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator;
6
7use Deriver\Deriver\Candidate;
8use Deriver\Deriver\Candidate\CandidateStatus;
9use Deriver\Deriver\Contributions;
10use Deriver\Deriver\DeriverConfig;
11use Deriver\Deriver\Evaluator\Call\Evaluators;
12use Deriver\Deriver\Evaluator\Call\Registrations;
13use Deriver\Deriver\Evaluator\EvaluatorConfig;
14use Deriver\Deriver\Evaluator\Evidence\Frontier;
15use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
16use Deriver\Deriver\Evaluator\Projection;
17use Deriver\Deriver\Evaluator\Request\Budget;
18use Deriver\Deriver\Evaluator\Request\EntryPoint;
19use Deriver\Deriver\Evaluator\Request\QueryScope;
20use Deriver\Deriver\Evaluator\Request\ReturnQuery;
21use Deriver\Deriver\Evaluator\Request\TupleQuery;
22use Deriver\Deriver\Evaluator\Request\ValueQuery;
23use Deriver\Deriver\Evaluator\Scalar\Identity;
24use Deriver\Deriver\Evaluator\TargetProfile;
25use Deriver\Deriver\Evaluator\Term;
26use Deriver\Deriver\Lowerer\Declaration\Index;
27use Deriver\Deriver\MicroEvaluatorConversion;
28use Deriver\Deriver\Parser\Node\File;
29use Deriver\Deriver\Parser\Parser;
30use Deriver\Deriver\Parser\ParserConfig;
31use Deriver\Deriver\Providers\ProviderExpansion;
32use Deriver\Deriver\Publication\CandidateQuery;
33use Deriver\Deriver\Resolver\Resolver;
34use Deriver\Deriver\Selection\CallObservations;
35use Deriver\Deriver\SnapshotIdentity;
36use Deriver\Deriver\Sources\Path;
37use Deriver\Deriver\Sources\Source;
38use Deriver\Deriver\Sources\SourceRef;
39use Deriver\Deriver\Sources\Sources;
40use PHPUnit\Framework\Attributes\CoversNothing;
41use PHPUnit\Framework\TestCase;
42use WeakMap;
43
44#[CoversNothing]
45final class DemandTest extends TestCase
46{
47    public function testFramesKeepExplicitBindingsOutOfOtherQueries(): void
48    {
49        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . 'function target($x){observe($x);}')]);
50        $prepared1Configuration = new DeriverConfig();
51        $prepared1Contributions = new Contributions();
52        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
53        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
54        $contributions = $prepared1Providers->contributions;
55        $prepared1Evaluators = new Evaluators(new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision), new Registrations($contributions->evaluators, $contributions->providers, $contributions->domains, $contributions->intrinsics, $contributions->stateSlots), $prepared1Configuration->standardEvaluators);
56        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
57        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
58        $snapshotId = $prepared1Identity->id;
59        $input = $prepared1Providers->input;
60        $parser = new Parser(new ParserConfig($prepared1Target->version));
61        $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input, false));
62        $prepared1Program = new Index($snapshotId, $trees);
63        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
64        $s = new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot);
65        $site = (new CallObservations($prepared1Program, $prepared1Evaluators))->find('observe')[0];
66        $scope = QueryScope::fromEntrypoints([new EntryPoint('target', [Term::constant(3)])]);
67        $r = $s->derive(new ValueQuery($site->argument(0), scope: $scope));
68        $slot = 'value';
69        self::assertSame([3], array_map(static fn (Term $value) => $value->native(), array_map(static fn ($candidate): Term => $candidate->result instanceof Term ? $candidate->result : Term::fromNative($candidate->result), iterator_to_array($r, false))));
70        $result = $s->derive(new ValueQuery((new CallObservations($prepared1Program, $prepared1Evaluators))->find('observe')[0]->argument(0)));
71        $frontiers = array_reduce(iterator_to_array($result, false), static function (array $frontiers, Candidate $candidate1): array {
72            if ($candidate1->result instanceof Term) {
73                $graph = $candidate1->result;
74                $snapshot = $candidate1->evidence[0]->snapshot;
75                $pending = [$graph];
76                $seen = new WeakMap();
77                $frontiers1 = [];
78                while ($pending !== []) {
79                    $node = array_pop($pending);
80                    if (isset($seen[$node])) {
81                        continue;
82                    }
83                    $seen[$node] = true;
84                    array_push($pending, ...array_values($node->operands));
85                    $reason = $node->attributes['reason'] ?? null;
86                    if (!is_string($reason)) {
87                        continue;
88                    }
89                    $source1 = new SourceRef($snapshot->id, (string) ($node->attributes['source'] ?? ''), (int) ($node->attributes['start'] ?? 0), (int) ($node->attributes['end'] ?? $node->attributes['start'] ?? 0));
90                    $identity = (string) ($node->attributes['identity'] ?? (new Identity())->key($node));
91                    $frontiers1[$identity] = new Frontier($reason, $source1, (string) $node->literal, ['value'], [$identity], $node, $reason);
92                }
93                array_push($frontiers, ...array_values($frontiers1));
94            }
95            return $frontiers;
96        }, []);
97        self::assertNotEmpty($frontiers);
98    }
99    public function testObserveProjectsADemandedArrayEntry(): void
100    {
101        $prepared2Input = new Sources([new Source('candidate.php', '<?php ' . 'function target(){observe([1,2]);}')]);
102        $prepared2Configuration = new DeriverConfig();
103        $prepared2Contributions = new Contributions();
104        $prepared2Target = new TargetProfile($prepared2Configuration->phpVersion, $prepared2Configuration->integerBits, $prepared2Configuration->floatPrecision);
105        $prepared2Providers = new ProviderExpansion($prepared2Input, array_map(Term::fromNative(...), $prepared2Configuration->environment), $prepared2Contributions);
106        $contributions = $prepared2Providers->contributions;
107        $prepared2Evaluators = new Evaluators(new TargetProfile($prepared2Configuration->phpVersion, $prepared2Configuration->integerBits, $prepared2Configuration->floatPrecision), new Registrations($contributions->evaluators, $contributions->providers, $contributions->domains, $contributions->intrinsics, $contributions->stateSlots), $prepared2Configuration->standardEvaluators);
108        $prepared2Settings = new EvaluatorConfig($prepared2Target, $prepared2Providers->environment, $prepared2Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared2Configuration->evaluators));
109        $prepared2Identity = new SnapshotIdentity($prepared2Providers->input, $prepared2Settings, $prepared2Evaluators, $prepared2Providers->manifest);
110        $snapshotId = $prepared2Identity->id;
111        $input = $prepared2Providers->input;
112        $parser = new Parser(new ParserConfig($prepared2Target->version));
113        $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input, false));
114        $prepared2Program = new Index($snapshotId, $trees);
115        $prepared2Snapshot = $prepared2Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared2Program->diagnostics()));
116        $s = new CandidateQuery(new DefinitionIndex($prepared2Program), $prepared2Settings, $prepared2Evaluators, $prepared2Snapshot);
117        $r = $s->derive(new ValueQuery((new CallObservations($prepared2Program, $prepared2Evaluators))->find('observe')[0]->argument(0), new Projection([1])));
118        $slot = 'value';
119        self::assertSame([2], array_map(static fn (Term $value) => $value->native(), array_map(static fn ($candidate): Term => $candidate->result instanceof Term ? $candidate->result : Term::fromNative($candidate->result), iterator_to_array($r, false))));
120    }
121    public function testTupleKeepsTheSelectedNames(): void
122    {
123        $prepared3Input = new Sources([new Source('candidate.php', '<?php ' . 'function target(){observe(1,2);}')]);
124        $prepared3Configuration = new DeriverConfig();
125        $prepared3Contributions = new Contributions();
126        $prepared3Target = new TargetProfile($prepared3Configuration->phpVersion, $prepared3Configuration->integerBits, $prepared3Configuration->floatPrecision);
127        $prepared3Providers = new ProviderExpansion($prepared3Input, array_map(Term::fromNative(...), $prepared3Configuration->environment), $prepared3Contributions);
128        $contributions = $prepared3Providers->contributions;
129        $prepared3Evaluators = new Evaluators(new TargetProfile($prepared3Configuration->phpVersion, $prepared3Configuration->integerBits, $prepared3Configuration->floatPrecision), new Registrations($contributions->evaluators, $contributions->providers, $contributions->domains, $contributions->intrinsics, $contributions->stateSlots), $prepared3Configuration->standardEvaluators);
130        $prepared3Settings = new EvaluatorConfig($prepared3Target, $prepared3Providers->environment, $prepared3Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared3Configuration->evaluators));
131        $prepared3Identity = new SnapshotIdentity($prepared3Providers->input, $prepared3Settings, $prepared3Evaluators, $prepared3Providers->manifest);
132        $snapshotId = $prepared3Identity->id;
133        $input = $prepared3Providers->input;
134        $parser = new Parser(new ParserConfig($prepared3Target->version));
135        $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input, false));
136        $prepared3Program = new Index($snapshotId, $trees);
137        $prepared3Snapshot = $prepared3Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared3Program->diagnostics()));
138        $s = new CandidateQuery(new DefinitionIndex($prepared3Program), $prepared3Settings, $prepared3Evaluators, $prepared3Snapshot);
139        $site = (new CallObservations($prepared3Program, $prepared3Evaluators))->find('observe')[0];
140        $r = $s->derive(new TupleQuery($site->beforeInvocation(), ['left' => $site->argument(0), 'right' => $site->argument(1)]));
141        self::assertIsArray($r[0]->result);
142        self::assertSame(['left', 'right'], array_keys($r[0]->result));
143    }
144    public function testReturnsRetainsConditionalImplementations(): void
145    {
146        $source = 'if($flag){function target(){return 1;}}else{function target(){return 2;}}';
147        $configuration = new DeriverConfig();
148        $budget = new Budget();
149        $contributions = new Contributions();
150        $prepared7Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
151        $prepared7Configuration = $configuration;
152        $prepared7Contributions = $contributions;
153        $prepared7Target = new TargetProfile($prepared7Configuration->phpVersion, $prepared7Configuration->integerBits, $prepared7Configuration->floatPrecision);
154        $prepared7Providers = new ProviderExpansion($prepared7Input, array_map(Term::fromNative(...), $prepared7Configuration->environment), $prepared7Contributions);
155        $contributions1 = $prepared7Providers->contributions;
156        $prepared7Evaluators = new Evaluators(new TargetProfile($prepared7Configuration->phpVersion, $prepared7Configuration->integerBits, $prepared7Configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $prepared7Configuration->standardEvaluators);
157        $prepared7Settings = new EvaluatorConfig($prepared7Target, $prepared7Providers->environment, $prepared7Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared7Configuration->evaluators));
158        $prepared7Identity = new SnapshotIdentity($prepared7Providers->input, $prepared7Settings, $prepared7Evaluators, $prepared7Providers->manifest);
159        $snapshotId = $prepared7Identity->id;
160        $input = $prepared7Providers->input;
161        $parser = new Parser(new ParserConfig($prepared7Target->version));
162        $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));
163        $prepared7Program = new Index($snapshotId, $trees);
164        $prepared7Snapshot = $prepared7Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared7Program->diagnostics()));
165        $result = (new CandidateQuery(new DefinitionIndex($prepared7Program), $prepared7Settings, $prepared7Evaluators, $prepared7Snapshot))->derive(new ReturnQuery('target', budget: $budget));
166        self::assertSame([1, 2], array_column(iterator_to_array($result, false), 'result'));
167    }
168    public function testProjectAppliesThePathOfTheTargetToTheSelectedValue(): void
169    {
170        $prepared4Input = new Sources([new Source('candidate.php', '<?php ' . 'function target(){observe([\'a\' => [1, 2]]);}')]);
171        $prepared4Configuration = new DeriverConfig();
172        $prepared4Contributions = new Contributions();
173        $prepared4Target = new TargetProfile($prepared4Configuration->phpVersion, $prepared4Configuration->integerBits, $prepared4Configuration->floatPrecision);
174        $prepared4Providers = new ProviderExpansion($prepared4Input, array_map(Term::fromNative(...), $prepared4Configuration->environment), $prepared4Contributions);
175        $contributions = $prepared4Providers->contributions;
176        $prepared4Evaluators = new Evaluators(new TargetProfile($prepared4Configuration->phpVersion, $prepared4Configuration->integerBits, $prepared4Configuration->floatPrecision), new Registrations($contributions->evaluators, $contributions->providers, $contributions->domains, $contributions->intrinsics, $contributions->stateSlots), $prepared4Configuration->standardEvaluators);
177        $prepared4Settings = new EvaluatorConfig($prepared4Target, $prepared4Providers->environment, $prepared4Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared4Configuration->evaluators));
178        $prepared4Identity = new SnapshotIdentity($prepared4Providers->input, $prepared4Settings, $prepared4Evaluators, $prepared4Providers->manifest);
179        $snapshotId = $prepared4Identity->id;
180        $input = $prepared4Providers->input;
181        $parser = new Parser(new ParserConfig($prepared4Target->version));
182        $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input, false));
183        $prepared4Program = new Index($snapshotId, $trees);
184        $prepared4Snapshot = $prepared4Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared4Program->diagnostics()));
185        $s = new CandidateQuery(new DefinitionIndex($prepared4Program), $prepared4Settings, $prepared4Evaluators, $prepared4Snapshot);
186        $r = $s->derive(new ValueQuery((new CallObservations($prepared4Program, $prepared4Evaluators))->find('observe')[0]->argument(0), new Projection(['a', 1])));
187        $slot = 'value';
188        self::assertSame([2], array_map(static fn (Term $value) => $value->native(), array_map(static fn ($candidate): Term => $candidate->result instanceof Term ? $candidate->result : Term::fromNative($candidate->result), iterator_to_array($r, false))));
189    }
190    public function testSelectKeepsAMissingSymbolAsAnUnexpandedReference(): void
191    {
192        $prepared5Input = new Sources([new Source('candidate.php', '<?php ' . 'function target(){return 1;}')]);
193        $prepared5Configuration = new DeriverConfig();
194        $prepared5Contributions = new Contributions();
195        $prepared5Target = new TargetProfile($prepared5Configuration->phpVersion, $prepared5Configuration->integerBits, $prepared5Configuration->floatPrecision);
196        $prepared5Providers = new ProviderExpansion($prepared5Input, array_map(Term::fromNative(...), $prepared5Configuration->environment), $prepared5Contributions);
197        $contributions = $prepared5Providers->contributions;
198        $prepared5Evaluators = new Evaluators(new TargetProfile($prepared5Configuration->phpVersion, $prepared5Configuration->integerBits, $prepared5Configuration->floatPrecision), new Registrations($contributions->evaluators, $contributions->providers, $contributions->domains, $contributions->intrinsics, $contributions->stateSlots), $prepared5Configuration->standardEvaluators);
199        $prepared5Settings = new EvaluatorConfig($prepared5Target, $prepared5Providers->environment, $prepared5Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared5Configuration->evaluators));
200        $prepared5Identity = new SnapshotIdentity($prepared5Providers->input, $prepared5Settings, $prepared5Evaluators, $prepared5Providers->manifest);
201        $snapshotId = $prepared5Identity->id;
202        $input = $prepared5Providers->input;
203        $parser = new Parser(new ParserConfig($prepared5Target->version));
204        $trees = array_map(static fn (Source $source): File => (new Resolver())->resolve($parser->parse(new Source(Path::normalize($source->path), $source->contents, $source->declarationsOnly))), iterator_to_array($input, false));
205        $prepared5Program = new Index($snapshotId, $trees);
206        $prepared5Snapshot = $prepared5Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared5Program->diagnostics()));
207        $r = (new CandidateQuery(new DefinitionIndex($prepared5Program), $prepared5Settings, $prepared5Evaluators, $prepared5Snapshot))->derive(new ReturnQuery('target'));
208        $slot = 'value';
209        self::assertSame([1], array_map(static fn (Term $value) => $value->native(), array_map(static fn ($candidate): Term => $candidate->result instanceof Term ? $candidate->result : Term::fromNative($candidate->result), iterator_to_array($r, false))));
210        $prepared6Input = new Sources([new Source('candidate.php', '<?php ' . 'function target(){return missing();}')]);
211        $prepared6Configuration = new DeriverConfig();
212        $prepared6Contributions = new Contributions();
213        $prepared6Target = new TargetProfile($prepared6Configuration->phpVersion, $prepared6Configuration->integerBits, $prepared6Configuration->floatPrecision);
214        $prepared6Providers = new ProviderExpansion($prepared6Input, array_map(Term::fromNative(...), $prepared6Configuration->environment), $prepared6Contributions);
215        $contributions1 = $prepared6Providers->contributions;
216        $prepared6Evaluators = new Evaluators(new TargetProfile($prepared6Configuration->phpVersion, $prepared6Configuration->integerBits, $prepared6Configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $prepared6Configuration->standardEvaluators);
217        $prepared6Settings = new EvaluatorConfig($prepared6Target, $prepared6Providers->environment, $prepared6Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared6Configuration->evaluators));
218        $prepared6Identity = new SnapshotIdentity($prepared6Providers->input, $prepared6Settings, $prepared6Evaluators, $prepared6Providers->manifest);
219        $snapshotId1 = $prepared6Identity->id;
220        $input1 = $prepared6Providers->input;
221        $parser1 = new Parser(new ParserConfig($prepared6Target->version));
222        $trees1 = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser1->parse(new Source(Path::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
223        $prepared6Program = new Index($snapshotId1, $trees1);
224        $prepared6Snapshot = $prepared6Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared6Program->diagnostics()));
225        $missing = (new CandidateQuery(new DefinitionIndex($prepared6Program), $prepared6Settings, $prepared6Evaluators, $prepared6Snapshot))->derive(new ReturnQuery('target'));
226        self::assertSame(CandidateStatus::Partials, $missing[0]->type);
227    }    public function testPathObservationPrunesAnExcludedArgumentSite(): void
228    {
229        $source = 'function foo($x){return $x;}function bar(){if(false){foo(7);}}';
230        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('fixture.php', '<?php ' . $source)));
231        $index = new DefinitionIndex(new Index('fixture', [$tree]));
232        $settings = new EvaluatorConfig(evaluators: array_map((new MicroEvaluatorConversion())->convert(...), \Deriver\Deriver\BranchEvaluators::truth()));
233        $engine = new \Deriver\Deriver\Evaluator\Evaluator(new \Deriver\Deriver\Evaluator\Scope\Context($index, $settings, new Evaluators($settings->target), new Budget()));
234        [$caller, $call] = (new \Deriver\Deriver\Evaluator\Invocation\Path\Callers($index))->sites('foo', 'bar')[0];
235        $engine->context->callPath = new \Deriver\Deriver\Evaluator\Request\CallPath(['bar'], true);
236        $target = new \Deriver\Deriver\Evaluator\Target('bar', \Deriver\Deriver\Evaluator\TargetKind::Value, $call->arguments[0]->register, register: $call->arguments[0]->register);
237        $value = (new \Deriver\Deriver\Evaluator\Demand($engine))->pathObservation(new \Deriver\Deriver\Evaluator\Scope\Frame($caller, 'bar'), $target);
238        self::assertSame(\Deriver\Deriver\Evaluator\TermKind::Choice, $value->kind);
239        self::assertSame([], $value->operands);
240    }
241
242
243}
244