Skip to content
2 changes: 1 addition & 1 deletion src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -1921,7 +1921,7 @@ public function resolveTypeByName(Name $name): TypeWithClassName
return new ObjectType($originalClass);
}

private function resolveTypeByNameWithLateStaticBinding(Name $class, Node\Identifier $name): TypeWithClassName
public function resolveTypeByNameWithLateStaticBinding(Name $class, Node\Identifier $name): TypeWithClassName
{
$classType = $this->resolveTypeByName($class);

Expand Down
13 changes: 13 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -3296,7 +3296,9 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto
}

$parametersAcceptor = null;
$classType = null;
$methodReflection = null;
$methodName = null;
if ($expr->name instanceof Expr) {
$result = $this->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep());
$hasYield = $hasYield || $result->hasYield();
Expand All @@ -3306,6 +3308,15 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto
} elseif ($expr->class instanceof Name) {
$classType = $scope->resolveTypeByName($expr->class);
$methodName = $expr->name->name;
} elseif ($expr->class instanceof Expr) {
$objectClasses = TypeCombinator::removeNull($scope->getType($expr->class))->getObjectTypeOrClassStringObjectType()->getObjectClassNames();
if (count($objectClasses) === 1) {
$classType = $scope->resolveTypeByNameWithLateStaticBinding(new Name($objectClasses[0]), $expr->name);
$methodName = $expr->name->name;
}
}

if ($classType !== null && $methodName !== null) {
if ($classType->hasMethod($methodName)->yes()) {
$methodReflection = $classType->getMethod($methodName, $scope);
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
Expand Down Expand Up @@ -3410,6 +3421,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto

if (
$methodReflection !== null
&& $expr->class instanceof Name
&& (
(
!$methodReflection->isStatic()
Expand All @@ -3425,6 +3437,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto

if (
$methodReflection !== null
&& $expr->class instanceof Name
&& !$methodReflection->isStatic()
&& $methodReflection->getName() === '__construct'
&& $scopeFunction instanceof MethodReflection
Expand Down
52 changes: 52 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-5020.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Bug5020;

use function PHPStan\Testing\assertType;

interface ITransformer
{
public static function Transform(string $theInput, bool &$theErrorEncountered): string;
}

class Transformer implements ITransformer
{
public static function Transform(string $theInput, bool &$theErrorEncountered): string
{
if ($theInput === 'invalid') {
$theErrorEncountered = true;
return '';
}
return strtoupper(trim($theInput));
}
}

function testConstantStringStaticCall(): void
{
$transformer = 'Bug5020\Transformer';
$input = ' asdasda asdasd ';
$error = false;
$output = $transformer::Transform($input, $error);
assertType('string', $output);
assertType('bool', $error);
}

function testDirectStaticCall(): void
{
$input = ' asdasda asdasd ';
$error = false;
$output = Transformer::Transform($input, $error);
assertType('string', $output);
assertType('bool', $error);
}

function testClassStringStaticCall(): void
{
/** @var class-string<ITransformer> $transformer */
$transformer = 'Bug5020\Transformer';
$input = ' asdasda asdasd ';
$error = false;
$output = $transformer::Transform($input, $error);
assertType('string', $output);
assertType('bool', $error);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Foo
public function doFoo(\DateTimeImmutable $dt)
{
DateTimeImmutable::createFromFormat('Y-m-d', '2019-07-24');
$dt::createFromFormat('Y-m-d', '2019-07-24');
$dt::createFromFormat('Y-m-d', '2019-07-24'); // method might be impure in DateTimeImmutable subclass
}

}
Expand Down
Loading