Skip to content

Commit aab3260

Browse files
committed
Merge branch '2.1.x' into merge21
2 parents 6a10956 + 1e062f1 commit aab3260

5 files changed

Lines changed: 140 additions & 12 deletions

File tree

src/Analyser/ExprHandler/FuncCallHandler.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
265265
}
266266
}
267267

268+
$scopeBeforeArgs = $scope;
268269
$argsResult = $nodeScopeResolver->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $normalizedExpr, $scope, $storage, $nodeCallbackForArgs, $context);
269270
$scope = $argsResult->getScope();
270271
$hasYield = $argsResult->hasYield();
@@ -395,8 +396,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
395396
$stmt,
396397
$arrayArg,
397398
new NativeTypeExpr(
398-
$this->getArrayFunctionAppendingType($functionReflection, $scope, $normalizedExpr),
399-
$this->getArrayFunctionAppendingType($functionReflection, $scope->doNotTreatPhpDocTypesAsCertain(), $normalizedExpr),
399+
$this->getArrayFunctionAppendingType($functionReflection, $scopeBeforeArgs, $normalizedExpr),
400+
$this->getArrayFunctionAppendingType($functionReflection, $scopeBeforeArgs->doNotTreatPhpDocTypesAsCertain(), $normalizedExpr),
400401
),
401402
$nodeCallback,
402403
)->getScope();
@@ -434,9 +435,22 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
434435
$arrayArgType = $scope->getType($arrayArg);
435436
$arrayArgNativeType = $scope->getNativeType($arrayArg);
436437

437-
$offsetType = $scope->getType($normalizedExpr->getArgs()[1]->value);
438-
$lengthType = isset($normalizedExpr->getArgs()[2]) ? $scope->getType($normalizedExpr->getArgs()[2]->value) : new NullType();
439-
$replacementType = isset($normalizedExpr->getArgs()[3]) ? $scope->getType($normalizedExpr->getArgs()[3]->value) : new ConstantArrayType([], []);
438+
$offsetType = $scopeBeforeArgs->getType($normalizedExpr->getArgs()[1]->value);
439+
440+
if (isset($normalizedExpr->getArgs()[2])) {
441+
$lengthType = $scopeBeforeArgs->getType($normalizedExpr->getArgs()[2]->value);
442+
} else {
443+
$lengthType = new NullType();
444+
}
445+
446+
if (isset($normalizedExpr->getArgs()[3])) {
447+
$replacementArg = $normalizedExpr->getArgs()[3]->value;
448+
$replacementType = $scopeBeforeArgs->getType($replacementArg);
449+
$replacementNativeType = $scopeBeforeArgs->getNativeType($replacementArg);
450+
} else {
451+
$replacementType = new ConstantArrayType([], []);
452+
$replacementNativeType = new ConstantArrayType([], []);
453+
}
440454

441455
$scope = $nodeScopeResolver->processVirtualAssign(
442456
$scope,
@@ -445,7 +459,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
445459
$arrayArg,
446460
new NativeTypeExpr(
447461
$arrayArgType->spliceArray($offsetType, $lengthType, $replacementType),
448-
$arrayArgNativeType->spliceArray($offsetType, $lengthType, $replacementType),
462+
$arrayArgNativeType->spliceArray($offsetType, $lengthType, $replacementNativeType),
449463
),
450464
$nodeCallback,
451465
)->getScope();

src/Type/ArrayType.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ class ArrayType implements Type
6161
private Type $keyType;
6262

6363
private ?Type $cachedIterableKeyType = null;
64+
65+
private ?TrinaryLogic $isList = null;
6466

6567
/** @api */
6668
public function __construct(Type $keyType, private Type $itemType)
@@ -296,15 +298,19 @@ public function isConstantArray(): TrinaryLogic
296298

