Skip to content

Commit 46c2519

Browse files
authored
add array_first and array_last return type extensions (#4499)
1 parent ae9d277 commit 46c2519

19 files changed

+136
-58
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -747,8 +747,8 @@ public function specifyTypesInCondition(
747747
) {
748748
$dimFetch = new ArrayDimFetch($arrayArg, $expr->var);
749749
$iterableValueType = $expr->expr->name->toLowerString() === 'array_key_first'
750-
? $arrayType->getFirstIterableValueType()
751-
: $arrayType->getLastIterableValueType();
750+
? $arrayType->getIterableValueType()
751+
: $arrayType->getIterableValueType();
752752

753753
return $specifiedTypes->unionWith(
754754
$this->create($dimFetch, $iterableValueType, TypeSpecifierContext::createTrue(), $scope),
@@ -775,7 +775,7 @@ public function specifyTypesInCondition(
775775
$dimFetch = new ArrayDimFetch($arrayArg, $expr->var);
776776

777777
return $specifiedTypes->unionWith(
778-
$this->create($dimFetch, $arrayType->getLastIterableValueType(), TypeSpecifierContext::createTrue(), $scope),
778+
$this->create($dimFetch, $arrayType->getIterableValueType(), TypeSpecifierContext::createTrue(), $scope),
779779
);
780780
}
781781
}

src/Type/IntersectionType.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -690,12 +690,12 @@ public function getIterableKeyType(): Type
690690

691691
public function getFirstIterableKeyType(): Type
692692
{
693-
return $this->intersectTypes(static fn (Type $type): Type => $type->getFirstIterableKeyType());
693+
return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableKeyType());
694694
}
695695

696696
public function getLastIterableKeyType(): Type
697697
{
698-
return $this->intersectTypes(static fn (Type $type): Type => $type->getLastIterableKeyType());
698+
return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableKeyType());
699699
}
700700

701701
public function getIterableValueType(): Type
@@ -705,12 +705,12 @@ public function getIterableValueType(): Type
705705

706706
public function getFirstIterableValueType(): Type
707707
{
708-
return $this->intersectTypes(static fn (Type $type): Type => $type->getFirstIterableValueType());
708+
return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableValueType());
709709
}
710710

711711
public function getLastIterableValueType(): Type
712712
{
713-
return $this->intersectTypes(static fn (Type $type): Type => $type->getLastIterableValueType());
713+
return $this->intersectTypes(static fn (Type $type): Type => $type->getIterableValueType());
714714
}
715715

716716
public function isArray(): TrinaryLogic
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\AutowiredService;
8+
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
10+
use PHPStan\Type\NullType;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\TypeCombinator;
13+
use function count;
14+
use function in_array;
15+
16+
#[AutowiredService]
17+
final class ArrayFirstLastDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
18+
{
19+
20+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
21+
{
22+
return in_array($functionReflection->getName(), ['array_first', 'array_last'], true);
23+
}
24+
25+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
26+
{
27+
$args = $functionCall->getArgs();
28+
29+
if (count($args) < 1) {
30+
return null;
31+
}
32+
33+
$argType = $scope->getType($args[0]->value);
34+
$iterableAtLeastOnce = $argType->isIterableAtLeastOnce();
35+
36+
if ($iterableAtLeastOnce->no()) {
37+
return new NullType();
38+
}
39+
40+
$valueType = $argType->getIterableValueType();
41+
42+
if ($iterableAtLeastOnce->yes()) {
43+
return $valueType;
44+
}
45+
46+
return TypeCombinator::union($valueType, new NullType());
47+
}
48+
49+
}

src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3232
return new NullType();
3333
}
3434

35-
$keyType = $argType->getFirstIterableKeyType();
35+
$keyType = $argType->getIterableKeyType();
3636
if ($iterableAtLeastOnce->yes()) {
3737
return $keyType;
3838
}

src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3232
return new NullType();
3333
}
3434

35-
$keyType = $argType->getLastIterableKeyType();
35+
$keyType = $argType->getIterableKeyType();
3636
if ($iterableAtLeastOnce->yes()) {
3737
return $keyType;
3838
}

src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ public function getTypeFromFunctionCall(
4545
}
4646

4747
$itemType = $functionReflection->getName() === 'reset'
48-
? $argType->getFirstIterableValueType()
49-
: $argType->getLastIterableValueType();
48+
? $argType->getIterableValueType()
49+
: $argType->getIterableValueType();
5050
if ($iterableAtLeastOnce->yes()) {
5151
return $itemType;
5252
}

src/Type/Php/ArrayPopFunctionReturnTypeExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3232
return new NullType();
3333
}
3434

35-
$itemType = $argType->getLastIterableValueType();
35+
$itemType = $argType->getIterableValueType();
3636
if ($iterableAtLeastOnce->yes()) {
3737
return $itemType;
3838
}

src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3232
return new NullType();
3333
}
3434

35-
$itemType = $argType->getFirstIterableValueType();
35+
$itemType = $argType->getIterableValueType();
3636
if ($iterableAtLeastOnce->yes()) {
3737
return $itemType;
3838
}

src/Type/StaticType.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -401,12 +401,12 @@ public function getIterableKeyType(): Type
401401

402402
public function getFirstIterableKeyType(): Type
403403
{
404-
return $this->getStaticObjectType()->getFirstIterableKeyType();
404+
return $this->getStaticObjectType()->getIterableKeyType();
405405
}
406406

407407
public function getLastIterableKeyType(): Type
408408
{
409-
return $this->getStaticObjectType()->getLastIterableKeyType();
409+
return $this->getStaticObjectType()->getIterableKeyType();
410410
}
411411

412412
public function getIterableValueType(): Type
@@ -416,12 +416,12 @@ public function getIterableValueType(): Type
416416

417417
public function getFirstIterableValueType(): Type
418418
{
419-
return $this->getStaticObjectType()->getFirstIterableValueType();
419+
return $this->getStaticObjectType()->getIterableValueType();
420420
}
421421

422422
public function getLastIterableValueType(): Type
423423
{
424-
return $this->getStaticObjectType()->getLastIterableValueType();
424+
return $this->getStaticObjectType()->getIterableValueType();
425425
}
426426

427427
public function isOffsetAccessible(): TrinaryLogic

src/Type/Traits/LateResolvableTypeTrait.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,12 @@ public function getIterableKeyType(): Type
200200

201201
public function getFirstIterableKeyType(): Type
202202
{
203-
return $this->resolve()->getFirstIterableKeyType();
203+
return $this->resolve()->getIterableKeyType();
204204
}
205205

206206
public function getLastIterableKeyType(): Type
207207
{
208-
return $this->resolve()->getLastIterableKeyType();
208+
return $this->resolve()->getIterableKeyType();
209209
}
210210

211211
public function getIterableValueType(): Type
@@ -215,12 +215,12 @@ public function getIterableValueType(): Type
215215

216216
public function getFirstIterableValueType(): Type
217217
{
218-
return $this->resolve()->getFirstIterableValueType();
218+
return $this->resolve()->getIterableValueType();
219219
}
220220

221221
public function getLastIterableValueType(): Type
222222
{
223-
return $this->resolve()->getLastIterableValueType();
223+
return $this->resolve()->getIterableValueType();
224224
}
225225

226226
public function isArray(): TrinaryLogic

0 commit comments

Comments
 (0)