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