mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Clear out property assignments when necessary
This commit is contained in:
parent
e7a69f715d
commit
85ae8f93d2
@ -1302,6 +1302,10 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
||||
&& !$method_pure_compatible
|
||||
) {
|
||||
$context->removeAllObjectVars();
|
||||
} elseif ($method_storage->this_property_mutations) {
|
||||
foreach ($method_storage->this_property_mutations as $name => $_) {
|
||||
$context->remove($lhs_var_id . '->' . $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -465,6 +465,23 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
$var_type->queueClassLikesForScanning($this->codebase, $this->file_storage);
|
||||
}
|
||||
}
|
||||
|
||||
if ($node instanceof PhpParser\Node\Expr\Assign
|
||||
|| $node instanceof PhpParser\Node\Expr\AssignOp
|
||||
|| $node instanceof PhpParser\Node\Expr\AssignRef
|
||||
) {
|
||||
if ($node->var instanceof PhpParser\Node\Expr\PropertyFetch
|
||||
&& $node->var->var instanceof PhpParser\Node\Expr\Variable
|
||||
&& $node->var->var->name === 'this'
|
||||
&& $node->var->name instanceof PhpParser\Node\Identifier
|
||||
) {
|
||||
$functionlike_storage = end($this->functionlike_storages);
|
||||
|
||||
if ($functionlike_storage instanceof MethodStorage) {
|
||||
$functionlike_storage->this_property_mutations[$node->var->name->name] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($node instanceof PhpParser\Node\Stmt\Const_) {
|
||||
foreach ($node->consts as $const) {
|
||||
$const_type = StatementsAnalyzer::getSimpleType($this->codebase, $const->value, $this->aliases)
|
||||
|
@ -57,4 +57,9 @@ class MethodStorage extends FunctionLikeStorage
|
||||
* @var bool
|
||||
*/
|
||||
public $external_mutation_free = false;
|
||||
|
||||
/**
|
||||
* @var ?array<string, bool>
|
||||
*/
|
||||
public $this_property_mutations = null;
|
||||
}
|
||||
|
@ -22,20 +22,63 @@ class PropertyTypeTest extends TestCase
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class XCollector {
|
||||
/** @var X[] */
|
||||
private static array $xs = [];
|
||||
|
||||
public static function modify() : void {
|
||||
foreach (self::$xs as $x) {
|
||||
$x->x = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class X {
|
||||
/** @var ?int **/
|
||||
private $x;
|
||||
public $x;
|
||||
|
||||
public function getX(): int {
|
||||
$this->x = 5;
|
||||
|
||||
$this->modifyX();
|
||||
XCollector::modify();
|
||||
|
||||
return $this->x;
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
private function modifyX(): void {
|
||||
$this->x = null;
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testForgetPropertyAssignmentsPassesNormally()
|
||||
{
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class XCollector {
|
||||
/** @var X[] */
|
||||
private static array $xs = [];
|
||||
|
||||
public static function modify() : void {
|
||||
foreach (self::$xs as $x) {
|
||||
$x->x = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class X {
|
||||
/** @var ?int **/
|
||||
public $x;
|
||||
|
||||
public function getX(): int {
|
||||
$this->x = 5;
|
||||
|
||||
XCollector::modify();
|
||||
|
||||
return $this->x;
|
||||
}
|
||||
}'
|
||||
);
|
||||
@ -53,24 +96,31 @@ class PropertyTypeTest extends TestCase
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class XCollector {
|
||||
/** @var X[] */
|
||||
private static array $xs = [];
|
||||
|
||||
public static function modify() : void {
|
||||
foreach (self::$xs as $x) {
|
||||
$x->x = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class X {
|
||||
/** @var ?int **/
|
||||
private $x;
|
||||
public $x;
|
||||
|
||||
public function getX(bool $b): int {
|
||||
$this->x = 5;
|
||||
|
||||
if ($b) {
|
||||
$this->modifyX();
|
||||
XCollector::modify();
|
||||
throw new \Exception("bad");
|
||||
}
|
||||
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
private function modifyX(): void {
|
||||
$this->x = null;
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
@ -80,70 +130,40 @@ class PropertyTypeTest extends TestCase
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testRemovePropertyAfterReassignment()
|
||||
public function testForgetPropertyAssignmentsInBranchWithThrowNormally()
|
||||
{
|
||||
Config::getInstance()->remember_property_assignments_after_call = false;
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class A {
|
||||
/** @var A|null */
|
||||
public $parent;
|
||||
class XCollector {
|
||||
/** @var X[] */
|
||||
private static array $xs = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->parent = rand(0, 1) ? new A : null;
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
|
||||
if ($a->parent === null) {
|
||||
throw new \Exception("bad");
|
||||
}
|
||||
|
||||
$a = $a->parent;'
|
||||
);
|
||||
|
||||
$context = new Context();
|
||||
|
||||
$this->analyzeFile('somefile.php', $context);
|
||||
|
||||
$this->assertSame('A', (string) $context->vars_in_scope['$a']);
|
||||
$this->assertFalse(isset($context->vars_in_scope['$a->parent']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testRemoveClauseAfterReassignment()
|
||||
{
|
||||
Config::getInstance()->remember_property_assignments_after_call = false;
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class Test {
|
||||
/** @var ?bool */
|
||||
private $foo;
|
||||
|
||||
public function run(): void {
|
||||
$this->foo = false;
|
||||
$this->bar();
|
||||
if ($this->foo === true) {}
|
||||
}
|
||||
|
||||
private function bar(): void {
|
||||
if (mt_rand(0, 1)) {
|
||||
$this->foo = true;
|
||||
public static function modify() : void {
|
||||
foreach (self::$xs as $x) {
|
||||
$x->x = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class X {
|
||||
/** @var ?int **/
|
||||
public $x;
|
||||
|
||||
public function getX(bool $b): int {
|
||||
$this->x = 5;
|
||||
|
||||
if ($b) {
|
||||
XCollector::modify();
|
||||
throw new \Exception("bad");
|
||||
}
|
||||
|
||||
return $this->x;
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
$context = new Context();
|
||||
|
||||
$this->analyzeFile('somefile.php', $context);
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1740,6 +1760,44 @@ class PropertyTypeTest extends TestCase
|
||||
}
|
||||
}',
|
||||
],
|
||||
'rememberThisPropertyAsssignmentsInMethod' => [
|
||||
'<?php
|
||||
class A {
|
||||
public bool $foo = false;
|
||||
|
||||
public function bar() : void {
|
||||
$this->foo = false;
|
||||
$this->maybeChange();
|
||||
|
||||
if ($this->foo) {}
|
||||
}
|
||||
|
||||
public function maybeChange() : void {
|
||||
if (rand(0, 1)) {
|
||||
$this->foo = true;
|
||||
}
|
||||
}
|
||||
}'
|
||||
],
|
||||
'testRemoveClauseAfterReassignment' => [
|
||||
'<?php
|
||||
class Test {
|
||||
/** @var ?bool */
|
||||
private $foo;
|
||||
|
||||
public function run(): void {
|
||||
$this->foo = false;
|
||||
$this->bar();
|
||||
if ($this->foo === true) {}
|
||||
}
|
||||
|
||||
private function bar(): void {
|
||||
if (mt_rand(0, 1)) {
|
||||
$this->foo = true;
|
||||
}
|
||||
}
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user