Skip to content

Commit

Permalink
Fix late static binding calls
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek authored and ondrejmirtes committed Sep 12, 2024
1 parent ae23a92 commit 31f6737
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 0 deletions.
24 changes: 24 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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();
}
Expand Down
142 changes: 142 additions & 0 deletions tests/PHPStan/Analyser/nsrt/static-late-binding.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

namespace StaticLateBinding;

use function PHPStan\Testing\assertType;

class A
{
public static function retStaticConst(): int
{
return 1;
}

/**
* @return static
*/
public static function retStatic()
{
return new static();
}

/**
* @return static
*/
public function retNonStatic()
{
return new static();
}

/**
* @param-out int $out
*/
public static function outStaticConst(&$out): int
{
$out = 1;
}
}

class B extends A
{
/**
* @return 2
*/
public static function retStaticConst(): int
{
return 2;
}

/**
* @param-out 2 $out
*/
public static function outStaticConst(&$out): int
{
$out = 2;
}

public function foo(): void
{
$clUnioned = mt_rand() === 0
? A::class
: X::class;

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('*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;
}
}

0 comments on commit 31f6737

Please sign in to comment.