1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Improve handling of external-mutation-free objects created in pure functions

This commit is contained in:
Matthew Brown 2019-08-31 09:49:32 -04:00
parent f096c3d29c
commit 900cfc0f05
6 changed files with 69 additions and 10 deletions

View File

@ -1206,7 +1206,11 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
if ($method_storage) {
if (!$context->collect_mutations && !$context->collect_initializations) {
if ($context->pure && !$method_storage->pure) {
$method_pure_compatible = $method_storage->external_mutation_free
&& (!empty($stmt->var->inferredType->external_mutation_free)
|| isset($stmt->var->pure));
if ($context->pure && !$method_storage->pure && !$method_pure_compatible) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an impure method ' . $method_id . ' from a pure context',
@ -1216,7 +1220,10 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
)) {
// fall through
}
} elseif ($context->mutation_free && !$method_storage->mutation_free) {
} elseif ($context->mutation_free
&& !$method_storage->mutation_free
&& !$method_pure_compatible
) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an possibly-mutating method '
@ -1230,6 +1237,7 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
} elseif ($context->external_mutation_free
&& !$method_storage->mutation_free
&& $fq_class_name !== $context->self
&& !$method_pure_compatible
) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
@ -1242,8 +1250,7 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
// fall through
}
} elseif (($method_storage->mutation_free
|| ($method_storage->external_mutation_free
&& (isset($stmt->var->external_mutation_free) || isset($stmt->var->pure))))
|| $method_pure_compatible)
&& $codebase->find_unused_variables
&& !$context->inside_conditional
&& !$context->inside_unset

View File

@ -306,11 +306,6 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
) {
$storage = $codebase->classlike_storage_provider->get($fq_class_name);
if ($storage->external_mutation_free) {
/** @psalm-suppress UndefinedPropertyAssignment */
$stmt->external_mutation_free = true;
}
// if we're not calling this constructor via new static()
if ($storage->abstract && !$can_extend) {
if (IssueBuffer::accepts(
@ -478,6 +473,10 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
// fall through
}
}
if ($storage->external_mutation_free) {
$stmt->inferredType->external_mutation_free = true;
}
}
}

View File

@ -46,7 +46,7 @@ class CheckTrivialExprVisitor extends PhpParser\NodeVisitorAbstract implements P
return false;
}
if ($node instanceof PhpParser\Node\Expr\New_ && isset($node->external_mutation_free)) {
if ($node instanceof PhpParser\Node\Expr\New_ && !empty($node->inferredType->external_mutation_free)) {
return false;
}

View File

@ -1463,6 +1463,10 @@ abstract class Type
$combined_type->had_template = true;
}
if ($type_1->external_mutation_free && $type_2->external_mutation_free) {
$combined_type->external_mutation_free = true;
}
if ($both_failed_reconciliation) {
$combined_type->failed_reconciliation = true;
}

View File

@ -154,6 +154,11 @@ class Union
*/
public $by_ref = false;
/**
* @var bool
*/
public $external_mutation_free = false;
/** @var null|string */
private $id;

View File

@ -77,6 +77,28 @@ class PureAnnotationTest extends TestCase
}
}',
],
'canCreateObjectWithNoExternalMutations' => [
'<?php
/** @psalm-external-mutation-free */
class Counter {
private int $count = 0;
public function __construct(int $count) {
$this->count = $count;
}
public function increment() : void {
$this->count++;
}
}
/** @psalm-pure */
function makesACounter(int $i) : Counter {
$c = new Counter($i);
$c->increment();
return $c;
}',
],
];
}
@ -175,6 +197,28 @@ class PureAnnotationTest extends TestCase
}',
'error_message' => 'ImpureMethodCall',
],
'canCreateObjectWithNoExternalMutations' => [
'<?php
class Counter {
private int $count = 0;
public function __construct(int $count) {
$this->count = $count;
}
public function increment() : void {
$this->count += rand(0, 5);
}
}
/** @psalm-pure */
function makesACounter(int $i) : Counter {
$c = new Counter($i);
$c->increment();
return $c;
}',
'error_message' => 'ImpureMethodCall',
],
];
}
}