Skip to content

Commit

Permalink
Add support for @psalm-external-mutation-free
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Aug 30, 2019
1 parent e8500e5 commit bbde2d6
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 4 deletions.
Expand Up @@ -619,7 +619,7 @@ public static function analyze(
);
}

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;
Expand All @@ -628,7 +628,7 @@ public static function analyze(
&& !$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',
Expand Down
Expand Up @@ -1226,7 +1226,23 @@ function (PhpParser\Node\Arg $arg) {
)) {
// 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
Expand Down
Expand Up @@ -306,6 +306,11 @@ public static function analyze(
) {
$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(
Expand Down
10 changes: 9 additions & 1 deletion src/Psalm/Internal/Visitor/CheckTrivialExprVisitor.php
Expand Up @@ -38,7 +38,15 @@ private function checkNonTrivialExpr(PhpParser\Node\Expr $node)
|| $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;
}

Expand Down
43 changes: 43 additions & 0 deletions tests/FileManipulation/UnusedVariableManipulationTest.php
Expand Up @@ -510,6 +510,49 @@ function foo() : void {
['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,
],
];
}
}
46 changes: 46 additions & 0 deletions tests/UnusedCodeTest.php
Expand Up @@ -552,6 +552,30 @@ public function bar(): void {
(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");',
],
];
}

Expand Down Expand Up @@ -747,6 +771,28 @@ public function handle(): void
(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',
],
];
}
}

0 comments on commit bbde2d6

Please sign in to comment.