From 3b64b125eeff1d99b52b1fc4162db05fcd80e64e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Mar 2020 14:51:47 +0100 Subject: [PATCH] Fixed iterable vs. empty constant array --- src/Type/Constant/ConstantArrayType.php | 5 ++ src/Type/IterableType.php | 8 +++ .../Rules/Methods/data/returnTypes.php | 14 +++++ .../Type/Constant/ConstantArrayTypeTest.php | 6 +++ tests/PHPStan/Type/IterableTypeTest.php | 51 +++++++++++++++++++ 5 files changed, 84 insertions(+) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index ac6b107472..21ef15b5fc 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -57,6 +57,11 @@ public function __construct(array $keyTypes, array $valueTypes, int $nextAutoInd $this->nextAutoIndex = $nextAutoIndex; } + public function isEmpty(): bool + { + return count($this->keyTypes) === 0; + } + public function getNextAutoIndex(): int { return $this->nextAutoIndex; diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 84fff0d475..98c6ad36af 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -3,6 +3,7 @@ namespace PHPStan\Type; use PHPStan\TrinaryLogic; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeMap; @@ -53,6 +54,9 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { + if ($type instanceof ConstantArrayType && $type->isEmpty()) { + return TrinaryLogic::createYes(); + } if ($type->isIterable()->yes()) { return $this->getIterableValueType()->accepts($type->getIterableValueType(), $strictTypes) ->and($this->getIterableKeyType()->accepts($type->getIterableKeyType(), $strictTypes)); @@ -118,6 +122,10 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic $limit = TrinaryLogic::createMaybe(); } + if ($otherType instanceof ConstantArrayType && $otherType->isEmpty()) { + return TrinaryLogic::createMaybe(); + } + return $limit->and( $otherType->isIterable(), $otherType->getIterableValueType()->isSuperTypeOf($this->itemType), diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes.php b/tests/PHPStan/Rules/Methods/data/returnTypes.php index fb69d5ae1b..8e8ff8407f 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes.php @@ -1212,3 +1212,17 @@ public function key() } } + +class Bug3072 +{ + + /** + * @template T + * @return iterable + */ + public function getIterable(): iterable + { + return []; + } + +} diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index 27e716495a..0bade9f230 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -182,6 +182,12 @@ public function dataIsSuperTypeOf(): iterable new ArrayType(new MixedType(), new MixedType()), TrinaryLogic::createMaybe(), ]; + + yield [ + new ConstantArrayType([], []), + new IterableType(new MixedType(false), new MixedType(true)), + TrinaryLogic::createMaybe(), + ]; } /** diff --git a/tests/PHPStan/Type/IterableTypeTest.php b/tests/PHPStan/Type/IterableTypeTest.php index 85853a517e..12ff48c7b3 100644 --- a/tests/PHPStan/Type/IterableTypeTest.php +++ b/tests/PHPStan/Type/IterableTypeTest.php @@ -6,6 +6,7 @@ use PHPStan\Type\Accessory\HasMethodType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; @@ -46,6 +47,11 @@ public function dataIsSuperTypeOf(): array new ObjectType('Iterator'), TrinaryLogic::createMaybe(), ], + [ + new IterableType(new MixedType(false), new MixedType(true)), + new ConstantArrayType([], []), + TrinaryLogic::createYes(), + ], ]; } @@ -299,4 +305,49 @@ public function testDescribe(Type $type, string $expect): void $this->assertSame($expect, $result); } + public function dataAccepts(): array + { + /** @var TemplateMixedType $t */ + $t = TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant() + ); + return [ + [ + new IterableType( + new MixedType(), + $t, + ), + new ConstantArrayType([], []), + TrinaryLogic::createYes(), + ], + [ + new IterableType( + new MixedType(), + $t->toArgument() + ), + new ConstantArrayType([], []), + TrinaryLogic::createYes(), + ], + ]; + } + + /** + * @dataProvider dataAccepts + * @param IterableType $iterableType + * @param Type $otherType + * @param TrinaryLogic $expectedResult + */ + public function testAccepts(IterableType $iterableType, Type $otherType, TrinaryLogic $expectedResult): void + { + $actualResult = $iterableType->accepts($otherType, true); + $this->assertSame( + $expectedResult->describe(), + $actualResult->describe(), + sprintf('%s -> accepts(%s)', $iterableType->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise())) + ); + } + }