Skip to content

Commit ddd520f

Browse files
authored
Fix false-positive when merging unions with plus operator
1 parent 0ebfea0 commit ddd520f

File tree

3 files changed

+77
-52
lines changed

3 files changed

+77
-52
lines changed

src/Analyser/MutatingScope.php

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,58 @@ private function resolveType(Expr $node): Type
12631263
$leftType = $this->getType($left);
12641264
$rightType = $this->getType($right);
12651265

1266+
if ($node instanceof Expr\AssignOp\Plus || $node instanceof Expr\BinaryOp\Plus) {
1267+
$leftConstantArrays = TypeUtils::getConstantArrays($leftType);
1268+
$rightConstantArrays = TypeUtils::getConstantArrays($rightType);
1269+
1270+
if (count($leftConstantArrays) > 0 && count($rightConstantArrays) > 0) {
1271+
$resultTypes = [];
1272+
foreach ($rightConstantArrays as $rightConstantArray) {
1273+
foreach ($leftConstantArrays as $leftConstantArray) {
1274+
$newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray);
1275+
foreach ($leftConstantArray->getKeyTypes() as $leftKeyType) {
1276+
$newArrayBuilder->setOffsetValueType(
1277+
$leftKeyType,
1278+
$leftConstantArray->getOffsetValueType($leftKeyType)
1279+
);
1280+
}
1281+
$resultTypes[] = $newArrayBuilder->getArray();
1282+
}
1283+
}
1284+
1285+
return TypeCombinator::union(...$resultTypes);
1286+
}
1287+
$arrayType = new ArrayType(new MixedType(), new MixedType());
1288+
1289+
if ($arrayType->isSuperTypeOf($leftType)->yes() && $arrayType->isSuperTypeOf($rightType)->yes()) {
1290+
if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) {
1291+
// to preserve BenevolentUnionType
1292+
$keyType = $leftType->getIterableKeyType();
1293+
} else {
1294+
$keyTypes = [];
1295+
foreach ([
1296+
$leftType->getIterableKeyType(),
1297+
$rightType->getIterableKeyType(),
1298+
] as $keyType) {
1299+
$keyTypes[] = $keyType;
1300+
}
1301+
$keyType = TypeCombinator::union(...$keyTypes);
1302+
}
1303+
return new ArrayType(
1304+
$keyType,
1305+
TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType())
1306+
);
1307+
}
1308+
1309+
if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1310+
return new BenevolentUnionType([
1311+
new FloatType(),
1312+
new IntegerType(),
1313+
new ArrayType(new MixedType(), new MixedType()),
1314+
]);
1315+
}
1316+
}
1317+
12661318
if (($leftType instanceof IntegerRangeType || $leftType instanceof ConstantIntegerType || $leftType instanceof UnionType) &&
12671319
($rightType instanceof IntegerRangeType || $rightType instanceof ConstantIntegerType || $rightType instanceof UnionType) &&
12681320
!($node instanceof Node\Expr\BinaryOp\Pow || $node instanceof Node\Expr\AssignOp\Pow)) {
@@ -1321,58 +1373,6 @@ private function resolveType(Expr $node): Type
13211373
}
13221374
}
13231375

1324-
if ($node instanceof Expr\AssignOp\Plus || $node instanceof Expr\BinaryOp\Plus) {
1325-
$leftConstantArrays = TypeUtils::getConstantArrays($leftType);
1326-
$rightConstantArrays = TypeUtils::getConstantArrays($rightType);
1327-
1328-
if (count($leftConstantArrays) > 0 && count($rightConstantArrays) > 0) {
1329-
$resultTypes = [];
1330-
foreach ($rightConstantArrays as $rightConstantArray) {
1331-
foreach ($leftConstantArrays as $leftConstantArray) {
1332-
$newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray);
1333-
foreach ($leftConstantArray->getKeyTypes() as $leftKeyType) {
1334-
$newArrayBuilder->setOffsetValueType(
1335-
$leftKeyType,
1336-
$leftConstantArray->getOffsetValueType($leftKeyType)
1337-
);
1338-
}
1339-
$resultTypes[] = $newArrayBuilder->getArray();
1340-
}
1341-
}
1342-
1343-
return TypeCombinator::union(...$resultTypes);
1344-
}
1345-
$arrayType = new ArrayType(new MixedType(), new MixedType());
1346-
1347-
if ($arrayType->isSuperTypeOf($leftType)->yes() && $arrayType->isSuperTypeOf($rightType)->yes()) {
1348-
if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) {
1349-
// to preserve BenevolentUnionType
1350-
$keyType = $leftType->getIterableKeyType();
1351-
} else {
1352-
$keyTypes = [];
1353-
foreach ([
1354-
$leftType->getIterableKeyType(),
1355-
$rightType->getIterableKeyType(),
1356-
] as $keyType) {
1357-
$keyTypes[] = $keyType;
1358-
}
1359-
$keyType = TypeCombinator::union(...$keyTypes);
1360-
}
1361-
return new ArrayType(
1362-
$keyType,
1363-
TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType())
1364-
);
1365-
}
1366-
1367-
if ($leftType instanceof MixedType && $rightType instanceof MixedType) {
1368-
return new BenevolentUnionType([
1369-
new FloatType(),
1370-
new IntegerType(),
1371-
new ArrayType(new MixedType(), new MixedType()),
1372-
]);
1373-
}
1374-
}
1375-
13761376
$types = TypeCombinator::union($leftType, $rightType);
13771377
if (
13781378
$leftType instanceof ArrayType

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ public function dataFileAsserts(): iterable
495495
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4602.php');
496496
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4499.php');
497497
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2142.php');
498+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5584.php');
498499

499500
yield from $this->gatherAssertTypes(__DIR__ . '/data/math.php');
500501

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Analyser\Bug5584;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo {
8+
public function unionSum(): void
9+
{
10+
$a = [];
11+
12+
if (rand(0,1) === 0) {
13+
$a = ['a' => 5];
14+
}
15+
16+
$b = [];
17+
18+
if (rand(0,1) === 0) {
19+
$b = ['b' => 6];
20+
}
21+
22+
assertType("array()|array(?'b' => 6, ?'a' => 5)", $a + $b);
23+
}
24+
}

0 commit comments

Comments
 (0)