diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index d5f62a9b8a..d731e40380 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5940,6 +5940,7 @@ private function processArgs( ) { $this->callNodeCallback($nodeCallback, new InvalidateExprNode($arg->value), $scope, $storage); $scope = $scope->invalidateExpression($arg->value, true); + $scope = $this->widenUniversalObjectCrateProperties($scope, $arg->value); } } elseif (!(new ResourceType())->isSuperTypeOf($argType)->no()) { $this->callNodeCallback($nodeCallback, new InvalidateExprNode($arg->value), $scope, $storage); @@ -5953,6 +5954,22 @@ private function processArgs( return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } + private function widenUniversalObjectCrateProperties(MutatingScope $scope, Expr $expr): MutatingScope + { + $argType = $scope->getType($expr); + $stdClassType = new ObjectType('stdClass'); + + if (!in_array('stdClass', $argType->getObjectClassNames(), true)) { + return $scope; + } + + if ($stdClassType->isSuperTypeOf($argType)->yes() && !$argType->equals($stdClassType)) { + return $scope->assignExpression($expr, $stdClassType, $stdClassType); + } + + return $scope; + } + /** * @param MethodReflection|FunctionReflection|null $calleeReflection */ diff --git a/tests/PHPStan/Analyser/nsrt/bug-9181.php b/tests/PHPStan/Analyser/nsrt/bug-9181.php new file mode 100644 index 0000000000..0b53a12628 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9181.php @@ -0,0 +1,27 @@ + null, + ]; + + assertType('null', $data->search); + + $this->possiblyModifyObject($data); + + assertType('mixed', $data->search); + + if (($search = $data->search) !== null) { + assertType('mixed~null', $search); + } + } +}