From efb9c74adbdcadc8c75ff5852b15a31fef16d054 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Tue, 13 Oct 2020 22:48:42 +0200 Subject: [PATCH 1/3] Fix an intersection with NeverType --- src/Type/Accessory/NonEmptyArrayType.php | 4 ++++ tests/PHPStan/Type/TypeCombinatorTest.php | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 1a7bdaf3ef..dd74ae2419 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -46,6 +46,10 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return TrinaryLogic::createYes(); } + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + return (new ArrayType(new MixedType(), new MixedType())) ->isSuperTypeOf($type) ->and($type->isIterableAtLeastOnce()); diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 4ace7a734a..90b402791b 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -2886,6 +2886,17 @@ public function dataIntersect(): array NeverType::class, '*NEVER*', ], + [ + [ + new IntersectionType([ + new ArrayType(new StringType(), new IntegerType()), + new NonEmptyArrayType(), + ]), + new NeverType(), + ], + NeverType::class, + '*NEVER*', + ], ]; } From aedcae8bc4416c396786f38cf033e4477ac08a79 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Tue, 13 Oct 2020 22:50:39 +0200 Subject: [PATCH 2/3] Fix GenericClassStringType being equal when it's not --- src/Type/Generic/GenericClassStringType.php | 17 +++++++ .../Generic/GenericClassStringTypeTest.php | 50 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index be4742974c..e4bfa782e1 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -156,6 +156,23 @@ public function getReferencedTemplateTypes(TemplateTypeVariance $positionVarianc return $this->type->getReferencedTemplateTypes($variance); } + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + if (!parent::equals($type)) { + return false; + } + + if (!$this->type->equals($type->type)) { + return false; + } + + return true; + } + /** * @param mixed[] $properties * @return Type diff --git a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php index ef75092463..277a3875f5 100644 --- a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php @@ -273,4 +273,54 @@ public function testAccepts( ); } + public function dataEquals(): array + { + return [ + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\Exception::class)), + true, + ], + [ + new GenericClassStringType(new ObjectType(\Exception::class)), + new GenericClassStringType(new ObjectType(\stdClass::class)), + false, + ], + [ + new GenericClassStringType(new StaticType(\Exception::class)), + new GenericClassStringType(new StaticType(\Exception::class)), + true, + ], + [ + new GenericClassStringType(new StaticType(\Exception::class)), + new GenericClassStringType(new StaticType(\stdClass::class)), + false, + ], + ]; + } + + /** + * @dataProvider dataEquals + */ + public function testEquals(GenericClassStringType $type, Type $otherType, bool $expected): void + { + $verbosityLevel = VerbosityLevel::precise(); + $typeDescription = $type->describe($verbosityLevel); + $otherTypeDescription = $otherType->describe($verbosityLevel); + + $actual = $type->equals($otherType); + $this->assertSame( + $expected, + $actual, + sprintf('%s -> equals(%s)', $typeDescription, $otherTypeDescription) + ); + + $actual = $otherType->equals($type); + $this->assertSame( + $expected, + $actual, + sprintf('%s -> equals(%s)', $otherTypeDescription, $typeDescription) + ); + } + } From 9ce01b9a79792d34bdd958cb53a7ae41396f7487 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Tue, 13 Oct 2020 22:50:47 +0200 Subject: [PATCH 3/3] Fix intersection of iterable and array --- src/Type/IterableType.php | 5 +++ src/Type/TypeCombinator.php | 17 +++++++++ tests/PHPStan/Type/TypeCombinatorTest.php | 44 +++++++++++++++++++++-- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 3a902ba09e..45cf284c58 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -34,6 +34,11 @@ public function __construct( $this->itemType = $itemType; } + public function getKeyType(): Type + { + return $this->keyType; + } + public function getItemType(): Type { return $this->itemType; diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 7c330fee06..9a84fc2978 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -738,11 +738,28 @@ public static function intersect(Type ...$types): Type if ($types[$i] instanceof ConstantArrayType && $types[$j] instanceof HasOffsetType) { $types[$i] = $types[$i]->makeOffsetRequired($types[$j]->getOffsetType()); array_splice($types, $j--, 1); + continue; } if ($types[$j] instanceof ConstantArrayType && $types[$i] instanceof HasOffsetType) { $types[$j] = $types[$j]->makeOffsetRequired($types[$i]->getOffsetType()); array_splice($types, $i--, 1); + continue 2; + } + + if ( + ($types[$i] instanceof ArrayType || $types[$i] instanceof IterableType) && + ($types[$j] instanceof ArrayType || $types[$j] instanceof IterableType) + ) { + $keyType = self::intersect($types[$i]->getKeyType(), $types[$j]->getKeyType()); + $itemType = self::intersect($types[$i]->getItemType(), $types[$j]->getItemType()); + if ($types[$i] instanceof IterableType && $types[$j] instanceof IterableType) { + $types[$j] = new IterableType($keyType, $itemType); + } else { + $types[$j] = new ArrayType($keyType, $itemType); + } + array_splice($types, $i--, 1); + continue 2; } continue; diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 90b402791b..35aa806db8 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -2002,8 +2002,48 @@ public function dataIntersect(): array new ArrayType(new MixedType(), new MixedType()), new IterableType(new MixedType(), new StringType()), ], - IntersectionType::class, - 'array&iterable', // this is correct but 'array' would be better + ArrayType::class, + 'array', + ], + [ + [ + new ArrayType(new IntegerType(), new MixedType()), + new IterableType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new ArrayType(new IntegerType(), new MixedType()), + new IterableType(new StringType(), new MixedType()), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ArrayType(new MixedType(), new IntegerType()), + new IterableType(new MixedType(), new StringType()), + ], + NeverType::class, + '*NEVER*', + ], + [ + [ + new ArrayType(new IntegerType(), new MixedType()), + new ArrayType(new MixedType(), new StringType()), + ], + ArrayType::class, + 'array', + ], + [ + [ + new IterableType(new IntegerType(), new MixedType()), + new IterableType(new MixedType(), new StringType()), + ], + IterableType::class, + 'iterable', ], [ [