From 67819b207349e7b62bfa4f9de1ff357b340e8ba3 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Fri, 14 Oct 2022 21:16:30 +0200 Subject: [PATCH] Add `Type::popArray()` and `Type::shiftArray()` --- src/Analyser/NodeScopeResolver.php | 21 +++---------------- src/Type/Accessory/AccessoryArrayListType.php | 10 +++++++++ src/Type/Accessory/NonEmptyArrayType.php | 10 +++++++++ src/Type/Accessory/OversizedArrayType.php | 10 +++++++++ src/Type/ArrayType.php | 10 +++++++++ src/Type/Constant/ConstantArrayType.php | 12 +++++++++++ src/Type/IntersectionType.php | 10 +++++++++ src/Type/MixedType.php | 10 +++++++++ src/Type/StaticType.php | 10 +++++++++ src/Type/Traits/LateResolvableTypeTrait.php | 10 +++++++++ src/Type/Traits/MaybeArrayTypeTrait.php | 10 +++++++++ src/Type/Traits/NonArrayTypeTrait.php | 10 +++++++++ src/Type/Type.php | 4 ++++ src/Type/UnionType.php | 10 +++++++++ tests/PHPStan/Analyser/data/array-pop.php | 7 +++++++ tests/PHPStan/Analyser/data/array-shift.php | 7 +++++++ tests/PHPStan/Analyser/data/bug-3993.php | 2 +- 17 files changed, 144 insertions(+), 19 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 3bc1566fda..c3ebe3c9af 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -134,7 +134,6 @@ use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\IntegerType; -use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; @@ -952,6 +951,7 @@ private function processStmtNode( do { $prevScope = $bodyScope; + $bodyScope = $bodyScope->mergeWith($scope); $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void { })->filterOutLoopExitPoints(); $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); @@ -1839,25 +1839,10 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression ) { $arrayArg = $expr->getArgs()[0]->value; $arrayArgType = $scope->getType($arrayArg); - $scope = $scope->invalidateExpression($arrayArg); - $functionName = $functionReflection->getName(); - $arrayArgType = TypeTraverser::map($arrayArgType, static function (Type $type, callable $traverse) use ($functionName): Type { - if ($type instanceof UnionType || $type instanceof IntersectionType) { - return $traverse($type); - } - if ($type instanceof ConstantArrayType) { - return $functionName === 'array_pop' ? $type->removeLast() : $type->removeFirst(); - } - if ($type->isIterableAtLeastOnce()->yes()) { - return $type->toArray(); - } - return $type; - }); - - $scope = $scope->assignExpression( + $scope = $scope->invalidateExpression($arrayArg)->assignExpression( $arrayArg, - $arrayArgType, + $functionReflection->getName() === 'array_pop' ? $arrayArgType->popArray() : $arrayArgType->shiftArray(), $scope->getNativeType($arrayArg), ); } diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 346282eeba..d2a967d77b 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -152,6 +152,16 @@ public function flipArray(): Type return new MixedType(); } + public function popArray(): Type + { + return $this; + } + + public function shiftArray(): Type + { + return $this; + } + public function isIterable(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index e89f274904..2f178493aa 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -142,6 +142,16 @@ public function flipArray(): Type return $this; } + public function popArray(): Type + { + return new MixedType(); + } + + public function shiftArray(): Type + { + return new MixedType(); + } + public function isIterable(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index b4c4d3706d..18bfcf73d5 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -141,6 +141,16 @@ public function flipArray(): Type return $this; } + public function popArray(): Type + { + return $this; + } + + public function shiftArray(): Type + { + return $this; + } + public function isIterable(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index e49efea0f7..9ccaae612d 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -369,6 +369,16 @@ public function flipArray(): Type return new self(self::castToArrayKeyType($this->getIterableValueType()), $this->getIterableKeyType()); } + public function popArray(): Type + { + return $this; + } + + public function shiftArray(): Type + { + return $this; + } + public function isCallable(): TrinaryLogic { return TrinaryLogic::createMaybe()->and($this->itemType->isString()); diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 6724cb0572..bf2c969f56 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -686,6 +686,16 @@ public function flipArray(): Type return $builder->getArray(); } + public function popArray(): Type + { + return $this->removeLastElements(1); + } + + public function shiftArray(): Type + { + return $this->removeFirstElements(1); + } + public function isIterableAtLeastOnce(): TrinaryLogic { $keysCount = count($this->keyTypes); @@ -778,6 +788,7 @@ public function isList(): TrinaryLogic return parent::isList(); } + /** @deprecated Use popArray() instead */ public function removeLast(): self { return $this->removeLastElements(1); @@ -837,6 +848,7 @@ private function removeLastElements(int $length): self ); } + /** @deprecated Use shiftArray() instead */ public function removeFirst(): self { return $this->removeFirstElements(1); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 5e62599a94..9d95728c1e 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -526,6 +526,16 @@ public function flipArray(): Type return $this->intersectTypes(static fn (Type $type): Type => $type->flipArray()); } + public function popArray(): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->popArray()); + } + + public function shiftArray(): Type + { + return $this->intersectTypes(static fn (Type $type): Type => $type->shiftArray()); + } + public function isCallable(): TrinaryLogic { return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isCallable()); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index a683c8e2c7..20eb8ecc63 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -159,6 +159,16 @@ public function flipArray(): Type return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed)); } + public function popArray(): Type + { + return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed)); + } + + public function shiftArray(): Type + { + return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed)); + } + public function isCallable(): TrinaryLogic { if ($this->subtractedType !== null) { diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 22f031de0c..ec7c14d800 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -371,6 +371,16 @@ public function flipArray(): Type return $this->getStaticObjectType()->flipArray(); } + public function popArray(): Type + { + return $this->getStaticObjectType()->popArray(); + } + + public function shiftArray(): Type + { + return $this->getStaticObjectType()->shiftArray(); + } + public function isCallable(): TrinaryLogic { return $this->getStaticObjectType()->isCallable(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 9fdd4c9bbf..9f62a0bda9 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -220,6 +220,16 @@ public function flipArray(): Type return $this->resolve()->flipArray(); } + public function popArray(): Type + { + return $this->resolve()->popArray(); + } + + public function shiftArray(): Type + { + return $this->resolve()->shiftArray(); + } + public function isCallable(): TrinaryLogic { return $this->resolve()->isCallable(); diff --git a/src/Type/Traits/MaybeArrayTypeTrait.php b/src/Type/Traits/MaybeArrayTypeTrait.php index 3d8aac7aec..dc029b0569 100644 --- a/src/Type/Traits/MaybeArrayTypeTrait.php +++ b/src/Type/Traits/MaybeArrayTypeTrait.php @@ -54,4 +54,14 @@ public function flipArray(): Type return new ErrorType(); } + public function popArray(): Type + { + return new ErrorType(); + } + + public function shiftArray(): Type + { + return new ErrorType(); + } + } diff --git a/src/Type/Traits/NonArrayTypeTrait.php b/src/Type/Traits/NonArrayTypeTrait.php index 9463701c62..a798bf18fd 100644 --- a/src/Type/Traits/NonArrayTypeTrait.php +++ b/src/Type/Traits/NonArrayTypeTrait.php @@ -54,4 +54,14 @@ public function flipArray(): Type return new ErrorType(); } + public function popArray(): Type + { + return new ErrorType(); + } + + public function shiftArray(): Type + { + return new ErrorType(); + } + } diff --git a/src/Type/Type.php b/src/Type/Type.php index 75377c3a8f..f2d82e9f22 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -102,6 +102,10 @@ public function getValuesArray(): Type; public function flipArray(): Type; + public function popArray(): Type; + + public function shiftArray(): Type; + public function isCallable(): TrinaryLogic; /** diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index fc7895bdda..f153238101 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -554,6 +554,16 @@ public function flipArray(): Type return $this->unionTypes(static fn (Type $type): Type => $type->flipArray()); } + public function popArray(): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->popArray()); + } + + public function shiftArray(): Type + { + return $this->unionTypes(static fn (Type $type): Type => $type->shiftArray()); + } + public function isCallable(): TrinaryLogic { return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isCallable()); diff --git a/tests/PHPStan/Analyser/data/array-pop.php b/tests/PHPStan/Analyser/data/array-pop.php index a19dcb395f..ae4feb13a6 100644 --- a/tests/PHPStan/Analyser/data/array-pop.php +++ b/tests/PHPStan/Analyser/data/array-pop.php @@ -58,4 +58,11 @@ public function constantArraysWithOptionalKeys(array $arr): void assertType('array{a?: 0, b?: 1}', $arr); } + public function list(array $arr): void + { + /** @var list $arr */ + assertType('string|null', array_pop($arr)); + assertType('list', $arr); + } + } diff --git a/tests/PHPStan/Analyser/data/array-shift.php b/tests/PHPStan/Analyser/data/array-shift.php index 0b2d37df0e..8679dbda29 100644 --- a/tests/PHPStan/Analyser/data/array-shift.php +++ b/tests/PHPStan/Analyser/data/array-shift.php @@ -58,4 +58,11 @@ public function constantArraysWithOptionalKeys(array $arr): void assertType('array{b?: 1, c?: 2}', $arr); } + public function list(array $arr): void + { + /** @var list $arr */ + assertType('string|null', array_shift($arr)); + assertType('list', $arr); + } + } diff --git a/tests/PHPStan/Analyser/data/bug-3993.php b/tests/PHPStan/Analyser/data/bug-3993.php index 4c106dbab8..e472a0d68c 100644 --- a/tests/PHPStan/Analyser/data/bug-3993.php +++ b/tests/PHPStan/Analyser/data/bug-3993.php @@ -17,7 +17,7 @@ public function doFoo($arguments) array_shift($arguments); - assertType('mixed~null', $arguments); + assertType('array', $arguments); assertType('int<0, max>', count($arguments)); }