packages/deriver/tests/Unit/Deriver/Evaluator/Memory/CapturesTest.php
1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Memory;
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\Index\DefinitionIndex;
14use Deriver\Deriver\Evaluator\Request\ValueQuery;
15use Deriver\Deriver\Evaluator\TargetProfile;
16use Deriver\Deriver\Evaluator\Term;
17use Deriver\Deriver\Lowerer\Declaration\Index;
18use Deriver\Deriver\MicroEvaluatorConversion;
19use Deriver\Deriver\Parser\Node\File;
20use Deriver\Deriver\Parser\Parser;
21use Deriver\Deriver\Parser\ParserConfig;
22use Deriver\Deriver\Providers\ProviderExpansion;
23use Deriver\Deriver\Publication\CandidateQuery;
24use Deriver\Deriver\Resolver\Resolver;
25use Deriver\Deriver\Selection\CallObservations;
26use Deriver\Deriver\SnapshotIdentity;
27use Deriver\Deriver\Sources\Path;
28use Deriver\Deriver\Sources\Source;
29use Deriver\Deriver\Sources\Sources;
30use PHPUnit\Framework\Attributes\CoversNothing;
31use PHPUnit\Framework\TestCase;
32
33#[CoversNothing]
34final class CapturesTest extends TestCase
35{
36 public function testValueFindsALexicalCaptureWithoutInvokingTheCallback(): void
37 {
38 $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . 'function owner(){$x="users";register(function()use($x){observe($x);});}')]);
39 $prepared1Configuration = new DeriverConfig();
40 $prepared1Contributions = new Contributions();
41 $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
42 $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
43 $contributions = $prepared1Providers->contributions;
44 $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);
45 $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
46 $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
47 $snapshotId = $prepared1Identity->id;
48 $input = $prepared1Providers->input;
49 $parser = new Parser(new ParserConfig($prepared1Target->version));
50 $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($input, false));
51 $prepared1Program = new Index($snapshotId, $trees);
52 $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
53 $s = new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot);
54 $result = $s->derive(new ValueQuery((new CallObservations($prepared1Program, $prepared1Evaluators))->find('observe')[0]->argument(0)));
55 $slot = 'value';
56 self::assertSame(['users'], array_map(static fn (Term $value) => $value->native(), array_map(static fn ($candidate): Term => $candidate->result instanceof Term ? $candidate->result : Term::fromNative($candidate->result), iterator_to_array($result, false))));
57 }
58 public function testAtRetainsTheDefinitionAtClosureCreation(): void
59 {
60 $prepared2Input = new Sources([new Source('candidate.php', '<?php ' . 'function owner(){$x=1;register(function()use($x){observe($x);});$x=2;}')]);
61 $prepared2Configuration = new DeriverConfig();
62 $prepared2Contributions = new Contributions();
63 $prepared2Target = new TargetProfile($prepared2Configuration->phpVersion, $prepared2Configuration->integerBits, $prepared2Configuration->floatPrecision);
64 $prepared2Providers = new ProviderExpansion($prepared2Input, array_map(Term::fromNative(...), $prepared2Configuration->environment), $prepared2Contributions);
65 $contributions = $prepared2Providers->contributions;
66 $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);
67 $prepared2Settings = new EvaluatorConfig($prepared2Target, $prepared2Providers->environment, $prepared2Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared2Configuration->evaluators));
68 $prepared2Identity = new SnapshotIdentity($prepared2Providers->input, $prepared2Settings, $prepared2Evaluators, $prepared2Providers->manifest);
69 $snapshotId = $prepared2Identity->id;
70 $input = $prepared2Providers->input;
71 $parser = new Parser(new ParserConfig($prepared2Target->version));
72 $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($input, false));
73 $prepared2Program = new Index($snapshotId, $trees);
74 $prepared2Snapshot = $prepared2Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared2Program->diagnostics()));
75 $s = new CandidateQuery(new DefinitionIndex($prepared2Program), $prepared2Settings, $prepared2Evaluators, $prepared2Snapshot);
76 $result = $s->derive(new ValueQuery((new CallObservations($prepared2Program, $prepared2Evaluators))->find('observe')[0]->argument(0)));
77 $slot = 'value';
78 self::assertSame([1], array_map(static fn (Term $value) => $value->native(), array_map(static fn ($candidate): Term => $candidate->result instanceof Term ? $candidate->result : Term::fromNative($candidate->result), iterator_to_array($result, false))));
79 }
80
81 public function testEffectWritesOnlyReferenceCaptures(): void
82 {
83 $source = new Source('subject.php', '<?php function subject() { $a = 1; $b = 2; $callback = function () use (&$a, $b) { $a = 17; $b = 19; }; $callback(); return [$a, $b]; }');
84 $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse($source));
85 $index = new DefinitionIndex(new Index('test', [$tree]));
86 $profile = new TargetProfile('8.3');
87 $engine = new \Deriver\Deriver\Evaluator\Evaluator(new \Deriver\Deriver\Evaluator\Scope\Context($index, new EvaluatorConfig($profile), new Evaluators($profile), new \Deriver\Deriver\Evaluator\Request\Budget()));
88 $graph = $index->graph('subject');
89 self::assertNotNull($graph);
90 $value = $engine->returns(new \Deriver\Deriver\Evaluator\Scope\Frame($graph, 'subject'), 64);
91 $alternatives = iterator_to_array((new \Deriver\Deriver\Evaluator\Enumeration\Choices())->alternatives($value), false);
92 self::assertCount(1, $alternatives);
93 self::assertSame([17, 2], $alternatives[0][0]->native());
94 }
95 public function testContainsDistinguishesValueAndReferenceCapture(): void
96 {
97 $source = new Source('subject.php', '<?php function subject() { $a = 1; $b = 2; $callback = function () use (&$a, $b) { $a = 17; $b = 19; }; $callback(); return [$a, $b]; }');
98 $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse($source));
99 $index = new DefinitionIndex(new Index('test', [$tree]));
100 $profile = new TargetProfile('8.3');
101 $engine = new \Deriver\Deriver\Evaluator\Evaluator(new \Deriver\Deriver\Evaluator\Scope\Context($index, new EvaluatorConfig($profile), new Evaluators($profile), new \Deriver\Deriver\Evaluator\Request\Budget()));
102 $graph = $index->graph('subject');
103 self::assertNotNull($graph);
104 self::assertTrue((new \Deriver\Deriver\Evaluator\Memory\Captures())->contains($engine, new \Deriver\Deriver\Evaluator\Scope\Frame($graph, 'subject'), 'a'));
105 self::assertFalse((new \Deriver\Deriver\Evaluator\Memory\Captures())->contains($engine, new \Deriver\Deriver\Evaluator\Scope\Frame($graph, 'subject'), 'b'));
106 }
107}
108