From 185e631ddfcb41d2cea021bb5a30910181b021f1 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 74a2903ea5..59f3918996 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -6,10 +6,14 @@ use PHPStan\Analyser\Scope; 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; @@ -27,7 +31,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; } @@ -36,7 +40,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 3c585ed90e220b40a3e79f46554d244ecb9a737b 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 5f60fe8eb7..4fc46f8e21 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 455f0de86e..31740ba9e9 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -343,7 +343,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 ec6e822a31..0553018c41 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 d4726fd5c2..a26a82247c 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 c43c86a903..0cea28eccc 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 68224a0f9b..c97876a331 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 76fdf42a5d..6007319bd5 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1250,9 +1250,21 @@ public function generalizeValues(): self return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList); } - 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 ab9f86d034..1505f9c95d 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -799,9 +799,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 487a474827..002952c24a 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -179,7 +179,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 518ffa8f4a..30b88f3f8b 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 59f3918996..2a22321f7c 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -6,14 +6,10 @@ use PHPStan\Analyser\Scope; 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; @@ -40,22 +36,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 9db20e5b34..0e60648b5c 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -396,9 +396,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 5eb703077f..e227bbee51 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 afafc91708..baf6c12d52 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 1d1b948242..48d194469a 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 15886a053c..178a340528 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 08d678152a..7933cb6068 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -730,9 +730,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 6b3e8fed25514445d656107831937e131e96a21c 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 4fc46f8e21..73ad744ea1 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 31740ba9e9..ffc46b93b4 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -343,7 +343,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 0553018c41..65a7c7f317 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 a26a82247c..3b6dbdd547 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 0cea28eccc..05a9a40434 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 c97876a331..0366089c2a 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 6007319bd5..85434e9b71 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1250,21 +1250,22 @@ public function generalizeValues(): self return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList); } - 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 1505f9c95d..f44af0db66 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -799,9 +799,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 002952c24a..caa8a424c2 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -179,7 +179,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 30b88f3f8b..b1c33d6f91 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 2a22321f7c..ee4748d180 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -36,15 +36,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 0e60648b5c..0f57404c6c 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -396,9 +396,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 e227bbee51..4e1ef7bd39 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 baf6c12d52..d844163169 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 48d194469a..a1ce37b3d7 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 178a340528..25d60c6f0f 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 7933cb6068..4eb05dfe03 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -730,9 +730,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 30b7937a5afa3632c89ed95126640e9c1f3607de 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 85434e9b71..ab5e62e990 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; @@ -1256,8 +1257,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 fdad51264168015d6d430e8764e7c80d660c5b68 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 aed8934c9223b4bec1f2a39c056bdc0c1013c01a 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 73ad744ea1..8b08e67bb3 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 ffc46b93b4..ae42e8e00f 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -343,7 +343,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 65a7c7f317..a6e98cd24a 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 3b6dbdd547..d8d18eda84 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 05a9a40434..767389ec9e 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 0366089c2a..ab5354d0d4 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 ab5e62e990..1f7d7cbbdf 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1251,7 +1251,7 @@ public function generalizeValues(): self return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys, $this->isList); } - 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 f44af0db66..e0ccb40519 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -799,7 +799,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 caa8a424c2..8c13827bcb 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -179,7 +179,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 b1c33d6f91..56bd9713ff 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 ee4748d180..247c079ec5 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; +use PHPStan\TrinaryLogic; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; @@ -39,9 +40,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 0f57404c6c..d53b8d3530 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -396,7 +396,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 4e1ef7bd39..1f66c36240 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 d844163169..05e61028fe 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 a1ce37b3d7..e048d1a148 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 25d60c6f0f..c9827f310c 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 4eb05dfe03..6423b05a5c 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -730,7 +730,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 8c7108e25e1e425380b30a1509e4398e2337fcde 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 247c079ec5..725d5e8b01 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -28,21 +28,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 2f72eef634b7298bd07225e973a63a4a6e7cffd0 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); }