diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 8bdf7894e6..69ab471021 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -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); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index bb361d5615..3c8ccdee17 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -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(); @@ -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( @@ -3410,6 +3421,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto if ( $methodReflection !== null + && $expr->class instanceof Name && ( ( !$methodReflection->isStatic() @@ -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 diff --git a/tests/PHPStan/Analyser/nsrt/bug-5020.php b/tests/PHPStan/Analyser/nsrt/bug-5020.php new file mode 100644 index 0000000000..cefd581368 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-5020.php @@ -0,0 +1,52 @@ + $transformer */ + $transformer = 'Bug5020\Transformer'; + $input = ' asdasda asdasd '; + $error = false; + $output = $transformer::Transform($input, $error); + assertType('string', $output); + assertType('bool', $error); +} diff --git a/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects.php b/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects.php index 9c4e984e42..8a21b6f47a 100644 --- a/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects.php +++ b/tests/PHPStan/Rules/Methods/data/static-method-call-statement-no-side-effects.php @@ -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 } }