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