Skip to content

Commit 0e03ad5

Browse files
committed
test nested classes
1 parent b8ee0bc commit 0e03ad5

File tree

4 files changed

+117
-16
lines changed

4 files changed

+117
-16
lines changed

src/Parser/VariadicMethodsVisitor.php

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PhpParser\Node\Name;
77
use PhpParser\Node\Stmt\ClassMethod;
88
use PhpParser\NodeVisitorAbstract;
9+
use PHPStan\Node\AnonymousClassNode;
910
use PHPStan\Reflection\ParametersAcceptor;
1011
use PHPStan\TrinaryLogic;
1112
use function array_key_exists;
@@ -20,6 +21,11 @@ final class VariadicMethodsVisitor extends NodeVisitorAbstract
2021

2122
private ?string $inClassLike = null;
2223

24+
/**
25+
* @var array<string>
26+
*/
27+
private array $classStack = [];
28+
2329
private ?string $inMethod = null;
2430

2531
/** @var array<string, array<string, TrinaryLogic>> */
@@ -32,6 +38,7 @@ public function beforeTraverse(array $nodes): ?array
3238
$this->topNode = null;
3339
$this->variadicMethods = [];
3440
$this->inNamespace = null;
41+
$this->classStack = [];
3542
$this->inClassLike = null;
3643
$this->inMethod = null;
3744

@@ -48,21 +55,28 @@ public function enterNode(Node $node): ?Node
4855
$this->inNamespace = $node->name->toString();
4956
}
5057

51-
if ($node instanceof Node\Stmt\ClassLike && $node->name instanceof Node\Identifier) {
52-
$this->inClassLike = $this->inNamespace !== null ? $this->inNamespace . '\\' . $node->name->name : $node->name->name;
58+
if (
59+
$node instanceof Node\Stmt\Class_
60+
|| $node instanceof Node\Stmt\ClassLike
61+
) {
62+
if (!$node->name instanceof Node\Identifier) {
63+
$className = 'class@anonymous';
64+
} else {
65+
$className = $node->name->name;
66+
}
67+
68+
$this->classStack[] = $className;
69+
$this->inClassLike = $this->inNamespace !== null ? $this->inNamespace . '\\' . implode('\\', $this->classStack) : implode('\\', $this->classStack);
5370
$this->variadicMethods[$this->inClassLike] ??= [];
5471
}
5572

5673
if ($this->inClassLike !== null && $node instanceof ClassMethod) {
57-
if ($node->getStmts() === null) {
58-
return null; // interface
59-
}
60-
6174
$this->inMethod = $node->name->name;
6275
}
6376

6477
if (
65-
$this->inMethod !== null
78+
$this->inClassLike !== null
79+
&& $this->inMethod !== null
6680
&& $node instanceof Node\Expr\FuncCall
6781
&& $node->name instanceof Name
6882
&& in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true)
@@ -80,17 +94,29 @@ public function enterNode(Node $node): ?Node
8094

8195
public function leaveNode(Node $node): ?Node
8296
{
83-
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
84-
$this->inNamespace = null;
97+
if (
98+
$node instanceof ClassMethod
99+
&& $this->inClassLike !== null
100+
) {
101+
$this->variadicMethods[$this->inClassLike][$node->name->name] ??= TrinaryLogic::createNo();
102+
$this->inMethod = null;
85103
}
86104

87-
if ($node instanceof Node\Stmt\ClassLike && $node->name instanceof Node\Identifier) {
88-
$this->inClassLike = null;
105+
if (
106+
$node instanceof Node\Stmt\Class_
107+
|| $node instanceof Node\Stmt\ClassLike
108+
) {
109+
array_pop($this->classStack);
110+
111+
if ($this->classStack !== []) {
112+
$this->inClassLike = $this->inNamespace !== null ? $this->inNamespace . '\\' . implode('\\', $this->classStack) : implode('\\', $this->classStack);
113+
} else {
114+
$this->inClassLike = null;
115+
}
89116
}
90117

91-
if ($node instanceof ClassMethod && $this->inClassLike !== null && $this->inMethod !== null) {
92-
$this->variadicMethods[$this->inClassLike][$this->inMethod] ??= TrinaryLogic::createNo();
93-
$this->inMethod = null;
118+
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
119+
$this->inNamespace = null;
94120
}
95121

96122
return null;

tests/PHPStan/Parser/ParserTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,37 @@ public function dataVariadicCallLikes(): iterable
3131
'VariadicMethod\X' => [
3232
'non_variadic_fn1' => TrinaryLogic::createNo(),
3333
'variadic_fn1' => TrinaryLogic::createNo(), // variadicness later on detected via reflection
34+
'implicit_variadic_fn1' => TrinaryLogic::createYes(),
35+
],
36+
'VariadicMethod\Z' => [
37+
'non_variadic_fnZ' => TrinaryLogic::createNo(),
38+
'variadic_fnZ' => TrinaryLogic::createNo(), // variadicness later on detected via reflection
39+
'implicit_variadic_fnZ' => TrinaryLogic::createYes(),
40+
],
41+
'VariadicMethod\Z\class@anonymous' => [
42+
'non_variadic_fn_subZ' => TrinaryLogic::createNo(),
43+
'variadic_fn_subZ' => TrinaryLogic::createNo(), // variadicness later on detected via reflection
44+
'implicit_variadic_subZ' => TrinaryLogic::createYes(),
45+
],
46+
'VariadicMethod\class@anonymous' => [
47+
'non_variadic_fn' => TrinaryLogic::createNo(),
48+
'variadic_fn' => TrinaryLogic::createNo(), // variadicness later on detected via reflection
49+
'implicit_variadic_fn' => TrinaryLogic::createYes(),
3450
],
3551
],
3652
];
53+
54+
yield [
55+
__DIR__ . '/data/variadic-methods-in-enum.php',
56+
VariadicMethodsVisitor::ATTRIBUTE_NAME,
57+
[
58+
'VariadicMethodEnum\X' => [
59+
'non_variadic_fn1' => TrinaryLogic::createNo(),
60+
'variadic_fn1' => TrinaryLogic::createNo(), // variadicness later on detected via reflection
61+
'implicit_variadic_fn1' => TrinaryLogic::createYes(),
62+
],
63+
]
64+
];
3765
}
3866

