Skip to content

Commit 52dfe08

Browse files
authored
Merge branch refs/heads/1.11.x into 1.12.x
2 parents 992072c + e9c60a2 commit 52dfe08

File tree

3 files changed

+177
-32
lines changed

3 files changed

+177
-32
lines changed

src/Analyser/TypeSpecifier.php

+63-31
Original file line numberDiff line numberDiff line change
@@ -244,12 +244,25 @@ public function specifyTypesInCondition(
244244
&& in_array(strtolower((string) $expr->right->name), ['count', 'sizeof'], true)
245245
&& $leftType->isInteger()->yes()
246246
) {
247+
$argType = $scope->getType($expr->right->getArgs()[0]->value);
248+
249+
if ($argType instanceof UnionType && $leftType instanceof ConstantIntegerType) {
250+
if ($orEqual) {
251+
$sizeType = IntegerRangeType::createAllGreaterThanOrEqualTo($leftType->getValue());
252+
} else {
253+
$sizeType = IntegerRangeType::createAllGreaterThan($leftType->getValue());
254+
}
255+
256+
$narrowed = $this->narrowUnionByArraySize($expr->right, $argType, $sizeType, $context, $scope, $rootExpr);
257+
if ($narrowed !== null) {
258+
return $narrowed;
259+
}
260+
}
261+
247262
if (
248263
$context->true() && (IntegerRangeType::createAllGreaterThanOrEqualTo(1 - $offset)->isSuperTypeOf($leftType)->yes())
249264
|| ($context->false() && (new ConstantIntegerType(1 - $offset))->isSuperTypeOf($leftType)->yes())
250265
) {
251-
$argType = $scope->getType($expr->right->getArgs()[0]->value);
252-
253266
if ($context->truthy() && $argType->isArray()->maybe()) {
254267
$countables = [];
255268
if ($argType instanceof UnionType) {
@@ -936,6 +949,43 @@ public function specifyTypesInCondition(
936949
return new SpecifiedTypes([], [], false, [], $rootExpr);
937950
}
938951

952+
private function narrowUnionByArraySize(FuncCall $countFuncCall, UnionType $argType, Type $sizeType, TypeSpecifierContext $context, Scope $scope, ?Expr $rootExpr): ?SpecifiedTypes
953+
{
954+
if (count($countFuncCall->getArgs()) === 1) {
955+
$isNormalCount = TrinaryLogic::createYes();
956+
} else {
957+
$mode = $scope->getType($countFuncCall->getArgs()[1]->value);
958+
$isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->or($argType->getIterableValueType()->isArray()->negate());
959+
}
960+
961+
if (
962+
$isNormalCount->yes()
963+
&& $argType->isConstantArray()->yes()
964+
) {
965+
$result = [];
966+
foreach ($argType->getTypes() as $innerType) {
967+
$arraySize = $innerType->getArraySize();
968+
$isSize = $sizeType->isSuperTypeOf($arraySize);
969+
if ($context->truthy()) {
970+
if ($isSize->no()) {
971+
continue;
972+
}
973+
}
974+
if ($context->falsey()) {
975+
if (!$isSize->yes()) {
976+
continue;
977+
}
978+
}
979+
980+
$result[] = $innerType;
981+
}
982+
983+
return $this->create($countFuncCall->getArgs()[0]->value, TypeCombinator::union(...$result), $context, false, $scope, $rootExpr);
984+
}
985+
986+
return null;
987+
}
988+
939989
private function specifyTypesForConstantBinaryExpression(
940990
Expr $exprNode,
941991
ConstantScalarType $constantType,
@@ -986,36 +1036,11 @@ private function specifyTypesForConstantBinaryExpression(
9861036
) {
9871037
$argType = $scope->getType($exprNode->getArgs()[0]->value);
9881038

989-
if (count($exprNode->getArgs()) === 1) {
990-
$isNormalCount = TrinaryLogic::createYes();
991-
} else {
992-
$mode = $scope->getType($exprNode->getArgs()[1]->value);
993-
$isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->or($argType->getIterableValueType()->isArray()->negate());
994-
}
995-
996-
if (
997-
$isNormalCount->yes()
998-
&& $argType instanceof UnionType
999-
) {
1000-
$result = [];
1001-
foreach ($argType->getTypes() as $innerType) {
1002-
$arraySize = $innerType->getArraySize();
1003-
$isSize = $constantType->isSuperTypeOf($arraySize);
1004-
if ($context->truthy()) {
1005-
if ($isSize->no()) {
1006-
continue;
1007-
}
1008-
}
1009-
if ($context->falsey()) {
1010-
if (!$isSize->yes()) {
1011-
continue;
1012-
}
1013-
}
1014-
1015-
$result[] = $innerType;
1039+
if ($argType instanceof UnionType) {
1040+
$narrowed = $this->narrowUnionByArraySize($exprNode, $argType, $constantType, $context, $scope, $rootExpr);
1041+
if ($narrowed !== null) {
1042+
return $narrowed;
10161043
}
1017-
1018-
return $this->create($exprNode->getArgs()[0]->value, TypeCombinator::union(...$result), $context, false, $scope, $rootExpr);
10191044
}
10201045

10211046
if ($context->truthy() || $constantType->getValue() === 0) {
@@ -1025,6 +1050,13 @@ private function specifyTypesForConstantBinaryExpression(
10251050
}
10261051

10271052
if ($argType->isArray()->yes()) {
1053+
if (count($exprNode->getArgs()) === 1) {
1054+
$isNormalCount = TrinaryLogic::createYes();
1055+
} else {
1056+
$mode = $scope->getType($exprNode->getArgs()[1]->value);
1057+
$isNormalCount = (new ConstantIntegerType(COUNT_NORMAL))->isSuperTypeOf($mode)->or($argType->getIterableValueType()->isArray()->negate());
1058+
}
1059+
10281060
$funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr);
10291061
if ($isNormalCount->yes() && $argType->isList()->yes() && $context->truthy() && $constantType->getValue() < ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
10301062
$valueTypesBuilder = ConstantArrayTypeBuilder::createEmpty();

tests/PHPStan/Analyser/nsrt/bug-3558.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ function (): void {
2828
}
2929

3030
if(count($idGroups) > 1){
31-
assertType('array{1, array{1, 2}, array{1, 2}, array{1, 2}}|array{1}', $idGroups);
31+
assertType('array{1, array{1, 2}, array{1, 2}, array{1, 2}}', $idGroups);
3232
}
3333
};
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug11480;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
public function arrayGreatherThan(): void
10+
{
11+
$x = [];
12+
if (rand(0, 1)) {
13+
$x[] = 'ab';
14+
}
15+
if (rand(0, 1)) {
16+
$x[] = 'xy';
17+
}
18+
19+
if (count($x) > 0) {
20+
assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
21+
} else {
22+
assertType("array{}", $x);
23+
}
24+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
25+
26+
if (count($x) > 1) {
27+
assertType("array{0: 'ab', 1?: 'xy'}", $x);
28+
} else {
29+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
30+
}
31+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
32+
33+
if (count($x) >= 1) {
34+
assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
35+
} else {
36+
assertType("array{}", $x);
37+
}
38+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
39+
}
40+
41+
public function arraySmallerThan(): void
42+
{
43+
$x = [];
44+
if (rand(0, 1)) {
45+
$x[] = 'ab';
46+
}
47+
if (rand(0, 1)) {
48+
$x[] = 'xy';
49+
}
50+
51+
if (count($x) < 1) {
52+
assertType("array{}", $x);
53+
} else {
54+
assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
55+
}
56+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
57+
58+
if (count($x) <= 1) {
59+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
60+
} else {
61+
assertType("array{0: 'ab', 1?: 'xy'}", $x);
62+
}
63+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
64+
}
65+
66+
public function intUnionCount(): void
67+
{
68+
$count = 1;
69+
if (rand(0, 1)) {
70+
$count++;
71+
}
72+
73+
$x = [];
74+
if (rand(0, 1)) {
75+
$x[] = 'ab';
76+
}
77+
if (rand(0, 1)) {
78+
$x[] = 'xy';
79+
}
80+
81+
assertType('1|2', $count);
82+
83+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
84+
if (count($x) >= $count) {
85+
assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
86+
} else {
87+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
88+
}
89+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
90+
}
91+
92+
/**
93+
* @param int<1,2> $count
94+
*/
95+
public function intRangeCount($count): void
96+
{
97+
$x = [];
98+
if (rand(0, 1)) {
99+
$x[] = 'ab';
100+
}
101+
if (rand(0, 1)) {
102+
$x[] = 'xy';
103+
}
104+
105+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
106+
if (count($x) >= $count) {
107+
assertType("array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
108+
} else {
109+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
110+
}
111+
assertType("array{}|array{'xy'}|array{0: 'ab', 1?: 'xy'}", $x);
112+
}
113+
}

0 commit comments

Comments
 (0)