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

Allow iterable to subsume array and traversable

Fixes #1242
This commit is contained in:
Brown 2019-01-25 14:43:08 -05:00
parent 262ba9bdec
commit 55913699d3
4 changed files with 192 additions and 6 deletions

View File

@ -19,6 +19,7 @@ use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TLiteralFloat;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralClassString;
@ -199,6 +200,33 @@ class TypeCombination
$combination->value_types['bool'] = new TBool();
}
if (isset($combination->type_params['array'])
&& (isset($combination->named_object_types['Traversable'])
|| isset($combination->type_params['Traversable']))
) {
$array_param_types = $combination->type_params['array'];
$traversable_param_types = $combination->type_params['Traversable'] ?? [Type::getMixed(), Type::getMixed()];
$combined_param_types = [];
foreach ($array_param_types as $i => $array_param_type) {
$combined_param_types[$i] = Type::combineUnionTypes($array_param_type, $traversable_param_types[$i]);
}
$combination->value_types['iterable'] = new TIterable($combined_param_types);
/**
* @psalm-suppress PossiblyNullArrayOffset
* @psalm-suppress PossiblyNullArrayAccess
*/
unset(
$combination->value_types['array'],
$combination->named_object_types['Traversable'],
$combination->type_params['array'],
$combination->type_params['Traversable']
);
}
if ($combination->empty_mixed && $combination->non_empty_mixed) {
$combination->value_types['mixed'] = new TMixed((bool) $combination->mixed_from_loop_isset);
}
@ -303,7 +331,11 @@ class TypeCombination
$new_types[] = $array_type;
} elseif (!isset($combination->value_types[$generic_type])) {
$new_types[] = new TGenericObject($generic_type, $generic_type_params);
if ($generic_type === 'iterable') {
$new_types[] = new TIterable($generic_type_params);
} else {
$new_types[] = new TGenericObject($generic_type, $generic_type_params);
}
}
}
@ -417,9 +449,78 @@ class TypeCombination
unset($combination->value_types['true']);
}
$type_key = $type->getKey();
if ($type instanceof TArray && isset($combination->type_params['iterable'])) {
$type_key = 'iterable';
} elseif ($type instanceof TArray
&& $type->type_params[1]->isMixed()
&& isset($combination->value_types['iterable'])
) {
$type_key = 'iterable';
$combination->type_params['iterable'] = [Type::getMixed(), Type::getMixed()];
} elseif ($type instanceof TNamedObject
&& $type->value === 'Traversable'
&& (isset($combination->type_params['iterable']) || isset($combination->value_types['iterable']))
) {
$type_key = 'iterable';
if ($type instanceof TArray || $type instanceof TGenericObject) {
if (!isset($combination->type_params['iterable'])) {
$combination->type_params['iterable'] = [Type::getMixed(), Type::getMixed()];
}
if (!$type instanceof TGenericObject) {
$type = new TGenericObject($type->value, [Type::getMixed(), Type::getMixed()]);
}
} else {
$type_key = $type->getKey();
}
if ($type instanceof TIterable
&& isset($combination->type_params['array'])
&& ($type->has_docblock_params || $combination->type_params['array'][1]->isMixed())
) {
if (!isset($combination->type_params['iterable'])) {
$combination->type_params['iterable'] = $combination->type_params['array'];
} else {
foreach ($combination->type_params['array'] as $i => $array_type_param) {
$iterable_type_param = $combination->type_params['iterable'][$i];
$combination->type_params['iterable'][$i] = Type::combineUnionTypes(
$iterable_type_param,
$array_type_param
);
}
}
unset($combination->type_params['array']);
}
if ($type instanceof TIterable
&& (isset($combination->named_object_types['Traversable'])
|| isset($combination->type_params['Traversable']))
) {
if (!isset($combination->type_params['iterable'])) {
$combination->type_params['iterable']
= $combination->type_params['Traversable'] ?? [Type::getMixed(), Type::getMixed()];
} else {
foreach ($combination->type_params['Traversable'] as $i => $array_type_param) {
$iterable_type_param = $combination->type_params['iterable'][$i];
$combination->type_params['iterable'][$i] = Type::combineUnionTypes(
$iterable_type_param,
$array_type_param
);
}
}
/** @psalm-suppress PossiblyNullArrayAccess */
unset(
$combination->named_object_types['Traversable'],
$combination->type_params['Traversable']
);
}
if ($type instanceof TArray
|| $type instanceof TGenericObject
|| ($type instanceof TIterable && $type->has_docblock_params)
) {
foreach ($type->type_params as $i => $type_param) {
if (isset($combination->type_params[$type_key][$i])) {
$combination->type_params[$type_key][$i] = Type::combineUnionTypes(
@ -489,6 +590,11 @@ class TypeCombination
return null;
}
if ($type instanceof TIterable) {
$combination->value_types[$type_key] = $type;
return null;
}
if ($type instanceof TNamedObject) {
if ($combination->named_object_types === null) {
return null;

View File

@ -92,7 +92,7 @@ abstract class Atomic
return new TArrayKey();
case 'iterable':
return new TIterable([new Union([new TMixed]), new Union([new TMixed])]);
return new TIterable();
case 'never-return':
case 'never-returns':

View File

@ -14,12 +14,22 @@ class TIterable extends Atomic
*/
public $value = 'iterable';
/**
* @var bool
*/
public $has_docblock_params = false;
/**
* @param array<int, \Psalm\Type\Union> $type_params
*/
public function __construct(array $type_params)
public function __construct(array $type_params = [])
{
$this->type_params = $type_params;
if ($type_params) {
$this->has_docblock_params = true;
$this->type_params = $type_params;
} else {
$this->type_params = [\Psalm\Type::getMixed(), \Psalm\Type::getMixed()];
}
}
/**

View File

@ -195,6 +195,76 @@ class TypeCombinationTest extends TestCase
'array<string>',
],
],
'arrayTraversableToIterable' => [
'iterable<array-key|mixed, mixed>',
[
'array',
'Traversable',
],
],
'arrayIterableToIterable' => [
'iterable<mixed, mixed>',
[
'array',
'iterable',
],
],
'iterableArrayToIterable' => [
'iterable<mixed, mixed>',
[
'iterable',
'array',
],
],
'traversableIterableToIterable' => [
'iterable<mixed, mixed>',
[
'Traversable',
'iterable',
],
],
'iterableTraversableToIterable' => [
'iterable<mixed, mixed>',
[
'iterable',
'Traversable',
],
],
'arrayTraversableToIterableWithParams' => [
'iterable<int, string|bool>',
[
'array<int, string>',
'Traversable<int, bool>',
],
],
'arrayIterableToIterableWithParams' => [
'iterable<int, string|bool>',
[
'array<int, string>',
'iterable<int, bool>',
],
],
'iterableArrayToIterableWithParams' => [
'iterable<int, string|bool>',
[
'iterable<int, string>',
'array<int, bool>',
],
],
'traversableIterableToIterableWithParams' => [
'iterable<int, string|bool>',
[
'Traversable<int, string>',
'iterable<int, bool>',
],
],
'iterableTraversableToIterableWithParams' => [
'iterable<int, string|bool>',
[
'iterable<int, string>',
'Traversable<int, bool>',
],
],
'falseDestruction' => [
'bool',
[