diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 9020da39a7..04a8c98833 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1007,56 +1007,6 @@ private function resolveType(Expr $node): Type return IntegerRangeType::fromInterval(-1, 1); } - if ($node instanceof Expr\AssignOp\Coalesce) { - return $this->getType(new BinaryOp\Coalesce($node->var, $node->expr, $node->getAttributes())); - } - - if ($node instanceof Expr\BinaryOp\Coalesce) { - if ($node->left instanceof Expr\ArrayDimFetch && $node->left->dim !== null) { - $dimType = $this->getType($node->left->dim); - $varType = $this->getType($node->left->var); - $hasOffset = $varType->hasOffsetValueType($dimType); - $leftType = $this->getType($node->left); - $rightType = $this->getType($node->right); - if ($hasOffset->no()) { - return $rightType; - } elseif ($hasOffset->yes()) { - $offsetValueType = $varType->getOffsetValueType($dimType); - if ($offsetValueType->isSuperTypeOf(new NullType())->no()) { - return TypeCombinator::removeNull($leftType); - } - } - - return TypeCombinator::union( - TypeCombinator::removeNull($leftType), - $rightType - ); - } - - $leftType = $this->getType($node->left); - $rightType = $this->getType($node->right); - if ($leftType instanceof ErrorType || $leftType instanceof NullType) { - return $rightType; - } - - if ( - TypeCombinator::containsNull($leftType) - || $node->left instanceof PropertyFetch - || ( - $node->left instanceof Variable - && is_string($node->left->name) - && !$this->hasVariableType($node->left->name)->yes() - ) - ) { - return TypeCombinator::union( - TypeCombinator::removeNull($leftType), - $rightType - ); - } - - return TypeCombinator::removeNull($leftType); - } - if ($node instanceof Expr\Clone_) { return $this->getType($node->expr); } @@ -1663,6 +1613,56 @@ private function resolveType(Expr $node): Type return $this->moreSpecificTypes[$exprString]->getType(); } + if ($node instanceof Expr\AssignOp\Coalesce) { + return $this->getType(new BinaryOp\Coalesce($node->var, $node->expr, $node->getAttributes())); + } + + if ($node instanceof Expr\BinaryOp\Coalesce) { + if ($node->left instanceof Expr\ArrayDimFetch && $node->left->dim !== null) { + $dimType = $this->getType($node->left->dim); + $varType = $this->getType($node->left->var); + $hasOffset = $varType->hasOffsetValueType($dimType); + $leftType = $this->getType($node->left); + $rightType = $this->getType($node->right); + if ($hasOffset->no()) { + return $rightType; + } elseif ($hasOffset->yes()) { + $offsetValueType = $varType->getOffsetValueType($dimType); + if ($offsetValueType->isSuperTypeOf(new NullType())->no()) { + return TypeCombinator::removeNull($leftType); + } + } + + return TypeCombinator::union( + TypeCombinator::removeNull($leftType), + $rightType + ); + } + + $leftType = $this->getType($node->left); + $rightType = $this->getType($node->right); + if ($leftType instanceof ErrorType || $leftType instanceof NullType) { + return $rightType; + } + + if ( + TypeCombinator::containsNull($leftType) + || $node->left instanceof PropertyFetch + || ( + $node->left instanceof Variable + && is_string($node->left->name) + && !$this->hasVariableType($node->left->name)->yes() + ) + ) { + return TypeCombinator::union( + TypeCombinator::removeNull($leftType), + $rightType + ); + } + + return TypeCombinator::removeNull($leftType); + } + if ($node instanceof ConstFetch) { $constName = (string) $node->name; $loweredConstName = strtolower($constName); diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 387825100a..7f423ce788 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -473,4 +473,15 @@ public function testBug3371(): void $this->analyse([__DIR__ . '/data/bug-3371.php'], []); } + public function testBug4527(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-4527.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-4527.php b/tests/PHPStan/Rules/Properties/data/bug-4527.php new file mode 100644 index 0000000000..bb2d57a792 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-4527.php @@ -0,0 +1,19 @@ += 8.0 + +namespace Bug4527; + +class Foo +{ + + /** + * @param Bar[] $bars + */ + public function foo(array $bars): void + { + ($bars['randomKey'] ?? null)?->bar; + } +} + +class Bar { + public $bar; +}