297299
public function isList(): TrinaryLogic
298300
{
299-
if (IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($this->getKeyType())->no()) {
300-
return TrinaryLogic::createNo();
301-
}
301+
if ($this->isList === null) {
302+
if (IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($this->getKeyType())->no()) {
303+
return $this->isList = TrinaryLogic::createNo();
304+
}
302305

303-
if ($this->getKeyType()->isSuperTypeOf(new ConstantIntegerType(0))->no()) {
304-
return TrinaryLogic::createNo();
306+
if ($this->getKeyType()->isSuperTypeOf(new ConstantIntegerType(0))->no()) {
307+
return $this->isList = TrinaryLogic::createNo();
308+
}
309+
310+
return $this->isList = TrinaryLogic::createMaybe();
305311
}
306312

307-
return TrinaryLogic::createMaybe();
313+
return $this->isList;
308314
}
309315

310316
public function isConstantValue(): TrinaryLogic

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,12 @@ public function testBug14596(): void
15781578
$this->assertNotEmpty($errors);
15791579
}
15801580

1581+
public function testBug9172(): void
1582+
{
1583+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-9172.php');
1584+
$this->assertNotEmpty($errors);
1585+
}
1586+
15811587
/**
15821588
* @param string[]|null $allAnalysedFiles
15831589
* @return list<Error>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Bug9172Integration;
4+
5+
final class HelloWorld
6+
{
7+
/** @var int<0, self::MAX_DEPOSIT> */
8+
public const MIN_DEPOSIT = 1_000;
9+
10+
/** @var int<self::MIN_DEPOSIT, max> */
11+
public const MAX_DEPOSIT = 20_000;
12+
13+
/** @param int<self::MIN_DEPOSIT, self::MAX_DEPOSIT> $amount */
14+
public function deposit(int $amount): void
15+
{
16+
}
17+
}
18+
19+
final class CircularValues
20+
{
21+
/** @var int<0, self::MAX> */
22+
public const MIN = self::MAX - 19_000;
23+
24+
/** @var int<self::MIN, max> */
25+
public const MAX = self::MIN + 19_000;
26+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace Bug13510;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
final class Foo
8+
{
9+
10+
/** @param non-empty-list<int> $arr */
11+
public function test(array $arr): void
12+
{
13+
array_unshift($arr, array_pop($arr));
14+
assertType('non-empty-list<int>', $arr);
15+
}
16+
17+
/** @param non-empty-list<int> $arr */
18+
public function testTwoLines(array $arr): void
19+
{
20+
$popped = array_pop($arr);
21+
array_unshift($arr, $popped);
22+
assertType('non-empty-list<int>', $arr);
23+
}
24+
}
25+
26+
class Bar
27+
{
28+
/** @var array<int> */
29+
public array $arr = [];
30+
31+
public function test(): void
32+
{
33+
if (count($this->arr) === 0) {
34+
throw new \Exception();
35+
}
36+
assertType('non-empty-array<int>', $this->arr);
37+
array_unshift($this->arr, array_pop($this->arr));
38+
assertType('non-empty-array<int>', $this->arr);
39+
}
40+
41+
public function testArrayPush(): void
42+
{
43+
if (count($this->arr) === 0) {
44+
throw new \Exception();
45+
}
46+
array_push($this->arr, array_pop($this->arr));
47+
assertType('non-empty-array<int>', $this->arr);
48+
}
49+
50+
public function testArrayUnshiftWithArrayShift(): void
51+
{
52+
if (count($this->arr) === 0) {
53+
throw new \Exception();
54+
}
55+
array_unshift($this->arr, array_shift($this->arr));
56+
assertType('non-empty-array<int>', $this->arr);
57+
}
58+
59+
public function testArrayPushWithArrayShift(): void
60+
{
61+
if (count($this->arr) === 0) {
62+
throw new \Exception();
63+
}
64+
array_push($this->arr, array_shift($this->arr));
65+
assertType('non-empty-array<int>', $this->arr);
66+
}
67+
68+
public function testArraySplice(): void
69+
{
70+
if (count($this->arr) === 0) {
71+
throw new \Exception();
72+
}
73+
array_splice($this->arr, 0, 0, [array_pop($this->arr)]);
74+
assertType('non-empty-array<(int<0, max>|string), int>', $this->arr);
75+
}
76+
}

0 commit comments

Comments
 (0)