diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 4f3e7f9c433..9ab77180d62 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -2198,6 +2198,7 @@ private function createForExpr( $expr = NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($expr); } + $coalescePropertyFetchTypes = null; if ( !$context->null() && $expr instanceof Expr\BinaryOp\Coalesce @@ -2207,6 +2208,17 @@ private function createForExpr( || ($context->false() && $type->isSuperTypeOf($scope->getType($expr->right))->yes()) ) { $expr = $expr->left; + + if ( + $context->true() + && $expr instanceof PropertyFetch + && $expr->name instanceof Node\Identifier + ) { + $coalescePropertyFetchTypes = $this->create($expr->var, new IntersectionType([ + new ObjectWithoutClassType(), + new HasPropertyType($expr->name->toString()), + ]), TypeSpecifierContext::createTruthy(), $scope)->setRootExpr($originalExpr); + } } } @@ -2321,6 +2333,9 @@ private function createForExpr( } $types = new SpecifiedTypes($sureTypes, $sureNotTypes); + if ($coalescePropertyFetchTypes !== null) { + $types = $types->unionWith($coalescePropertyFetchTypes); + } if (isset($containsNull) && !$containsNull) { return $this->createNullsafeTypes($originalExpr, $scope, $context, $type)->unionWith($types); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-11610.php b/tests/PHPStan/Analyser/nsrt/bug-11610.php new file mode 100644 index 00000000000..5184e395e17 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11610.php @@ -0,0 +1,25 @@ +} $responseJson */ + $responseJson = json_decode($value); + if (!is_array($responseJson->containers ?? null)) { + throw new \Exception(); + } + assertType('object{containers: array}', $responseJson); +} + +function sayHello2(string $value): void +{ + /** @var object{containers?: array} $responseJson */ + $responseJson = json_decode($value); + if (!isset($responseJson->containers) || !is_array($responseJson->containers)) { + throw new \Exception(); + } + assertType('object{containers: array}', $responseJson); +}