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:
parent
e53c79b66e
commit
aa3846758e
@ -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(
|
||||
|
@ -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
|
||||
);
|
||||
} else {
|
||||
$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_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
|
||||
);
|
||||
} else {
|
||||
$all_conditional_return_types[] = $candidate;
|
||||
$candidate_types = is_array($candidate) ? $candidate : [$candidate];
|
||||
|
||||
$else_conditional_return_types = array_merge(
|
||||
$else_conditional_return_types,
|
||||
$candidate_types
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user