Skip to content

Commit 3116a1b

Browse files
herndlmondrejmirtes
authored andcommitted
Add Type::reverseArray()
1 parent 08dc679 commit 3116a1b

20 files changed

+177
-27
lines changed

src/Type/Accessory/AccessoryArrayListType.php

+9
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,15 @@ public function popArray(): Type
218218
return $this;
219219
}
220220

221+
public function reverseArray(TrinaryLogic $preserveKeys): Type
222+
{
223+
if ($preserveKeys->no()) {
224+
return $this;
225+
}
226+
227+
return new MixedType();
228+
}
229+
221230
public function searchArray(Type $needleType): Type
222231
{
223232
return new MixedType();

src/Type/Accessory/HasOffsetType.php

+9
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,15 @@ public function intersectKeyArray(Type $otherArraysType): Type
189189
return new MixedType();
190190
}
191191

192+
public function reverseArray(TrinaryLogic $preserveKeys): Type
193+
{
194+
if ($preserveKeys->yes()) {
195+
return $this;
196+
}
197+
198+
return new NonEmptyArrayType();
199+
}
200+
192201
public function shuffleArray(): Type
193202
{
194203
return new NonEmptyArrayType();

src/Type/Accessory/HasOffsetValueType.php

+9
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,15 @@ public function intersectKeyArray(Type $otherArraysType): Type
233233
return new MixedType();
234234
}
235235

236+
public function reverseArray(TrinaryLogic $preserveKeys): Type
237+
{
238+
if ($preserveKeys->yes()) {
239+
return $this;
240+
}
241+
242+
return new NonEmptyArrayType();
243+
}
244+
236245
public function searchArray(Type $needleType): Type
237246
{
238247
if (

src/Type/Accessory/NonEmptyArrayType.php

+5
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ public function popArray(): Type
199199
return new MixedType();
200200
}
201201

202+
public function reverseArray(TrinaryLogic $preserveKeys): Type
203+
{
204+
return $this;
205+
}
206+
202207
public function searchArray(Type $needleType): Type
203208
{
204209
return new MixedType();

src/Type/Accessory/OversizedArrayType.php

+5
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ public function popArray(): Type
195195
return $this;
196196
}
197197

198+
public function reverseArray(TrinaryLogic $preserveKeys): Type
199+
{
200+
return $this;
201+
}
202+
198203
public function searchArray(Type $needleType): Type
199204
{
200205
return new MixedType();

src/Type/ArrayType.php

+5
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,11 @@ public function popArray(): Type
552552
return $this;
553553
}
554554

555+
public function reverseArray(TrinaryLogic $preserveKeys): Type
556+
{
557+
return $this;
558+
}
559+
555560
public function searchArray(Type $needleType): Type
556561
{
557562
return TypeCombinator::union($this->getIterableKeyType(), new ConstantBooleanType(false));

src/Type/Constant/ConstantArrayType.php

+21-10
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,23 @@ public function popArray(): Type
837837
return $this->removeLastElements(1);
838838
}
839839

840+
private function reverseConstantArray(TrinaryLogic $preserveKeys): self
841+
{
842+
$keyTypesReversed = array_reverse($this->keyTypes, true);
843+
$keyTypes = array_values($keyTypesReversed);
844+
$keyTypesReversedKeys = array_keys($keyTypesReversed);
845+
$optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys);
846+
847+
$reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, TrinaryLogic::createNo());
848+
849+
return $preserveKeys->yes() ? $reversed : $reversed->reindex();
850+
}
851+
852+
public function reverseArray(TrinaryLogic $preserveKeys): Type
853+
{
854+
return $this->reverseConstantArray($preserveKeys);
855+
}
856+
840857
public function searchArray(Type $needleType): Type
841858
{
842859
$matches = [];
@@ -1121,9 +1138,9 @@ public function slice(int $offset, ?int $limit, bool $preserveKeys = false): sel
11211138
$offset *= -1;
11221139
$reversedLimit = min($limit, $offset);
11231140
$reversedOffset = $offset - $reversedLimit;
1124-
return $this->reverse(true)
1141+
return $this->reverseConstantArray(TrinaryLogic::createYes())
11251142
->slice($reversedOffset, $reversedLimit, $preserveKeys)
1126-
->reverse(true);
1143+
->reverseConstantArray(TrinaryLogic::createYes());
11271144
}
11281145

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

