From ff575788078edcaa0040e52aa60066236acf5e79 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 10 Oct 2022 17:20:31 +0200 Subject: [PATCH 1/3] Add `Type::getLastIterableValueType()` --- src/Type/Accessory/AccessoryArrayListType.php | 5 ++++ src/Type/Accessory/NonEmptyArrayType.php | 5 ++++ src/Type/Accessory/OversizedArrayType.php | 5 ++++ src/Type/ArrayType.php | 5 ++++ src/Type/Constant/ConstantArrayType.php | 24 +++++++++++------- src/Type/IntersectionType.php | 5 ++++ src/Type/MixedType.php | 5 ++++ ...terFunctionsDynamicReturnTypeExtension.php | 2 +- .../ArrayPopFunctionReturnTypeExtension.php | 25 +++---------------- src/Type/StaticType.php | 5 ++++ src/Type/Traits/LateResolvableTypeTrait.php | 5 ++++ src/Type/Traits/MaybeArrayTypeTrait.php | 7 ++++++ src/Type/Traits/NonArrayTypeTrait.php | 7 ++++++ src/Type/Type.php | 2 ++ src/Type/UnionType.php | 5 ++++ tests/PHPStan/Analyser/data/array-pop.php | 4 +++ 16 files changed, 84 insertions(+), 32 deletions(-) diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 10e1d2b4e2..93ac544b0d 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -157,6 +157,11 @@ public function getIterableValueType(): Type return new MixedType(); } + public function getLastIterableValueType(): Type + { + return new MixedType(); + } + public function isArray(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 6e192f4bd5..198d3ceb89 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -146,6 +146,11 @@ public function getIterableValueType(): Type return new MixedType(); } + public function getLastIterableValueType(): Type + { + return new MixedType(); + } + public function isArray(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index a8617ee968..dcd1c7dae7 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -145,6 +145,11 @@ public function getIterableValueType(): Type return new MixedType(); } + public function getLastIterableValueType(): Type + { + return new MixedType(); + } + public function isArray(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 79f28adf7c..f5ebcd7056 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -213,6 +213,11 @@ public function getIterableValueType(): Type return $this->getItemType(); } + public function getLastIterableValueType(): Type + { + return $this->getItemType(); + } + public function isArray(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index c10e202aa9..17dc421f9c 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -274,17 +274,10 @@ public function getFirstValueType(): Type return TypeCombinator::union(...$valueTypes); } + /** @deprecated Use getLastIterableValueType() instead */ public function getLastValueType(): Type { - $valueTypes = []; - for ($i = count($this->keyTypes) - 1; $i >= 0; $i--) { - $valueTypes[] = $this->valueTypes[$i]; - if (!$this->isOptionalKey($i)) { - break; - } - } - - return TypeCombinator::union(...$valueTypes); + return $this->getLastIterableValueType(); } public function isOptionalKey(int $i): bool @@ -712,6 +705,19 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getLastIterableValueType(): Type + { + $valueTypes = []; + for ($i = count($this->keyTypes) - 1; $i >= 0; $i--) { + $valueTypes[] = $this->valueTypes[$i]; + if (!$this->isOptionalKey($i)) { + break; + } + } + + return TypeCombinator::union(...$valueTypes); + } + public function isList(): TrinaryLogic { if ($this->isList) { diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index b587d97d90..6c51e93ba6 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -416,6 +416,11 @@ public function getIterableValueType(): Type return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableValueType()); } + public function getLastIterableValueType(): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->getLastIterableValueType()); + } + public function isArray(): TrinaryLogic { return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isArray()); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 47e240b05b..e9c8d1edc0 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -376,6 +376,11 @@ public function getIterableValueType(): Type return new self($this->isExplicitMixed); } + public function getLastIterableValueType(): Type + { + return new self($this->isExplicitMixed); + } + public function isOffsetAccessible(): TrinaryLogic { if ($this->subtractedType !== null) { diff --git a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php index bbf101ee8a..4048a779f0 100644 --- a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php @@ -57,7 +57,7 @@ public function getTypeFromFunctionCall( $keyTypes[] = $functionReflection->getName() === 'reset' ? $constantArray->getFirstValueType() - : $constantArray->getLastValueType(); + : $constantArray->getLastIterableValueType(); } return TypeCombinator::union(...$keyTypes); diff --git a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php index 4bd6e39005..64935edaea 100644 --- a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php @@ -5,12 +5,10 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -use function count; class ArrayPopFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -20,10 +18,10 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo return $functionReflection->getName() === 'array_pop'; } - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { if (!isset($functionCall->getArgs()[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + return null; } $argType = $scope->getType($functionCall->getArgs()[0]->value); @@ -32,24 +30,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new NullType(); } - $constantArrays = $argType->getConstantArrays(); - if (count($constantArrays) > 0) { - $valueTypes = []; - foreach ($constantArrays as $constantArray) { - $iterableAtLeastOnce = $constantArray->isIterableAtLeastOnce(); - if (!$iterableAtLeastOnce->yes()) { - $valueTypes[] = new NullType(); - } - if ($iterableAtLeastOnce->no()) { - continue; - } - $valueTypes[] = $constantArray->getLastValueType(); - } - - return TypeCombinator::union(...$valueTypes); - } - - $itemType = $argType->getIterableValueType(); + $itemType = $argType->getLastIterableValueType(); if ($iterableAtLeastOnce->yes()) { return $itemType; } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 09a83b89b5..84499967a8 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -307,6 +307,11 @@ public function getIterableValueType(): Type return $this->getStaticObjectType()->getIterableValueType(); } + public function getLastIterableValueType(): Type + { + return $this->getStaticObjectType()->getLastIterableValueType(); + } + public function isOffsetAccessible(): TrinaryLogic { return $this->getStaticObjectType()->isOffsetAccessible(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 2b31d08ddb..84ecc6400e 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -135,6 +135,11 @@ public function getIterableValueType(): Type return $this->resolve()->getIterableValueType(); } + public function getLastIterableValueType(): Type + { + return $this->resolve()->getLastIterableValueType(); + } + public function isArray(): TrinaryLogic { return $this->resolve()->isArray(); diff --git a/src/Type/Traits/MaybeArrayTypeTrait.php b/src/Type/Traits/MaybeArrayTypeTrait.php index 970b9cf0ac..043f546f1a 100644 --- a/src/Type/Traits/MaybeArrayTypeTrait.php +++ b/src/Type/Traits/MaybeArrayTypeTrait.php @@ -3,6 +3,8 @@ namespace PHPStan\Type\Traits; use PHPStan\TrinaryLogic; +use PHPStan\Type\MixedType; +use PHPStan\Type\Type; trait MaybeArrayTypeTrait { @@ -17,6 +19,11 @@ public function getConstantArrays(): array return []; } + public function getLastIterableValueType(): Type + { + return new MixedType(); + } + public function isArray(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Traits/NonArrayTypeTrait.php b/src/Type/Traits/NonArrayTypeTrait.php index 52da9e6611..58ac700efc 100644 --- a/src/Type/Traits/NonArrayTypeTrait.php +++ b/src/Type/Traits/NonArrayTypeTrait.php @@ -3,6 +3,8 @@ namespace PHPStan\Type\Traits; use PHPStan\TrinaryLogic; +use PHPStan\Type\ErrorType; +use PHPStan\Type\Type; trait NonArrayTypeTrait { @@ -17,6 +19,11 @@ public function getConstantArrays(): array return []; } + public function getLastIterableValueType(): Type + { + return new ErrorType(); + } + public function isArray(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 950092b2d3..a017c368ed 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -68,6 +68,8 @@ public function getIterableKeyType(): Type; public function getIterableValueType(): Type; + public function getLastIterableValueType(): Type; + public function isArray(): TrinaryLogic; public function isOversizedArray(): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 1c4a48ed36..1c04f9d8e4 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -430,6 +430,11 @@ public function getIterableValueType(): Type return $this->unionTypes(static fn (Type $type): Type => $type->getIterableValueType()); } + public function getLastIterableValueType(): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->getLastIterableValueType()); + } + public function isArray(): TrinaryLogic { return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isArray()); diff --git a/tests/PHPStan/Analyser/data/array-pop.php b/tests/PHPStan/Analyser/data/array-pop.php index 9cce1e6c7c..a19dcb395f 100644 --- a/tests/PHPStan/Analyser/data/array-pop.php +++ b/tests/PHPStan/Analyser/data/array-pop.php @@ -33,6 +33,10 @@ public function constantArrays(array $arr): void /** @var array{a: 0, b: 1, c: 2} $arr */ assertType('2', array_pop($arr)); assertType('array{a: 0, b: 1}', $arr); + + /** @var array{} $arr */ + assertType('null', array_pop($arr)); + assertType('array{}', $arr); } public function constantArraysWithOptionalKeys(array $arr): void From 05e3d3f78a23bb6840768bb4242887a7d4adc7d9 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 10 Oct 2022 18:03:44 +0200 Subject: [PATCH 2/3] Add `Type::getFirstIterableValueType()` --- src/Type/Accessory/AccessoryArrayListType.php | 5 ++++ src/Type/Accessory/NonEmptyArrayType.php | 5 ++++ src/Type/Accessory/OversizedArrayType.php | 5 ++++ src/Type/ArrayType.php | 5 ++++ src/Type/Constant/ConstantArrayType.php | 24 ++++++++++------- src/Type/IntersectionType.php | 5 ++++ src/Type/MixedType.php | 5 ++++ ...terFunctionsDynamicReturnTypeExtension.php | 2 +- .../ArrayShiftFunctionReturnTypeExtension.php | 26 +++---------------- src/Type/StaticType.php | 5 ++++ src/Type/Traits/LateResolvableTypeTrait.php | 5 ++++ src/Type/Traits/MaybeArrayTypeTrait.php | 5 ++++ src/Type/Traits/NonArrayTypeTrait.php | 5 ++++ src/Type/Type.php | 2 ++ src/Type/UnionType.php | 5 ++++ tests/PHPStan/Analyser/data/array-shift.php | 4 +++ 16 files changed, 80 insertions(+), 33 deletions(-) diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 93ac544b0d..2804d05f19 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -157,6 +157,11 @@ public function getIterableValueType(): Type return new MixedType(); } + public function getFirstIterableValueType(): Type + { + return new MixedType(); + } + public function getLastIterableValueType(): Type { return new MixedType(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 198d3ceb89..9b13bab553 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -146,6 +146,11 @@ public function getIterableValueType(): Type return new MixedType(); } + public function getFirstIterableValueType(): Type + { + return new MixedType(); + } + public function getLastIterableValueType(): Type { return new MixedType(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index dcd1c7dae7..6d37451965 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -145,6 +145,11 @@ public function getIterableValueType(): Type return new MixedType(); } + public function getFirstIterableValueType(): Type + { + return new MixedType(); + } + public function getLastIterableValueType(): Type { return new MixedType(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index f5ebcd7056..b1305b287b 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -213,6 +213,11 @@ public function getIterableValueType(): Type return $this->getItemType(); } + public function getFirstIterableValueType(): Type + { + return $this->getItemType(); + } + public function getLastIterableValueType(): Type { return $this->getItemType(); diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 17dc421f9c..d8d8ea11b3 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -261,17 +261,10 @@ public function getValueTypes(): array return $this->valueTypes; } + /** @deprecated Use getFirstIterableValueType() instead */ public function getFirstValueType(): Type { - $valueTypes = []; - foreach ($this->valueTypes as $i => $valueType) { - $valueTypes[] = $valueType; - if (!$this->isOptionalKey($i)) { - break; - } - } - - return TypeCombinator::union(...$valueTypes); + return $this->getFirstIterableValueType(); } /** @deprecated Use getLastIterableValueType() instead */ @@ -705,6 +698,19 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getFirstIterableValueType(): Type + { + $valueTypes = []; + foreach ($this->valueTypes as $i => $valueType) { + $valueTypes[] = $valueType; + if (!$this->isOptionalKey($i)) { + break; + } + } + + return TypeCombinator::union(...$valueTypes); + } + public function getLastIterableValueType(): Type { $valueTypes = []; diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 6c51e93ba6..8981c74561 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -416,6 +416,11 @@ public function getIterableValueType(): Type return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableValueType()); } + public function getFirstIterableValueType(): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->getFirstIterableValueType()); + } + public function getLastIterableValueType(): Type { return $this->intersectTypes(static fn (Type $type): Type => $type->getLastIterableValueType()); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index e9c8d1edc0..4843305618 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -376,6 +376,11 @@ public function getIterableValueType(): Type return new self($this->isExplicitMixed); } + public function getFirstIterableValueType(): Type + { + return new self($this->isExplicitMixed); + } + public function getLastIterableValueType(): Type { return new self($this->isExplicitMixed); diff --git a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php index 4048a779f0..086a5adf38 100644 --- a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php @@ -56,7 +56,7 @@ public function getTypeFromFunctionCall( } $keyTypes[] = $functionReflection->getName() === 'reset' - ? $constantArray->getFirstValueType() + ? $constantArray->getFirstIterableValueType() : $constantArray->getLastIterableValueType(); } diff --git a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php index 1b38aaf047..47552e875e 100644 --- a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php @@ -5,12 +5,10 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -use function count; class ArrayShiftFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -20,10 +18,10 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo return $functionReflection->getName() === 'array_shift'; } - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type { if (!isset($functionCall->getArgs()[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + return null; } $argType = $scope->getType($functionCall->getArgs()[0]->value); @@ -32,25 +30,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new NullType(); } - $constantArrays = $argType->getConstantArrays(); - if (count($constantArrays) > 0) { - $valueTypes = []; - foreach ($constantArrays as $constantArray) { - $iterableAtLeastOnce = $constantArray->isIterableAtLeastOnce(); - if (!$iterableAtLeastOnce->yes()) { - $valueTypes[] = new NullType(); - } - if ($iterableAtLeastOnce->no()) { - continue; - } - - $valueTypes[] = $constantArray->getFirstValueType(); - } - - return TypeCombinator::union(...$valueTypes); - } - - $itemType = $argType->getIterableValueType(); + $itemType = $argType->getFirstIterableValueType(); if ($iterableAtLeastOnce->yes()) { return $itemType; } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 84499967a8..6a89f8df77 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -307,6 +307,11 @@ public function getIterableValueType(): Type return $this->getStaticObjectType()->getIterableValueType(); } + public function getFirstIterableValueType(): Type + { + return $this->getStaticObjectType()->getFirstIterableValueType(); + } + public function getLastIterableValueType(): Type { return $this->getStaticObjectType()->getLastIterableValueType(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 84ecc6400e..aee4653610 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -135,6 +135,11 @@ public function getIterableValueType(): Type return $this->resolve()->getIterableValueType(); } + public function getFirstIterableValueType(): Type + { + return $this->resolve()->getFirstIterableValueType(); + } + public function getLastIterableValueType(): Type { return $this->resolve()->getLastIterableValueType(); diff --git a/src/Type/Traits/MaybeArrayTypeTrait.php b/src/Type/Traits/MaybeArrayTypeTrait.php index 043f546f1a..b583c25c82 100644 --- a/src/Type/Traits/MaybeArrayTypeTrait.php +++ b/src/Type/Traits/MaybeArrayTypeTrait.php @@ -19,6 +19,11 @@ public function getConstantArrays(): array return []; } + public function getFirstIterableValueType(): Type + { + return new MixedType(); + } + public function getLastIterableValueType(): Type { return new MixedType(); diff --git a/src/Type/Traits/NonArrayTypeTrait.php b/src/Type/Traits/NonArrayTypeTrait.php index 58ac700efc..822e1d5c81 100644 --- a/src/Type/Traits/NonArrayTypeTrait.php +++ b/src/Type/Traits/NonArrayTypeTrait.php @@ -19,6 +19,11 @@ public function getConstantArrays(): array return []; } + public function getFirstIterableValueType(): Type + { + return new ErrorType(); + } + public function getLastIterableValueType(): Type { return new ErrorType(); diff --git a/src/Type/Type.php b/src/Type/Type.php index a017c368ed..8af10c5306 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -68,6 +68,8 @@ public function getIterableKeyType(): Type; public function getIterableValueType(): Type; + public function getFirstIterableValueType(): Type; + public function getLastIterableValueType(): Type; public function isArray(): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 1c04f9d8e4..3d7b666fb3 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -430,6 +430,11 @@ public function getIterableValueType(): Type return $this->unionTypes(static fn (Type $type): Type => $type->getIterableValueType()); } + public function getFirstIterableValueType(): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->getFirstIterableValueType()); + } + public function getLastIterableValueType(): Type { return $this->unionTypes(static fn (Type $type): Type => $type->getLastIterableValueType()); diff --git a/tests/PHPStan/Analyser/data/array-shift.php b/tests/PHPStan/Analyser/data/array-shift.php index 7d937981d3..0b2d37df0e 100644 --- a/tests/PHPStan/Analyser/data/array-shift.php +++ b/tests/PHPStan/Analyser/data/array-shift.php @@ -33,6 +33,10 @@ public function constantArrays(array $arr): void /** @var array{a: 0, b: 1, c: 2} $arr */ assertType('0', array_shift($arr)); assertType('array{b: 1, c: 2}', $arr); + + /** @var array{} $arr */ + assertType('null', array_shift($arr)); + assertType('array{}', $arr); } public function constantArraysWithOptionalKeys(array $arr): void From 401b2f59077625afec373dd7d2952c5dfcd9b951 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 10 Oct 2022 18:06:22 +0200 Subject: [PATCH 3/3] Simplify `ArrayPointerFunctionsDynamicReturnTypeExtension` --- ...terFunctionsDynamicReturnTypeExtension.php | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php index 086a5adf38..cd12f678d1 100644 --- a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php @@ -5,7 +5,6 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; @@ -31,10 +30,10 @@ public function getTypeFromFunctionCall( FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope, - ): Type + ): ?Type { if (count($functionCall->getArgs()) === 0) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + return null; } $argType = $scope->getType($functionCall->getArgs()[0]->value); @@ -43,27 +42,9 @@ public function getTypeFromFunctionCall( return new ConstantBooleanType(false); } - $constantArrays = $argType->getConstantArrays(); - if (count($constantArrays) > 0) { - $keyTypes = []; - foreach ($constantArrays as $constantArray) { - $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); - if (!$iterableAtLeastOnce->yes()) { - $keyTypes[] = new ConstantBooleanType(false); - } - if ($iterableAtLeastOnce->no()) { - continue; - } - - $keyTypes[] = $functionReflection->getName() === 'reset' - ? $constantArray->getFirstIterableValueType() - : $constantArray->getLastIterableValueType(); - } - - return TypeCombinator::union(...$keyTypes); - } - - $itemType = $argType->getIterableValueType(); + $itemType = $functionReflection->getName() === 'reset' + ? $argType->getFirstIterableValueType() + : $argType->getLastIterableValueType(); if ($iterableAtLeastOnce->yes()) { return $itemType; }