packages/deriver/tests/Integration/CandidateEvidenceTest.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\EvidenceKind;
13use Deriver\Deriver\Evaluator\Evidence\Frontier;
14use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
15use Deriver\Deriver\Evaluator\Request\ReturnQuery;
16use Deriver\Deriver\Evaluator\Request\ValueQuery;
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\Publication\CandidateQuery;
26use Deriver\Deriver\Resolver\Resolver;
27use Deriver\Deriver\Selection\ExpressionResolution;
28use Deriver\Deriver\SnapshotIdentity;
29use Deriver\Deriver\Sources\Path;
30use Deriver\Deriver\Sources\Source;
31use Deriver\Deriver\Sources\Sources;
32use PHPUnit\Framework\Attributes\CoversNothing;
33use PHPUnit\Framework\Attributes\DataProvider;
34use PHPUnit\Framework\Attributes\Large;
35use PHPUnit\Framework\TestCase;
36
37#[CoversNothing]
38#[Large]
39final class CandidateEvidenceTest extends TestCase
40{
41    /**
42     * @return array<string, array{string, int}> Distinct outer caller selections
43     */
44    public static function providerCallers(): array
45    {
46        return ['first' => ['controllerA', 35], 'second' => ['controllerB', 65]];
47    }
48    #[DataProvider('providerCallers')]
49    public function testE01E02OuterCallersDisambiguateAnIdenticalInnerSite(string $caller, int $expected): void
50    {
51        $source = '<?php function ttl(int $n){return $n+5;} function recipe(int $n){return ttl($n);} function controllerA(){return recipe(30);} function controllerB(){return recipe(60);}';
52        $prepared1Input = new Sources([new Source('calls.php', $source)]);
53        $prepared1Configuration = new DeriverConfig();
54        $prepared1Contributions = new Contributions();
55        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
56        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
57        $contributions = $prepared1Providers->contributions;
58        $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);
59        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
60        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
61        $snapshotId = $prepared1Identity->id;
62        $input = $prepared1Providers->input;
63        $parser = new Parser(new ParserConfig($prepared1Target->version));
64        $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));
65        $prepared1Program = new Index($snapshotId, $trees);
66        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
67        $session = new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot);
68        $result = $session->derive(new ReturnQuery('ttl'));
69        self::assertSame([35, 65], array_map(static fn ($candidate) => $candidate->result, iterator_to_array($result, false)));
70        $selected = $result->forCaller($caller);
71        self::assertCount(1, $selected);
72        self::assertSame($expected, $selected[0]->result);
73        $kinds = array_column($selected[0]->evidence[0]->nodes(), 'kind');
74        self::assertContains(EvidenceKind::ArgumentBinding, $kinds);
75        self::assertContains(EvidenceKind::SourceDefinition, $kinds);
76        self::assertCount(0, $result->forCaller('absent'));
77    }
78    public function testE03EqualValuesKeepBothCallers(): void
79    {
80        $source = '<?php function ttl($n){return $n+5;}function a(){return ttl(30);}function b(){return ttl(30);}';
81        $prepared2Input = new Sources([new Source('calls.php', $source)]);
82        $prepared2Configuration = new DeriverConfig();
83        $prepared2Contributions = new Contributions();
84        $prepared2Target = new TargetProfile($prepared2Configuration->phpVersion, $prepared2Configuration->integerBits, $prepared2Configuration->floatPrecision);
85        $prepared2Providers = new ProviderExpansion($prepared2Input, array_map(Term::fromNative(...), $prepared2Configuration->environment), $prepared2Contributions);
86        $contributions = $prepared2Providers->contributions;
87        $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);
88        $prepared2Settings = new EvaluatorConfig($prepared2Target, $prepared2Providers->environment, $prepared2Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared2Configuration->evaluators));
89        $prepared2Identity = new SnapshotIdentity($prepared2Providers->input, $prepared2Settings, $prepared2Evaluators, $prepared2Providers->manifest);
90        $snapshotId = $prepared2Identity->id;
91        $input = $prepared2Providers->input;
92        $parser = new Parser(new ParserConfig($prepared2Target->version));
93        $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));
94        $prepared2Program = new Index($snapshotId, $trees);
95        $prepared2Snapshot = $prepared2Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared2Program->diagnostics()));
96        $session = new CandidateQuery(new DefinitionIndex($prepared2Program), $prepared2Settings, $prepared2Evaluators, $prepared2Snapshot);
97        $result = $session->derive(new ReturnQuery('ttl'));
98        self::assertCount(1, $result);
99        self::assertCount(2, $result[0]->evidence);
100        self::assertCount(1, $result->forCaller('a'));
101        self::assertCount(1, $result->forCaller('b'));
102    }
103    public function testE07E08IndependentQueriesKeepTheirOwnEvidence(): void
104    {
105        $source = '<?php function f($n){$x=5+$n;observe($x);return $x;}function caller(){return f(30);}';
106        $prepared3Input = new Sources([new Source('calls.php', $source)]);
107        $prepared3Configuration = new DeriverConfig();
108        $prepared3Contributions = new Contributions();
109        $prepared3Target = new TargetProfile($prepared3Configuration->phpVersion, $prepared3Configuration->integerBits, $prepared3Configuration->floatPrecision);
110        $prepared3Providers = new ProviderExpansion($prepared3Input, array_map(Term::fromNative(...), $prepared3Configuration->environment), $prepared3Contributions);
111        $contributions = $prepared3Providers->contributions;
112        $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);
113        $prepared3Settings = new EvaluatorConfig($prepared3Target, $prepared3Providers->environment, $prepared3Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared3Configuration->evaluators));
114        $prepared3Identity = new SnapshotIdentity($prepared3Providers->input, $prepared3Settings, $prepared3Evaluators, $prepared3Providers->manifest);
115        $snapshotId = $prepared3Identity->id;
116        $input = $prepared3Providers->input;
117        $parser = new Parser(new ParserConfig($prepared3Target->version));
118        $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));
119        $prepared3Program = new Index($snapshotId, $trees);
120        $prepared3Snapshot = $prepared3Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared3Program->diagnostics()));
121        $session = new CandidateQuery(new DefinitionIndex($prepared3Program), $prepared3Settings, $prepared3Evaluators, $prepared3Snapshot);
122        $query = new ReturnQuery('f');
123        $first = iterator_to_array($session->derive($query));
124        $start = strrpos($source, '$x');
125        self::assertNotFalse($start);
126        $session->derive(new ValueQuery((new ExpressionResolution())->expression($prepared3Program, 'calls.php', $start, $start + 2, 'value')));
127        $repeated = $session->derive($query);
128        self::assertEquals($first, iterator_to_array($repeated));
129        unset($session);
130        self::assertEquals($first, iterator_to_array($repeated));
131    }
132    public function testE09LiteralHasExactUtf8AndCrlfSourceCoordinates(): void
133    {
134        $source = "<?php\r\nfunction f(){ /* 日本語 */ observe(42);observe(65);}\r\n";
135        $prepared4Input = new Sources([new Source('unicode.php', $source)]);
136        $prepared4Configuration = new DeriverConfig();
137        $prepared4Contributions = new Contributions();
138        $prepared4Target = new TargetProfile($prepared4Configuration->phpVersion, $prepared4Configuration->integerBits, $prepared4Configuration->floatPrecision);
139        $prepared4Providers = new ProviderExpansion($prepared4Input, array_map(Term::fromNative(...), $prepared4Configuration->environment), $prepared4Contributions);
140        $contributions = $prepared4Providers->contributions;
141        $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);
142        $prepared4Settings = new EvaluatorConfig($prepared4Target, $prepared4Providers->environment, $prepared4Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared4Configuration->evaluators));
143        $prepared4Identity = new SnapshotIdentity($prepared4Providers->input, $prepared4Settings, $prepared4Evaluators, $prepared4Providers->manifest);
144        $snapshotId = $prepared4Identity->id;
145        $input = $prepared4Providers->input;
146        $parser = new Parser(new ParserConfig($prepared4Target->version));
147        $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));
148        $prepared4Program = new Index($snapshotId, $trees);
149        $prepared4Snapshot = $prepared4Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared4Program->diagnostics()));
150        $session = new CandidateQuery(new DefinitionIndex($prepared4Program), $prepared4Settings, $prepared4Evaluators, $prepared4Snapshot);
151        $start = strpos($source, '65');
152        self::assertNotFalse($start);
153        $result = $session->derive(new ValueQuery((new ExpressionResolution())->expression($prepared4Program, 'unicode.php', $start, $start + 2, 'value')));
154        self::assertSame(65, $result[0]->result);
155        $literals = array_values(array_filter($result[0]->evidence[0]->nodes(), static fn ($node): bool => $node->kind === EvidenceKind::SourceDefinition));
156        self::assertCount(1, $literals);
157        self::assertNotNull($literals[0]->source);
158        self::assertSame([$start, $start + 2, 2, $start - 6, 2, $start - 4], [$literals[0]->source->start, $literals[0]->source->end, $literals[0]->source->line, $literals[0]->source->column, $literals[0]->source->endLine, $literals[0]->source->endColumn]);
159    }
160}
161