Skip to content

Commit 6ebd2d7

Browse files
MrPunyapalondrejmirtes
authored andcommitted
Test generated subclass scopes in closure-this extensions
1 parent f9bc9bd commit 6ebd2d7

4 files changed

Lines changed: 147 additions & 0 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<CallMethodsRule>
10+
*/
11+
class CallMethodsRuleClosureThisSubclassTest extends RuleTestCase
12+
{
13+
14+
public static function getAdditionalConfigFiles(): array
15+
{
16+
return [__DIR__ . '/closure-this-subclass.neon'];
17+
}
18+
19+
protected function getRule(): Rule
20+
{
21+
return self::getContainer()->getByType(CallMethodsRule::class);
22+
}
23+
24+
public function testClosureThisSubclassScope(): void
25+
{
26+
$this->analyse([__DIR__ . '/data/closure-this-subclass.php'], [
27+
[
28+
'Call to private method privateHelper() of class ClosureThisSubclass\\BaseTestCase.',
29+
31,
30+
],
31+
[
32+
'Call to private method privateHelper() of class ClosureThisSubclass\\BaseTestCase.',
33+
37,
34+
],
35+
[
36+
'Call to protected method protectedHelper() of class ClosureThisSubclass\\BaseTestCase.',
37+
49,
38+
],
39+
[
40+
'Call to private method privateHelper() of class ClosureThisSubclass\\BaseTestCase.',
41+
50,
42+
],
43+
]);
44+
}
45+
46+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PhpParser\Node\Name\FullyQualified;
7+
use PhpParser\Node\Stmt\Class_;
8+
use PHPStan\Analyser\DependencyTracker;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Reflection\FunctionReflection;
11+
use PHPStan\Reflection\ParameterReflection;
12+
use PHPStan\Reflection\ReflectionProvider;
13+
use PHPStan\Type\FunctionParameterClosureThisExtension;
14+
use PHPStan\Type\ObjectType;
15+
use PHPStan\Type\Type;
16+
use function in_array;
17+
18+
class ClosureThisSubclassExtension implements FunctionParameterClosureThisExtension
19+
{
20+
21+
public function __construct(private ReflectionProvider $reflectionProvider)
22+
{
23+
}
24+
25+
public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool
26+
{
27+
return in_array($functionReflection->getName(), ['ClosureThisSubclass\\runInSubclass', 'ClosureThisSubclass\\runInBase'], true)
28+
&& $parameter->getName() === 'callback';
29+
}
30+
31+
public function getClosureThisTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope&DependencyTracker $scope): ?Type
32+
{
33+
if ($functionReflection->getName() === 'ClosureThisSubclass\\runInBase') {
34+
return new ObjectType('ClosureThisSubclass\\BaseTestCase');
35+
}
36+
37+
$classNode = new Class_(null, [
38+
'extends' => new FullyQualified('ClosureThisSubclass\\BaseTestCase'),
39+
], $functionCall->getAttributes());
40+
$classReflection = $this->reflectionProvider->getAnonymousClassReflection($classNode, $scope);
41+
42+
return new ObjectType($classReflection->getName(), classReflection: $classReflection);
43+
}
44+
45+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
services:
2+
-
3+
class: PHPStan\Rules\Methods\ClosureThisSubclassExtension
4+
tags:
5+
- phpstan.functionParameterClosureThisExtension
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace ClosureThisSubclass;
4+
5+
class BaseTestCase
6+
{
7+
8+
public function publicHelper(): string
9+
{
10+
return 'public';
11+
}
12+
13+
protected function protectedHelper(): string
14+
{
15+
return 'protected';
16+
}
17+
18+
private function privateHelper(): string
19+
{
20+
return 'private';
21+
}
22+
23+
}
24+
25+
function runInSubclass(\Closure $callback): void {}
26+
function runInBase(\Closure $callback): void {}
27+
28+
runInSubclass(function (): void {
29+
$this->publicHelper();
30+
$this->protectedHelper();
31+
$this->privateHelper();
32+
});
33+
34+
runInSubclass(fn (): array => [
35+
$this->publicHelper(),
36+
$this->protectedHelper(),
37+
$this->privateHelper(),
38+
]);
39+
40+
runInBase(function (): void {
41+
$this->publicHelper();
42+
$this->protectedHelper();
43+
$this->privateHelper();
44+
});
45+
46+
function outsideClosure(BaseTestCase $testCase): void
47+
{
48+
$testCase->publicHelper();
49+
$testCase->protectedHelper();
50+
$testCase->privateHelper();
51+
}

0 commit comments

Comments
 (0)