From eb1f950aace40dc4fec48b962f9ef018baa39872 Mon Sep 17 00:00:00 2001 From: Punyapal Shah Date: Thu, 29 Oct 2026 19:55:27 +0530 Subject: [PATCH] Test generated subclass scopes in closure-this extensions --- ...CallMethodsRuleClosureThisSubclassTest.php | 46 +++++++++++++++++ .../Methods/ClosureThisSubclassExtension.php | 45 ++++++++++++++++ .../Rules/Methods/closure-this-subclass.neon | 5 ++ .../Methods/data/closure-this-subclass.php | 51 +++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/CallMethodsRuleClosureThisSubclassTest.php create mode 100644 tests/PHPStan/Rules/Methods/ClosureThisSubclassExtension.php create mode 100644 tests/PHPStan/Rules/Methods/closure-this-subclass.neon create mode 100644 tests/PHPStan/Rules/Methods/data/closure-this-subclass.php diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleClosureThisSubclassTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleClosureThisSubclassTest.php new file mode 100644 index 00000000000..48d1392443a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleClosureThisSubclassTest.php @@ -0,0 +1,46 @@ + + */ +class CallMethodsRuleClosureThisSubclassTest extends RuleTestCase +{ + + public static function getAdditionalConfigFiles(): array + { + return [__DIR__ . '/closure-this-subclass.neon']; + } + + protected function getRule(): Rule + { + return self::getContainer()->getByType(CallMethodsRule::class); + } + + public function testClosureThisSubclassScope(): void + { + $this->analyse([__DIR__ . '/data/closure-this-subclass.php'], [ + [ + 'Call to private method privateHelper() of class ClosureThisSubclass\\BaseTestCase.', + 31, + ], + [ + 'Call to private method privateHelper() of class ClosureThisSubclass\\BaseTestCase.', + 37, + ], + [ + 'Call to protected method protectedHelper() of class ClosureThisSubclass\\BaseTestCase.', + 49, + ], + [ + 'Call to private method privateHelper() of class ClosureThisSubclass\\BaseTestCase.', + 50, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/ClosureThisSubclassExtension.php b/tests/PHPStan/Rules/Methods/ClosureThisSubclassExtension.php new file mode 100644 index 00000000000..46c8f41ac2f --- /dev/null +++ b/tests/PHPStan/Rules/Methods/ClosureThisSubclassExtension.php @@ -0,0 +1,45 @@ +getName(), ['ClosureThisSubclass\\runInSubclass', 'ClosureThisSubclass\\runInBase'], true) + && $parameter->getName() === 'callback'; + } + + public function getClosureThisTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope&DependencyTracker $scope): ?Type + { + if ($functionReflection->getName() === 'ClosureThisSubclass\\runInBase') { + return new ObjectType('ClosureThisSubclass\\BaseTestCase'); + } + + $classNode = new Class_(null, [ + 'extends' => new FullyQualified('ClosureThisSubclass\\BaseTestCase'), + ], $functionCall->getAttributes()); + $classReflection = $this->reflectionProvider->getAnonymousClassReflection($classNode, $scope); + + return new ObjectType($classReflection->getName(), classReflection: $classReflection); + } + +} diff --git a/tests/PHPStan/Rules/Methods/closure-this-subclass.neon b/tests/PHPStan/Rules/Methods/closure-this-subclass.neon new file mode 100644 index 00000000000..b3d06f39a24 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/closure-this-subclass.neon @@ -0,0 +1,5 @@ +services: + - + class: PHPStan\Rules\Methods\ClosureThisSubclassExtension + tags: + - phpstan.functionParameterClosureThisExtension diff --git a/tests/PHPStan/Rules/Methods/data/closure-this-subclass.php b/tests/PHPStan/Rules/Methods/data/closure-this-subclass.php new file mode 100644 index 00000000000..40b1467d78b --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/closure-this-subclass.php @@ -0,0 +1,51 @@ +publicHelper(); + $this->protectedHelper(); + $this->privateHelper(); +}); + +runInSubclass(fn (): array => [ + $this->publicHelper(), + $this->protectedHelper(), + $this->privateHelper(), +]); + +runInBase(function (): void { + $this->publicHelper(); + $this->protectedHelper(); + $this->privateHelper(); +}); + +function outsideClosure(BaseTestCase $testCase): void +{ + $testCase->publicHelper(); + $testCase->protectedHelper(); + $testCase->privateHelper(); +}