1182+
/** @deprecated Use reverseArray() instead */
11651183
public function reverse(bool $preserveKeys = false): self
11661184
{
1167-
$keyTypesReversed = array_reverse($this->keyTypes, true);
1168-
$keyTypes = array_values($keyTypesReversed);
1169-
$keyTypesReversedKeys = array_keys($keyTypesReversed);
1170-
$optionalKeys = array_map(static fn (int $optionalKey): int => $keyTypesReversedKeys[$optionalKey], $this->optionalKeys);
1171-
1172-
$reversed = new self($keyTypes, array_reverse($this->valueTypes), $this->nextAutoIndexes, $optionalKeys, TrinaryLogic::createNo());
1173-
1174-
return $preserveKeys ? $reversed : $reversed->reindex();
1185+
return $this->reverseConstantArray(TrinaryLogic::createFromBoolean($preserveKeys));
11751186
}
11761187

11771188
/** @param positive-int $length */

src/Type/IntersectionType.php

+5
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,11 @@ public function popArray(): Type
744744
return $this->intersectTypes(static fn (Type $type): Type => $type->popArray());
745745
}
746746

747+
public function reverseArray(TrinaryLogic $preserveKeys): Type
748+
{
749+
return $this->intersectTypes(static fn (Type $type): Type => $type->reverseArray($preserveKeys));
750+
}
751+
747752
public function searchArray(Type $needleType): Type
748753
{
749754
return $this->intersectTypes(static fn (Type $type): Type => $type->searchArray($needleType));

src/Type/MixedType.php

+9
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,15 @@ public function popArray(): Type
225225
return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
226226
}
227227

