From 112d0dff35836dc2b49001725b3a2cfc8fc996c1 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Wed, 12 Oct 2022 22:40:15 +0200 Subject: [PATCH] Add `Type::getIterableCount()` --- src/Rules/FunctionCallParametersCheck.php | 2 +- src/Type/Accessory/AccessoryArrayListType.php | 5 ++++ src/Type/Accessory/NonEmptyArrayType.php | 6 +++++ src/Type/Accessory/OversizedArrayType.php | 6 +++++ src/Type/ArrayType.php | 8 +++++- src/Type/Constant/ConstantArrayType.php | 22 ++++++++++------ src/Type/IntersectionType.php | 5 ++++ src/Type/IterableType.php | 5 ++++ src/Type/MixedType.php | 9 +++++++ .../Php/CountFunctionReturnTypeExtension.php | 25 +++---------------- .../Php/MinMaxFunctionReturnTypeExtension.php | 4 +-- src/Type/StaticType.php | 5 ++++ src/Type/Traits/LateResolvableTypeTrait.php | 5 ++++ src/Type/Traits/MaybeIterableTypeTrait.php | 15 +++++++++++ src/Type/Traits/NonIterableTypeTrait.php | 5 ++++ src/Type/Type.php | 2 ++ src/Type/UnionType.php | 5 ++++ 17 files changed, 101 insertions(+), 33 deletions(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 744adc9afe..b00dfec655 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -104,7 +104,7 @@ public function check( if (count($arrays) > 0) { $minKeys = null; foreach ($arrays as $array) { - $countType = $array->count(); + $countType = $array->getIterableCount(); if ($countType instanceof ConstantIntegerType) { $keysCount = $countType->getValue(); } elseif ($countType instanceof IntegerRangeType) { diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index d3fcdb5d93..3a3ccd7709 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -147,6 +147,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getIterableCount(): Type + { + return IntegerRangeType::fromInterval(0, null); + } + public function getIterableKeyType(): Type { return IntegerRangeType::fromInterval(0, null); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 4059b694e8..2131323d52 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -7,6 +7,7 @@ use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -136,6 +137,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createYes(); } + public function getIterableCount(): Type + { + return IntegerRangeType::fromInterval(1, null); + } + public function getIterableKeyType(): Type { return new MixedType(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 7f797bf804..b3d1588338 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -7,6 +7,7 @@ use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; @@ -135,6 +136,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createYes(); } + public function getIterableCount(): Type + { + return IntegerRangeType::fromInterval(0, null); + } + public function getIterableKeyType(): Type { return new MixedType(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index e1a0a7e938..aa18d562eb 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -195,6 +195,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getIterableCount(): Type + { + return IntegerRangeType::fromInterval(0, null); + } + public function getIterableKeyType(): Type { $keyType = $this->keyType; @@ -407,9 +412,10 @@ public function toArray(): Type return $this; } + /** @deprecated Use getIterableCount() instead */ public function count(): Type { - return IntegerRangeType::fromInterval(0, null); + return $this->getIterableCount(); } public static function castToArrayKeyType(Type $offsetType): Type diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index f37678a9d7..7c56a91ba4 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -685,6 +685,17 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getIterableCount(): Type + { + $optionalKeysCount = count($this->optionalKeys); + $totalKeysCount = count($this->getKeyTypes()); + if ($optionalKeysCount === 0) { + return new ConstantIntegerType($totalKeysCount); + } + + return IntegerRangeType::fromInterval($totalKeysCount - $optionalKeysCount, $totalKeysCount); + } + public function getFirstIterableKeyType(): Type { $keyTypes = []; @@ -979,7 +990,7 @@ private function reindex(): self public function toBoolean(): BooleanType { - return $this->count()->toBoolean(); + return $this->getIterableCount()->toBoolean(); } public function toInteger(): Type @@ -1116,15 +1127,10 @@ public function getValuesArray(): Type return new self($keyTypes, $valueTypes, $autoIndex, $optionalKeys, true); } + /** @deprecated Use getIterableCount() instead */ public function count(): Type { - $optionalKeysCount = count($this->optionalKeys); - $totalKeysCount = count($this->getKeyTypes()); - if ($optionalKeysCount === 0) { - return new ConstantIntegerType($totalKeysCount); - } - - return IntegerRangeType::fromInterval($totalKeysCount - $optionalKeysCount, $totalKeysCount); + return $this->getIterableCount(); } public function describe(VerbosityLevel $level): string diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index b9b15e6320..8ed33199fe 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -406,6 +406,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce()); } + public function getIterableCount(): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableCount()); + } + public function getIterableKeyType(): Type { return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableKeyType()); diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 9fca279261..824f5e9e3d 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -220,6 +220,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getIterableCount(): Type + { + return IntegerRangeType::fromInterval(0, null); + } + public function getIterableKeyType(): Type { return $this->keyType; diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 46b772a52b..a311c924c6 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -366,6 +366,15 @@ public function isIterableAtLeastOnce(): TrinaryLogic return $this->isIterable(); } + public function getIterableCount(): Type + { + if ($this->isIterable()->no()) { + return new ErrorType(); + } + + return IntegerRangeType::fromInterval(0, null); + } + public function getIterableKeyType(): Type { return new self($this->isExplicitMixed); diff --git a/src/Type/Php/CountFunctionReturnTypeExtension.php b/src/Type/Php/CountFunctionReturnTypeExtension.php index 283baebc37..1ced5fbe43 100644 --- a/src/Type/Php/CountFunctionReturnTypeExtension.php +++ b/src/Type/Php/CountFunctionReturnTypeExtension.php @@ -5,12 +5,9 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\IntegerRangeType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; use function count; use function in_array; use const COUNT_RECURSIVE; @@ -27,34 +24,20 @@ public function getTypeFromFunctionCall( FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope, - ): Type + ): ?Type { if (count($functionCall->getArgs()) < 1) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + return null; } if (count($functionCall->getArgs()) > 1) { $mode = $scope->getType($functionCall->getArgs()[1]->value); if ($mode->isSuperTypeOf(new ConstantIntegerType(COUNT_RECURSIVE))->yes()) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + return null; } } - $argType = $scope->getType($functionCall->getArgs()[0]->value); - $constantArrays = $scope->getType($functionCall->getArgs()[0]->value)->getConstantArrays(); - if (count($constantArrays) === 0) { - if ($argType->isIterableAtLeastOnce()->yes()) { - return IntegerRangeType::fromInterval(1, null); - } - - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - $countTypes = []; - foreach ($constantArrays as $array) { - $countTypes[] = $array->count(); - } - - return TypeCombinator::union(...$countTypes); + return $scope->getType($functionCall->getArgs()[0]->value)->getIterableCount(); } } diff --git a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php index 8727600a83..ecee5afaee 100644 --- a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php +++ b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php @@ -178,9 +178,9 @@ private function compareTypes( $firstType instanceof ConstantArrayType && $secondType instanceof ConstantArrayType ) { - if ($secondType->count() < $firstType->count()) { + if ($secondType->getIterableCount() < $firstType->getIterableCount()) { return $secondType; - } elseif ($firstType->count() < $secondType->count()) { + } elseif ($firstType->getIterableCount() < $secondType->getIterableCount()) { return $firstType; } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index e83b9480d7..b7dee1b5be 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -297,6 +297,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return $this->getStaticObjectType()->isIterableAtLeastOnce(); } + public function getIterableCount(): Type + { + return $this->getStaticObjectType()->getIterableCount(); + } + public function getIterableKeyType(): Type { return $this->getStaticObjectType()->getIterableKeyType(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 34bb668961..f05ed88d45 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -125,6 +125,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return $this->resolve()->isIterableAtLeastOnce(); } + public function getIterableCount(): Type + { + return $this->resolve()->getIterableCount(); + } + public function getIterableKeyType(): Type { return $this->resolve()->getIterableKeyType(); diff --git a/src/Type/Traits/MaybeIterableTypeTrait.php b/src/Type/Traits/MaybeIterableTypeTrait.php index e0c9dad216..d3fbece57e 100644 --- a/src/Type/Traits/MaybeIterableTypeTrait.php +++ b/src/Type/Traits/MaybeIterableTypeTrait.php @@ -3,6 +3,8 @@ namespace PHPStan\Type\Traits; use PHPStan\TrinaryLogic; +use PHPStan\Type\ErrorType; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -19,6 +21,19 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getIterableCount(): Type + { + if ($this->isIterable()->no()) { + return new ErrorType(); + } + + if ($this->isIterableAtLeastOnce()->yes()) { + return IntegerRangeType::fromInterval(1, null); + } + + return IntegerRangeType::fromInterval(0, null); + } + public function getIterableKeyType(): Type { return new MixedType(); diff --git a/src/Type/Traits/NonIterableTypeTrait.php b/src/Type/Traits/NonIterableTypeTrait.php index 37edc5f541..7de050f4b0 100644 --- a/src/Type/Traits/NonIterableTypeTrait.php +++ b/src/Type/Traits/NonIterableTypeTrait.php @@ -19,6 +19,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return TrinaryLogic::createNo(); } + public function getIterableCount(): Type + { + return new ErrorType(); + } + public function getIterableKeyType(): Type { return new ErrorType(); diff --git a/src/Type/Type.php b/src/Type/Type.php index c105a63387..3865eabf37 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -64,6 +64,8 @@ public function isIterable(): TrinaryLogic; public function isIterableAtLeastOnce(): TrinaryLogic; + public function getIterableCount(): Type; + public function getIterableKeyType(): Type; public function getFirstIterableKeyType(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index ca1efbfbbe..5cddd65e09 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -420,6 +420,11 @@ public function isIterableAtLeastOnce(): TrinaryLogic return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isIterableAtLeastOnce()); } + public function getIterableCount(): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->getIterableCount()); + } + public function getIterableKeyType(): Type { return $this->unionTypes(static fn (Type $type): Type => $type->getIterableKeyType());