3967
/**
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php // lint >= 8.1
2+
3+
namespace VariadicMethodEnum;
4+
5+
enum X {
6+
7+
function non_variadic_fn1($v) {
8+
}
9+
10+
function variadic_fn1(...$v) {
11+
}
12+
13+
function implicit_variadic_fn1() {
14+
$args = func_get_args();
15+
}
16+
}

tests/PHPStan/Parser/data/variadic-methods.php

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,44 @@ function non_variadic_fn1($v) {
99

1010
function variadic_fn1(...$v) {
1111
}
12+
13+
function implicit_variadic_fn1() {
14+
$args = func_get_args();
15+
}
16+
}
17+
18+
class Z {
19+
function non_variadic_fnZ($v) {
20+
return $x = new class {
21+
function non_variadic_fn_subZ($v) {
22+
}
23+
24+
function variadic_fn_subZ(...$v) {
25+
}
26+
27+
function implicit_variadic_subZ() {
28+
$args = func_get_args();
29+
}
30+
};
31+
}
32+
33+
function variadic_fnZ(...$v) {
34+
}
35+
36+
function implicit_variadic_fnZ() {
37+
$args = func_get_args();
38+
}
1239
}
1340

1441

1542
$x = new class {
16-
function non_variadic_fn1($v) {
43+
function non_variadic_fn($v) {
1744
}
1845

19-
function variadic_fn1(...$v) {
46+
function variadic_fn(...$v) {
47+
}
48+
49+
function implicit_variadic_fn() {
50+
$args = func_get_args();
2051
}
2152
};

0 commit comments

Comments
 (0)