From 29d5e1d150609f4bd9633db1a051112933fbba34 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 28 Oct 2024 18:27:45 +0100 Subject: [PATCH 1/8] More precise `array_keys` return type --- ...KeysFunctionDynamicReturnTypeExtension.php | 23 ++++++++- tests/PHPStan/Analyser/nsrt/bug-11928.php | 51 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11928.php diff --git a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php index 550e668893..0766ae6ad9 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -7,10 +7,14 @@ use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\ArrayType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use function count; use function strtolower; @@ -29,7 +33,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { - if (count($functionCall->getArgs()) !== 1) { + if (count($functionCall->getArgs()) < 1) { return null; } @@ -38,7 +42,22 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType(); } - return $arrayType->getKeysArray(); + $keysArray = $arrayType->getKeysArray(); + if (count($functionCall->getArgs()) === 1) { + return $keysArray; + } + + $newArrayType = $keysArray; + if (!$keysArray->isConstantArray()->no()) { + $newArrayType = new ArrayType( + $keysArray->getIterableKeyType()->generalize(GeneralizePrecision::lessSpecific()), + $keysArray->getIterableValueType()->generalize(GeneralizePrecision::lessSpecific()), + ); + } + if ($keysArray->isList()->yes()) { + $newArrayType = TypeCombinator::intersect($newArrayType, new AccessoryArrayListType()); + } + return $newArrayType; } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11928.php b/tests/PHPStan/Analyser/nsrt/bug-11928.php new file mode 100644 index 0000000000..b6a254a773 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11928.php @@ -0,0 +1,51 @@ + 1, 3 => 2, 4 => 1]; + + $keys = array_keys($a, 1); // returns [2, 4] + assertType('list', $keys); + + $keys = array_keys($a); // returns [2, 3, 4] + assertType('array{2, 3, 4}', $keys); +} + +function doFooStrings() { + $a = [2 => 'hi', 3 => '123', 'xy' => 5]; + $keys = array_keys($a, 1); + assertType('list', $keys); + + $keys = array_keys($a); + assertType("array{2, 3, 'xy'}", $keys); +} + +/** + * @param array $array + * @param list $list + * @param array $strings + * @return void + */ +function doFooBar(array $array, array $list, array $strings) { + $keys = array_keys($strings, "a", true); + assertType('list', $keys); + + $keys = array_keys($strings, "a", false); + assertType('list', $keys); + + $keys = array_keys($array, 1, true); + assertType('list', $keys); + + $keys = array_keys($array, 1, false); + assertType('list', $keys); + + $keys = array_keys($list, 1, true); + assertType('list>', $keys); + + $keys = array_keys($list, 1, true); + assertType('list>', $keys); +} From 23084d47b7b75fb968448d12deb2c7e01cdd91d1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 29 Oct 2024 13:36:51 +0100 Subject: [PATCH 2/8] refactor --- src/Type/Accessory/AccessoryArrayListType.php | 2 +- src/Type/Accessory/HasOffsetType.php | 2 +- src/Type/Accessory/HasOffsetValueType.php | 2 +- src/Type/Accessory/NonEmptyArrayType.php | 2 +- src/Type/Accessory/OversizedArrayType.php | 2 +- src/Type/ArrayType.php | 2 +- src/Type/Constant/ConstantArrayType.php | 16 ++++++++++-- src/Type/IntersectionType.php | 4 +-- src/Type/MixedType.php | 2 +- src/Type/NeverType.php | 2 +- ...KeysFunctionDynamicReturnTypeExtension.php | 25 ++++++------------- src/Type/StaticType.php | 4 +-- src/Type/Traits/LateResolvableTypeTrait.php | 4 +-- src/Type/Traits/MaybeArrayTypeTrait.php | 2 +- src/Type/Traits/NonArrayTypeTrait.php | 2 +- src/Type/Type.php | 2 +- src/Type/UnionType.php | 4 +-- 17 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 4f2a5ab1aa..5803d60234 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -172,7 +172,7 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { return $this; } diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 42d0178921..cf4734db69 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -352,7 +352,7 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return new BooleanType(); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { return new NonEmptyArrayType(); } diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 7adcd66148..123bb1e521 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -195,7 +195,7 @@ public function unsetOffset(Type $offsetType): Type return $this; } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { return new NonEmptyArrayType(); } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index be9dcd817b..def7b4c5e3 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -159,7 +159,7 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { return $this; } diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 1f0918a760..8772a34406 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -154,7 +154,7 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { return $this; } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index e8d812b33f..278056a656 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -170,7 +170,7 @@ public function generalizeValues(): self return new self($this->keyType, $this->itemType->generalize(GeneralizePrecision::lessSpecific())); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { return TypeCombinator::intersect(new self(new IntegerType(), $this->getIterableKeyType()), new AccessoryArrayListType()); } diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index a21d176835..a3fa44d6ea 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1372,9 +1372,21 @@ private function degradeToGeneralArray(): Type return $builder->getArray(); } - public function getKeysArray(): self + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { - return $this->getKeysOrValuesArray($this->keyTypes); + $keysArray = $this->getKeysOrValuesArray($this->keyTypes); + + if ($filterValueType !== null) { + return TypeCombinator::intersect( + new ArrayType( + $keysArray->getIterableKeyType()->generalize(GeneralizePrecision::lessSpecific()), + $keysArray->getIterableValueType()->generalize(GeneralizePrecision::lessSpecific()), + ), + new AccessoryArrayListType(), + ); + } + + return $keysArray; } public function getValuesArray(): self diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 085e0b636f..5ce9f518d1 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -842,9 +842,9 @@ public function unsetOffset(Type $offsetType): Type return $this->intersectTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType)); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getKeysArray()); + return $this->intersectTypes(static fn (Type $type): Type => $type->getKeysArray($filterValueType, $strict)); } public function getValuesArray(): Type diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 5858d8ed03..5fe5a1c6f5 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -180,7 +180,7 @@ public function unsetOffset(Type $offsetType): Type return $this; } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { if ($this->isArray()->no()) { return new ErrorType(); diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 46a9369699..cad6fc8366 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -273,7 +273,7 @@ public function unsetOffset(Type $offsetType): Type return new NeverType(); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { return new NeverType(); } diff --git a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php index 0766ae6ad9..7a41c14e26 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -7,14 +7,10 @@ use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\Accessory\AccessoryArrayListType; -use PHPStan\Type\ArrayType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; use function count; use function strtolower; @@ -42,22 +38,15 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType(); } - $keysArray = $arrayType->getKeysArray(); - if (count($functionCall->getArgs()) === 1) { - return $keysArray; + $strict = false; + $filterType = null; + if (count($functionCall->getArgs()) >= 2) { + $filterType = $scope->getType($functionCall->getArgs()[1]->value); } - - $newArrayType = $keysArray; - if (!$keysArray->isConstantArray()->no()) { - $newArrayType = new ArrayType( - $keysArray->getIterableKeyType()->generalize(GeneralizePrecision::lessSpecific()), - $keysArray->getIterableValueType()->generalize(GeneralizePrecision::lessSpecific()), - ); - } - if ($keysArray->isList()->yes()) { - $newArrayType = TypeCombinator::intersect($newArrayType, new AccessoryArrayListType()); + if (count($functionCall->getArgs()) >= 3) { + $strict = $scope->getType($functionCall->getArgs()[2]->value)->isTrue()->yes(); } - return $newArrayType; + return $arrayType->getKeysArray($filterType, $strict); } } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index cd82cdb9ea..c897e43825 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -395,9 +395,9 @@ public function unsetOffset(Type $offsetType): Type return $this->getStaticObjectType()->unsetOffset($offsetType); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { - return $this->getStaticObjectType()->getKeysArray(); + return $this->getStaticObjectType()->getKeysArray($filterValueType, $strict); } public function getValuesArray(): Type diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index a909030230..61f1144787 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -248,9 +248,9 @@ public function unsetOffset(Type $offsetType): Type return $this->resolve()->unsetOffset($offsetType); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { - return $this->resolve()->getKeysArray(); + return $this->resolve()->getKeysArray($filterValueType, $strict); } public function getValuesArray(): Type diff --git a/src/Type/Traits/MaybeArrayTypeTrait.php b/src/Type/Traits/MaybeArrayTypeTrait.php index f83bce156f..46cf54125d 100644 --- a/src/Type/Traits/MaybeArrayTypeTrait.php +++ b/src/Type/Traits/MaybeArrayTypeTrait.php @@ -39,7 +39,7 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { return new ErrorType(); } diff --git a/src/Type/Traits/NonArrayTypeTrait.php b/src/Type/Traits/NonArrayTypeTrait.php index 897ffdb2ef..76b1486454 100644 --- a/src/Type/Traits/NonArrayTypeTrait.php +++ b/src/Type/Traits/NonArrayTypeTrait.php @@ -39,7 +39,7 @@ public function isList(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { return new ErrorType(); } diff --git a/src/Type/Type.php b/src/Type/Type.php index 3882f6b5d3..a2d4401bc0 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -134,7 +134,7 @@ public function setExistingOffsetValueType(Type $offsetType, Type $valueType): T public function unsetOffset(Type $offsetType): Type; - public function getKeysArray(): Type; + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type; public function getValuesArray(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index d8116a737d..b5affeacdc 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -731,9 +731,9 @@ public function unsetOffset(Type $offsetType): Type return $this->unionTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType)); } - public function getKeysArray(): Type + public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArray()); + return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArray($filterValueType, $strict)); } public function getValuesArray(): Type From c2242d13935b85875dc9d1155deceebd2296ee34 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 Jan 2025 16:10:53 +0100 Subject: [PATCH 3/8] add separate Type method --- src/Type/Accessory/AccessoryArrayListType.php | 7 +++++- src/Type/Accessory/HasOffsetType.php | 7 +++++- src/Type/Accessory/HasOffsetValueType.php | 7 +++++- src/Type/Accessory/NonEmptyArrayType.php | 7 +++++- src/Type/Accessory/OversizedArrayType.php | 7 +++++- src/Type/ArrayType.php | 7 +++++- src/Type/Constant/ConstantArrayType.php | 23 ++++++++++--------- src/Type/IntersectionType.php | 9 ++++++-- src/Type/MixedType.php | 7 +++++- src/Type/NeverType.php | 7 +++++- ...KeysFunctionDynamicReturnTypeExtension.php | 15 +++++++----- src/Type/StaticType.php | 9 ++++++-- src/Type/Traits/LateResolvableTypeTrait.php | 9 ++++++-- src/Type/Traits/MaybeArrayTypeTrait.php | 7 +++++- src/Type/Traits/NonArrayTypeTrait.php | 7 +++++- src/Type/Type.php | 4 +++- src/Type/UnionType.php | 9 ++++++-- 17 files changed, 112 insertions(+), 36 deletions(-) diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 5803d60234..050e34ff63 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -172,7 +172,12 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { return $this; } diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index cf4734db69..65a16ebca1 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -352,7 +352,12 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return new BooleanType(); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { return new NonEmptyArrayType(); } diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 123bb1e521..ba4c0eec3c 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -195,7 +195,12 @@ public function unsetOffset(Type $offsetType): Type return $this; } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { return new NonEmptyArrayType(); } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index def7b4c5e3..499d66164d 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -159,7 +159,12 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { return $this; } diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 8772a34406..8cf2c71990 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -154,7 +154,12 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { return $this; } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 278056a656..144f336d9e 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -170,7 +170,12 @@ public function generalizeValues(): self return new self($this->keyType, $this->itemType->generalize(GeneralizePrecision::lessSpecific())); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { return TypeCombinator::intersect(new self(new IntegerType(), $this->getIterableKeyType()), new AccessoryArrayListType()); } diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index a3fa44d6ea..59ad786da3 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1372,21 +1372,22 @@ private function degradeToGeneralArray(): Type return $builder->getArray(); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type { $keysArray = $this->getKeysOrValuesArray($this->keyTypes); - if ($filterValueType !== null) { - return TypeCombinator::intersect( - new ArrayType( - $keysArray->getIterableKeyType()->generalize(GeneralizePrecision::lessSpecific()), - $keysArray->getIterableValueType()->generalize(GeneralizePrecision::lessSpecific()), - ), - new AccessoryArrayListType(), - ); - } + return TypeCombinator::intersect( + new ArrayType( + $keysArray->getIterableKeyType()->generalize(GeneralizePrecision::lessSpecific()), + $keysArray->getIterableValueType()->generalize(GeneralizePrecision::lessSpecific()), + ), + new AccessoryArrayListType(), + ); + } - return $keysArray; + public function getKeysArray(): self + { + return $this->getKeysOrValuesArray($this->keyTypes); } public function getValuesArray(): self diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 5ce9f518d1..755aed256c 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -842,9 +842,14 @@ public function unsetOffset(Type $offsetType): Type return $this->intersectTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType)); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getKeysArray($filterValueType, $strict)); + return $this->intersectTypes(static fn (Type $type): Type => $type->getKeysArrayFiltered($filterValueType, $strict)); + } + + public function getKeysArray(): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->getKeysArray()); } public function getValuesArray(): Type diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 5fe5a1c6f5..1203bfa27a 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -180,7 +180,12 @@ public function unsetOffset(Type $offsetType): Type return $this; } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { if ($this->isArray()->no()) { return new ErrorType(); diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index cad6fc8366..7d3b44af61 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -273,7 +273,12 @@ public function unsetOffset(Type $offsetType): Type return new NeverType(); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { return new NeverType(); } diff --git a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php index 7a41c14e26..2c58751ae1 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -38,15 +38,18 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType(); } - $strict = false; - $filterType = null; if (count($functionCall->getArgs()) >= 2) { $filterType = $scope->getType($functionCall->getArgs()[1]->value); + + $strict = false; + if (count($functionCall->getArgs()) >= 3) { + $strict = $scope->getType($functionCall->getArgs()[2]->value)->isTrue()->yes(); + } + + return $arrayType->getKeysArrayFiltered($filterType, $strict); } - if (count($functionCall->getArgs()) >= 3) { - $strict = $scope->getType($functionCall->getArgs()[2]->value)->isTrue()->yes(); - } - return $arrayType->getKeysArray($filterType, $strict); + + return $arrayType->getKeysArray(); } } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index c897e43825..3761cd5c87 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -395,9 +395,14 @@ public function unsetOffset(Type $offsetType): Type return $this->getStaticObjectType()->unsetOffset($offsetType); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type { - return $this->getStaticObjectType()->getKeysArray($filterValueType, $strict); + return $this->getStaticObjectType()->getKeysArrayFiltered($filterValueType, $strict); + } + + public function getKeysArray(): Type + { + return $this->getStaticObjectType()->getKeysArray(); } public function getValuesArray(): Type diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 61f1144787..40b1c0dd0f 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -248,9 +248,14 @@ public function unsetOffset(Type $offsetType): Type return $this->resolve()->unsetOffset($offsetType); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type { - return $this->resolve()->getKeysArray($filterValueType, $strict); + return $this->resolve()->getKeysArrayFiltered($filterValueType, $strict); + } + + public function getKeysArray(): Type + { + return $this->resolve()->getKeysArray(); } public function getValuesArray(): Type diff --git a/src/Type/Traits/MaybeArrayTypeTrait.php b/src/Type/Traits/MaybeArrayTypeTrait.php index 46cf54125d..cab10b3a00 100644 --- a/src/Type/Traits/MaybeArrayTypeTrait.php +++ b/src/Type/Traits/MaybeArrayTypeTrait.php @@ -39,7 +39,12 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { return new ErrorType(); } diff --git a/src/Type/Traits/NonArrayTypeTrait.php b/src/Type/Traits/NonArrayTypeTrait.php index 76b1486454..b98c4cb083 100644 --- a/src/Type/Traits/NonArrayTypeTrait.php +++ b/src/Type/Traits/NonArrayTypeTrait.php @@ -39,7 +39,12 @@ public function isList(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + { + return $this->getKeysArray(); + } + + public function getKeysArray(): Type { return new ErrorType(); } diff --git a/src/Type/Type.php b/src/Type/Type.php index a2d4401bc0..d2690e2242 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -134,7 +134,9 @@ public function setExistingOffsetValueType(Type $offsetType, Type $valueType): T public function unsetOffset(Type $offsetType): Type; - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type; + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type; + + public function getKeysArray(): Type; public function getValuesArray(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index b5affeacdc..59314554b1 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -731,9 +731,14 @@ public function unsetOffset(Type $offsetType): Type return $this->unionTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType)); } - public function getKeysArray(?Type $filterValueType = null, bool $strict = false): Type + public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArray($filterValueType, $strict)); + return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArrayFiltered($filterValueType, $strict)); + } + + public function getKeysArray(): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArray()); } public function getValuesArray(): Type From 5811a83237daaa4a8d8dbb31ecec4ba53dc2a150 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 Jan 2025 20:38:25 +0100 Subject: [PATCH 4/8] more precise --- src/Type/Constant/ConstantArrayType.php | 5 +++-- tests/PHPStan/Analyser/nsrt/bug-11928.php | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 59ad786da3..d98cf43fb1 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -35,6 +35,7 @@ use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\MixedType; @@ -1378,8 +1379,8 @@ public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type return TypeCombinator::intersect( new ArrayType( - $keysArray->getIterableKeyType()->generalize(GeneralizePrecision::lessSpecific()), - $keysArray->getIterableValueType()->generalize(GeneralizePrecision::lessSpecific()), + new IntegerType(), + $keysArray->getIterableValueType(), ), new AccessoryArrayListType(), ); diff --git a/tests/PHPStan/Analyser/nsrt/bug-11928.php b/tests/PHPStan/Analyser/nsrt/bug-11928.php index b6a254a773..7bc44d8144 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11928.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11928.php @@ -9,7 +9,7 @@ function doFoo() $a = [2 => 1, 3 => 2, 4 => 1]; $keys = array_keys($a, 1); // returns [2, 4] - assertType('list', $keys); + assertType('list<2|3|4>', $keys); $keys = array_keys($a); // returns [2, 3, 4] assertType('array{2, 3, 4}', $keys); @@ -18,7 +18,7 @@ function doFoo() function doFooStrings() { $a = [2 => 'hi', 3 => '123', 'xy' => 5]; $keys = array_keys($a, 1); - assertType('list', $keys); + assertType("list<2|3|'xy'>", $keys); $keys = array_keys($a); assertType("array{2, 3, 'xy'}", $keys); From 92acf38430bf65873d6c35530da9218376ed6d82 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 Jan 2025 21:03:29 +0100 Subject: [PATCH 5/8] more tests --- tests/PHPStan/Analyser/nsrt/bug-11928.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-11928.php b/tests/PHPStan/Analyser/nsrt/bug-11928.php index 7bc44d8144..2e6b3ca961 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11928.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11928.php @@ -15,13 +15,23 @@ function doFoo() assertType('array{2, 3, 4}', $keys); } -function doFooStrings() { +/** + * @param array<1|2|3, 4|5|6> $unionKeyedArray + * @return void + */ +function doFooStrings($unionKeyedArray) { $a = [2 => 'hi', 3 => '123', 'xy' => 5]; $keys = array_keys($a, 1); assertType("list<2|3|'xy'>", $keys); $keys = array_keys($a); assertType("array{2, 3, 'xy'}", $keys); + + $keys = array_keys($unionKeyedArray, 1); + assertType("list<1|2|3>", $keys); // could be array{} + + $keys = array_keys($unionKeyedArray); + assertType("list<1|2|3>", $keys); } /** From 79d10eaeb34a0b2d9f539bd1687558e22624b978 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 Jan 2025 21:04:38 +0100 Subject: [PATCH 6/8] use TrinaryLogic over bool --- src/Type/Accessory/AccessoryArrayListType.php | 2 +- src/Type/Accessory/HasOffsetType.php | 2 +- src/Type/Accessory/HasOffsetValueType.php | 2 +- src/Type/Accessory/NonEmptyArrayType.php | 2 +- src/Type/Accessory/OversizedArrayType.php | 2 +- src/Type/ArrayType.php | 2 +- src/Type/Constant/ConstantArrayType.php | 2 +- src/Type/IntersectionType.php | 2 +- src/Type/MixedType.php | 2 +- src/Type/NeverType.php | 2 +- src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php | 5 +++-- src/Type/StaticType.php | 2 +- src/Type/Traits/LateResolvableTypeTrait.php | 2 +- src/Type/Traits/MaybeArrayTypeTrait.php | 2 +- src/Type/Traits/NonArrayTypeTrait.php | 2 +- src/Type/Type.php | 2 +- src/Type/UnionType.php | 2 +- 17 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 050e34ff63..435fd9f2ca 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -172,7 +172,7 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 65a16ebca1..da6c176655 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -352,7 +352,7 @@ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType return new BooleanType(); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index ba4c0eec3c..5c60ac764c 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -195,7 +195,7 @@ public function unsetOffset(Type $offsetType): Type return $this; } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 499d66164d..ae4db44bec 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -159,7 +159,7 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 8cf2c71990..3b56b813e0 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -154,7 +154,7 @@ public function unsetOffset(Type $offsetType): Type return new ErrorType(); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 144f336d9e..413c5c6ec3 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -170,7 +170,7 @@ public function generalizeValues(): self return new self($this->keyType, $this->itemType->generalize(GeneralizePrecision::lessSpecific())); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index d98cf43fb1..9bfd5cd364 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1373,7 +1373,7 @@ private function degradeToGeneralArray(): Type return $builder->getArray(); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { $keysArray = $this->getKeysOrValuesArray($this->keyTypes); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 755aed256c..bf8d74298c 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -842,7 +842,7 @@ public function unsetOffset(Type $offsetType): Type return $this->intersectTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType)); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->intersectTypes(static fn (Type $type): Type => $type->getKeysArrayFiltered($filterValueType, $strict)); } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 1203bfa27a..4a2fe6bf2c 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -180,7 +180,7 @@ public function unsetOffset(Type $offsetType): Type return $this; } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 7d3b44af61..77a07d300c 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -273,7 +273,7 @@ public function unsetOffset(Type $offsetType): Type return new NeverType(); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php index 2c58751ae1..46a0507f5e 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -7,6 +7,7 @@ use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; +use PHPStan\TrinaryLogic; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; @@ -41,9 +42,9 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, if (count($functionCall->getArgs()) >= 2) { $filterType = $scope->getType($functionCall->getArgs()[1]->value); - $strict = false; + $strict = TrinaryLogic::createNo(); if (count($functionCall->getArgs()) >= 3) { - $strict = $scope->getType($functionCall->getArgs()[2]->value)->isTrue()->yes(); + $strict = $scope->getType($functionCall->getArgs()[2]->value)->isTrue(); } return $arrayType->getKeysArrayFiltered($filterType, $strict); diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 3761cd5c87..e9b06e82c8 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -395,7 +395,7 @@ public function unsetOffset(Type $offsetType): Type return $this->getStaticObjectType()->unsetOffset($offsetType); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getStaticObjectType()->getKeysArrayFiltered($filterValueType, $strict); } diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 40b1c0dd0f..11f8f3048b 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -248,7 +248,7 @@ public function unsetOffset(Type $offsetType): Type return $this->resolve()->unsetOffset($offsetType); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->resolve()->getKeysArrayFiltered($filterValueType, $strict); } diff --git a/src/Type/Traits/MaybeArrayTypeTrait.php b/src/Type/Traits/MaybeArrayTypeTrait.php index cab10b3a00..654429b3c6 100644 --- a/src/Type/Traits/MaybeArrayTypeTrait.php +++ b/src/Type/Traits/MaybeArrayTypeTrait.php @@ -39,7 +39,7 @@ public function isList(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/Traits/NonArrayTypeTrait.php b/src/Type/Traits/NonArrayTypeTrait.php index b98c4cb083..6ce17430db 100644 --- a/src/Type/Traits/NonArrayTypeTrait.php +++ b/src/Type/Traits/NonArrayTypeTrait.php @@ -39,7 +39,7 @@ public function isList(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->getKeysArray(); } diff --git a/src/Type/Type.php b/src/Type/Type.php index d2690e2242..7732a77639 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -134,7 +134,7 @@ public function setExistingOffsetValueType(Type $offsetType, Type $valueType): T public function unsetOffset(Type $offsetType): Type; - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type; + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type; public function getKeysArray(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 59314554b1..717c812cd9 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -731,7 +731,7 @@ public function unsetOffset(Type $offsetType): Type return $this->unionTypes(static fn (Type $type): Type => $type->unsetOffset($offsetType)); } - public function getKeysArrayFiltered(Type $filterValueType, bool $strict): Type + public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type { return $this->unionTypes(static fn (Type $type): Type => $type->getKeysArrayFiltered($filterValueType, $strict)); } From 771ebe77ee92d2efb6250ba1544c9eb5de344a34 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 Jan 2025 21:08:47 +0100 Subject: [PATCH 7/8] simplify --- .../ArrayKeysFunctionDynamicReturnTypeExtension.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php index 46a0507f5e..bb7a7bea88 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -30,21 +30,22 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { - if (count($functionCall->getArgs()) < 1) { + $args = $functionCall->getArgs(); + if (count($args) < 1) { return null; } - $arrayType = $scope->getType($functionCall->getArgs()[0]->value); + $arrayType = $scope->getType($args[0]->value); if ($arrayType->isArray()->no()) { return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType(); } - if (count($functionCall->getArgs()) >= 2) { - $filterType = $scope->getType($functionCall->getArgs()[1]->value); + if (count($args) >= 2) { + $filterType = $scope->getType($args[1]->value); $strict = TrinaryLogic::createNo(); - if (count($functionCall->getArgs()) >= 3) { - $strict = $scope->getType($functionCall->getArgs()[2]->value)->isTrue(); + if (count($args) >= 3) { + $strict = $scope->getType($args[2]->value)->isTrue(); } return $arrayType->getKeysArrayFiltered($filterType, $strict); From e1cbed5b5c2b56627350f75ea863e14e982b5027 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 19 Jan 2025 21:22:54 +0100 Subject: [PATCH 8/8] more tests --- tests/PHPStan/Analyser/nsrt/bug-11928.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-11928.php b/tests/PHPStan/Analyser/nsrt/bug-11928.php index 2e6b3ca961..94317f690f 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-11928.php +++ b/tests/PHPStan/Analyser/nsrt/bug-11928.php @@ -17,9 +17,10 @@ function doFoo() /** * @param array<1|2|3, 4|5|6> $unionKeyedArray + * @param 4|5 $fourOrFive * @return void */ -function doFooStrings($unionKeyedArray) { +function doFooStrings($unionKeyedArray, $fourOrFive) { $a = [2 => 'hi', 3 => '123', 'xy' => 5]; $keys = array_keys($a, 1); assertType("list<2|3|'xy'>", $keys); @@ -30,6 +31,12 @@ function doFooStrings($unionKeyedArray) { $keys = array_keys($unionKeyedArray, 1); assertType("list<1|2|3>", $keys); // could be array{} + $keys = array_keys($unionKeyedArray, 4); + assertType("list<1|2|3>", $keys); + + $keys = array_keys($unionKeyedArray, $fourOrFive); + assertType("list<1|2|3>", $keys); + $keys = array_keys($unionKeyedArray); assertType("list<1|2|3>", $keys); }