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

Fix #3153 - narrow template types in conditional branches

This commit is contained in:
Brown 2020-04-17 09:53:58 -04:00
parent e53c79b66e
commit aa3846758e
4 changed files with 157 additions and 17 deletions

View File

@ -765,7 +765,9 @@ class ReturnTypeAnalyzer
$storage->return_type,
$classlike_storage ? $classlike_storage->name : null,
$classlike_storage ? $classlike_storage->name : null,
$parent_class
$parent_class,
true,
true
);
if ($fleshed_out_return_type->check(

View File

@ -1535,7 +1535,28 @@ class ExpressionAnalyzer
if ($return_type instanceof Type\Atomic\TConditional) {
if ($evaluate_conditional_types) {
$all_conditional_return_types = [];
$assertion = null;
if ($return_type->conditional_type->isSingle()) {
foreach ($return_type->conditional_type->getAtomicTypes() as $condition_atomic_type) {
$candidate = self::fleshOutAtomicType(
$codebase,
$condition_atomic_type,
$self_class,
$static_class_type,
$parent_class,
$evaluate_class_constants,
$evaluate_conditional_types,
$final
);
if (!is_array($candidate)) {
$assertion = $candidate->getAssertionString();
}
}
}
$if_conditional_return_types = [];
foreach ($return_type->if_type->getAtomicTypes() as $if_atomic_type) {
$candidate = self::fleshOutAtomicType(
@ -1549,16 +1570,16 @@ class ExpressionAnalyzer
$final
);
if (is_array($candidate)) {
$all_conditional_return_types = array_merge(
$all_conditional_return_types,
$candidate
$candidate_types = is_array($candidate) ? $candidate : [$candidate];
$if_conditional_return_types = array_merge(
$if_conditional_return_types,
$candidate_types
);
} else {
$all_conditional_return_types[] = $candidate;
}
}
$else_conditional_return_types = [];
foreach ($return_type->else_type->getAtomicTypes() as $else_atomic_type) {
$candidate = self::fleshOutAtomicType(
$codebase,
@ -1571,16 +1592,53 @@ class ExpressionAnalyzer
$final
);
if (is_array($candidate)) {
$all_conditional_return_types = array_merge(
$all_conditional_return_types,
$candidate
$candidate_types = is_array($candidate) ? $candidate : [$candidate];
$else_conditional_return_types = array_merge(
$else_conditional_return_types,
$candidate_types
);
} else {
$all_conditional_return_types[] = $candidate;
}
if ($assertion && $return_type->param_name === (string) $return_type->if_type) {
$if_conditional_return_type = TypeCombination::combineTypes(
$if_conditional_return_types,
$codebase
);
$if_conditional_return_type = \Psalm\Internal\Type\SimpleAssertionReconciler::reconcile(
$assertion,
$codebase,
$if_conditional_return_type
);
if ($if_conditional_return_type) {
$if_conditional_return_types = array_values($if_conditional_return_type->getAtomicTypes());
}
}
if ($assertion && $return_type->param_name === (string) $return_type->else_type) {
$else_conditional_return_type = TypeCombination::combineTypes(
$else_conditional_return_types,
$codebase
);
$else_conditional_return_type = \Psalm\Internal\Type\SimpleNegatedAssertionReconciler::reconcile(
$assertion,
$else_conditional_return_type
);
if ($else_conditional_return_type) {
$else_conditional_return_types = array_values($else_conditional_return_type->getAtomicTypes());
}
}
$all_conditional_return_types = array_merge(
$if_conditional_return_types,
$else_conditional_return_types
);
foreach ($all_conditional_return_types as $i => $conditional_return_type) {
if ($conditional_return_type instanceof Type\Atomic\TVoid
&& count($all_conditional_return_types) > 1

View File

@ -351,6 +351,23 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
);
}
if ($existing_var_type->isSingle()
&& $existing_var_type->hasTemplate()
&& strpos($assertion, '-') === false
) {
foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof TTemplateParam) {
if ($atomic_type->as->hasMixed()
|| $atomic_type->as->hasObject()
) {
$atomic_type->as = Type::parseString($assertion);
return $existing_var_type;
}
}
}
}
return null;
}
@ -573,6 +590,17 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
$did_remove_type = true;
} elseif ($type instanceof TTemplateParam) {
if ($type->as->hasString() || $type->as->hasMixed()) {
$type = clone $type;
$type->as = self::reconcileString(
$type->as,
null,
null,
$suppressed_issues,
$failed_reconciliation,
$is_equality
);
$string_types[] = $type;
}

View File

@ -284,6 +284,58 @@ class ConditionalReturnTypeTest extends TestCase
app(A::class)->test1();
app()->test2();'
],
'refineTypeInConditionalWithString' => [
'<?php
/**
* @template TInput
*
* @param TInput $input
*
* @return (TInput is string ? TInput : \'hello\')
*/
function foobaz($input): string {
if (is_string($input)) {
return $input;
}
return "hello";
}
$a = foobaz("boop");
$b = foobaz(4);',
[
'$a' => 'string',
'$b' => 'string',
]
],
'refineTypeInConditionalWithClassName' => [
'<?php
class A {}
class AChild extends A {}
class B {}
/**
* @template TInput as object
*
* @param TInput $input
*
* @return (TInput is A ? TInput : A)
*/
function foobaz(object $input): A {
if ($input instanceof A) {
return $input;
}
return new A();
}
$a = foobaz(new AChild());
$b = foobaz(new B());',
[
'$a' => 'AChild',
'$b' => 'A',
]
],
];
}
}