packages/deriver/tests/Unit/Deriver/Evaluator/Memory/PropertyEffectsTest.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\Evaluator;
12use Deriver\Deriver\Evaluator\EvaluatorConfig;
13use Deriver\Deriver\Evaluator\Evidence\Frontier;
14use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
15use Deriver\Deriver\Evaluator\Request\Budget;
16use Deriver\Deriver\Evaluator\Request\ReturnQuery;
17use Deriver\Deriver\Evaluator\Scope\Context;
18use Deriver\Deriver\Evaluator\Scope\Frame;
19use Deriver\Deriver\Evaluator\TargetProfile;
20use Deriver\Deriver\Evaluator\Term;
21use Deriver\Deriver\Lowerer\Declaration\Index;
22use Deriver\Deriver\MicroEvaluatorConversion;
23use Deriver\Deriver\Parser\Node\File;
24use Deriver\Deriver\Parser\Parser;
25use Deriver\Deriver\Parser\ParserConfig;
26use Deriver\Deriver\Providers\ProviderExpansion;
27use Deriver\Deriver\Publication\CandidateQuery;
28use Deriver\Deriver\Resolver\Resolver;
29use Deriver\Deriver\SnapshotIdentity;
30use Deriver\Deriver\Sources\Path;
31use Deriver\Deriver\Sources\Source;
32use Deriver\Deriver\Sources\Sources;
33use PHPUnit\Framework\Attributes\CoversNothing;
34use PHPUnit\Framework\TestCase;
35
36#[CoversNothing]
37final class PropertyEffectsTest extends TestCase
38{
39    public function testSelectedDoesNotExpandMethodsThatCannotWriteAnInitializedReadonlyField(): void
40    {
41        $source = new Source('candidate.php', '<?php class A{public function __construct(public readonly int $x){} public function touch($flag){if($flag){return external();}return other();}}function target($flag){$a=new A(7);$a->touch($flag);return $a->x;}');
42        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.1')))->parse($source));
43        $index = new DefinitionIndex(new Index('test', [$tree]));
44        $engine = new Evaluator(new Context($index, new EvaluatorConfig(), new Evaluators(), new Budget()));
45        $graph = $index->graph('target');
46        self::assertNotNull($graph);
47
48        $value = $engine->returns(new Frame($graph, 'source:target'), 64);
49
50        self::assertSame(7, $value->native());
51        self::assertArrayNotHasKey('A::touch', $engine->context->bodies);
52        self::assertNull($engine->context->stopReason);
53    }
54
55    public function testSelectedFollowsReadonlyInitializersOutsideConstructors(): void
56    {
57        $source = new Source('candidate.php', '<?php class A{public readonly int $x;public function initialize(){$this->x=9;}}function target(){$a=new A;$a->initialize();return $a->x;}');
58        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.1')))->parse($source));
59        $index = new DefinitionIndex(new Index('test', [$tree]));
60        $engine = new Evaluator(new Context($index, new EvaluatorConfig(), new Evaluators(), new Budget()));
61        $graph = $index->graph('target');
62        self::assertNotNull($graph);
63
64        $value = $engine->returns(new Frame($graph, 'source:target'), 64);
65
66        self::assertSame(9, $value->native());
67        self::assertArrayHasKey('A::initialize', $engine->context->bodies);
68        self::assertNull($engine->context->stopReason);
69    }
70
71    public function testAddressUsesTheObservedAllocation(): void
72    {
73        $source = 'class A{public $x=1;}function target(){$a=new A;$b=$a;$b->x=9;return $a->x;}';
74        $configuration = new DeriverConfig();
75        $budget = new Budget();
76        $contributions = new Contributions();
77        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
78        $prepared1Configuration = $configuration;
79        $prepared1Contributions = $contributions;
80        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
81        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
82        $contributions1 = $prepared1Providers->contributions;
83        $prepared1Evaluators = new Evaluators(new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $prepared1Configuration->standardEvaluators);
84        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
85        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
86        $snapshotId = $prepared1Identity->id;
87        $input = $prepared1Providers->input;
88        $parser = new Parser(new ParserConfig($prepared1Target->version));
89        $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));
90        $prepared1Program = new Index($snapshotId, $trees);
91        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
92        self::assertSame(9, (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget))[0]->result);
93    }
94    public function testIncomingUsesTheObservedAllocation(): void
95    {
96        $source = 'class A{public $x=1;function set($x){$this->x=$x;}function get(){return $this->x;}}function target(){$a=new A;$a->set(7);return $a->get();}';
97        $configuration = new DeriverConfig();
98        $budget = new Budget();
99        $contributions = new Contributions();
100        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
101        $prepared1Configuration = $configuration;
102        $prepared1Contributions = $contributions;
103        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
104        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
105        $contributions1 = $prepared1Providers->contributions;
106        $prepared1Evaluators = new Evaluators(new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $prepared1Configuration->standardEvaluators);
107        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
108        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
109        $snapshotId = $prepared1Identity->id;
110        $input = $prepared1Providers->input;
111        $parser = new Parser(new ParserConfig($prepared1Target->version));
112        $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));
113        $prepared1Program = new Index($snapshotId, $trees);
114        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
115        self::assertSame(7, (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget))[0]->result);
116    }
117    public function testEffectUsesTheObservedAllocation(): void
118    {
119        $source = 'class A{public $x=1;function set($x){$this->x=$x;}}function target(){$a=new A;$a->set(7);return $a->x;}';
120        $configuration = new DeriverConfig();
121        $budget = new Budget();
122        $contributions = new Contributions();
123        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
124        $prepared1Configuration = $configuration;
125        $prepared1Contributions = $contributions;
126        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
127        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
128        $contributions1 = $prepared1Providers->contributions;
129        $prepared1Evaluators = new Evaluators(new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $prepared1Configuration->standardEvaluators);
130        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
131        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
132        $snapshotId = $prepared1Identity->id;
133        $input = $prepared1Providers->input;
134        $parser = new Parser(new ParserConfig($prepared1Target->version));
135        $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));
136        $prepared1Program = new Index($snapshotId, $trees);
137        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
138        self::assertSame(7, (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget))[0]->result);
139    }
140    public function testSelectedUsesTheObservedAllocation(): void
141    {
142        $source = 'class A{public $x=1;function __construct($x){$this->x=$x;}}class B extends A{function __construct(){parent::__construct(7);}}function target(){return (new B)->x;}';
143        $configuration = new DeriverConfig();
144        $budget = new Budget();
145        $contributions = new Contributions();
146        $prepared1Input = new Sources([new Source('candidate.php', '<?php ' . $source)]);
147        $prepared1Configuration = $configuration;
148        $prepared1Contributions = $contributions;
149        $prepared1Target = new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision);
150        $prepared1Providers = new ProviderExpansion($prepared1Input, array_map(Term::fromNative(...), $prepared1Configuration->environment), $prepared1Contributions);
151        $contributions1 = $prepared1Providers->contributions;
152        $prepared1Evaluators = new Evaluators(new TargetProfile($prepared1Configuration->phpVersion, $prepared1Configuration->integerBits, $prepared1Configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $prepared1Configuration->standardEvaluators);
153        $prepared1Settings = new EvaluatorConfig($prepared1Target, $prepared1Providers->environment, $prepared1Configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $prepared1Configuration->evaluators));
154        $prepared1Identity = new SnapshotIdentity($prepared1Providers->input, $prepared1Settings, $prepared1Evaluators, $prepared1Providers->manifest);
155        $snapshotId = $prepared1Identity->id;
156        $input = $prepared1Providers->input;
157        $parser = new Parser(new ParserConfig($prepared1Target->version));
158        $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));
159        $prepared1Program = new Index($snapshotId, $trees);
160        $prepared1Snapshot = $prepared1Identity->snapshot(array_map(Frontier::fromDiagnostic(...), $prepared1Program->diagnostics()));
161        self::assertSame(7, (new CandidateQuery(new DefinitionIndex($prepared1Program), $prepared1Settings, $prepared1Evaluators, $prepared1Snapshot))->derive(new ReturnQuery('target', budget: $budget))[0]->result);
162    }
163}
164