Skip to content

Add Type::reverseArray() #3344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Type/Accessory/AccessoryArrayListType.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,15 @@ public function popArray(): Type
return $this;
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
if ($preserveKeys->no()) {
return $this;
}

return new MixedType();
}

public function searchArray(Type $needleType): Type
{
return new MixedType();
Expand Down
9 changes: 9 additions & 0 deletions src/Type/Accessory/HasOffsetType.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,15 @@ public function intersectKeyArray(Type $otherArraysType): Type
return new MixedType();
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
if ($preserveKeys->yes()) {
return $this;
}

return new NonEmptyArrayType();
}

public function shuffleArray(): Type
{
return new NonEmptyArrayType();
Expand Down
9 changes: 9 additions & 0 deletions src/Type/Accessory/HasOffsetValueType.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,15 @@ public function intersectKeyArray(Type $otherArraysType): Type
return new MixedType();
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
if ($preserveKeys->yes()) {
return $this;
}

return new NonEmptyArrayType();
}

public function searchArray(Type $needleType): Type
{
if (
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Accessory/NonEmptyArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ public function popArray(): Type
return new MixedType();
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return $this;
}

public function searchArray(Type $needleType): Type
{
return new MixedType();
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Accessory/OversizedArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ public function popArray(): Type
return $this;
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return $this;
}

public function searchArray(Type $needleType): Type
{
return new MixedType();
Expand Down
5 changes: 5 additions & 0 deletions src/Type/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,11 @@ public function popArray(): Type
return $this;
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return $this;
}

public function searchArray(Type $needleType): Type
{
return TypeCombinator::union($this->getIterableKeyType(), new ConstantBooleanType(false));
Expand Down
31 changes: 21 additions & 10 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,23 @@ public function popArray(): Type
return $this->removeLastElements(1);
}

private function reverseConstantArray(TrinaryLogic $preserveKeys): self
{
$keyTypesReversed = array_reverse($this->keyTypes, true);
$keyTypes = array_values($keyTypesReversed);
$keyTypesReversedKeys = array_keys($keyTypesReversed);
$optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys);

$reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, TrinaryLogic::createNo());

return $preserveKeys->yes() ? $reversed : $reversed->reindex();
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return $this->reverseConstantArray($preserveKeys);
}

public function searchArray(Type $needleType): Type
{
$matches = [];
Expand Down Expand Up @@ -1121,9 +1138,9 @@ public function slice(int $offset, ?int $limit, bool $preserveKeys = false): sel
$offset *= -1;
$reversedLimit = min($limit, $offset);
$reversedOffset = $offset - $reversedLimit;
return $this->reverse(true)
return $this->reverseConstantArray(TrinaryLogic::createYes())
->slice($reversedOffset, $reversedLimit, $preserveKeys)
->reverse(true);
->reverseConstantArray(TrinaryLogic::createYes());
}

if ($offset > 0) {
Expand Down Expand Up @@ -1162,16 +1179,10 @@ public function slice(int $offset, ?int $limit, bool $preserveKeys = false): sel
return $preserveKeys ? $slice : $slice->reindex();
}

/** @deprecated Use reverseArray() instead */
public function reverse(bool $preserveKeys = false): self
{
$keyTypesReversed = array_reverse($this->keyTypes, true);
$keyTypes = array_values($keyTypesReversed);
$keyTypesReversedKeys = array_keys($keyTypesReversed);
$optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys);

$reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, TrinaryLogic::createNo());

return $preserveKeys ? $reversed : $reversed->reindex();
return $this->reverseConstantArray(TrinaryLogic::createFromBoolean($preserveKeys));
}

/** @param positive-int $length */
Expand Down
5 changes: 5 additions & 0 deletions src/Type/IntersectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,11 @@ public function popArray(): Type
return $this->intersectTypes(static fn (Type $type): Type => $type->popArray());
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return $this->intersectTypes(static fn (Type $type): Type => $type->reverseArray($preserveKeys));
}

public function searchArray(Type $needleType): Type
{
return $this->intersectTypes(static fn (Type $type): Type => $type->searchArray($needleType));
Expand Down
9 changes: 9 additions & 0 deletions src/Type/MixedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@ public function popArray(): Type
return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
if ($this->isArray()->no()) {
return new ErrorType();
}

return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
}

