diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 03c2109d86..7ac09d1e92 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1202,7 +1202,7 @@ private function resolveType(string $exprString, Expr $node): Type return new ObjectType(Closure::class); } - $classType = $this->resolveTypeByName($node->class); + $classType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); $methodName = $node->name->toString(); if (!$classType->hasMethod($methodName)->yes()) { return new ObjectType(Closure::class); @@ -2082,19 +2082,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu if ($this->nativeTypesPromoted) { $typeCallback = function () use ($node): Type { if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByName($node->class); - if ( - $staticMethodCalledOnType instanceof StaticType - && !in_array($node->class->toLowerString(), ['self', 'static', 'parent'], true) - ) { - $methodReflectionCandidate = $this->getMethodReflection( - $staticMethodCalledOnType, - $node->name->name, - ); - if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) { - $staticMethodCalledOnType = $staticMethodCalledOnType->getStaticObjectType(); - } - } + $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); } else { $staticMethodCalledOnType = $this->getNativeType($node->class); } @@ -2119,19 +2107,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu $typeCallback = function () use ($node): Type { if ($node->class instanceof Name) { - $staticMethodCalledOnType = $this->resolveTypeByName($node->class); - if ( - $staticMethodCalledOnType instanceof StaticType - && !in_array($node->class->toLowerString(), ['self', 'static', 'parent'], true) - ) { - $methodReflectionCandidate = $this->getMethodReflection( - $staticMethodCalledOnType, - $node->name->name, - ); - if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) { - $staticMethodCalledOnType = $staticMethodCalledOnType->getStaticObjectType(); - } - } + $staticMethodCalledOnType = $this->resolveTypeByNameWithLateStaticBinding($node->class, $node->name); } else { $staticMethodCalledOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); } @@ -2780,6 +2756,26 @@ public function resolveTypeByName(Name $name): TypeWithClassName return new ObjectType($originalClass); } + private function resolveTypeByNameWithLateStaticBinding(Name $class, Node\Identifier $name): TypeWithClassName + { + $classType = $this->resolveTypeByName($class); + + if ( + $classType instanceof StaticType + && !in_array($class->toLowerString(), ['self', 'static', 'parent'], true) + ) { + $methodReflectionCandidate = $this->getMethodReflection( + $classType, + $name->name, + ); + if ($methodReflectionCandidate !== null && $methodReflectionCandidate->isStatic()) { + $classType = $classType->getStaticObjectType(); + } + } + + return $classType; + } + /** * @api * @param mixed $value diff --git a/tests/PHPStan/Analyser/nsrt/static-late-binding.php b/tests/PHPStan/Analyser/nsrt/static-late-binding.php index 5421bbc1de..53313ceddd 100644 --- a/tests/PHPStan/Analyser/nsrt/static-late-binding.php +++ b/tests/PHPStan/Analyser/nsrt/static-late-binding.php @@ -67,7 +67,7 @@ public function foo(): void assertType('int', parent::retStaticConst()); assertType('2', $this->retStaticConst()); assertType('bool', X::retStaticConst()); - assertType('*ERROR*', $clUnioned->retStaticConst()); // should be bool|int + assertType('*ERROR*', $clUnioned->retStaticConst()); // should be bool|int https://github.com/phpstan/phpstan/issues/11687 assertType('int', A::retStaticConst(...)()); assertType('2', B::retStaticConst(...)()); @@ -76,7 +76,7 @@ public function foo(): void assertType('int', parent::retStaticConst(...)()); assertType('2', $this->retStaticConst(...)()); assertType('bool', X::retStaticConst(...)()); - assertType('mixed', $clUnioned->retStaticConst(...)()); // should be bool|int + assertType('mixed', $clUnioned->retStaticConst(...)()); // should be bool|int https://github.com/phpstan/phpstan/issues/11687 assertType('StaticLateBinding\A', A::retStatic()); assertType('StaticLateBinding\B', B::retStatic()); @@ -85,7 +85,16 @@ public function foo(): void assertType('static(StaticLateBinding\B)', parent::retStatic()); assertType('static(StaticLateBinding\B)', $this->retStatic()); assertType('bool', X::retStatic()); - assertType('bool|StaticLateBinding\A|StaticLateBinding\X', $clUnioned::retStatic()); // should be bool|StaticLateBinding\A + assertType('bool|StaticLateBinding\A|StaticLateBinding\X', $clUnioned::retStatic()); // should be bool|StaticLateBinding\A https://github.com/phpstan/phpstan/issues/11687 + + assertType('StaticLateBinding\A', A::retStatic(...)()); + assertType('StaticLateBinding\B', B::retStatic(...)()); + assertType('static(StaticLateBinding\B)', self::retStatic(...)()); + assertType('static(StaticLateBinding\B)', static::retStatic(...)()); + assertType('static(StaticLateBinding\B)', parent::retStatic(...)()); + assertType('static(StaticLateBinding\B)', $this->retStatic(...)()); + assertType('bool', X::retStatic(...)()); + assertType('mixed', $clUnioned::retStatic(...)()); // should be bool|StaticLateBinding\A https://github.com/phpstan/phpstan/issues/11687 assertType('static(StaticLateBinding\B)', A::retNonStatic()); assertType('static(StaticLateBinding\B)', B::retNonStatic()); @@ -94,7 +103,7 @@ public function foo(): void assertType('static(StaticLateBinding\B)', parent::retNonStatic()); assertType('static(StaticLateBinding\B)', $this->retNonStatic()); assertType('bool', X::retNonStatic()); - assertType('*ERROR*', $clUnioned->retNonStatic()); // should be bool|static(StaticLateBinding\B) + assertType('*ERROR*', $clUnioned->retNonStatic()); // should be bool|static(StaticLateBinding\B) https://github.com/phpstan/phpstan/issues/11687 A::outStaticConst($v); assertType('int', $v);