1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Allow reconciling between object and iterable (#4706)

* Allow reconciling between object and iterable

* add tests
This commit is contained in:
orklah 2020-11-26 15:25:49 +01:00 committed by GitHub
parent 4bbb72329e
commit f7cfdaabd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 21 deletions

View File

@ -1207,6 +1207,15 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
$object_types[] = $type; $object_types[] = $type;
} }
$did_remove_type = true;
} elseif ($type instanceof Atomic\TIterable) {
$clone_type = clone $type;
self::refineArrayKey($clone_type->type_params[0]);
$object_types[] = new TArray($clone_type->type_params);
$object_types[] = new TNamedObject('ArrayAccess');
$did_remove_type = true; $did_remove_type = true;
} else { } else {
$did_remove_type = true; $did_remove_type = true;
@ -1649,27 +1658,6 @@ class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
: Type::getEmpty(); : Type::getEmpty();
} }
private static function refineArrayKey(Union $key_type) : void
{
foreach ($key_type->getAtomicTypes() as $key => $cat) {
if ($cat instanceof TTemplateParam) {
self::refineArrayKey($cat->as);
$key_type->bustCache();
} elseif ($cat instanceof TScalar || $cat instanceof TMixed) {
$key_type->removeType($key);
$key_type->addType(new Type\Atomic\TArrayKey());
} elseif (!$cat instanceof TString && !$cat instanceof TInt) {
$key_type->removeType($key);
$key_type->addType(new Type\Atomic\TArrayKey());
}
}
if (!$key_type->getAtomicTypes()) {
// this should ideally prompt some sort of error
$key_type->addType(new Type\Atomic\TArrayKey());
}
}
/** /**
* @param string[] $suppressed_issues * @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation * @param 0|1|2 $failed_reconciliation

View File

@ -940,6 +940,14 @@ class SimpleNegatedAssertionReconciler extends Reconciler
Type::getMixed() Type::getMixed()
]); ]);
$non_object_types[] = new Atomic\TCallableString(); $non_object_types[] = new Atomic\TCallableString();
$did_remove_type = true;
} elseif ($type instanceof Atomic\TIterable) {
$clone_type = clone $type;
self::refineArrayKey($clone_type->type_params[0]);
$non_object_types[] = new TArray($clone_type->type_params);
$did_remove_type = true; $did_remove_type = true;
} elseif (!$type->isObjectType()) { } elseif (!$type->isObjectType()) {
$non_object_types[] = $type; $non_object_types[] = $type;

View File

@ -1,6 +1,8 @@
<?php <?php
namespace Psalm\Type; namespace Psalm\Type;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TScalar;
use function array_pop; use function array_pop;
use function array_shift; use function array_shift;
use function count; use function count;
@ -1015,4 +1017,25 @@ class Reconciler
} }
} }
} }
protected static function refineArrayKey(Union $key_type) : void
{
foreach ($key_type->getAtomicTypes() as $key => $cat) {
if ($cat instanceof TTemplateParam) {
self::refineArrayKey($cat->as);
$key_type->bustCache();
} elseif ($cat instanceof TScalar || $cat instanceof TMixed) {
$key_type->removeType($key);
$key_type->addType(new Type\Atomic\TArrayKey());
} elseif (!$cat instanceof TString && !$cat instanceof TInt) {
$key_type->removeType($key);
$key_type->addType(new Type\Atomic\TArrayKey());
}
}
if (!$key_type->getAtomicTypes()) {
// this should ideally prompt some sort of error
$key_type->addType(new Type\Atomic\TArrayKey());
}
}
} }

View File

@ -134,6 +134,8 @@ class ReconcilerTest extends \Psalm\Tests\TestCase
'traversableToIntersection' => ['Countable&Traversable', 'Traversable', 'Countable'], 'traversableToIntersection' => ['Countable&Traversable', 'Traversable', 'Countable'],
'iterableWithoutParamsToTraversableWithoutParams' => ['Traversable', '!array', 'iterable'], 'iterableWithoutParamsToTraversableWithoutParams' => ['Traversable', '!array', 'iterable'],
'iterableWithParamsToTraversableWithParams' => ['Traversable<int, string>', '!array', 'iterable<int, string>'], 'iterableWithParamsToTraversableWithParams' => ['Traversable<int, string>', '!array', 'iterable<int, string>'],
'iterableAndObject' => ['ArrayAccess|array<int, string>', 'object', 'iterable<int, string>'],
'iterableAndNotObject' => ['array<int, string>', '!object', 'iterable<int, string>'],
]; ];
} }