Skip to content

Commit dddf984

Browse files
staabmondrejmirtes
andauthored
Remove inefficient caching from PhpMethodReflection and PhpFunctionReflection::isVariadic()
Co-authored-by: Ondrej Mirtes <[email protected]>
1 parent 9d19cd0 commit dddf984

14 files changed

+528
-202
lines changed

conf/config.neon

+10-3
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,16 @@ services:
302302
tags:
303303
- phpstan.parser.richParserNodeVisitor
304304

305+
-
306+
class: PHPStan\Parser\VariadicMethodsVisitor
307+
tags:
308+
- phpstan.parser.richParserNodeVisitor
309+
310+
-
311+
class: PHPStan\Parser\VariadicFunctionsVisitor
312+
tags:
313+
- phpstan.parser.richParserNodeVisitor
314+
305315
-
306316
class: PHPStan\Node\Printer\ExprPrinter
307317

@@ -634,9 +644,6 @@ services:
634644
tags:
635645
- phpstan.diagnoseExtension
636646

637-
-
638-
class: PHPStan\Parser\FunctionCallStatementFinder
639-
640647
-
641648
class: PHPStan\Process\CpuCoreCounter
642649

src/Parser/FunctionCallStatementFinder.php

-47
This file was deleted.

src/Parser/SimpleParser.php

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ final class SimpleParser implements Parser
1515
public function __construct(
1616
private \PhpParser\Parser $parser,
1717
private NameResolver $nameResolver,
18+
private VariadicMethodsVisitor $variadicMethodsVisitor,
19+
private VariadicFunctionsVisitor $variadicFunctionsVisitor,
1820
)
1921
{
2022
}
@@ -48,6 +50,8 @@ public function parseString(string $sourceCode): array
4850

4951
$nodeTraverser = new NodeTraverser();
5052
$nodeTraverser->addVisitor($this->nameResolver);
53+
$nodeTraverser->addVisitor($this->variadicMethodsVisitor);
54+
$nodeTraverser->addVisitor($this->variadicFunctionsVisitor);
5155

5256
/** @var array<Node\Stmt> */
5357
return $nodeTraverser->traverse($nodes);
+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Name;
7+
use PhpParser\NodeVisitorAbstract;
8+
use PHPStan\Reflection\ParametersAcceptor;
9+
use function array_filter;
10+
use function array_key_exists;
11+
use function in_array;
12+
13+
final class VariadicFunctionsVisitor extends NodeVisitorAbstract
14+
{
15+
16+
private ?Node $topNode = null;
17+
18+
private ?string $inNamespace = null;
19+
20+
private ?string $inFunction = null;
21+
22+
/** @var array<string, bool> */
23+
public static array $cache = [];
24+
25+
/** @var array<string, bool> */
26+
private array $variadicFunctions = [];
27+
28+
public const ATTRIBUTE_NAME = 'variadicFunctions';
29+
30+
public function beforeTraverse(array $nodes): ?array
31+
{
32+
$this->topNode = null;
33+
$this->variadicFunctions = [];
34+
$this->inNamespace = null;
35+
$this->inFunction = null;
36+
37+
return null;
38+
}
39+
40+
public function enterNode(Node $node): ?Node
41+
{
42+
if ($this->topNode === null) {
43+
$this->topNode = $node;
44+
}
45+
46+
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
47+
$this->inNamespace = $node->name->toString();
48+
}
49+
50+
if ($node instanceof Node\Stmt\Function_) {
51+
$this->inFunction = $this->inNamespace !== null ? $this->inNamespace . '\\' . $node->name->name : $node->name->name;
52+
}
53+
54+
if (
55+
$this->inFunction !== null
56+
&& $node instanceof Node\Expr\FuncCall
57+
&& $node->name instanceof Name
58+
&& in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true)
59+
&& !array_key_exists($this->inFunction, $this->variadicFunctions)
60+
) {
61+
$this->variadicFunctions[$this->inFunction] = true;
62+
}
63+
64+
return null;
65+
}
66+
67+
public function leaveNode(Node $node): ?Node
68+
{
69+
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
70+
$this->inNamespace = null;
71+
}
72+
73+
if ($node instanceof Node\Stmt\Function_ && $this->inFunction !== null) {
74+
$this->variadicFunctions[$this->inFunction] ??= false;
75+
$this->inFunction = null;
76+
}
77+
78+
return null;
79+
}
80+
81+
public function afterTraverse(array $nodes): ?array
82+
{
83+
if ($this->topNode !== null && $this->variadicFunctions !== []) {
84+
foreach ($this->variadicFunctions as $name => $variadic) {
85+
self::$cache[$name] = $variadic;
86+
}
87+
$functions = array_filter($this->variadicFunctions, static fn (bool $variadic) => $variadic);
88+
$this->topNode->setAttribute(self::ATTRIBUTE_NAME, $functions);
89+
}
90+
91+
return null;
92+
}
93+
94+
}

