From 94e3443b2d21404a821e05b901dd4b57fcbd4e7f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 22 Dec 2020 10:18:33 +0100 Subject: [PATCH] Generics - do not generalize array shape --- src/Type/Constant/ConstantArrayType.php | 16 +--------------- src/Type/Generic/TemplateMixedType.php | 3 +-- src/Type/Generic/TemplateObjectType.php | 3 +-- .../Generic/TemplateObjectWithoutClassType.php | 3 +-- src/Type/Generic/TemplateTypeHelper.php | 13 +++++++++++++ tests/PHPStan/Analyser/data/generics.php | 7 ++++++- 6 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index fc499f004b..4f28069155 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -793,23 +793,9 @@ public function getReferencedTemplateTypes(TemplateTypeVariance $positionVarianc public function traverse(callable $cb): Type { - $keyTypes = []; $valueTypes = []; $stillOriginal = true; - foreach ($this->keyTypes as $keyType) { - $transformedKeyType = $cb($keyType); - if ($transformedKeyType !== $keyType) { - $stillOriginal = false; - } - - if (!$transformedKeyType instanceof ConstantIntegerType && !$transformedKeyType instanceof ConstantStringType) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $keyTypes[] = $transformedKeyType; - } - foreach ($this->valueTypes as $valueType) { $transformedValueType = $cb($valueType); if ($transformedValueType !== $valueType) { @@ -823,7 +809,7 @@ public function traverse(callable $cb): Type return $this; } - return new self($keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys); + return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys); } public function isKeysSupersetOf(self $otherArray): bool diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index 27adda72d5..2e4513097f 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -7,7 +7,6 @@ use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; -use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -134,7 +133,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap if ($this->getBound()->isSuperTypeOf($receivedType)->yes()) { return new TemplateTypeMap([ - $this->name => TypeUtils::generalizeType($receivedType), + $this->name => TemplateTypeHelper::generalizeType($receivedType), ]); } diff --git a/src/Type/Generic/TemplateObjectType.php b/src/Type/Generic/TemplateObjectType.php index 0de2ccd6fe..bf55b908b2 100644 --- a/src/Type/Generic/TemplateObjectType.php +++ b/src/Type/Generic/TemplateObjectType.php @@ -9,7 +9,6 @@ use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; -use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -150,7 +149,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap if ($this->getBound()->isSuperTypeOf($receivedType)->yes()) { return new TemplateTypeMap([ - $this->name => TypeUtils::generalizeType($receivedType), + $this->name => TemplateTypeHelper::generalizeType($receivedType), ]); } diff --git a/src/Type/Generic/TemplateObjectWithoutClassType.php b/src/Type/Generic/TemplateObjectWithoutClassType.php index 924cbd2209..460c85049e 100644 --- a/src/Type/Generic/TemplateObjectWithoutClassType.php +++ b/src/Type/Generic/TemplateObjectWithoutClassType.php @@ -8,7 +8,6 @@ use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; -use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -177,7 +176,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap if ($this->getBound()->isSuperTypeOf($receivedType)->yes()) { return new TemplateTypeMap([ - $this->name => TypeUtils::generalizeType($receivedType), + $this->name => TemplateTypeHelper::generalizeType($receivedType), ]); } diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index 2e8e306c4c..614b56f7eb 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -2,6 +2,8 @@ namespace PHPStan\Type\Generic; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\ConstantType; use PHPStan\Type\ErrorType; use PHPStan\Type\StaticType; use PHPStan\Type\Type; @@ -58,4 +60,15 @@ public static function toArgument(Type $type): Type }); } + public static function generalizeType(Type $type): Type + { + return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + if ($type instanceof ConstantType && !$type instanceof ConstantArrayType) { + return $type->generalize(); + } + + return $traverse($type); + }); + } + } diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index eba25f516d..c7d9f88789 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -97,7 +97,7 @@ function testD($int, $float, $intFloat) assertType('DateTime|int', d($int, new \DateTime())); assertType('DateTime|float|int', d($intFloat, new \DateTime())); assertType('array()|DateTime', d([], new \DateTime())); - assertType('(array&nonEmpty)|DateTime', d(['blabla' => 'barrrr'], new \DateTime())); + assertType('array(\'blabla\' => string)|DateTime', d(['blabla' => 'barrrr'], new \DateTime())); } /** @@ -1394,3 +1394,8 @@ public function process($class): void { function (\Throwable $e): void { assertType('mixed', $e->getCode()); }; + +function (): void { + $array = ['a' => 1, 'b' => 2]; + assertType('array(\'a\' => int, \'b\' => int)', a($array)); +};