mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
Improve Generator typing
This commit is contained in:
parent
4c51e02107
commit
07636468a2
@ -403,9 +403,10 @@ abstract class FunctionLikeChecker implements StatementsSource
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($declared_return_type) {
|
if ($declared_return_type) {
|
||||||
$inferred_return_types = \Psalm\EffectsAnalyser::getReturnTypes($this->function->getStmts(), true);
|
$inferred_yield_types = [];
|
||||||
|
$inferred_return_types = \Psalm\EffectsAnalyser::getReturnTypes($this->function->getStmts(), $inferred_yield_types, true);
|
||||||
|
|
||||||
if (!$inferred_return_types) {
|
if (!$inferred_return_types && !$inferred_yield_types) {
|
||||||
if ($declared_return_type->isVoid()) {
|
if ($declared_return_type->isVoid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -428,7 +429,16 @@ abstract class FunctionLikeChecker implements StatementsSource
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$inferred_return_type = Type::combineTypes($inferred_return_types);
|
$inferred_return_type = $inferred_return_types ? Type::combineTypes($inferred_return_types) : null;
|
||||||
|
|
||||||
|
$inferred_yield_type = $inferred_yield_types ? Type::combineTypes($inferred_yield_types) : null;
|
||||||
|
|
||||||
|
$inferred_generator_return_type = null;
|
||||||
|
|
||||||
|
if ($inferred_yield_type) {
|
||||||
|
$inferred_generator_return_type = $inferred_return_type;
|
||||||
|
$inferred_return_type = $inferred_yield_type;
|
||||||
|
}
|
||||||
|
|
||||||
if ($inferred_return_type && !$inferred_return_type->isMixed() && !$declared_return_type->isMixed()) {
|
if ($inferred_return_type && !$inferred_return_type->isMixed() && !$declared_return_type->isMixed()) {
|
||||||
if ($inferred_return_type->isNull() && $declared_return_type->isVoid()) {
|
if ($inferred_return_type->isNull() && $declared_return_type->isVoid()) {
|
||||||
|
@ -14,9 +14,10 @@ class EffectsAnalyser
|
|||||||
* Gets the return types from a list of statements
|
* Gets the return types from a list of statements
|
||||||
*
|
*
|
||||||
* @param array<int,PhpParser\Node\Stmt> $stmts
|
* @param array<int,PhpParser\Node\Stmt> $stmts
|
||||||
|
* @param array<int,Type\Atomic> $yield_types
|
||||||
* @return array<int,Type\Atomic> a list of return types
|
* @return array<int,Type\Atomic> a list of return types
|
||||||
*/
|
*/
|
||||||
public static function getReturnTypes(array $stmts, $collapse_types = false)
|
public static function getReturnTypes(array $stmts, array &$yield_types, $collapse_types = false)
|
||||||
{
|
{
|
||||||
/** @var array<int,Type\Atomic> */
|
/** @var array<int,Type\Atomic> */
|
||||||
$return_types = [];
|
$return_types = [];
|
||||||
@ -29,36 +30,20 @@ class EffectsAnalyser
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($stmt instanceof PhpParser\Node\Stmt\Return_) {
|
if ($stmt instanceof PhpParser\Node\Stmt\Return_) {
|
||||||
|
if ($stmt->expr instanceof PhpParser\Node\Expr\Yield_ || $stmt->expr instanceof PhpParser\Node\Expr\YieldFrom) {
|
||||||
|
$yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($stmt->expr));
|
||||||
|
}
|
||||||
|
else {
|
||||||
if (isset($stmt->inferredType)) {
|
if (isset($stmt->inferredType)) {
|
||||||
$return_types = array_merge(array_values($stmt->inferredType->types), $return_types);
|
$return_types = array_merge(array_values($stmt->inferredType->types), $return_types);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$return_types[] = new Type\Atomic('mixed');
|
$return_types[] = new Type\Atomic('mixed');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
elseif ($stmt instanceof PhpParser\Node\Expr\Yield_) {
|
|
||||||
$key_type = null;
|
|
||||||
|
|
||||||
if (isset($stmt->key->inferredType)) {
|
|
||||||
$key_type = $stmt->key->inferredType;
|
|
||||||
}
|
}
|
||||||
|
elseif ($stmt instanceof PhpParser\Node\Expr\Yield_ || $stmt instanceof PhpParser\Node\Expr\YieldFrom) {
|
||||||
if (isset($stmt->inferredType)) {
|
$yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($stmt));
|
||||||
$generator_type = new Type\Generic(
|
|
||||||
'Generator',
|
|
||||||
[
|
|
||||||
$key_type ?: Type::getInt(),
|
|
||||||
$stmt->inferredType
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$return_types = array_merge([$generator_type], $return_types);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$return_types[] = new Type\Atomic('mixed');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
elseif ($stmt instanceof PhpParser\Node\Expr\YieldFrom) {
|
elseif ($stmt instanceof PhpParser\Node\Expr\YieldFrom) {
|
||||||
$key_type = null;
|
$key_type = null;
|
||||||
@ -72,68 +57,60 @@ class EffectsAnalyser
|
|||||||
|
|
||||||
}
|
}
|
||||||
elseif ($stmt instanceof PhpParser\Node\Stmt\If_) {
|
elseif ($stmt instanceof PhpParser\Node\Stmt\If_) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
|
||||||
|
|
||||||
foreach ($stmt->elseifs as $elseif) {
|
foreach ($stmt->elseifs as $elseif) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($elseif->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($elseif->stmts, $yield_types));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($stmt->else) {
|
if ($stmt->else) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($stmt->else->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($stmt->else->stmts, $yield_types));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
|
elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
|
||||||
|
|
||||||
foreach ($stmt->catches as $catch) {
|
foreach ($stmt->catches as $catch) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($catch->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($catch->stmts, $yield_types));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($stmt->finallyStmts) {
|
if ($stmt->finallyStmts) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($stmt->finallyStmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($stmt->finallyStmts, $yield_types));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
elseif ($stmt instanceof PhpParser\Node\Stmt\For_) {
|
elseif ($stmt instanceof PhpParser\Node\Stmt\For_) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
|
||||||
|
|
||||||
}
|
}
|
||||||
elseif ($stmt instanceof PhpParser\Node\Stmt\Foreach_) {
|
elseif ($stmt instanceof PhpParser\Node\Stmt\Foreach_) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
|
||||||
|
|
||||||
}
|
}
|
||||||
elseif ($stmt instanceof PhpParser\Node\Stmt\While_) {
|
elseif ($stmt instanceof PhpParser\Node\Stmt\While_) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
|
||||||
|
|
||||||
}
|
}
|
||||||
elseif ($stmt instanceof PhpParser\Node\Stmt\Do_) {
|
elseif ($stmt instanceof PhpParser\Node\Stmt\Do_) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
|
||||||
|
|
||||||
}
|
}
|
||||||
elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
|
elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
|
||||||
foreach ($stmt->cases as $case) {
|
foreach ($stmt->cases as $case) {
|
||||||
$return_types = array_merge($return_types, self::getReturnTypes($case->stmts));
|
$return_types = array_merge($return_types, self::getReturnTypes($case->stmts, $yield_types));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're at the top level and we're not ending in a return, make sure to add possible null
|
// if we're at the top level and we're not ending in a return, make sure to add possible null
|
||||||
if ($collapse_types) {
|
if ($collapse_types) {
|
||||||
$has_generator = false;
|
|
||||||
|
|
||||||
foreach ($return_types as $return_type) {
|
|
||||||
if ($return_type->isGenerator()) {
|
|
||||||
$has_generator = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's a generator, boil everything down to a single generator return type
|
// if it's a generator, boil everything down to a single generator return type
|
||||||
if ($has_generator) {
|
if ($yield_types) {
|
||||||
$key_type = null;
|
$key_type = null;
|
||||||
$value_type = null;
|
$value_type = null;
|
||||||
|
|
||||||
foreach ($return_types as $type) {
|
foreach ($yield_types as $type) {
|
||||||
if ($type instanceof Type\Generic) {
|
if ($type instanceof Type\Generic) {
|
||||||
$first_type_param = count($type->type_params) ? $type->type_params[0] : null;
|
$first_type_param = count($type->type_params) ? $type->type_params[0] : null;
|
||||||
$last_type_param = $type->type_params[count($type->type_params) - 1];
|
$last_type_param = $type->type_params[count($type->type_params) - 1];
|
||||||
@ -154,7 +131,7 @@ class EffectsAnalyser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$return_types = [
|
$yield_types = [
|
||||||
new Type\Generic(
|
new Type\Generic(
|
||||||
'Generator',
|
'Generator',
|
||||||
[
|
[
|
||||||
@ -164,13 +141,49 @@ class EffectsAnalyser
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
if (!$last_stmt instanceof PhpParser\Node\Stmt\Return_ && !Checker\ScopeChecker::doesAlwaysReturnOrThrow($stmts)) {
|
if (!$last_stmt instanceof PhpParser\Node\Stmt\Return_ && !Checker\ScopeChecker::doesAlwaysReturnOrThrow($stmts) && !$yield_types) {
|
||||||
$return_types[] = new Type\Atomic('null');
|
$return_types[] = new Type\Atomic('null');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return $return_types;
|
return $return_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static function getYieldTypeFromExpression($stmt)
|
||||||
|
{
|
||||||
|
if ($stmt instanceof PhpParser\Node\Expr\Yield_) {
|
||||||
|
$key_type = null;
|
||||||
|
|
||||||
|
if (isset($stmt->key->inferredType)) {
|
||||||
|
$key_type = $stmt->key->inferredType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($stmt->inferredType)) {
|
||||||
|
$generator_type = new Type\Generic(
|
||||||
|
'Generator',
|
||||||
|
[
|
||||||
|
$key_type ?: Type::getInt(),
|
||||||
|
$stmt->inferredType
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [$generator_type];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [new Type\Atomic('mixed')];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
elseif ($stmt instanceof PhpParser\Node\Expr\YieldFrom) {
|
||||||
|
$key_type = null;
|
||||||
|
|
||||||
|
if (isset($stmt->inferredType)) {
|
||||||
|
return [$stmt->inferredType->types];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [new Type\Atomic('mixed')];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user