src/Parser/VariadicMethodsVisitor.php

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Name;
7+
use PhpParser\Node\Stmt\ClassMethod;
8+
use PhpParser\NodeVisitorAbstract;
9+
use PHPStan\Reflection\ParametersAcceptor;
10+
use function array_key_exists;
11+
use function array_pop;
12+
use function count;
13+
use function in_array;
14+
use function sprintf;
15+
16+
final class VariadicMethodsVisitor extends NodeVisitorAbstract
17+
{
18+
19+
public const ATTRIBUTE_NAME = 'variadicMethods';
20+
21+
public const ANONYMOUS_CLASS_PREFIX = 'class@anonymous';
22+
23+
private ?Node $topNode = null;
24+
25+
private ?string $inNamespace = null;
26+
27+
/** @var array<string> */
28+
private array $classStack = [];
29+
30+
private ?string $inMethod = null;
31+
32+
/** @var array<string, array<string, true>> */
33+
public static array $cache = [];
34+
35+
/** @var array<string, array<string, true>> */
36+
private array $variadicMethods = [];
37+
38+
public function beforeTraverse(array $nodes): ?array
39+
{
40+
$this->topNode = null;
41+
$this->variadicMethods = [];
42+
$this->inNamespace = null;
43+
$this->classStack = [];
44+
$this->inMethod = null;
45+
46+
return null;
47+
}
48+
49+
public function enterNode(Node $node): ?Node
50+
{
51+
if ($this->topNode === null) {
52+
$this->topNode = $node;
53+
}
54+
55+
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
56+
$this->inNamespace = $node->name->toString();
57+
}
58+
59+
if ($node instanceof Node\Stmt\ClassLike) {
60+
if (!$node->name instanceof Node\Identifier) {
61+
$className = sprintf('%s:%s:%s', self::ANONYMOUS_CLASS_PREFIX, $node->getStartLine(), $node->getEndLine());
62+
$this->classStack[] = $className;
63+
} else {
64+
$className = $node->name->name;
65+
$this->classStack[] = $this->inNamespace !== null ? $this->inNamespace . '\\' . $className : $className;
66+
}
67+
}
68+
69+
if ($node instanceof ClassMethod) {
70+
$this->inMethod = $node->name->name;
71+
}
72+
73+
if (
74+
$this->inMethod !== null
75+
&& $node instanceof Node\Expr\FuncCall
76+
&& $node->name instanceof Name
77+
&& in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true)
78+
) {
79+
$lastClass = $this->classStack[count($this->classStack) - 1] ?? null;
80+
if ($lastClass !== null) {
81+
if (
82+
!array_key_exists($lastClass, $this->variadicMethods)
83+
|| !array_key_exists($this->inMethod, $this->variadicMethods[$lastClass])
84+
) {
85+
$this->variadicMethods[$lastClass][$this->inMethod] = true;
86+
}
87+
}
88+
89+
}
90+
91+
return null;
92+
}
93+
94+
public function leaveNode(Node $node): ?Node
95+
{
96+
if ($node instanceof ClassMethod) {
97+
$this->inMethod = null;
98+
}
99+
100+
if ($node instanceof Node\Stmt\ClassLike) {
101+
array_pop($this->classStack);
102+
}
103+
104+
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
105+
$this->inNamespace = null;
106+
}
107+
108+
return null;
109+
}
110+
111+
public function afterTraverse(array $nodes): ?array
112+
{
113+
if ($this->topNode !== null && $this->variadicMethods !== []) {
114+
foreach ($this->variadicMethods as $class => $methods) {
115+
foreach ($methods as $name => $variadic) {
116+
self::$cache[$class][$name] = $variadic;
117+
}
118+
}
119+
$this->topNode->setAttribute(self::ATTRIBUTE_NAME, $this->variadicMethods);
120+
}
121+
122+
return null;
123+
}
124+
125+
}

0 commit comments

Comments
 (0)