1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Add support for @psalm-external-mutation-free

This commit is contained in:
Matthew Brown 2019-08-30 17:26:55 -04:00
parent e8500e5e51
commit bbde2d6239
6 changed files with 122 additions and 4 deletions

View File

@ -619,7 +619,7 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio
);
}
if ($context->mutation_free || $codebase->find_unused_variables) {
if ($context->mutation_free || $context->external_mutation_free || $codebase->find_unused_variables) {
$callmap_function_pure = $function_id && $in_call_map
? $codebase->functions->isCallMapFunctionPure($codebase, $function_id, $stmt->args)
: null;
@ -628,7 +628,7 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio
&& !$function_storage->pure)
|| ($callmap_function_pure === false)
) {
if ($context->mutation_free) {
if ($context->mutation_free || $context->external_mutation_free) {
if (IssueBuffer::accepts(
new ImpureFunctionCall(
'Cannot call an impure function from a mutation-free context',

View File

@ -1226,7 +1226,23 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
)) {
// fall through
}
} elseif ($method_storage->mutation_free
} elseif ($context->external_mutation_free
&& !$method_storage->mutation_free
&& $fq_class_name !== $context->self
) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an possibly-mutating method '
. $method_id . ' from a mutation-free context',
new CodeLocation($source, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
} elseif (($method_storage->mutation_free
|| ($method_storage->external_mutation_free
&& isset($stmt->var->external_mutation_free)))
&& $codebase->find_unused_variables
&& !$context->inside_conditional
&& !$context->inside_unset

View File

@ -306,6 +306,11 @@ 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(

View File

@ -38,7 +38,15 @@ class CheckTrivialExprVisitor extends PhpParser\NodeVisitorAbstract implements P
|| $node instanceof PhpParser\Node\Expr\YieldFrom
|| $node instanceof PhpParser\Node\Expr\New_
) {
if ($node instanceof PhpParser\Node\Expr\FuncCall && isset($node->pure)) {
if (($node instanceof PhpParser\Node\Expr\FuncCall
|| $node instanceof PhpParser\Node\Expr\MethodCall
|| $node instanceof PhpParser\Node\Expr\StaticCall)
&& isset($node->pure)
) {
return false;
}
if ($node instanceof PhpParser\Node\Expr\New_ && isset($node->external_mutation_free)) {
return false;
}

View File

@ -510,6 +510,49 @@ class UnusedVariableManipulationTest extends FileManipulationTest
['UnusedVariable'],
true,
],
'removeLongUnusedAssignment' => [
'<?php
/**
* @psalm-external-mutation-free
*/
class A {
private string $foo;
public function __construct(string $foo) {
$this->foo = $foo;
}
public function setFoo(string $foo) : void {
$this->foo = $foo;
}
}
function foo() : void {
$a = (new A("hello"))->setFoo("goodbye");
}',
'<?php
/**
* @psalm-external-mutation-free
*/
class A {
private string $foo;
public function __construct(string $foo) {
$this->foo = $foo;
}
public function setFoo(string $foo) : void {
$this->foo = $foo;
}
}
function foo() : void {
}',
'7.1',
['UnusedVariable'],
true,
],
];
}
}

View File

@ -552,6 +552,30 @@ class UnusedCodeTest extends TestCase
(new Foo)->bar();'
],
'usedMethodCallForExternalMutationFreeClass' => [
'<?php
/**
* @psalm-external-mutation-free
*/
class A {
private string $foo;
public function __construct(string $foo) {
$this->foo = $foo;
}
public function setFoo(string $foo) : void {
$this->foo = $foo;
}
public function getFoo() : string {
return $this->foo;
}
}
$a = new A("hello");
$a->setFoo($a->getFoo() . "cool");',
],
];
}
@ -747,6 +771,28 @@ class UnusedCodeTest extends TestCase
(new A())->handle();',
'error_message' => 'UnusedProperty',
],
'unusedMethodCallForExternalMutationFreeClass' => [
'<?php
/**
* @psalm-external-mutation-free
*/
class A {
private string $foo;
public function __construct(string $foo) {
$this->foo = $foo;
}
public function setFoo(string $foo) : void {
$this->foo = $foo;
}
}
function foo() : void {
(new A("hello"))->setFoo("goodbye");
}',
'error_message' => 'UnusedMethodCall',
],
];
}
}