diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c1e8b62bd6..4a7a75af33 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1536,10 +1536,8 @@ private function lookForExpressionCallback(MutatingScope $scope, Expr $expr, Clo $scope = $this->lookForExpressionCallback($scope, $expr->var, $callback); } elseif ($expr instanceof PropertyFetch || $expr instanceof Expr\NullsafePropertyFetch) { $scope = $this->lookForExpressionCallback($scope, $expr->var, $callback); - } elseif ($expr instanceof StaticPropertyFetch) { - if ($expr->class instanceof Expr) { - $scope = $this->lookForExpressionCallback($scope, $expr->class, $callback); - } + } elseif ($expr instanceof StaticPropertyFetch && $expr->class instanceof Expr) { + $scope = $this->lookForExpressionCallback($scope, $expr->class, $callback); } elseif ($expr instanceof Array_ || $expr instanceof List_) { foreach ($expr->items as $item) { if ($item === null) { @@ -1576,29 +1574,16 @@ private function ensureShallowNonNullability(MutatingScope $scope, Expr $exprToS return new EnsuredNonNullabilityResult($scope, []); } - private function ensureNonNullability(MutatingScope $scope, Expr $expr, bool $findMethods): EnsuredNonNullabilityResult + private function ensureNonNullability(MutatingScope $scope, Expr $expr): EnsuredNonNullabilityResult { - $exprToSpecify = $expr; $specifiedExpressions = []; - while (true) { - $result = $this->ensureShallowNonNullability($scope, $exprToSpecify); - $scope = $result->getScope(); + $scope = $this->lookForExpressionCallback($scope, $expr, function ($scope, $expr) use (&$specifiedExpressions) { + $result = $this->ensureShallowNonNullability($scope, $expr); foreach ($result->getSpecifiedExpressions() as $specifiedExpression) { $specifiedExpressions[] = $specifiedExpression; } - - if ($exprToSpecify instanceof PropertyFetch) { - $exprToSpecify = $exprToSpecify->var; - } elseif ($exprToSpecify instanceof StaticPropertyFetch && $exprToSpecify->class instanceof Expr) { - $exprToSpecify = $exprToSpecify->class; - } elseif ($findMethods && $exprToSpecify instanceof MethodCall) { - $exprToSpecify = $exprToSpecify->var; - } elseif ($findMethods && $exprToSpecify instanceof StaticCall && $exprToSpecify->class instanceof Expr) { - $exprToSpecify = $exprToSpecify->class; - } else { - break; - } - } + return $result->getScope(); + }); return new EnsuredNonNullabilityResult($scope, $specifiedExpressions); } @@ -2304,7 +2289,7 @@ static function (?Type $offsetType, Type $valueType) use (&$arrayType): void { static fn (): MutatingScope => $rightResult->getScope()->filterByFalseyValue($expr), ); } elseif ($expr instanceof Coalesce) { - $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->left, false); + $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->left); $condScope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->left); $condResult = $this->processExprNode($expr->left, $condScope, $nodeCallback, $context->enterDeep()); $scope = $this->revertNonNullability($condResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); @@ -2378,7 +2363,7 @@ static function (?Type $offsetType, Type $valueType) use (&$arrayType): void { $throwPoints = $result->getThrowPoints(); } } elseif ($expr instanceof Expr\Empty_) { - $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr, true); + $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr); $scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->expr); $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); @@ -2391,7 +2376,7 @@ static function (?Type $offsetType, Type $valueType) use (&$arrayType): void { $throwPoints = []; $nonNullabilityResults = []; foreach ($expr->vars as $var) { - $nonNullabilityResult = $this->ensureNonNullability($scope, $var, true); + $nonNullabilityResult = $this->ensureNonNullability($scope, $var); $scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $var); $result = $this->processExprNode($var, $scope, $nodeCallback, $context->enterDeep()); $scope = $result->getScope(); diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 1ee7b1aec8..614b5aef6a 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -510,10 +510,6 @@ public function testAccessPropertiesOnDynamicProperties(): void 'Cannot access property $selfOrNull on TestAccessProperties\RevertNonNullabilityForIsset|null.', 402, ], - [ - 'Access to an undefined property stdClass|null::$array.', - 412, - ], ], ); } @@ -840,4 +836,12 @@ public function testBug3171(): void $this->analyse([__DIR__ . '/data/bug-3171.php'], []); } + public function testBug3171OnDynamicProperties(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = true; + $this->analyse([__DIR__ . '/data/bug-3171.php'], []); + } + }