1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 13:51:54 +01:00

Fix #4038 - don’t remove null types unnecessarily in mixed union

This commit is contained in:
Brown 2020-08-25 15:50:33 -04:00 committed by Daniil Gentili
parent a466feda18
commit a0a9c9b7bc
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
5 changed files with 75 additions and 98 deletions

View File

@ -238,15 +238,12 @@ class ReturnTypeAnalyzer
}
}
$inferred_return_type = TypeAnalyzer::simplifyUnionType(
$inferred_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
$codebase,
\Psalm\Internal\Type\TypeExpander::expandUnion(
$codebase,
$inferred_return_type,
$source->getFQCLN(),
$source->getFQCLN(),
$source->getParentFQCLN()
)
$inferred_return_type,
$source->getFQCLN(),
$source->getFQCLN(),
$source->getParentFQCLN()
);
// hack until we have proper yield type collection
@ -552,8 +549,9 @@ class ReturnTypeAnalyzer
return null;
}
} elseif ((!$inferred_return_type->isNullable() && $declared_return_type->isNullable())
|| (!$inferred_return_type->isFalsable() && $declared_return_type->isFalsable())
} elseif (!$inferred_return_type->hasMixed()
&& ((!$inferred_return_type->isNullable() && $declared_return_type->isNullable())
|| (!$inferred_return_type->isFalsable() && $declared_return_type->isFalsable()))
) {
if ($function instanceof Function_
|| $function instanceof Closure

View File

@ -255,7 +255,7 @@ class ReturnAnalyzer
return null;
}
if ($stmt_type->isMixed()) {
if ($stmt_type->hasMixed()) {
if ($local_return_type->isVoid() || $local_return_type->isNever()) {
if (IssueBuffer::accepts(
new InvalidReturnStatement(
@ -276,17 +276,29 @@ class ReturnAnalyzer
$codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath());
}
if ($stmt_type->isMixed()) {
if (IssueBuffer::accepts(
new MixedReturnStatement(
'Could not infer a return type',
new CodeLocation($source, $stmt->expr)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
return null;
}
if (IssueBuffer::accepts(
new MixedReturnStatement(
'Could not infer a return type',
'Possibly-mixed return value',
new CodeLocation($source, $stmt->expr)
),
$statements_analyzer->getSuppressedIssues()
)) {
return false;
// fall through
}
return null;
}
if ($local_return_type->isMixed()) {

View File

@ -120,87 +120,4 @@ class TypeAnalyzer
return $result_types;
}
/**
* @return Type\Union
*/
public static function simplifyUnionType(Codebase $codebase, Type\Union $union)
{
$union_type_count = count($union->getAtomicTypes());
if ($union_type_count === 1 || ($union_type_count === 2 && $union->isNullable())) {
return $union;
}
$from_docblock = $union->from_docblock;
$ignore_nullable_issues = $union->ignore_nullable_issues;
$ignore_falsable_issues = $union->ignore_falsable_issues;
$possibly_undefined = $union->possibly_undefined;
$unique_types = [];
$inverse_contains = [];
foreach ($union->getAtomicTypes() as $type_part) {
$is_contained_by_other = false;
// don't try to simplify intersection types
if (($type_part instanceof TNamedObject
|| $type_part instanceof TTemplateParam
|| $type_part instanceof TIterable)
&& $type_part->extra_types
) {
return $union;
}
foreach ($union->getAtomicTypes() as $container_type_part) {
$string_container_part = $container_type_part->getId();
$string_input_part = $type_part->getId();
$atomic_comparison_result = new TypeComparisonResult();
if ($type_part !== $container_type_part &&
!(
$container_type_part instanceof TInt
|| $container_type_part instanceof TFloat
|| $container_type_part instanceof TCallable
|| ($container_type_part instanceof TString && $type_part instanceof TCallable)
|| ($container_type_part instanceof TArray && $type_part instanceof TCallable)
) &&
!isset($inverse_contains[$string_input_part][$string_container_part]) &&
AtomicTypeComparator::isContainedBy(
$codebase,
$type_part,
$container_type_part,
false,
false,
$atomic_comparison_result
) &&
!$atomic_comparison_result->to_string_cast
) {
$inverse_contains[$string_container_part][$string_input_part] = true;
$is_contained_by_other = true;
break;
}
}
if (!$is_contained_by_other) {
$unique_types[] = $type_part;
}
}
if (!$unique_types) {
throw new \UnexpectedValueException('There must be more than one unique type');
}
$unique_type = new Type\Union($unique_types);
$unique_type->from_docblock = $from_docblock;
$unique_type->ignore_nullable_issues = $ignore_nullable_issues;
$unique_type->ignore_falsable_issues = $ignore_falsable_issues;
$unique_type->possibly_undefined = $possibly_undefined;
return $unique_type;
}
}

View File

@ -1717,6 +1717,9 @@ class ArrayAssignmentTest extends TestCase
$cache[$locale] = 5;
}
/**
* @psalm-suppress MixedReturnStatement
*/
return $cache[$locale];
}',
'error_message' => 'InvalidReturnStatement',
@ -1733,6 +1736,7 @@ class ArrayAssignmentTest extends TestCase
/**
* @psalm-suppress MixedArrayAccess
* @psalm-suppress MixedReturnStatement
*/
return $cache[$locale][$locale];
}',

View File

@ -1337,6 +1337,52 @@ class FunctionTemplateTest extends TestCase
[],
'7.4'
],
'mixedDoesntSwallowNull' => [
'<?php
/**
* @template E
* @param E $e
* @param mixed $d
* @return ?E
* @psalm-suppress MixedInferredReturnType
*/
function reduce_values($e, $d) {
if (rand(0, 1)) {
$c = $e;
} elseif (rand(0, 1)) {
/** @psalm-suppress MixedAssignment */
$c = $d;
} else {
$c = null;
}
/** @psalm-suppress MixedReturnStatement */
return $c;
}'
],
'mixedDoesntSwallowNullProgressive' => [
'<?php
/**
* @template E
* @param E $e
* @param mixed $d
* @return ?E
* @psalm-suppress MixedInferredReturnType
*/
function reduce_values($e, $d)
{
if (rand(0, 1)) {
$d = $e;
}
if (rand(0, 1)) {
/** @psalm-suppress MixedReturnStatement */
return $d;
}
return null;
}'
],
];
}