From d21decde66f77184096ac1962f7bc84d17740ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Wed, 21 Aug 2024 23:36:38 +0200 Subject: [PATCH] Fix late static binding calls --- src/Analyser/MutatingScope.php | 24 +++ .../Analyser/nsrt/static-late-binding.php | 142 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/static-late-binding.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index cb4dd6b858..03c2109d86 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2083,6 +2083,18 @@ 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(); + } + } } else { $staticMethodCalledOnType = $this->getNativeType($node->class); } @@ -2108,6 +2120,18 @@ 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(); + } + } } else { $staticMethodCalledOnType = TypeCombinator::removeNull($this->getType($node->class))->getObjectTypeOrClassStringObjectType(); } diff --git a/tests/PHPStan/Analyser/nsrt/static-late-binding.php b/tests/PHPStan/Analyser/nsrt/static-late-binding.php new file mode 100644 index 0000000000..5421bbc1de --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/static-late-binding.php @@ -0,0 +1,142 @@ +retStaticConst()); + assertType('bool', X::retStaticConst()); + assertType('*ERROR*', $clUnioned->retStaticConst()); // should be bool|int + + assertType('int', A::retStaticConst(...)()); + assertType('2', B::retStaticConst(...)()); + assertType('2', self::retStaticConst(...)()); + assertType('2', static::retStaticConst(...)()); + assertType('int', parent::retStaticConst(...)()); + assertType('2', $this->retStaticConst(...)()); + assertType('bool', X::retStaticConst(...)()); + assertType('mixed', $clUnioned->retStaticConst(...)()); // should be bool|int + + 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('bool|StaticLateBinding\A|StaticLateBinding\X', $clUnioned::retStatic()); // should be bool|StaticLateBinding\A + + assertType('static(StaticLateBinding\B)', A::retNonStatic()); + assertType('static(StaticLateBinding\B)', B::retNonStatic()); + assertType('static(StaticLateBinding\B)', self::retNonStatic()); + assertType('static(StaticLateBinding\B)', static::retNonStatic()); + 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) + + A::outStaticConst($v); + assertType('int', $v); + B::outStaticConst($v); + assertType('2', $v); + self::outStaticConst($v); + assertType('2', $v); + static::outStaticConst($v); + assertType('2', $v); + parent::outStaticConst($v); + assertType('int', $v); + $this->outStaticConst($v); + assertType('2', $v); + X::outStaticConst($v); + assertType('bool', $v); + $clUnioned->outStaticConst($v); + assertType('bool', $v); // should be bool|int + } +} + +class X +{ + public static function retStaticConst(): bool + { + return false; + } + + /** + * @param-out bool $out + */ + public static function outStaticConst(&$out): void + { + $out = false; + } + + public static function retStatic(): bool + { + return false; + } + + public function retNonStatic(): bool + { + return false; + } +}