From 5ef1b3f95d0960354742869a479965d8ee36aa5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Wed, 13 Oct 2021 16:16:08 +0200 Subject: [PATCH] Fix 4949, support closure bind with class-string --- src/Analyser/NodeScopeResolver.php | 16 ++++++++-------- .../Rules/Methods/CallMethodsRuleTest.php | 6 +++++- .../PHPStan/Rules/Methods/data/closure-bind.php | 5 +++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 08c2de70c6..d04fe50701 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -118,6 +118,7 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\FileTypeMapper; +use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\IntegerType; @@ -132,6 +133,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; +use PHPStan\Type\TypeWithClassName; use PHPStan\Type\UnionType; use PHPStan\Type\VoidType; use Throwable; @@ -2065,17 +2067,15 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression if (count($directClassNames) === 1) { $scopeClass = $directClassNames[0]; $thisType = new ObjectType($scopeClass); - } elseif ( - $argValue instanceof Expr\ClassConstFetch - && $argValue->name instanceof Node\Identifier - && strtolower($argValue->name->name) === 'class' - && $argValue->class instanceof Name - ) { - $scopeClass = $scope->resolveName($argValue->class); - $thisType = new ObjectType($scopeClass); } elseif ($argValueType instanceof ConstantStringType) { $scopeClass = $argValueType->getValue(); $thisType = new ObjectType($scopeClass); + } elseif ( + $argValueType instanceof GenericClassStringType + && $argValueType->getGenericType() instanceof TypeWithClassName + ) { + $scopeClass = $argValueType->getGenericType()->getClassName(); + $thisType = $argValueType->getGenericType(); } } $closureBindScope = $scope->enterClosureBind($thisType, $scopeClass); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 45a755abcc..5c661ef72b 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -873,7 +873,11 @@ public function testClosureBind(): void ], [ 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', - 39, + 38, + ], + [ + 'Call to an undefined method CallClosureBind\Foo::nonexistentMethod().', + 44, ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/closure-bind.php b/tests/PHPStan/Rules/Methods/data/closure-bind.php index e4dec44d16..f6aa9e05b7 100644 --- a/tests/PHPStan/Rules/Methods/data/closure-bind.php +++ b/tests/PHPStan/Rules/Methods/data/closure-bind.php @@ -33,6 +33,11 @@ public function fooMethod(): Foo $foo->nonexistentMethod(); }, null, new Foo()); + \Closure::bind(function (Foo $foo) { + $foo->privateMethod(); + $foo->nonexistentMethod(); + }, null, get_class(new Foo())); + \Closure::bind(function () { // $this is Foo $this->privateMethod();