228+
public function reverseArray(TrinaryLogic $preserveKeys): Type
229+
{
230+
if ($this->isArray()->no()) {
231+
return new ErrorType();
232+
}
233+
234+
return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
235+
}
236+
228237
public function searchArray(Type $needleType): Type
229238
{
230239
if ($this->isArray()->no()) {

src/Type/NeverType.php

+5
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,11 @@ public function popArray(): Type
316316
return new NeverType();
317317
}
318318

319+
public function reverseArray(TrinaryLogic $preserveKeys): Type
320+
{
321+
return new NeverType();
322+
}
323+
319324
public function searchArray(Type $needleType): Type
320325
{
321326
return new NeverType();

src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php

+12-17
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@
44

55
use PhpParser\Node\Expr\FuncCall;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Php\PhpVersion;
78
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Type\Constant\ConstantBooleanType;
810
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
911
use PHPStan\Type\NeverType;
12+
use PHPStan\Type\NullType;
1013
use PHPStan\Type\Type;
11-
use PHPStan\Type\TypeCombinator;
12-
use function count;
1314

1415
final class ArrayReverseFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1516
{
1617

18+
public function __construct(private PhpVersion $phpVersion)
19+
{
20+
}
21+
1722
public function isFunctionSupported(FunctionReflection $functionReflection): bool
1823
{
1924
return $functionReflection->getName() === 'array_reverse';
@@ -26,24 +31,14 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
2631
}
2732

2833
$type = $scope->getType($functionCall->getArgs()[0]->value);
29-
$preserveKeysType = isset($functionCall->getArgs()[1]) ? $scope->getType($functionCall->getArgs()[1]->value) : new NeverType();
30-
$preserveKeys = $preserveKeysType->isTrue()->yes();
31-
32-
if (!$type->isArray()->yes()) {
33-
return null;
34+
if ($type->isArray()->no()) {
35+
return $this->phpVersion->arrayFunctionsReturnNullWithNonArray() ? new NullType() : new NeverType();
3436
}
3537

36-
$constantArrays = $type->getConstantArrays();
37-
if (count($constantArrays) > 0) {
38-
$results = [];
39-
foreach ($constantArrays as $constantArray) {
40-
$results[] = $constantArray->reverse($preserveKeys);
41-
}
42-
43-
return TypeCombinator::union(...$results);
44-
}
38+
$preserveKeysType = isset($functionCall->getArgs()[1]) ? $scope->getType($functionCall->getArgs()[1]->value) : new ConstantBooleanType(false);
39+
$preserveKeys = (new ConstantBooleanType(true))->isSuperTypeOf($preserveKeysType);
4540

46-
return $type;
41+
return $type->reverseArray($preserveKeys);
4742
}
4843

4944
}

src/Type/StaticType.php

+5
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,11 @@ public function popArray(): Type
435435
return $this->getStaticObjectType()->popArray();
436436
}
437437

438+
public function reverseArray(TrinaryLogic $preserveKeys): Type
439+
{
440+
return $this->getStaticObjectType()->reverseArray($preserveKeys);
441+
}
442+
438443
public function searchArray(Type $needleType): Type
439444
{
440445
return $this->getStaticObjectType()->searchArray($needleType);

src/Type/Traits/LateResolvableTypeTrait.php

+5
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ public function popArray(): Type
282282
return $this->resolve()->popArray();
283283
}
284284

285+
public function reverseArray(TrinaryLogic $preserveKeys): Type
286+
{
287+
return $this->resolve()->reverseArray($preserveKeys);
288+
}
289+
285290
public function searchArray(Type $needleType): Type
286291
{
287292
return $this->resolve()->searchArray($needleType);

src/Type/Traits/MaybeArrayTypeTrait.php

+5
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public function popArray(): Type
6969
return new ErrorType();
7070
}
7171

72+
public function reverseArray(TrinaryLogic $preserveKeys): Type
73+
{
74+
return new ErrorType();
75+
}
76+
7277
public function searchArray(Type $needleType): Type
7378
{
7479
return new ErrorType();

src/Type/Traits/NonArrayTypeTrait.php

+5
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public function popArray(): Type
6969
return new ErrorType();
7070
}
7171

72+
public function reverseArray(TrinaryLogic $preserveKeys): Type
73+
{
74+
return new ErrorType();
75+
}
76+
7277
public function searchArray(Type $needleType): Type
7378
{
7479
return new ErrorType();

src/Type/Type.php

+2
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ public function intersectKeyArray(Type $otherArraysType): Type;
159159

160160
public function popArray(): Type;
161161

162+
public function reverseArray(TrinaryLogic $preserveKeys): Type;
163+
162164
public function searchArray(Type $needleType): Type;
163165

164166
public function shiftArray(): Type;

src/Type/UnionType.php

+5
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,11 @@ public function popArray(): Type
721721
return $this->unionTypes(static fn (Type $type): Type => $type->popArray());
722722
}
723723

724+
public function reverseArray(TrinaryLogic $preserveKeys): Type
725+
{
726+
return $this->unionTypes(static fn (Type $type): Type => $type->reverseArray($preserveKeys));
727+
}
728+
724729
public function searchArray(Type $needleType): Type
725730
{
726731
return $this->unionTypes(static fn (Type $type): Type => $type->searchArray($needleType));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php // lint < 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace ArrayReversePhp7;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class Foo
10+
{
11+
public function notArray(bool $bool): void
12+
{
13+
assertType('null', array_reverse($bool));
14+
assertType('null', array_reverse($bool, true));
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace ArrayReversePhp8;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class Foo
10+
{
11+
public function notArray(bool $bool): void
12+
{
13+
assertType('*NEVER*', array_reverse($bool));
14+
assertType('*NEVER*', array_reverse($bool, true));
15+
}
16+
}

tests/PHPStan/Analyser/nsrt/array-reverse.php

+24
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,28 @@ public function constantArrays(array $a, array $b): void
4646
assertType('array{\'bar\', \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b));
4747
assertType('array{19: \'bar\', 17: \'foo\'}|array{bar: 19, foo: 17}', array_reverse($b, true));
4848
}
49+
50+
/**
51+
* @param list<string> $a
52+
* @param non-empty-list<string> $b
53+
*/
54+
public function list(array $a, array $b): void
55+
{
56+
assertType('list<string>', array_reverse($a));
57+
assertType('array<int<0, max>, string>', array_reverse($a, true));
58+
59+
assertType('non-empty-list<string>', array_reverse($b));
60+
assertType('non-empty-array<int<0, max>, string>', array_reverse($b, true));
61+
}
62+
63+
public function mixed(mixed $mixed): void
64+
{
65+
assertType('array', array_reverse($mixed));
66+
assertType('array', array_reverse($mixed, true));
67+
68+
if (array_key_exists('foo', $mixed)) {
69+
assertType('non-empty-array', array_reverse($mixed));
70+
assertType("array&hasOffset('foo')", array_reverse($mixed, true));
71+
}
72+
}
4973
}

0 commit comments

Comments
 (0)