public function searchArray(Type $needleType): Type
{
if ($this->isArray()->no()) {
Expand Down
5 changes: 5 additions & 0 deletions src/Type/NeverType.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,11 @@ public function popArray(): Type
return new NeverType();
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return new NeverType();
}

public function searchArray(Type $needleType): Type
{
return new NeverType();
Expand Down
29 changes: 12 additions & 17 deletions src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\NeverType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use function count;

final class ArrayReverseFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{

public function __construct(private PhpVersion $phpVersion)
{
}

public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
return $functionReflection->getName() === 'array_reverse';
Expand All @@ -26,24 +31,14 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
}

$type = $scope->getType($functionCall->getArgs()[0]->value);
$preserveKeysType = isset($functionCall->getArgs()[1]) ? $scope->getType($functionCall->getArgs()[1]->value) : new NeverType();
$preserveKeys = $preserveKeysType->isTrue()->yes();

if (!$type->isArray()->yes()) {
return null;
if ($type->isArray()->no()) {
return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType();
}

$constantArrays = $type->getConstantArrays();
if (count($constantArrays) > 0) {
$results = [];
foreach ($constantArrays as $constantArray) {
$results[] = $constantArray->reverse($preserveKeys);
}

return TypeCombinator::union(...$results);
}
$preserveKeysType = isset($functionCall->getArgs()[1]) ? $scope->getType($functionCall->getArgs()[1]->value) : new ConstantBooleanType(false);
$preserveKeys = (new ConstantBooleanType(true))->isSuperTypeOf($preserveKeysType);

return $type;
return $type->reverseArray($preserveKeys);
}

}
5 changes: 5 additions & 0 deletions src/Type/StaticType.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,11 @@ public function popArray(): Type
return $this->getStaticObjectType()->popArray();
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return $this->getStaticObjectType()->reverseArray($preserveKeys);
}

public function searchArray(Type $needleType): Type
{
return $this->getStaticObjectType()->searchArray($needleType);
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Traits/LateResolvableTypeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ public function popArray(): Type
return $this->resolve()->popArray();
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return $this->resolve()->reverseArray($preserveKeys);
}

public function searchArray(Type $needleType): Type
{
return $this->resolve()->searchArray($needleType);
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Traits/MaybeArrayTypeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public function popArray(): Type
return new ErrorType();
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return new ErrorType();
}

public function searchArray(Type $needleType): Type
{
return new ErrorType();
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Traits/NonArrayTypeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public function popArray(): Type
return new ErrorType();
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return new ErrorType();
}

public function searchArray(Type $needleType): Type
{
return new ErrorType();
Expand Down
2 changes: 2 additions & 0 deletions src/Type/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ public function intersectKeyArray(Type $otherArraysType): Type;

public function popArray(): Type;

public function reverseArray(TrinaryLogic $preserveKeys): Type;

public function searchArray(Type $needleType): Type;

public function shiftArray(): Type;
Expand Down
5 changes: 5 additions & 0 deletions src/Type/UnionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,11 @@ public function popArray(): Type
return $this->unionTypes(static fn (Type $type): Type => $type->popArray());
}

public function reverseArray(TrinaryLogic $preserveKeys): Type
{
return $this->unionTypes(static fn (Type $type): Type => $type->reverseArray($preserveKeys));
}

public function searchArray(Type $needleType): Type
{
return $this->unionTypes(static fn (Type $type): Type => $type->searchArray($needleType));
Expand Down
16 changes: 16 additions & 0 deletions tests/PHPStan/Analyser/nsrt/array-reverse-php7.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php // lint < 8.0

declare(strict_types = 1);

namespace ArrayReversePhp7;

use function PHPStan\Testing\assertType;

class Foo
{
public function notArray(bool $bool): void
{
assertType('null', array_reverse($bool));
assertType('null', array_reverse($bool, true));
}
}
16 changes: 16 additions & 0 deletions tests/PHPStan/Analyser/nsrt/array-reverse-php8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace ArrayReversePhp8;

use function PHPStan\Testing\assertType;

class Foo
{
public function notArray(bool $bool): void
{
assertType('*NEVER*', array_reverse($bool));
assertType('*NEVER*', array_reverse($bool, true));
}
}
24 changes: 24 additions & 0 deletions tests/PHPStan/Analyser/nsrt/array-reverse.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,28 @@ public function constantArrays(array $a, array $b): void
assertType('array{\'bar\', \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b));
assertType('array{19: \'bar\', 17: \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b, true));
}

/**
* @param list<string> $a
* @param non-empty-list<string> $b
*/
public function list(array $a, array $b): void
{
assertType('list<string>', array_reverse($a));
assertType('array<int<0, max>, string>', array_reverse($a, true));

assertType('non-empty-list<string>', array_reverse($b));
assertType('non-empty-array<int<0, max>, string>', array_reverse($b, true));
}

public function mixed(mixed $mixed): void
{
assertType('array', array_reverse($mixed));
assertType('array', array_reverse($mixed, true));

if (array_key_exists('foo', $mixed)) {
assertType('non-empty-array', array_reverse($mixed));
assertType("array&hasOffset('foo')", array_reverse($mixed, true));
}
}
}
Loading