From 4e29e4e43a42323a4c3f48cd534fdf060baa6944 Mon Sep 17 00:00:00 2001 From: Can Vural Date: Thu, 30 Oct 2025 19:52:54 +0100 Subject: [PATCH 1/3] add array_first and array_last return type extensions --- .../ArrayFirstDynamicReturnTypeExtension.php | 48 +++++++++++++++++++ .../ArrayLastDynamicReturnTypeExtension.php | 48 +++++++++++++++++++ .../Analyser/nsrt/array_first_last.php | 22 +++++++++ 3 files changed, 118 insertions(+) create mode 100644 src/Type/Php/ArrayFirstDynamicReturnTypeExtension.php create mode 100644 src/Type/Php/ArrayLastDynamicReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/nsrt/array_first_last.php diff --git a/src/Type/Php/ArrayFirstDynamicReturnTypeExtension.php b/src/Type/Php/ArrayFirstDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..fefff0daf0 --- /dev/null +++ b/src/Type/Php/ArrayFirstDynamicReturnTypeExtension.php @@ -0,0 +1,48 @@ +getName() === 'array_first' && $functionReflection->isBuiltin(); + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type + { + $args = $functionCall->getArgs(); + + if (count($args) < 1) { + return null; + } + + $argType = $scope->getType($args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + + if ($iterableAtLeastOnce->no()) { + return new NullType(); + } + + $valueType = $argType->getFirstIterableValueType(); + + if ($iterableAtLeastOnce->yes()) { + return $valueType; + } + + return TypeCombinator::union($valueType, new NullType()); + } + +} diff --git a/src/Type/Php/ArrayLastDynamicReturnTypeExtension.php b/src/Type/Php/ArrayLastDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..b7ebe8f881 --- /dev/null +++ b/src/Type/Php/ArrayLastDynamicReturnTypeExtension.php @@ -0,0 +1,48 @@ +getName() === 'array_last' && $functionReflection->isBuiltin(); + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type + { + $args = $functionCall->getArgs(); + + if (count($args) < 1) { + return null; + } + + $argType = $scope->getType($args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + + if ($iterableAtLeastOnce->no()) { + return new NullType(); + } + + $valueType = $argType->getLastIterableValueType(); + + if ($iterableAtLeastOnce->yes()) { + return $valueType; + } + + return TypeCombinator::union($valueType, new NullType()); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/array_first_last.php b/tests/PHPStan/Analyser/nsrt/array_first_last.php new file mode 100644 index 0000000000..c995a25e72 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array_first_last.php @@ -0,0 +1,22 @@ += 8.5 + +namespace ArrayFirstLast; + +use function PHPStan\Testing\assertType; + +/** + * @param string[] $stringArray + * @param non-empty-array $nonEmptyArray + */ +function doFoo(array $stringArray, array $nonEmptyArray, $mixed): void +{ + assertType("'a'", array_first([1 => 'a', 0 => 'b', 2 => 'c'])); + assertType('string|null', array_first($stringArray)); + assertType('string', array_first($nonEmptyArray)); + assertType('mixed', array_first($mixed)); + + assertType("'c'", array_last([1 => 'a', 0 => 'b', 2 => 'c'])); + assertType('string|null', array_last($stringArray)); + assertType('string', array_last($nonEmptyArray)); + assertType('mixed', array_last($mixed)); +} From 73651b561e9bf8c8c06ea83025d7e03f92595d0a Mon Sep 17 00:00:00 2001 From: Can Vural Date: Fri, 31 Oct 2025 10:56:42 +0100 Subject: [PATCH 2/3] use getIterableValueType --- ...ayFirstLastDynamicReturnTypeExtension.php} | 7 +-- .../ArrayLastDynamicReturnTypeExtension.php | 48 ------------------- .../Analyser/nsrt/array_first_last.php | 9 ++-- 3 files changed, 10 insertions(+), 54 deletions(-) rename src/Type/Php/{ArrayFirstDynamicReturnTypeExtension.php => ArrayFirstLastDynamicReturnTypeExtension.php} (79%) delete mode 100644 src/Type/Php/ArrayLastDynamicReturnTypeExtension.php diff --git a/src/Type/Php/ArrayFirstDynamicReturnTypeExtension.php b/src/Type/Php/ArrayFirstLastDynamicReturnTypeExtension.php similarity index 79% rename from src/Type/Php/ArrayFirstDynamicReturnTypeExtension.php rename to src/Type/Php/ArrayFirstLastDynamicReturnTypeExtension.php index fefff0daf0..18a7d547ec 100644 --- a/src/Type/Php/ArrayFirstDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayFirstLastDynamicReturnTypeExtension.php @@ -11,14 +11,15 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use function count; +use function in_array; #[AutowiredService] -final class ArrayFirstDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension +final class ArrayFirstLastDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { public function isFunctionSupported(FunctionReflection $functionReflection): bool { - return $functionReflection->getName() === 'array_first' && $functionReflection->isBuiltin(); + return in_array($functionReflection->getName(), ['array_first', 'array_last'], true); } public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type @@ -36,7 +37,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new NullType(); } - $valueType = $argType->getFirstIterableValueType(); + $valueType = $argType->getIterableValueType(); if ($iterableAtLeastOnce->yes()) { return $valueType; diff --git a/src/Type/Php/ArrayLastDynamicReturnTypeExtension.php b/src/Type/Php/ArrayLastDynamicReturnTypeExtension.php deleted file mode 100644 index b7ebe8f881..0000000000 --- a/src/Type/Php/ArrayLastDynamicReturnTypeExtension.php +++ /dev/null @@ -1,48 +0,0 @@ -getName() === 'array_last' && $functionReflection->isBuiltin(); - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type - { - $args = $functionCall->getArgs(); - - if (count($args) < 1) { - return null; - } - - $argType = $scope->getType($args[0]->value); - $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); - - if ($iterableAtLeastOnce->no()) { - return new NullType(); - } - - $valueType = $argType->getLastIterableValueType(); - - if ($iterableAtLeastOnce->yes()) { - return $valueType; - } - - return TypeCombinator::union($valueType, new NullType()); - } - -} diff --git a/tests/PHPStan/Analyser/nsrt/array_first_last.php b/tests/PHPStan/Analyser/nsrt/array_first_last.php index c995a25e72..cf3ba4857e 100644 --- a/tests/PHPStan/Analyser/nsrt/array_first_last.php +++ b/tests/PHPStan/Analyser/nsrt/array_first_last.php @@ -7,15 +7,18 @@ /** * @param string[] $stringArray * @param non-empty-array $nonEmptyArray + * @param array{1: 'bar', baz: 'foo'} $arrayShape */ -function doFoo(array $stringArray, array $nonEmptyArray, $mixed): void +function doFoo(array $stringArray, array $nonEmptyArray, $mixed, $arrayShape): void { - assertType("'a'", array_first([1 => 'a', 0 => 'b', 2 => 'c'])); + assertType("'a'|'b'|'c'", array_first([1 => 'a', 0 => 'b', 2 => 'c'])); + assertType("'bar'|'foo'", array_first($arrayShape)); assertType('string|null', array_first($stringArray)); assertType('string', array_first($nonEmptyArray)); assertType('mixed', array_first($mixed)); - assertType("'c'", array_last([1 => 'a', 0 => 'b', 2 => 'c'])); + assertType("'a'|'b'|'c'", array_last([1 => 'a', 0 => 'b', 2 => 'c'])); + assertType("'bar'|'foo'", array_last($arrayShape)); assertType('string|null', array_last($stringArray)); assertType('string', array_last($nonEmptyArray)); assertType('mixed', array_last($mixed)); From 5f3237afa1b3eb2759b6a333dcd8c3a27bf32b8c Mon Sep 17 00:00:00 2001 From: Can Vural Date: Fri, 31 Oct 2025 11:27:32 +0100 Subject: [PATCH 3/3] deprecate getFirstIterable*Type and getLastIterable*Type methods --- src/Analyser/TypeSpecifier.php | 6 +-- src/Type/IntersectionType.php | 8 ++-- ...rrayKeyFirstDynamicReturnTypeExtension.php | 2 +- ...ArrayKeyLastDynamicReturnTypeExtension.php | 2 +- ...terFunctionsDynamicReturnTypeExtension.php | 4 +- .../ArrayPopFunctionReturnTypeExtension.php | 2 +- .../ArrayShiftFunctionReturnTypeExtension.php | 2 +- src/Type/StaticType.php | 8 ++-- src/Type/Traits/LateResolvableTypeTrait.php | 8 ++-- src/Type/Type.php | 4 ++ src/Type/TypeCombinator.php | 2 +- src/Type/UnionType.php | 8 ++-- .../Analyser/LegacyNodeScopeResolverTest.php | 44 +++++++++---------- tests/PHPStan/Analyser/nsrt/array-pop.php | 8 ++-- tests/PHPStan/Analyser/nsrt/array-shift.php | 8 ++-- tests/PHPStan/Analyser/nsrt/bug-8084.php | 2 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 2 +- 17 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 1abb42b684..1e22d530a4 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -747,8 +747,8 @@ public function specifyTypesInCondition( ) { $dimFetch = new ArrayDimFetch($arrayArg, $expr->var); $iterableValueType = $expr->expr->name->toLowerString() === 'array_key_first' - ? $arrayType->getFirstIterableValueType() - : $arrayType->getLastIterableValueType(); + ? $arrayType->getIterableValueType() + : $arrayType->getIterableValueType(); return $specifiedTypes->unionWith( $this->create($dimFetch, $iterableValueType, TypeSpecifierContext::createTrue(), $scope), @@ -775,7 +775,7 @@ public function specifyTypesInCondition( $dimFetch = new ArrayDimFetch($arrayArg, $expr->var); return $specifiedTypes->unionWith( - $this->create($dimFetch, $arrayType->getLastIterableValueType(), TypeSpecifierContext::createTrue(), $scope), + $this->create($dimFetch, $arrayType->getIterableValueType(), TypeSpecifierContext::createTrue(), $scope), ); } } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index a065b77612..16cb33d2dd 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -690,12 +690,12 @@ public function getIterableKeyType(): Type public function getFirstIterableKeyType(): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getFirstIterableKeyType()); + return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableKeyType()); } public function getLastIterableKeyType(): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getLastIterableKeyType()); + return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableKeyType()); } public function getIterableValueType(): Type @@ -705,12 +705,12 @@ public function getIterableValueType(): Type public function getFirstIterableValueType(): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getFirstIterableValueType()); + return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableValueType()); } public function getLastIterableValueType(): Type { - return $this->intersectTypes(static fn (Type $type): Type => $type->getLastIterableValueType()); + return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableValueType()); } public function isArray(): TrinaryLogic diff --git a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php index 64fea966d6..2e926f7554 100644 --- a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php @@ -32,7 +32,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new NullType(); } - $keyType = $argType->getFirstIterableKeyType(); + $keyType = $argType->getIterableKeyType(); if ($iterableAtLeastOnce->yes()) { return $keyType; } diff --git a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php index c3865ada3c..a750c9bea5 100644 --- a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php @@ -32,7 +32,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new NullType(); } - $keyType = $argType->getLastIterableKeyType(); + $keyType = $argType->getIterableKeyType(); if ($iterableAtLeastOnce->yes()) { return $keyType; } diff --git a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php index eef5642028..0a73278d0c 100644 --- a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php @@ -45,8 +45,8 @@ public function getTypeFromFunctionCall( } $itemType = $functionReflection->getName() === 'reset' - ? $argType->getFirstIterableValueType() - : $argType->getLastIterableValueType(); + ? $argType->getIterableValueType() + : $argType->getIterableValueType(); if ($iterableAtLeastOnce->yes()) { return $itemType; } diff --git a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php index 61bfdf69e6..5a570752ed 100644 --- a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php @@ -32,7 +32,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new NullType(); } - $itemType = $argType->getLastIterableValueType(); + $itemType = $argType->getIterableValueType(); if ($iterableAtLeastOnce->yes()) { return $itemType; } diff --git a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php index b961e624e0..704756157d 100644 --- a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php @@ -32,7 +32,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new NullType(); } - $itemType = $argType->getFirstIterableValueType(); + $itemType = $argType->getIterableValueType(); if ($iterableAtLeastOnce->yes()) { return $itemType; } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 1f659eb4f5..21622b5b93 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -401,12 +401,12 @@ public function getIterableKeyType(): Type public function getFirstIterableKeyType(): Type { - return $this->getStaticObjectType()->getFirstIterableKeyType(); + return $this->getStaticObjectType()->getIterableKeyType(); } public function getLastIterableKeyType(): Type { - return $this->getStaticObjectType()->getLastIterableKeyType(); + return $this->getStaticObjectType()->getIterableKeyType(); } public function getIterableValueType(): Type @@ -416,12 +416,12 @@ public function getIterableValueType(): Type public function getFirstIterableValueType(): Type { - return $this->getStaticObjectType()->getFirstIterableValueType(); + return $this->getStaticObjectType()->getIterableValueType(); } public function getLastIterableValueType(): Type { - return $this->getStaticObjectType()->getLastIterableValueType(); + return $this->getStaticObjectType()->getIterableValueType(); } public function isOffsetAccessible(): TrinaryLogic diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 6377fb21eb..d35ee72461 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -200,12 +200,12 @@ public function getIterableKeyType(): Type public function getFirstIterableKeyType(): Type { - return $this->resolve()->getFirstIterableKeyType(); + return $this->resolve()->getIterableKeyType(); } public function getLastIterableKeyType(): Type { - return $this->resolve()->getLastIterableKeyType(); + return $this->resolve()->getIterableKeyType(); } public function getIterableValueType(): Type @@ -215,12 +215,12 @@ public function getIterableValueType(): Type public function getFirstIterableValueType(): Type { - return $this->resolve()->getFirstIterableValueType(); + return $this->resolve()->getIterableValueType(); } public function getLastIterableValueType(): Type { - return $this->resolve()->getLastIterableValueType(); + return $this->resolve()->getIterableValueType(); } public function isArray(): TrinaryLogic diff --git a/src/Type/Type.php b/src/Type/Type.php index 9e3480016a..e9cbca0b7a 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -117,14 +117,18 @@ public function getArraySize(): Type; public function getIterableKeyType(): Type; + /** @deprecated use getIterableKeyType */ public function getFirstIterableKeyType(): Type; + /** @deprecated use getIterableKeyType */ public function getLastIterableKeyType(): Type; public function getIterableValueType(): Type; + /** @deprecated use getIterableValueType */ public function getFirstIterableValueType(): Type; + /** @deprecated use getIterableValueType */ public function getLastIterableValueType(): Type; public function isArray(): TrinaryLogic; diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 5d98439a93..4ac858b067 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -933,7 +933,7 @@ private static function optimizeConstantArrays(array $types): array $valueTypes = []; foreach ($results as $result) { $keyTypes[] = $result->getIterableKeyType(); - $valueTypes[] = $result->getLastIterableValueType(); + $valueTypes[] = $result->getIterableValueType(); if ($result->isList()->yes()) { continue; } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 77928d2cea..a1855a713b 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -639,12 +639,12 @@ public function getIterableKeyType(): Type public function getFirstIterableKeyType(): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getFirstIterableKeyType()); + return $this->unionTypes(static fn (Type $type): Type => $type->getIterableKeyType()); } public function getLastIterableKeyType(): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getLastIterableKeyType()); + return $this->unionTypes(static fn (Type $type): Type => $type->getIterableKeyType()); } public function getIterableValueType(): Type @@ -654,12 +654,12 @@ public function getIterableValueType(): Type public function getFirstIterableValueType(): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getFirstIterableValueType()); + return $this->unionTypes(static fn (Type $type): Type => $type->getIterableValueType()); } public function getLastIterableValueType(): Type { - return $this->unionTypes(static fn (Type $type): Type => $type->getLastIterableValueType()); + return $this->unionTypes(static fn (Type $type): Type => $type->getIterableValueType()); } public function isArray(): TrinaryLogic diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 22f21b50a2..41358ea82e 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -4790,7 +4790,7 @@ public static function dataArrayFunctions(): array 'array_map(function (): \stdClass {}, $conditionalKeysArray)', ], [ - 'stdClass', + '\'foo\'|stdClass', 'array_pop($stringKeys)', ], [ @@ -4802,7 +4802,7 @@ public static function dataArrayFunctions(): array 'array_pop($stdClassesWithIsset)', ], [ - '\'foo\'', + '\'foo\'|stdClass', 'array_shift($stringKeys)', ], [ @@ -7169,23 +7169,23 @@ public static function dataArrayPointerFunctions(): array 'reset($emptyConstantArray)', ], [ - '1', + '1|2', 'reset($constantArray)', ], [ - '\'baz\'|\'foo\'', + '\'bar\'|\'baz\'|\'foo\'', 'reset($conditionalArray)', ], [ - '0|1', + '0|1|2', 'reset($constantArrayOptionalKeys1)', ], [ - '0', + '0|1|2', 'reset($constantArrayOptionalKeys2)', ], [ - '0', + '0|1|2', 'reset($constantArrayOptionalKeys3)', ], [ @@ -7205,23 +7205,23 @@ public static function dataArrayPointerFunctions(): array 'end($emptyConstantArray)', ], [ - '2', + '1|2', 'end($constantArray)', ], [ - '\'bar\'|\'baz\'', + '\'bar\'|\'baz\'|\'foo\'', 'end($secondConditionalArray)', ], [ - '2', + '0|1|2', 'end($constantArrayOptionalKeys1)', ], [ - '2', + '0|1|2', 'end($constantArrayOptionalKeys2)', ], [ - '1|2', + '0|1|2', 'end($constantArrayOptionalKeys3)', ], ]; @@ -8644,43 +8644,43 @@ public static function dataPhp73Functions(): array 'array_key_last($emptyArray)', ], [ - '0', + '0|1|2', 'array_key_first($literalArray)', ], [ - '2', + '0|1|2', 'array_key_last($literalArray)', ], [ - '0', + '0|1|2|3', 'array_key_first($anotherLiteralArray)', ], [ - '2|3', + '0|1|2|3', 'array_key_last($anotherLiteralArray)', ], [ - "'a'|'b'", + "'a'|'b'|'c'", 'array_key_first($constantArrayOptionalKeys1)', ], [ - "'c'", + "'a'|'b'|'c'", 'array_key_last($constantArrayOptionalKeys1)', ], [ - "'a'", + "'a'|'b'|'c'", 'array_key_first($constantArrayOptionalKeys2)', ], [ - "'c'", + "'a'|'b'|'c'", 'array_key_last($constantArrayOptionalKeys2)', ], [ - "'a'", + "'a'|'b'|'c'", 'array_key_first($constantArrayOptionalKeys3)', ], [ - "'b'|'c'", + "'a'|'b'|'c'", 'array_key_last($constantArrayOptionalKeys3)', ], [ diff --git a/tests/PHPStan/Analyser/nsrt/array-pop.php b/tests/PHPStan/Analyser/nsrt/array-pop.php index 37a986b0ce..971859d166 100644 --- a/tests/PHPStan/Analyser/nsrt/array-pop.php +++ b/tests/PHPStan/Analyser/nsrt/array-pop.php @@ -32,7 +32,7 @@ public function compoundTypes(array $arr): void public function constantArrays(array $arr): void { /** @var array{a: 0, b: 1, c: 2} $arr */ - assertType('2', array_pop($arr)); + assertType('0|1|2', array_pop($arr)); assertType('array{a: 0, b: 1}', $arr); /** @var array{} $arr */ @@ -43,15 +43,15 @@ public function constantArrays(array $arr): void public function constantArraysWithOptionalKeys(array $arr): void { /** @var array{a?: 0, b: 1, c: 2} $arr */ - assertType('2', array_pop($arr)); + assertType('0|1|2', array_pop($arr)); assertType('array{a?: 0, b: 1}', $arr); /** @var array{a: 0, b?: 1, c: 2} $arr */ - assertType('2', array_pop($arr)); + assertType('0|1|2', array_pop($arr)); assertType('array{a: 0, b?: 1}', $arr); /** @var array{a: 0, b: 1, c?: 2} $arr */ - assertType('1|2', array_pop($arr)); + assertType('0|1|2', array_pop($arr)); assertType('array{a: 0, b?: 1}', $arr); /** @var array{a?: 0, b?: 1, c?: 2} $arr */ diff --git a/tests/PHPStan/Analyser/nsrt/array-shift.php b/tests/PHPStan/Analyser/nsrt/array-shift.php index 2d8ef21d7e..12bbd0a1ba 100644 --- a/tests/PHPStan/Analyser/nsrt/array-shift.php +++ b/tests/PHPStan/Analyser/nsrt/array-shift.php @@ -32,7 +32,7 @@ public function compoundTypes(array $arr): void public function constantArrays(array $arr): void { /** @var array{a: 0, b: 1, c: 2} $arr */ - assertType('0', array_shift($arr)); + assertType('0|1|2', array_shift($arr)); assertType('array{b: 1, c: 2}', $arr); /** @var array{} $arr */ @@ -43,15 +43,15 @@ public function constantArrays(array $arr): void public function constantArraysWithOptionalKeys(array $arr): void { /** @var array{a?: 0, b: 1, c: 2} $arr */ - assertType('0|1', array_shift($arr)); + assertType('0|1|2', array_shift($arr)); assertType('array{b?: 1, c: 2}', $arr); /** @var array{a: 0, b?: 1, c: 2} $arr */ - assertType('0', array_shift($arr)); + assertType('0|1|2', array_shift($arr)); assertType('array{b?: 1, c: 2}', $arr); /** @var array{a: 0, b: 1, c?: 2} $arr */ - assertType('0', array_shift($arr)); + assertType('0|1|2', array_shift($arr)); assertType('array{b: 1, c?: 2}', $arr); /** @var array{a?: 0, b?: 1, c?: 2} $arr */ diff --git a/tests/PHPStan/Analyser/nsrt/bug-8084.php b/tests/PHPStan/Analyser/nsrt/bug-8084.php index fe5869e8e2..5879176649 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8084.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8084.php @@ -14,7 +14,7 @@ class Bug8084 */ public function run(array $arr): void { - assertType('0', array_shift($arr) ?? throw new Exception()); + assertType('0|1', array_shift($arr) ?? throw new Exception()); assertType('1|null', array_shift($arr)); } } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 597b4000e3..879c86f791 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -852,7 +852,7 @@ public function testArrayDimFetchOnArrayKeyFirsOrLastOrCount(): void $this->analyse([__DIR__ . '/data/array-dim-fetch-on-array-key-first-last.php'], [ [ - 'Offset 0|null might not exist on list.', + 'Offset int<0, max>|null might not exist on list.', 12, ], [