1
0
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:
Matthew Brown 2019-09-07 18:34:18 -04:00
parent e7a69f715d
commit 85ae8f93d2
4 changed files with 148 additions and 64 deletions

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -57,4 +57,9 @@ class MethodStorage extends FunctionLikeStorage
* @var bool
*/
public $external_mutation_free = false;
/**
* @var ?array<string, bool>
*/
public $this_property_mutations = null;
}

View File

@ -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;
}
}
}',
],
];
}