Skip to content

Commit 990ba51

Browse files
committed
Fixed nullsafe operator with null coalesce
1 parent 46103ad commit 990ba51

File tree

3 files changed

+80
-50
lines changed

3 files changed

+80
-50
lines changed

src/Analyser/MutatingScope.php

+50-50
Original file line numberDiff line numberDiff line change
@@ -1007,56 +1007,6 @@ private function resolveType(Expr $node): Type
10071007
return IntegerRangeType::fromInterval(-1, 1);
10081008
}
10091009

1010-
if ($node instanceof Expr\AssignOp\Coalesce) {
1011-
return $this->getType(new BinaryOp\Coalesce($node->var, $node->expr, $node->getAttributes()));
1012-
}
1013-
1014-
if ($node instanceof Expr\BinaryOp\Coalesce) {
1015-
if ($node->left instanceof Expr\ArrayDimFetch && $node->left->dim !== null) {
1016-
$dimType = $this->getType($node->left->dim);
1017-
$varType = $this->getType($node->left->var);
1018-
$hasOffset = $varType->hasOffsetValueType($dimType);
1019-
$leftType = $this->getType($node->left);
1020-
$rightType = $this->getType($node->right);
1021-
if ($hasOffset->no()) {
1022-
return $rightType;
1023-
} elseif ($hasOffset->yes()) {
1024-
$offsetValueType = $varType->getOffsetValueType($dimType);
1025-
if ($offsetValueType->isSuperTypeOf(new NullType())->no()) {
1026-
return TypeCombinator::removeNull($leftType);
1027-
}
1028-
}
1029-
1030-
return TypeCombinator::union(
1031-
TypeCombinator::removeNull($leftType),
1032-
$rightType
1033-
);
1034-
}
1035-
1036-
$leftType = $this->getType($node->left);
1037-
$rightType = $this->getType($node->right);
1038-
if ($leftType instanceof ErrorType || $leftType instanceof NullType) {
1039-
return $rightType;
1040-
}
1041-
1042-
if (
1043-
TypeCombinator::containsNull($leftType)
1044-
|| $node->left instanceof PropertyFetch
1045-
|| (
1046-
$node->left instanceof Variable
1047-
&& is_string($node->left->name)
1048-
&& !$this->hasVariableType($node->left->name)->yes()
1049-
)
1050-
) {
1051-
return TypeCombinator::union(
1052-
TypeCombinator::removeNull($leftType),
1053-
$rightType
1054-
);
1055-
}
1056-
1057-
return TypeCombinator::removeNull($leftType);
1058-
}
1059-
10601010
if ($node instanceof Expr\Clone_) {
10611011
return $this->getType($node->expr);
10621012
}
@@ -1663,6 +1613,56 @@ private function resolveType(Expr $node): Type
16631613
return $this->moreSpecificTypes[$exprString]->getType();
16641614
}
16651615

1616+
if ($node instanceof Expr\AssignOp\Coalesce) {
1617+
return $this->getType(new BinaryOp\Coalesce($node->var, $node->expr, $node->getAttributes()));
1618+
}
1619+
1620+
if ($node instanceof Expr\BinaryOp\Coalesce) {
1621+
if ($node->left instanceof Expr\ArrayDimFetch && $node->left->dim !== null) {
1622+
$dimType = $this->getType($node->left->dim);
1623+
$varType = $this->getType($node->left->var);
1624+
$hasOffset = $varType->hasOffsetValueType($dimType);
1625+
$leftType = $this->getType($node->left);
1626+
$rightType = $this->getType($node->right);
1627+
if ($hasOffset->no()) {
1628+
return $rightType;
1629+
} elseif ($hasOffset->yes()) {
1630+
$offsetValueType = $varType->getOffsetValueType($dimType);
1631+
if ($offsetValueType->isSuperTypeOf(new NullType())->no()) {
1632+
return TypeCombinator::removeNull($leftType);
1633+
}
1634+
}
1635+
1636+
return TypeCombinator::union(
1637+
TypeCombinator::removeNull($leftType),
1638+
$rightType
1639+
);
1640+
}
1641+
1642+
$leftType = $this->getType($node->left);
1643+
$rightType = $this->getType($node->right);
1644+
if ($leftType instanceof ErrorType || $leftType instanceof NullType) {
1645+
return $rightType;
1646+
}
1647+
1648+
if (
1649+
TypeCombinator::containsNull($leftType)
1650+
|| $node->left instanceof PropertyFetch
1651+
|| (
1652+
$node->left instanceof Variable
1653+
&& is_string($node->left->name)
1654+
&& !$this->hasVariableType($node->left->name)->yes()
1655+
)
1656+
) {
1657+
return TypeCombinator::union(
1658+
TypeCombinator::removeNull($leftType),
1659+
$rightType
1660+
);
1661+
}
1662+
1663+
return TypeCombinator::removeNull($leftType);
1664+
}
1665+
16661666
if ($node instanceof ConstFetch) {
16671667
$constName = (string) $node->name;
16681668
$loweredConstName = strtolower($constName);

tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php

+11
Original file line numberDiff line numberDiff line change
@@ -473,4 +473,15 @@ public function testBug3371(): void
473473
$this->analyse([__DIR__ . '/data/bug-3371.php'], []);
474474
}
475475

476+
public function testBug4527(): void
477+
{
478+
if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) {
479+
$this->markTestSkipped('Test requires PHP 8.0.');
480+
}
481+
482+
$this->checkThisOnly = false;
483+
$this->checkUnionTypes = true;
484+
$this->analyse([__DIR__ . '/data/bug-4527.php'], []);
485+
}
486+
476487
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug4527;
4+
5+
class Foo
6+
{
7+
8+
/**
9+
* @param Bar[] $bars
10+
*/
11+
public function foo(array $bars): void
12+
{
13+
($bars['randomKey'] ?? null)?->bar;
14+
}
15+
}
16+
17+
class Bar {
18+
public $bar;
19+
}

0 commit comments

Comments
 (0)