1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-16 11:26:55 +01:00
psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php

133 lines
5.0 KiB
PHP
Raw Normal View History

2020-03-12 04:09:09 +01:00
<?php
2020-05-19 01:10:48 +02:00
namespace Psalm\Internal\Analyzer\Statements\Expression\Call\Method;
2020-03-12 04:09:09 +01:00
use PhpParser;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\MethodIdentifier;
use Psalm\Issue\ImpureMethodCall;
use Psalm\IssueBuffer;
use Psalm\Type;
class MethodCallPurityAnalyzer
{
public static function analyze(
StatementsAnalyzer $statements_analyzer,
Codebase $codebase,
PhpParser\Node\Expr\MethodCall $stmt,
?string $lhs_var_id,
?string $cased_method_id,
MethodIdentifier $method_id,
\Psalm\Storage\MethodStorage $method_storage,
\Psalm\Storage\ClassLikeStorage $class_storage,
Context $context,
\Psalm\Config $config
) : bool {
$can_memoize = false;
$method_pure_compatible = $method_storage->external_mutation_free
&& $statements_analyzer->node_data->isPureCompatible($stmt->var);
if ($context->pure
&& !$method_storage->mutation_free
&& !$method_pure_compatible
) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an mutation-free method '
. $cased_method_id . ' from a pure context',
new CodeLocation($statements_analyzer, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
} elseif ($context->mutation_free
&& !$method_storage->mutation_free
&& !$method_pure_compatible
) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an possibly-mutating method '
. $cased_method_id . ' from a mutation-free context',
new CodeLocation($statements_analyzer, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
} elseif ($context->external_mutation_free
&& !$method_storage->mutation_free
&& $method_id->fq_class_name !== $context->self
&& !$method_pure_compatible
) {
if (IssueBuffer::accepts(
new ImpureMethodCall(
'Cannot call an possibly-mutating method '
. $cased_method_id . ' from a mutation-free context',
new CodeLocation($statements_analyzer, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
} elseif (($method_storage->mutation_free
|| ($method_storage->external_mutation_free
&& (isset($stmt->var->external_mutation_free) || isset($stmt->var->pure))))
&& !$context->inside_unset
) {
if ($method_storage->mutation_free && !$method_storage->mutation_free_inferred) {
if ($context->inside_conditional) {
/** @psalm-suppress UndefinedPropertyAssignment */
$stmt->pure = true;
}
$can_memoize = true;
}
if ($codebase->find_unused_variables && !$context->inside_conditional) {
if (!$context->inside_assignment && !$context->inside_call) {
if (IssueBuffer::accepts(
new \Psalm\Issue\UnusedMethodCall(
'The call to ' . $cased_method_id . ' is not used',
new CodeLocation($statements_analyzer, $stmt->name),
(string) $method_id
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
} elseif (!$method_storage->mutation_free_inferred) {
/** @psalm-suppress UndefinedPropertyAssignment */
$stmt->pure = true;
}
}
}
if (!$config->remember_property_assignments_after_call
&& !$method_storage->mutation_free
&& !$method_pure_compatible
) {
$context->removeAllObjectVars();
} elseif ($method_storage->this_property_mutations) {
foreach ($method_storage->this_property_mutations as $name => $_) {
$mutation_var_id = $lhs_var_id . '->' . $name;
$this_property_didnt_exist = $lhs_var_id === '$this'
&& isset($context->vars_in_scope[$mutation_var_id])
&& !isset($class_storage->declaring_property_ids[$name]);
$context->remove($mutation_var_id);
if ($this_property_didnt_exist) {
$context->vars_in_scope[$mutation_var_id] = Type::getMixed();
}
}
}
return $can_memoize;
}
}