1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Prevent implicit __toString method calls in a pure context

This commit is contained in:
Matthew Brown 2020-02-23 18:18:25 -05:00
parent 1abece4f7c
commit 618ae77846
2 changed files with 100 additions and 0 deletions

View File

@ -12,6 +12,7 @@ use Psalm\Config;
use Psalm\Context; use Psalm\Context;
use Psalm\Issue\FalseOperand; use Psalm\Issue\FalseOperand;
use Psalm\Issue\ImplicitToStringCast; use Psalm\Issue\ImplicitToStringCast;
use Psalm\Issue\ImpureMethodCall;
use Psalm\Issue\InvalidOperand; use Psalm\Issue\InvalidOperand;
use Psalm\Issue\MixedOperand; use Psalm\Issue\MixedOperand;
use Psalm\Issue\NullOperand; use Psalm\Issue\NullOperand;
@ -925,6 +926,70 @@ class BinaryOpAnalyzer
$statements_analyzer->node_data->setType($stmt, Type::getBool()); $statements_analyzer->node_data->setType($stmt, Type::getBool());
} }
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal) {
if ($stmt_left_type->hasString() && $stmt_right_type->hasObjectType()) {
foreach ($stmt_right_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof TNamedObject) {
try {
$storage = $codebase->methods->getStorage(
new \Psalm\Internal\MethodIdentifier(
$atomic_type->value,
'__tostring'
)
);
} catch (\UnexpectedValueException $e) {
continue;
}
if ($context->mutation_free
&& !$storage->mutation_free
) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an mutation-free method '
. $atomic_type->value . '::__toString from a pure context',
new CodeLocation($source, $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
}
} elseif ($stmt_right_type->hasString() && $stmt_left_type->hasObjectType()) {
foreach ($stmt_left_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof TNamedObject) {
try {
$storage = $codebase->methods->getStorage(
new \Psalm\Internal\MethodIdentifier(
$atomic_type->value,
'__tostring'
)
);
} catch (\UnexpectedValueException $e) {
continue;
}
if ($context->mutation_free
&& !$storage->mutation_free
) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an mutation-free method '
. $atomic_type->value . '::__toString from a pure context',
new CodeLocation($statements_analyzer, $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
}
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) { if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) {
$statements_analyzer->node_data->setType($stmt, Type::getInt()); $statements_analyzer->node_data->setType($stmt, Type::getInt());
} }

View File

@ -182,6 +182,23 @@ class PureAnnotationTest extends TestCase
return $ar[0] ?? 0; return $ar[0] ?? 0;
}', }',
], ],
'allowPureToString' => [
'<?php
class A {
/** @psalm-pure */
public function __toString() {
return "bar";
}
}
/**
* @psalm-pure
*/
function foo(string $s, A $a) : string {
if ($a == $s) {}
return $s;
}',
],
]; ];
} }
@ -383,6 +400,24 @@ class PureAnnotationTest extends TestCase
}', }',
'error_message' => 'ImpureStaticProperty', 'error_message' => 'ImpureStaticProperty',
], ],
'preventImpureToString' => [
'<?php
class A {
public function __toString() {
echo "hi";
return "bar";
}
}
/**
* @psalm-pure
*/
function foo(string $s, A $a) : string {
if ($a == $s) {}
return $s;
}',
'error_message' => 'ImpureMethodCall'
],
]; ];
} }
} }