1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-13 17:57:37 +01:00
psalm/src/Psalm/EffectsAnalyser.php

185 lines
7.4 KiB
PHP
Raw Normal View History

<?php
2016-07-26 00:37:44 +02:00
namespace Psalm;
use PhpParser;
/**
2016-11-02 07:29:00 +01:00
* A class for analysing a given method call's effects in relation to $this/self and also looking at return types
*
*/
class EffectsAnalyser
{
/**
* Gets the return types from a list of statements
*
2016-10-20 21:26:02 +02:00
* @param array<int,PhpParser\Node\Stmt> $stmts
* @param array<int,Type\Atomic> $yield_types
2016-11-02 07:29:00 +01:00
* @param bool $collapse_types
2016-10-20 20:26:03 +02:00
* @return array<int,Type\Atomic> a list of return types
*/
2016-10-20 21:26:02 +02:00
public static function getReturnTypes(array $stmts, array &$yield_types, $collapse_types = false)
{
2016-10-20 20:26:03 +02:00
/** @var array<int,Type\Atomic> */
$return_types = [];
2016-06-20 07:05:44 +02:00
$last_stmt = null;
foreach ($stmts as $stmt) {
2016-06-20 07:05:44 +02:00
if (!$stmt instanceof PhpParser\Node\Stmt\Nop) {
$last_stmt = $stmt;
}
if ($stmt instanceof PhpParser\Node\Stmt\Return_) {
2016-11-02 07:29:00 +01:00
if ($stmt->expr instanceof PhpParser\Node\Expr\Yield_ ||
$stmt->expr instanceof PhpParser\Node\Expr\YieldFrom) {
2016-10-20 21:26:02 +02:00
$yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($stmt->expr));
2016-11-02 07:29:00 +01:00
} else {
2016-10-20 21:26:02 +02:00
if (isset($stmt->inferredType)) {
$return_types = array_merge(array_values($stmt->inferredType->types), $return_types);
2016-11-02 07:29:00 +01:00
} else {
2016-10-20 21:26:02 +02:00
$return_types[] = new Type\Atomic('mixed');
}
2016-06-17 22:05:28 +02:00
}
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Expr\Yield_ || $stmt instanceof PhpParser\Node\Expr\YieldFrom) {
2016-10-20 21:26:02 +02:00
$yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($stmt));
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Expr\YieldFrom) {
2016-10-20 20:26:03 +02:00
$key_type = null;
if (isset($stmt->inferredType)) {
$return_types = array_merge(array_values($stmt->inferredType->types), $return_types);
2016-11-02 07:29:00 +01:00
} else {
2016-10-20 20:26:03 +02:00
$return_types[] = new Type\Atomic('mixed');
}
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Stmt\If_) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
foreach ($stmt->elseifs as $elseif) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($elseif->stmts, $yield_types));
}
if ($stmt->else) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($stmt->else->stmts, $yield_types));
}
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
foreach ($stmt->catches as $catch) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($catch->stmts, $yield_types));
}
if ($stmt->finally) {
$return_types = array_merge($return_types, self::getReturnTypes($stmt->finally->stmts, $yield_types));
}
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Stmt\For_) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Stmt\Foreach_) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Stmt\While_) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Stmt\Do_) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($stmt->stmts, $yield_types));
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
foreach ($stmt->cases as $case) {
2016-10-20 21:26:02 +02:00
$return_types = array_merge($return_types, self::getReturnTypes($case->stmts, $yield_types));
}
}
}
2016-06-20 07:05:44 +02:00
// if we're at the top level and we're not ending in a return, make sure to add possible null
2016-10-20 20:26:03 +02:00
if ($collapse_types) {
// if it's a generator, boil everything down to a single generator return type
2016-10-20 21:26:02 +02:00
if ($yield_types) {
2016-10-30 17:46:18 +01:00
/** @var Type\Union */
2016-10-20 20:26:03 +02:00
$key_type = null;
2016-11-02 07:29:00 +01:00
2016-10-30 17:46:18 +01:00
/** @var Type\Union */
2016-10-20 20:26:03 +02:00
$value_type = null;
2016-10-20 21:26:02 +02:00
foreach ($yield_types as $type) {
2016-10-20 20:26:03 +02:00
if ($type instanceof Type\Generic) {
$first_type_param = count($type->type_params) ? $type->type_params[0] : null;
$last_type_param = $type->type_params[count($type->type_params) - 1];
if ($value_type === null) {
$value_type = clone $last_type_param;
2016-11-02 07:29:00 +01:00
} else {
2016-10-20 20:26:03 +02:00
$value_type = Type::combineUnionTypes($value_type, $last_type_param);
}
if (!$key_type || !$first_type_param) {
$key_type = $first_type_param ? clone $first_type_param : Type::getMixed();
2016-11-02 07:29:00 +01:00
} else {
2016-10-20 20:26:03 +02:00
$key_type = Type::combineUnionTypes($key_type, $first_type_param);
}
}
}
2016-10-20 21:26:02 +02:00
$yield_types = [
2016-10-20 20:26:03 +02:00
new Type\Generic(
'Generator',
[
2016-11-21 05:31:10 +01:00
$key_type ?: Type::getMixed(),
$value_type ?: Type::getMixed()
2016-10-20 20:26:03 +02:00
]
)
];
}
2016-10-20 21:26:02 +02:00
2016-11-02 07:29:00 +01:00
if (!$last_stmt instanceof PhpParser\Node\Stmt\Return_ &&
!Checker\ScopeChecker::doesAlwaysReturnOrThrow($stmts) &&
2016-11-13 05:59:31 +01:00
!$yield_types &&
count($return_types)
2016-11-02 07:29:00 +01:00
) {
2016-11-13 05:59:31 +01:00
// only add null if we have a return statement elsewhere and it wasn't void
foreach ($return_types as $return_type) {
if (!$return_type->isVoid()) {
$return_types[] = new Type\Atomic('null');
break;
}
}
2016-10-20 20:26:03 +02:00
}
2016-06-20 07:05:44 +02:00
}
2016-06-16 02:16:40 +02:00
return $return_types;
}
2016-10-20 21:26:02 +02:00
2016-11-02 07:29:00 +01:00
/**
* @param PhpParser\Node\Expr $stmt
* @return array|null
*/
2016-10-20 21:26:02 +02:00
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];
2016-11-02 07:29:00 +01:00
} else {
2016-10-20 21:26:02 +02:00
return [new Type\Atomic('mixed')];
}
2016-11-02 07:29:00 +01:00
} elseif ($stmt instanceof PhpParser\Node\Expr\YieldFrom) {
2016-10-20 21:26:02 +02:00
$key_type = null;
if (isset($stmt->inferredType)) {
return [$stmt->inferredType->types];
2016-11-02 07:29:00 +01:00
} else {
2016-10-20 21:26:02 +02:00
return [new Type\Atomic('mixed')];
}
}
2016-11-02 07:29:00 +01:00
return null;
2016-10-20 21:26:02 +02:00
}
}