Skip to content

Commit 53a99af

Browse files
committed
Adding a check for using "$old" (Ensure) variable inside static methods
1 parent d204f75 commit 53a99af

File tree

1 file changed

+56
-43
lines changed

1 file changed

+56
-43
lines changed

Diff for: src/Compiler/MethodInjector.php

+56-43
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Serafim\Contracts\Compiler;
1313

1414
use PhpParser\Comment;
15+
use PhpParser\Node\Attribute;
1516
use PhpParser\Node\Expr\Assign;
1617
use PhpParser\Node\Expr\Clone_;
1718
use PhpParser\Node\Expr\ConstFetch;
@@ -30,13 +31,19 @@
3031
use Serafim\Contracts\Compiler\Visitor\ContractsApplicatorVisitor\InvariantStatement;
3132
use Serafim\Contracts\Compiler\Visitor\ContractsApplicatorVisitor\VerifyStatement;
3233
use Serafim\Contracts\Compiler\Visitor\ReturnDecoratorVisitor;
34+
use Serafim\Contracts\Exception\SpecificationException;
3335

3436
/**
3537
* @internal This is an internal library class, please do not use it in your code.
3638
* @psalm-internal Serafim\Contracts
3739
*/
3840
final class MethodInjector
3941
{
42+
/**
43+
* @var string
44+
*/
45+
private const ERROR_OLD_INSIDE_STATIC = 'Could not use "$old" variable inside static method %s()';
46+
4047
/**
4148
* @param ContractsParser $parser
4249
*/
@@ -54,6 +61,7 @@ public function __construct(
5461
*/
5562
public function inject(string $file, ClassMethod $method, array $invariants): ClassMethod
5663
{
64+
$isResultUsed = $isOldStateUsed = false;
5765
$preconditions = $postconditions = [];
5866

5967
foreach ($this->getPreconditions($file, $method) as $precondition) {
@@ -63,27 +71,56 @@ public function inject(string $file, ClassMethod $method, array $invariants): Cl
6371
$old = $this->generateVariable('old');
6472
$result = $this->generateVariable('result');
6573

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();
68100
}
69101

70102
// Has Ensure Statements
71103
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+
}
87124

88125
// Decorate return
89126
$method->stmts = $this->wrapReturnStatement($result, $method->stmts);
@@ -159,34 +196,10 @@ private function generateVariable(string $prefix): Variable
159196
private function getPostconditions(string $file, ClassMethod $method): iterable
160197
{
161198
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;
186201
}
187202
}
188-
189-
return $expr;
190203
}
191204

192205
/**

0 commit comments

Comments
 (0)