packages/deriver/tests/Unit/Deriver/Evaluator/CallEvaluationTest.php
1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator;
6
7use Deriver\Deriver\Contributions;
8use Deriver\Deriver\DeriverConfig;
9use Deriver\Deriver\Evaluator\Call\CallDecision;
10use Deriver\Deriver\Evaluator\Call\CallDescription;
11use Deriver\Deriver\Evaluator\Call\CallDescriptor;
12use Deriver\Deriver\Evaluator\Call\CallEvaluator;
13use Deriver\Deriver\Evaluator\Call\Evaluators;
14use Deriver\Deriver\Evaluator\Call\Plan\Action;
15use Deriver\Deriver\Evaluator\Call\Plan\Expression;
16use Deriver\Deriver\Evaluator\Call\Plan\SemanticPlan;
17use Deriver\Deriver\Evaluator\Call\Registrations;
18use Deriver\Deriver\Evaluator\CallEvaluation;
19use Deriver\Deriver\Evaluator\Declaration\Parameter;
20use Deriver\Deriver\Evaluator\Declaration\Signature;
21use Deriver\Deriver\Evaluator\Enumeration\Choices;
22use Deriver\Deriver\Evaluator\Evaluator;
23use Deriver\Deriver\Evaluator\EvaluatorConfig;
24use Deriver\Deriver\Evaluator\Evidence\Frontier;
25use Deriver\Deriver\Evaluator\Index\BodyIndex;
26use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
27use Deriver\Deriver\Evaluator\Micro\Request;
28use Deriver\Deriver\Evaluator\Request\Budget;
29use Deriver\Deriver\Evaluator\Request\ReturnQuery;
30use Deriver\Deriver\Evaluator\Scalar\Identity;
31use Deriver\Deriver\Evaluator\Scope\Context;
32use Deriver\Deriver\Evaluator\Scope\Frame;
33use Deriver\Deriver\Evaluator\TargetProfile;
34use Deriver\Deriver\Evaluator\Term;
35use Deriver\Deriver\Lowerer\Declaration\Index;
36use Deriver\Deriver\MicroEvaluatorConversion;
37use Deriver\Deriver\Parser\Node\File;
38use Deriver\Deriver\Parser\Parser;
39use Deriver\Deriver\Parser\ParserConfig;
40use Deriver\Deriver\Providers\ProviderExpansion;
41use Deriver\Deriver\Publication\CandidateQuery;
42use Deriver\Deriver\Resolver\Resolver;
43use Deriver\Deriver\SnapshotIdentity;
44use Deriver\Deriver\Sources\Path;
45use Deriver\Deriver\Sources\Source;
46use Deriver\Deriver\Sources\Sources;
47use PHPUnit\Framework\Attributes\CoversNothing;
48use PHPUnit\Framework\TestCase;
49
50#[CoversNothing]
51final class CallEvaluationTest extends TestCase
52{
53 public function testEvaluateReplacesSourceWithoutDemandingUnusedInputs(): void
54 {
55 $value = Expression::literal(Term::constant(7));
56 $source = 'function heavy($input,$unused){return missing();}function target(){return heavy(missing(),missing());}';
57 $configuration = new DeriverConfig();
58 $budget = new Budget();
59 $contributions = new Contributions(evaluators: [new class ($value) implements CallEvaluator {
60 /**
61 * Captures the dependencies used by this component.
62 */
63 public function __construct(private readonly Expression $value)
64 {
65 }
66 /**
67 * Verifies descriptor.
68 */
69 public function descriptor(): CallDescriptor
70 {
71 return new CallDescriptor('heavy.override', '1', 'heavy', new Signature([new Parameter('input'), new Parameter('unused')]));
72 }
73 /**
74 * Verifies describe.
75 */
76 public function evaluate(CallDescription $call): CallDecision
77 {
78 return CallDecision::handled(new SemanticPlan([Action::returns($this->value)]));
79 }
80 }]);
81 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
82 $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
83 $contributions1 = $providers->contributions;
84 $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);
85 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
86 $snapshotId = 'test';
87 $input = $providers->input;
88 $parser = new Parser(new ParserConfig($target->version));
89 $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));
90 $program = new Index($snapshotId, $trees);
91 $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
92 $symbol = 'target';
93 $graph = $e->context->index->graph($symbol);
94 self::assertNotNull($graph);
95 $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
96 $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
97 [$value1, $_] = $alternative;
98 $values[(new Identity())->key($value1)] = $value1;
99 return $values;
100 }, []);
101 self::assertCount(1, $values);
102 $resolvedValue = array_values($values)[0];
103 self::assertInstanceOf(Term::class, $resolvedValue);
104 self::assertSame(7, $resolvedValue->native());
105 self::assertSame(0, $e->context->bodyExpansions);
106 }
107 public function testEvaluateSelectsBuiltInFunctionSemantics(): void
108 {
109 $source = 'function target(){return strlen("abc");}';
110 $configuration = new DeriverConfig();
111 $budget = new Budget();
112 $contributions = new Contributions();
113 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
114 $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
115 $contributions1 = $providers->contributions;
116 $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);
117 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
118 $snapshotId = 'test';
119 $input = $providers->input;
120 $parser = new Parser(new ParserConfig($target->version));
121 $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));
122 $program = new Index($snapshotId, $trees);
123 $engine = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
124 $symbol = 'target';
125 $graph = $engine->context->index->graph($symbol);
126 self::assertNotNull($graph);
127 $frame = new Frame($graph, 'source:' . $symbol);
128 $operation = 'invoke';
129 $instruction = array_values(array_filter($frame->graph->definitions, static fn ($instruction): bool => $instruction->operation === $operation))[0] ?? null;
130 self::assertNotNull($instruction);
131 $request = new Request($engine, $frame, $instruction, 20, 'strlen');
132 $body = (new CallEvaluation($engine))->evaluate($request);
133 self::assertInstanceOf(BodyIndex::class, $body);
134 }
135 public function testActualBindsNamedArguments(): void
136 {
137 $source = 'function helper($a,$b){return $b;}function target(){return helper(b:7,a:9);}';
138 $configuration = new DeriverConfig();
139 $budget = new Budget();
140 $contributions = new Contributions();
141 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
142 $providers = new ProviderExpansion(new Sources([new Source('candidate.php', '<?php ' . $source)]), array_map(Term::fromNative(...), $configuration->environment), $contributions);
143 $contributions1 = $providers->contributions;
144 $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);
145 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
146 $snapshotId = 'test';
147 $input = $providers->input;
148 $parser = new Parser(new ParserConfig($target->version));
149 $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));
150 $program = new Index($snapshotId, $trees);
151 $e = new Evaluator(new Context(new DefinitionIndex($program), $settings, $evaluators, $budget));
152 $symbol = 'target';
153 $graph = $e->context->index->graph($symbol);
154 self::assertNotNull($graph);
155 $alternatives = (new Choices())->alternatives($e->returns(new Frame($graph, 'source:' . $symbol), 64));
156 $values = array_reduce(iterator_to_array($alternatives, false), static function (array $values, array $alternative): array {
157 [$value, $_] = $alternative;
158 $values[(new Identity())->key($value)] = $value;
159 return $values;
160 }, []);
161 self::assertCount(1, $values);
162 $resolvedValue = array_values($values)[0];
163 self::assertInstanceOf(Term::class, $resolvedValue);
164 self::assertSame(7, $resolvedValue->native());
165 }
166 public function testDemandUsesActualBindingsBeforeSelectingAPlan(): void
167 {
168 $source = 'function target(){return count([1,2,3]);}';
169 $configuration = new DeriverConfig();
170 $budget = new Budget();
171 $contributions = new Contributions();
172 $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
173 $prepared1Configuration = $configuration;
174 $prepared1Contributions = $contributions;
175 $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
176 $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
177 $contributions1 = $prepared1Providers->contributions;
178 $prepared1Evaluators = new Evaluators(new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $prepared1Configuration->standardEvaluators);
179 $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
180 $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
181 $snapshotId = $prepared1Identity->id;
182 $input = $prepared1Providers->input;
183 $parser = new Parser(new ParserConfig($prepared1Target->version));
184 $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));
185 $prepared1Program = new Index($snapshotId, $trees);
186 $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
187 self::assertSame(3, (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget))[0]->result);
188 }
189}
190