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