packages/deriver/tests/Integration/ProjectionValueTest.php
1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Integration;
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\EvaluatorConfig;
12use Deriver\Deriver\Evaluator\Evidence\Frontier;
13use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
14use Deriver\Deriver\Evaluator\Projection;
15use Deriver\Deriver\Evaluator\Request\EntryPoint;
16use Deriver\Deriver\Evaluator\Request\QueryScope;
17use Deriver\Deriver\Evaluator\Request\ReturnQuery;
18use Deriver\Deriver\Evaluator\Request\StateQuery;
19use Deriver\Deriver\Evaluator\Request\ValueQuery;
20use Deriver\Deriver\Evaluator\TargetProfile;
21use Deriver\Deriver\Evaluator\Term;
22use Deriver\Deriver\Evaluator\TermKind;
23use Deriver\Deriver\Execution\QueryExecution;
24use Deriver\Deriver\Lowerer\Declaration\Index;
25use Deriver\Deriver\MicroEvaluatorConversion;
26use Deriver\Deriver\Parser\Node\File;
27use Deriver\Deriver\Parser\Parser;
28use Deriver\Deriver\Parser\ParserConfig;
29use Deriver\Deriver\Providers\ProviderExpansion;
30use Deriver\Deriver\Resolver\Resolver;
31use Deriver\Deriver\Selection\CallObservations;
32use Deriver\Deriver\SnapshotIdentity;
33use Deriver\Deriver\Sources\Path;
34use Deriver\Deriver\Sources\Source;
35use Deriver\Deriver\Sources\Sources;
36use PHPUnit\Framework\Attributes\CoversNothing;
37use PHPUnit\Framework\Attributes\DataProvider;
38use PHPUnit\Framework\Attributes\Small;
39use PHPUnit\Framework\TestCase;
40
41#[CoversNothing]
42#[Small]
43final class ProjectionValueTest extends TestCase
44{
45 public function testValueProjectionSelectsAnAggregateField(): void
46 {
47 $input1 = new Sources([new Source('fixture.php', '<?php function sink($value){}function target($input){sink($input);}')]);
48 $configuration = new DeriverConfig();
49 $contributions = new Contributions();
50 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
51 $providers = new ProviderExpansion($input1, array_map(Term::fromNative(...), $configuration->environment), $contributions);
52 $contributions1 = $providers->contributions;
53 $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);
54 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
55 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
56 $snapshotId = $identity->id;
57 $input2 = $providers->input;
58 $parser = new Parser(new ParserConfig($target->version));
59 $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($input2, false));
60 $program = new Index($snapshotId, $trees);
61 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
62 $input = new Term(TermKind::Array, operands: ['keep' => Term::constant('projected-value')], attributes: ['open' => false]);
63 $scope = QueryScope::fromEntrypoints([new EntryPoint('target', [$input])]);
64 $query = new ValueQuery((new CallObservations($session->program, $session->evaluators))->find('sink')[0]->argument(0), new Projection(['keep']), $scope);
65 $result = $session->derive($query);
66 self::assertSame([], $result->frontiers);
67 self::assertCount(1, $result->normalOutcomes);
68 self::assertSame('projected-value', $result->normalOutcomes[0]->values['value']->native());
69 }
70 public function testStateProjectionSelectsAnAggregateField(): void
71 {
72 $input1 = new Sources([new Source('fixture.php', '<?php function sink($value){}function target($input){sink($input);}')]);
73 $configuration = new DeriverConfig();
74 $contributions = new Contributions();
75 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
76 $providers = new ProviderExpansion($input1, array_map(Term::fromNative(...), $configuration->environment), $contributions);
77 $contributions1 = $providers->contributions;
78 $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);
79 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
80 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
81 $snapshotId = $identity->id;
82 $input2 = $providers->input;
83 $parser = new Parser(new ParserConfig($target->version));
84 $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($input2, false));
85 $program = new Index($snapshotId, $trees);
86 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
87 $input = new Term(TermKind::Array, operands: ['keep' => Term::constant('projected-value')], attributes: ['open' => false]);
88 $scope = QueryScope::fromEntrypoints([new EntryPoint('target', [$input])]);
89 $query = new StateQuery((new CallObservations($session->program, $session->evaluators))->find('sink')[0]->beforeInvocation(), 'input', new Projection(['keep']), $scope);
90 $result = $session->derive($query);
91 self::assertSame([], $result->frontiers);
92 self::assertCount(1, $result->normalOutcomes);
93 self::assertSame('projected-value', $result->normalOutcomes[0]->values['state']->native());
94 }
95 #[DataProvider('providerArrayOperations')]
96 public function testDerivedArrayOperationsPreserveValues(string $expression, Term $input, mixed $expected): void
97 {
98 $input1 = new Sources([new Source('fixture.php', '<?php function target($input){return ' . $expression . ';}')]);
99 $configuration = new DeriverConfig();
100 $contributions = new Contributions();
101 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
102 $providers = new ProviderExpansion($input1, array_map(Term::fromNative(...), $configuration->environment), $contributions);
103 $contributions1 = $providers->contributions;
104 $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);
105 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
106 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
107 $snapshotId = $identity->id;
108 $input2 = $providers->input;
109 $parser = new Parser(new ParserConfig($target->version));
110 $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($input2, false));
111 $program = new Index($snapshotId, $trees);
112 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
113 $query = new ReturnQuery('target', scope: QueryScope::fromEntrypoints([new EntryPoint('target', [$input])]));
114 $result = $session->derive($query);
115 self::assertSame([], $result->frontiers);
116 self::assertSame([], $result->exceptionalOutcomes);
117 self::assertCount(1, $result->normalOutcomes);
118 self::assertSame($expected, $result->normalOutcomes[0]->values['return']->native());
119 }
120 /**
121 * @return iterable<string,array{string,Term,string|array<int|string,string>}>
122 */
123 public static function providerArrayOperations(): iterable
124 {
125 $array = new Term(TermKind::Array, operands: ['item' => Term::constant('union-value')], attributes: ['open' => false]);
126 yield 'left union' => ['$input + []', $array, ['item' => 'union-value']];
127 yield 'right union' => ['[] + $input', $array, ['item' => 'union-value']];
128 yield 'selected left union' => ['($input + [])["item"]', $array, 'union-value'];
129 yield 'selected right union' => ['([] + $input)["item"]', $array, 'union-value'];
130 yield 'scalar cast' => ['(array)$input', Term::constant('union-value'), ['union-value']];
131 }
132 public function testNullConvertsToAnEmptyArray(): void
133 {
134 $input = new Sources([new Source('fixture.php', '<?php function target($input){return (array)$input;}')]);
135 $configuration = new DeriverConfig();
136 $contributions = new Contributions();
137 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
138 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
139 $contributions1 = $providers->contributions;
140 $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);
141 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
142 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
143 $snapshotId = $identity->id;
144 $input1 = $providers->input;
145 $parser = new Parser(new ParserConfig($target->version));
146 $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($input1, false));
147 $program = new Index($snapshotId, $trees);
148 $session = new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics())));
149 $result = $session->derive(new ReturnQuery('target', scope: QueryScope::fromEntrypoints([new EntryPoint('target', [Term::constant(null)])])));
150 self::assertSame([], $result->frontiers);
151 self::assertCount(1, $result->normalOutcomes);
152 self::assertSame([], $result->normalOutcomes[0]->values['return']->native());
153 }
154}
155