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

Overwrite memoized return type after sum-type candidate is calculated (#4805)

* Overwrite memoized return type after sum-type candidate is calculated

* Fix mismatched types

* Fix code style
This commit is contained in:
2e3s 2020-12-08 09:35:11 -05:00 committed by Daniil Gentili
parent 7825a71351
commit df8d2e2296
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
4 changed files with 58 additions and 20 deletions

View File

@ -79,4 +79,9 @@ class AtomicMethodCallAnalysisResult
* @var list<\Psalm\Internal\MethodIdentifier> * @var list<\Psalm\Internal\MethodIdentifier>
*/ */
public $too_few_arguments_method_ids = []; public $too_few_arguments_method_ids = [];
/**
* @var bool
*/
public $can_memoize = false;
} }

View File

@ -200,8 +200,6 @@ class ExistingAtomicMethodCallAnalyzer extends CallAnalyzer
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
$can_memoize = false;
$return_type_candidate = MethodCallReturnTypeFetcher::fetch( $return_type_candidate = MethodCallReturnTypeFetcher::fetch(
$statements_analyzer, $statements_analyzer,
$codebase, $codebase,
@ -253,7 +251,7 @@ class ExistingAtomicMethodCallAnalyzer extends CallAnalyzer
if ($method_storage) { if ($method_storage) {
if (!$context->collect_mutations && !$context->collect_initializations) { if (!$context->collect_mutations && !$context->collect_initializations) {
$can_memoize = MethodCallPurityAnalyzer::analyze( $result->can_memoize = MethodCallPurityAnalyzer::analyze(
$statements_analyzer, $statements_analyzer,
$codebase, $codebase,
$stmt, $stmt,
@ -349,23 +347,6 @@ class ExistingAtomicMethodCallAnalyzer extends CallAnalyzer
} }
} }
if (!$args && $lhs_var_id) {
if ($config->memoize_method_calls || $can_memoize) {
$method_var_id = $lhs_var_id . '->' . $method_name_lc . '()';
if (isset($context->vars_in_scope[$method_var_id])) {
$return_type_candidate = clone $context->vars_in_scope[$method_var_id];
if ($can_memoize) {
/** @psalm-suppress UndefinedPropertyAssignment */
$stmt->pure = true;
}
} else {
$context->vars_in_scope[$method_var_id] = $return_type_candidate;
}
}
}
if ($codebase->methods_to_rename) { if ($codebase->methods_to_rename) {
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);

View File

@ -28,6 +28,7 @@ use Psalm\Type\Atomic\TNamedObject;
use function count; use function count;
use function is_string; use function is_string;
use function array_reduce; use function array_reduce;
use function strtolower;
/** /**
* @internal * @internal
@ -201,6 +202,20 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$possible_new_class_types[] = $context->vars_in_scope[$lhs_var_id]; $possible_new_class_types[] = $context->vars_in_scope[$lhs_var_id];
} }
} }
if (!$stmt->args && $lhs_var_id && $stmt->name instanceof PhpParser\Node\Identifier) {
if ($codebase->config->memoize_method_calls || $result->can_memoize) {
$method_var_id = $lhs_var_id . '->' . strtolower($stmt->name->name) . '()';
if (isset($context->vars_in_scope[$method_var_id])) {
$result->return_type = clone $context->vars_in_scope[$method_var_id];
if ($result->can_memoize) {
/** @psalm-suppress UndefinedPropertyAssignment */
$stmt->pure = true;
}
} elseif ($result->return_type !== null) {
$context->vars_in_scope[$method_var_id] = $result->return_type;
}
}
}
if (count($possible_new_class_types) > 0) { if (count($possible_new_class_types) > 0) {
$class_type = array_reduce( $class_type = array_reduce(

View File

@ -1476,6 +1476,43 @@ class ClassTemplateTest extends TestCase
'$a_or_b' => 'A|B', '$a_or_b' => 'A|B',
], ],
], ],
'doNotCombineTypesWhenMemoized' => [
'<?php
class A {}
class B {}
/**
* @template T
*/
class C {
/**
* @var T
*/
private $t;
/**
* @param T $t
*/
public function __construct($t) {
$this->t = $t;
}
/**
* @return T
* @psalm-mutation-free
*/
public function get() {
return $this->t;
}
}
/** @var C<A>|C<B> $random_collection **/
$a_or_b = $random_collection->get();',
[
'$random_collection' => 'C<A>|C<B>',
'$a_or_b' => 'A|B',
],
],
'inferClosureParamTypeFromContext' => [ 'inferClosureParamTypeFromContext' => [
'<?php '<?php
/** /**