mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Fix #710 - treat keyed intersections of iterators properly
This commit is contained in:
parent
8955120bd1
commit
962d8f30a1
@ -408,19 +408,62 @@ class ForeachAnalyzer
|
||||
$has_valid_iterator = true;
|
||||
$value_type = Type::getMixed();
|
||||
} elseif ($iterator_atomic_type instanceof Type\Atomic\TIterable) {
|
||||
$value_type_part = $iterator_atomic_type->type_params[1];
|
||||
$key_type_part = $iterator_atomic_type->type_params[0];
|
||||
if ($iterator_atomic_type->extra_types) {
|
||||
$iterator_atomic_type_copy = clone $iterator_atomic_type;
|
||||
$iterator_atomic_type_copy->extra_types = [];
|
||||
$iterator_atomic_types = [$iterator_atomic_type_copy];
|
||||
$iterator_atomic_types = array_merge(
|
||||
$iterator_atomic_types,
|
||||
$iterator_atomic_type->extra_types
|
||||
);
|
||||
} else {
|
||||
$iterator_atomic_types = [$iterator_atomic_type];
|
||||
}
|
||||
|
||||
$intersection_value_type = null;
|
||||
$intersection_key_type = null;
|
||||
|
||||
foreach ($iterator_atomic_types as $iat) {
|
||||
if (!$iat instanceof Type\Atomic\TIterable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value_type_part = $iat->type_params[1];
|
||||
$key_type_part = $iat->type_params[0];
|
||||
|
||||
if (!$intersection_value_type) {
|
||||
$intersection_value_type = $value_type_part;
|
||||
} else {
|
||||
$intersection_value_type = Type::intersectUnionTypes(
|
||||
$intersection_value_type,
|
||||
$value_type_part
|
||||
);
|
||||
}
|
||||
|
||||
if (!$intersection_key_type) {
|
||||
$intersection_key_type = $key_type_part;
|
||||
} else {
|
||||
$intersection_key_type = Type::intersectUnionTypes(
|
||||
$intersection_key_type,
|
||||
$key_type_part
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$intersection_value_type || !$intersection_key_type) {
|
||||
throw new \UnexpectedValueException('Should not happen');
|
||||
}
|
||||
|
||||
if (!$value_type) {
|
||||
$value_type = $value_type_part;
|
||||
$value_type = $intersection_value_type;
|
||||
} else {
|
||||
$value_type = Type::combineUnionTypes($value_type, $value_type_part);
|
||||
$value_type = Type::combineUnionTypes($value_type, $intersection_value_type);
|
||||
}
|
||||
|
||||
if (!$key_type) {
|
||||
$key_type = $key_type_part;
|
||||
$key_type = $intersection_key_type;
|
||||
} else {
|
||||
$key_type = Type::combineUnionTypes($key_type, $key_type_part);
|
||||
$key_type = Type::combineUnionTypes($key_type, $intersection_key_type);
|
||||
}
|
||||
|
||||
$has_valid_iterator = true;
|
||||
|
@ -908,9 +908,10 @@ class Populator
|
||||
if (isset($atomic_types['array']) && count($atomic_types) > 1 && !isset($atomic_types['null'])) {
|
||||
$iterator_name = null;
|
||||
$generic_params = null;
|
||||
$iterator_key = null;
|
||||
|
||||
try {
|
||||
foreach ($atomic_types as $type) {
|
||||
foreach ($atomic_types as $type_key => $type) {
|
||||
if ($type instanceof Type\Atomic\TIterable
|
||||
|| ($type instanceof Type\Atomic\TNamedObject
|
||||
&& (!$type->from_docblock || $is_property)
|
||||
@ -927,6 +928,7 @@ class Populator
|
||||
))
|
||||
) {
|
||||
$iterator_name = $type->value;
|
||||
$iterator_key = $type_key;
|
||||
} elseif ($type instanceof Type\Atomic\TArray) {
|
||||
$generic_params = $type->type_params;
|
||||
}
|
||||
@ -935,7 +937,7 @@ class Populator
|
||||
// ignore class-not-found issues
|
||||
}
|
||||
|
||||
if ($iterator_name && $generic_params) {
|
||||
if ($iterator_name && $iterator_key && $generic_params) {
|
||||
if ($iterator_name === 'iterable') {
|
||||
$generic_iterator = new Type\Atomic\TIterable($generic_params);
|
||||
} else {
|
||||
@ -947,7 +949,7 @@ class Populator
|
||||
}
|
||||
|
||||
$candidate->removeType('array');
|
||||
$candidate->removeType($iterator_name);
|
||||
$candidate->removeType($iterator_key);
|
||||
$candidate->addType($generic_iterator);
|
||||
}
|
||||
}
|
||||
|
@ -402,7 +402,11 @@ abstract class Type
|
||||
);
|
||||
}
|
||||
|
||||
$keyed_intersection_types[$intersection_type->getKey()] = $intersection_type;
|
||||
$keyed_intersection_types[
|
||||
$intersection_type instanceof TIterable
|
||||
? $intersection_type->getId()
|
||||
: $intersection_type->getKey()
|
||||
] = $intersection_type;
|
||||
}
|
||||
|
||||
$first_type = array_shift($keyed_intersection_types);
|
||||
|
@ -39,6 +39,27 @@ class TIterable extends Atomic
|
||||
return 'iterable';
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
$s = '';
|
||||
foreach ($this->type_params as $type_param) {
|
||||
$s .= $type_param->getKey() . ', ';
|
||||
}
|
||||
|
||||
$extra_types = '';
|
||||
|
||||
if ($this->extra_types) {
|
||||
$extra_types = '&' . implode('&', $this->extra_types);
|
||||
}
|
||||
|
||||
return $this->value . '<' . substr($s, 0, -2) . '>' . $extra_types;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $namespace
|
||||
* @param array<string> $aliased_classes
|
||||
|
@ -618,6 +618,27 @@ class InterfaceTest extends TestCase
|
||||
foo($i);
|
||||
}'
|
||||
],
|
||||
'intersectIterators' => [
|
||||
'<?php
|
||||
class A {} function takesA(A $p): void {}
|
||||
class B {} function takesB(B $p): void {}
|
||||
|
||||
/** @psalm-param iterable<A>&iterable<B> $i */
|
||||
function takesIntersectionOfIterables(iterable $i): void {
|
||||
foreach ($i as $c) {
|
||||
takesA($c);
|
||||
takesB($c);
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-param iterable<A&B> $i */
|
||||
function takesIterableOfIntersections(iterable $i): void {
|
||||
foreach ($i as $c) {
|
||||
takesA($c);
|
||||
takesB($c);
|
||||
}
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -203,6 +203,14 @@ class TypeParseTest extends TestCase
|
||||
$this->assertSame('Countable&iterable<mixed, int>&I', (string) Type::parseString('Countable&iterable<int>&I'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testIntersectionOfIterables()
|
||||
{
|
||||
$this->assertSame('iterable<mixed, A>&iterable<mixed, B>', (string) Type::parseString('iterable<A>&iterable<B>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user