12
12
namespace Serafim \Contracts \Compiler ;
13
13
14
14
use PhpParser \Comment ;
15
+ use PhpParser \Node \Attribute ;
15
16
use PhpParser \Node \Expr \Assign ;
16
17
use PhpParser \Node \Expr \Clone_ ;
17
18
use PhpParser \Node \Expr \ConstFetch ;
30
31
use Serafim \Contracts \Compiler \Visitor \ContractsApplicatorVisitor \InvariantStatement ;
31
32
use Serafim \Contracts \Compiler \Visitor \ContractsApplicatorVisitor \VerifyStatement ;
32
33
use Serafim \Contracts \Compiler \Visitor \ReturnDecoratorVisitor ;
34
+ use Serafim \Contracts \Exception \SpecificationException ;
33
35
34
36
/**
35
37
* @internal This is an internal library class, please do not use it in your code.
36
38
* @psalm-internal Serafim\Contracts
37
39
*/
38
40
final class MethodInjector
39
41
{
42
+ /**
43
+ * @var string
44
+ */
45
+ private const ERROR_OLD_INSIDE_STATIC = 'Could not use "$old" variable inside static method %s() ' ;
46
+
40
47
/**
41
48
* @param ContractsParser $parser
42
49
*/
@@ -54,6 +61,7 @@ public function __construct(
54
61
*/
55
62
public function inject (string $ file , ClassMethod $ method , array $ invariants ): ClassMethod
56
63
{
64
+ $ isResultUsed = $ isOldStateUsed = false ;
57
65
$ preconditions = $ postconditions = [];
58
66
59
67
foreach ($ this ->getPreconditions ($ file , $ method ) as $ precondition ) {
@@ -63,27 +71,56 @@ public function inject(string $file, ClassMethod $method, array $invariants): Cl
63
71
$ old = $ this ->generateVariable ('old ' );
64
72
$ result = $ this ->generateVariable ('result ' );
65
73
66
- foreach ($ this ->getPostconditions ($ file , $ method ) as $ postcondition ) {
67
- $ postconditions [] = $ this ->modifyPostcondition ($ old , $ result , $ postcondition ->getExpression ());
74
+ /** @var Attribute $ensure */
75
+ foreach ($ this ->getPostconditions ($ file , $ method ) as $ ensure => $ postcondition ) {
76
+ $ finder = new NodeFinder ();
77
+
78
+ /** @var Variable $variable */
79
+ foreach ($ finder ->findInstanceOf ([$ postcondition ->getExpression ()], Variable::class) as $ variable ) {
80
+ switch ($ variable ->name ) {
81
+ case 'result ' :
82
+ $ isResultUsed = true ;
83
+ $ variable ->name = $ result ->name ;
84
+ break ;
85
+
86
+ case 'old ' :
87
+ // Check that method is not static
88
+ if (($ method ->flags & Stmt \Class_::MODIFIER_STATIC ) === Stmt \Class_::MODIFIER_STATIC ) {
89
+ $ message = \sprintf (self ::ERROR_OLD_INSIDE_STATIC , $ method ->name ->toString ());
90
+ throw SpecificationException::create ($ message , $ file , $ ensure ->getLine ());
91
+ }
92
+
93
+ $ isOldStateUsed = true ;
94
+ $ variable ->name = $ old ->name ;
95
+ break ;
96
+ }
97
+ }
98
+
99
+ $ postconditions [] = $ postcondition ->getExpression ();
68
100
}
69
101
70
102
// Has Ensure Statements
71
103
if (\count ($ postconditions )) {
72
- $ preconditions = [
73
- // Add clone expression
74
- new Expression (
75
- new Assign ($ old , new Clone_ (new Variable ('this ' ))),
76
- $ this ->comment ('Clone object for future comparison '
77
- . 'with the "$old" variable value ' )
78
- ),
79
- // Initialize return variable
80
- new Expression (
81
- new Assign ($ result , new ConstFetch (new Name ('null ' ))),
82
- $ this ->comment ('Initialize the return variable for '
83
- . 'comparison in case of an empty return statement ' )
84
- ),
85
- ...$ preconditions ,
86
- ];
104
+ $ preconditions = [];
105
+
106
+ // Add clone expression in case of "$old" variable has been used.
107
+ if ($ isOldStateUsed ) {
108
+ $ cloneExpression = new Expression (
109
+ new Assign ($ old , new Clone_ (new Variable ('this ' )))
110
+ );
111
+
112
+ \array_unshift ($ preconditions , $ cloneExpression );
113
+ }
114
+
115
+ // Add result variable initialization in case of "$result"
116
+ // variable has been used.
117
+ if ($ isResultUsed ) {
118
+ $ resultExpression = new Expression (
119
+ new Assign ($ result , new ConstFetch (new Name ('null ' )))
120
+ );
121
+
122
+ \array_unshift ($ preconditions , $ resultExpression );
123
+ }
87
124
88
125
// Decorate return
89
126
$ method ->stmts = $ this ->wrapReturnStatement ($ result , $ method ->stmts );
@@ -159,34 +196,10 @@ private function generateVariable(string $prefix): Variable
159
196
private function getPostconditions (string $ file , ClassMethod $ method ): iterable
160
197
{
161
198
foreach ($ this ->getAttributes ($ method , Ensure::class) as $ attribute ) {
162
- yield from $ this ->parser ->ensure ($ file , $ attribute );
163
- }
164
- }
165
-
166
- /**
167
- * @param Variable $old
168
- * @param Variable $result
169
- * @param Expression $expr
170
- * @return Expression
171
- */
172
- private function modifyPostcondition (Variable $ old , Variable $ result , Expression $ expr ): Expression
173
- {
174
- $ finder = new NodeFinder ();
175
-
176
- /** @var Variable $variable */
177
- foreach ($ finder ->findInstanceOf ([$ expr ], Variable::class) as $ variable ) {
178
- switch ($ variable ->name ) {
179
- case 'result ' :
180
- $ variable ->name = $ result ->name ;
181
- break ;
182
-
183
- case 'old ' :
184
- $ variable ->name = $ old ->name ;
185
- break ;
199
+ foreach ($ this ->parser ->ensure ($ file , $ attribute ) as $ item ) {
200
+ yield $ attribute => $ item ;
186
201
}
187
202
}
188
-
189
- return $ expr ;
190
203
}
191
204
192
205
/**
0 commit comments