From d5bf23b18f0c7484771c0298a6cd5359345eab94 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 16 Mar 2024 09:20:46 +0100 Subject: [PATCH] ArrayType - use getIterableKeyType, it preserves array-key --- src/Type/ArrayType.php | 6 +-- src/Type/Constant/ConstantArrayType.php | 6 +-- src/Type/TypeCombinator.php | 4 +- src/Type/TypehintHelper.php | 2 +- tests/PHPStan/Analyser/data/bug-4498.php | 2 +- tests/PHPStan/Analyser/data/bug-7805.php | 4 +- .../ParameterOutAssignedTypeRuleTest.php | 5 ++ .../Variables/data/benevolent-array-key.php | 53 +++++++++++++++++++ 8 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Rules/Variables/data/benevolent-array-key.php diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 3c519af68b..2368793e1b 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -132,7 +132,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic { if ($type instanceof self) { return $this->getItemType()->isSuperTypeOf($type->getItemType()) - ->and($this->keyType->isSuperTypeOf($type->keyType)); + ->and($this->getIterableKeyType()->isSuperTypeOf($type->getIterableKeyType())); } if ($type instanceof CompoundType) { @@ -637,7 +637,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap } if ($receivedType->isArray()->yes()) { - $keyTypeMap = $this->getKeyType()->inferTemplateTypes($receivedType->getIterableKeyType()); + $keyTypeMap = $this->getIterableKeyType()->inferTemplateTypes($receivedType->getIterableKeyType()); $itemTypeMap = $this->getItemType()->inferTemplateTypes($receivedType->getIterableValueType()); return $keyTypeMap->union($itemTypeMap); @@ -651,7 +651,7 @@ public function getReferencedTemplateTypes(TemplateTypeVariance $positionVarianc $variance = $positionVariance->compose(TemplateTypeVariance::createCovariant()); return array_merge( - $this->getKeyType()->getReferencedTemplateTypes($variance), + $this->getIterableKeyType()->getReferencedTemplateTypes($variance), $this->getItemType()->getReferencedTemplateTypes($variance), ); } diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 5363230c83..210cede622 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -875,7 +875,7 @@ public function shuffleArray(): Type return $valuesArray; } - $generalizedArray = new ArrayType($valuesArray->getKeyType(), $valuesArray->getItemType()); + $generalizedArray = new ArrayType($valuesArray->getIterableKeyType(), $valuesArray->getItemType()); if ($isIterableAtLeastOnce->yes()) { $generalizedArray = TypeCombinator::intersect($generalizedArray, new NonEmptyArrayType()); @@ -1229,7 +1229,7 @@ public function generalize(GeneralizePrecision $precision): Type } $arrayType = new ArrayType( - $this->getKeyType()->generalize($precision), + $this->getIterableKeyType()->generalize($precision), $this->getItemType()->generalize($precision), ); @@ -1281,7 +1281,7 @@ public function generalizeToArray(): Type return $this; } - $arrayType = new ArrayType($this->getKeyType(), $this->getItemType()); + $arrayType = new ArrayType($this->getIterableKeyType(), $this->getItemType()); if ($isIterableAtLeastOnce->yes()) { $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 5d8c425343..e31faed83a 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -671,7 +671,7 @@ private static function processArrayTypes(array $arrayTypes): array foreach ($arrayTypes as $arrayType) { if ($generalArrayOccurred || !$arrayType->isConstantArray()->yes()) { foreach ($arrayType->getArrays() as $type) { - $keyTypesForGeneralArray[] = $type->getKeyType(); + $keyTypesForGeneralArray[] = $type->getIterableKeyType(); $valueTypesForGeneralArray[] = $type->getItemType(); $generalArrayOccurred = true; } @@ -1200,7 +1200,7 @@ public static function intersect(Type ...$types): Type ($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()); + $keyType = self::intersect($types[$i]->getIterableKeyType(), $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); diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 03cc1c9d5f..09e33f5c9b 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -184,7 +184,7 @@ public static function decideType( foreach ($phpDocType->getTypes() as $innerType) { if ($innerType instanceof ArrayType) { $innerTypes[] = new IterableType( - $innerType->getKeyType(), + $innerType->getIterableKeyType(), $innerType->getItemType(), ); } else { diff --git a/tests/PHPStan/Analyser/data/bug-4498.php b/tests/PHPStan/Analyser/data/bug-4498.php index 19e878c763..ad07baa3db 100644 --- a/tests/PHPStan/Analyser/data/bug-4498.php +++ b/tests/PHPStan/Analyser/data/bug-4498.php @@ -38,7 +38,7 @@ public function fcn(iterable $iterable): iterable public function bar(iterable $iterable): iterable { if (is_array($iterable)) { - assertType('array', $iterable); + assertType('array<((int&TKey (method Bug4498\Foo::bar(), argument))|(string&TKey (method Bug4498\Foo::bar(), argument))), TValue (method Bug4498\Foo::bar(), argument)>', $iterable); return $iterable; } diff --git a/tests/PHPStan/Analyser/data/bug-7805.php b/tests/PHPStan/Analyser/data/bug-7805.php index 8d59d97184..8a1d360f63 100644 --- a/tests/PHPStan/Analyser/data/bug-7805.php +++ b/tests/PHPStan/Analyser/data/bug-7805.php @@ -21,10 +21,10 @@ function foo(array $params) assertNativeType("array", $params); $params = $params === [] ? ['list'] : $params; assertType("array{'list'}", $params); - assertNativeType("non-empty-array", $params); + assertNativeType("non-empty-array', mixed>", $params); array_unshift($params, 'help'); assertType("array{'help', 'list'}", $params); - assertNativeType("non-empty-array", $params); + assertNativeType("non-empty-array", $params); } assertType("array{}|array{'help', 'list'}", $params); assertNativeType('array', $params); diff --git a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php index f744305376..77bdcc2c1f 100644 --- a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php +++ b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php @@ -59,4 +59,9 @@ public function testBug10699(): void $this->analyse([__DIR__ . '/../../Analyser/data/bug-10699.php'], []); } + public function testBenevolentArrayKey(): void + { + $this->analyse([__DIR__ . '/data/benevolent-array-key.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/benevolent-array-key.php b/tests/PHPStan/Rules/Variables/data/benevolent-array-key.php new file mode 100644 index 0000000000..83be0c39f6 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/benevolent-array-key.php @@ -0,0 +1,53 @@ + $matches + * @param-out array> $matches + */ + public static function matchAllStrictGroups(array &$matches): int + { + $result = self::matchAll($matches); + + return $result; + } + + /** + * @param array $matches + * @param-out array> $matches + */ + public static function matchAll(array &$matches): int + { + $matches = [['foo']]; + + return 1; + } +} + +class HelloWorld2 +{ + /** + * @param array $matches + * @param-out array> $matches + */ + public static function matchAllStrictGroups(array &$matches): int + { + $result = self::matchAll($matches); + + return $result; + } + + /** + * @param array $matches + * @param-out array> $matches + */ + public static function matchAll(array &$matches): int + { + $matches = [['foo']]; + + return 1; + } +}