Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove inefficient caching from PhpMethodReflection #3534

Merged
merged 15 commits into from
Oct 8, 2024
13 changes: 10 additions & 3 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,16 @@ services:
tags:
- phpstan.parser.richParserNodeVisitor

-
class: PHPStan\Parser\VariadicMethodsVisitor
tags:
- phpstan.parser.richParserNodeVisitor
staabm marked this conversation as resolved.
Show resolved Hide resolved

-
class: PHPStan\Parser\VariadicFunctionsVisitor
tags:
- phpstan.parser.richParserNodeVisitor

-
class: PHPStan\Node\Printer\ExprPrinter

Expand Down Expand Up @@ -634,9 +644,6 @@ services:
tags:
- phpstan.diagnoseExtension

-
class: PHPStan\Parser\FunctionCallStatementFinder

-
class: PHPStan\Process\CpuCoreCounter

Expand Down
47 changes: 0 additions & 47 deletions src/Parser/FunctionCallStatementFinder.php

This file was deleted.

4 changes: 4 additions & 0 deletions src/Parser/SimpleParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ final class SimpleParser implements Parser
public function __construct(
private \PhpParser\Parser $parser,
private NameResolver $nameResolver,
private VariadicMethodsVisitor $variadicMethodsVisitor,
private VariadicFunctionsVisitor $variadicFunctionsVisitor,
)
{
}
Expand Down Expand Up @@ -48,6 +50,8 @@ public function parseString(string $sourceCode): array

$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor($this->nameResolver);
$nodeTraverser->addVisitor($this->variadicMethodsVisitor);
$nodeTraverser->addVisitor($this->variadicFunctionsVisitor);

/** @var array<Node\Stmt> */
return $nodeTraverser->traverse($nodes);
Expand Down
94 changes: 94 additions & 0 deletions src/Parser/VariadicFunctionsVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php declare(strict_types = 1);

namespace PHPStan\Parser;

use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Reflection\ParametersAcceptor;
use function array_filter;
use function array_key_exists;
use function in_array;

final class VariadicFunctionsVisitor extends NodeVisitorAbstract
{

private ?Node $topNode = null;

private ?string $inNamespace = null;

private ?string $inFunction = null;

/** @var array<string, bool> */
public static array $cache = [];

/** @var array<string, bool> */
private array $variadicFunctions = [];

public const ATTRIBUTE_NAME = 'variadicFunctions';

public function beforeTraverse(array $nodes): ?array
{
$this->topNode = null;
$this->variadicFunctions = [];
$this->inNamespace = null;
$this->inFunction = null;

return null;
}

public function enterNode(Node $node): ?Node
{
if ($this->topNode === null) {
$this->topNode = $node;
}

if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
$this->inNamespace = $node->name->toString();
}

if ($node instanceof Node\Stmt\Function_) {
$this->inFunction = $this->inNamespace !== null ? $this->inNamespace . '\\' . $node->name->name : $node->name->name;
}

if (
$this->inFunction !== null
&& $node instanceof Node\Expr\FuncCall
&& $node->name instanceof Name
&& in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true)
&& !array_key_exists($this->inFunction, $this->variadicFunctions)
) {
$this->variadicFunctions[$this->inFunction] = true;
}

return null;
}

public function leaveNode(Node $node): ?Node
{
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
$this->inNamespace = null;
}

if ($node instanceof Node\Stmt\Function_ && $this->inFunction !== null) {
$this->variadicFunctions[$this->inFunction] ??= false;
$this->inFunction = null;
}

return null;
}

public function afterTraverse(array $nodes): ?array
{
if ($this->topNode !== null && $this->variadicFunctions !== []) {
foreach ($this->variadicFunctions as $name => $variadic) {
self::$cache[$name] = $variadic;
}
$functions = array_filter($this->variadicFunctions, static fn (bool $variadic) => $variadic);
$this->topNode->setAttribute(self::ATTRIBUTE_NAME, $functions);
}

return null;
}

}
125 changes: 125 additions & 0 deletions src/Parser/VariadicMethodsVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php declare(strict_types = 1);

namespace PHPStan\Parser;

use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Reflection\ParametersAcceptor;
use function array_key_exists;
use function array_pop;
use function count;
use function in_array;
use function sprintf;

final class VariadicMethodsVisitor extends NodeVisitorAbstract
{

public const ATTRIBUTE_NAME = 'variadicMethods';

public const ANONYMOUS_CLASS_PREFIX = 'class@anonymous';

private ?Node $topNode = null;

private ?string $inNamespace = null;

/** @var array<string> */
private array $classStack = [];

private ?string $inMethod = null;

/** @var array<string, array<string, true>> */
public static array $cache = [];

/** @var array<string, array<string, true>> */
private array $variadicMethods = [];

public function beforeTraverse(array $nodes): ?array
{
$this->topNode = null;
$this->variadicMethods = [];
$this->inNamespace = null;
$this->classStack = [];
$this->inMethod = null;

return null;
}

public function enterNode(Node $node): ?Node
{
if ($this->topNode === null) {
$this->topNode = $node;
}

if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
$this->inNamespace = $node->name->toString();
}

if ($node instanceof Node\Stmt\ClassLike) {
if (!$node->name instanceof Node\Identifier) {
$className = sprintf('%s:%s:%s', self::ANONYMOUS_CLASS_PREFIX, $node->getStartLine(), $node->getEndLine());
$this->classStack[] = $className;
} else {
$className = $node->name->name;
$this->classStack[] = $this->inNamespace !== null ? $this->inNamespace . '\\' . $className : $className;
}
}

if ($node instanceof ClassMethod) {
$this->inMethod = $node->name->name;
}

if (
$this->inMethod !== null
&& $node instanceof Node\Expr\FuncCall
&& $node->name instanceof Name
&& in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true)
) {
$lastClass = $this->classStack[count($this->classStack) - 1] ?? null;
if ($lastClass !== null) {
if (
!array_key_exists($lastClass, $this->variadicMethods)
|| !array_key_exists($this->inMethod, $this->variadicMethods[$lastClass])
) {
$this->variadicMethods[$lastClass][$this->inMethod] = true;
}
}

}

return null;
}

public function leaveNode(Node $node): ?Node
{
if ($node instanceof ClassMethod) {
$this->inMethod = null;
}

if ($node instanceof Node\Stmt\ClassLike) {
array_pop($this->classStack);
}

if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
$this->inNamespace = null;
}

return null;
}

public function afterTraverse(array $nodes): ?array
{
if ($this->topNode !== null && $this->variadicMethods !== []) {
foreach ($this->variadicMethods as $class => $methods) {
foreach ($methods as $name => $variadic) {
self::$cache[$class][$name] = $variadic;
}
}
$this->topNode->setAttribute(self::ATTRIBUTE_NAME, $this->variadicMethods);
}

return null;
}

}
Loading
Loading