Skip to content

Commit

Permalink
Make assigning property via reference impure
Browse files Browse the repository at this point in the history
  • Loading branch information
schlndh authored May 30, 2024
1 parent 43d3ac4 commit 93d85c4
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/Analyser/ImpurePoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use PHPStan\Node\VirtualNode;

/**
* @phpstan-type ImpurePointIdentifier = 'echo'|'die'|'exit'|'propertyAssign'|'propertyUnset'|'methodCall'|'new'|'functionCall'|'include'|'require'|'print'|'eval'|'superglobal'|'yield'|'yieldFrom'|'static'|'global'|'betweenPhpTags'
* @phpstan-type ImpurePointIdentifier = 'echo'|'die'|'exit'|'propertyAssign'|'propertyAssignByRef'|'propertyUnset'|'methodCall'|'new'|'functionCall'|'include'|'require'|'print'|'eval'|'superglobal'|'yield'|'yieldFrom'|'static'|'global'|'betweenPhpTags'
* @api
*/
class ImpurePoint
Expand Down
18 changes: 17 additions & 1 deletion src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2138,7 +2138,23 @@ public function processExprNode(Node\Stmt $stmt, Expr $expr, MutatingScope $scop
$nodeCallback,
$context,
function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): ExpressionResult {
$impurePoints = [];
if ($expr instanceof AssignRef) {
$referencedExpr = $expr->expr;
while ($referencedExpr instanceof ArrayDimFetch) {
$referencedExpr = $referencedExpr->var;
}

if ($referencedExpr instanceof PropertyFetch || $referencedExpr instanceof StaticPropertyFetch) {
$impurePoints[] = new ImpurePoint(
$scope,
$expr,
'propertyAssignByRef',
'property assignment by reference',
false,
);
}

$scope = $scope->enterExpressionAssign($expr->expr);
}

Expand All @@ -2153,7 +2169,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep());
$hasYield = $result->hasYield();
$throwPoints = $result->getThrowPoints();
$impurePoints = $result->getImpurePoints();
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
$scope = $result->getScope();

if ($expr instanceof AssignRef) {
Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Rules/Pure/PureMethodRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,14 @@ public function testPureConstructor(): void
]);
}

public function testImpureAssignRef(): void
{
$this->analyse([__DIR__ . '/data/impure-assign-ref.php'], [
[
'Possibly impure property assignment by reference in pure method ImpureAssignRef\HelloWorld::bar6().',
49,
],
]);
}

}
63 changes: 63 additions & 0 deletions tests/PHPStan/Rules/Pure/data/impure-assign-ref.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php declare(strict_types = 1); // lint >= 7.4

namespace ImpureAssignRef;

class HelloWorld
{
public int $value = 0;
/** @var array<int, int> */
public array $arr = [];
/** @var array<int, \stdClass> */
public array $objectArr = [];
public static int $staticValue = 0;
/** @var array<int, int> */
public static array $staticArr = [];

private function bar1(): void
{
$value = &$this->value;
$value = 1;
}

private function bar2(): void
{
$value = &$this->arr[0];
$value = 1;
}

private function bar3(): void
{
$value = &self::$staticValue;
$value = 1;
}

private function bar4(): void
{
$value = &self::$staticArr[0];
$value = 1;
}

private function bar5(self $other): void
{
$value = &$other->value;
$value = 1;
}

/** @phpstan-pure */
private function bar6(): int
{
$value = &$this->objectArr[0]->foo;

return 1;
}

public function foo(): void
{
$this->bar1();
$this->bar2();
$this->bar3();
$this->bar4();
$this->bar5(new self());
$this->bar6();
}
}

0 comments on commit 93d85c4

Please sign in to comment.