Skip to content

Commit

Permalink
My vision
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Oct 8, 2024
1 parent 676d440 commit d4d5c1a
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 93 deletions.
28 changes: 7 additions & 21 deletions src/Parser/VariadicFunctionsVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use PhpParser\Node\Name;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\TrinaryLogic;
use function array_filter;
use function array_key_exists;
use function in_array;

Expand All @@ -19,7 +19,7 @@ final class VariadicFunctionsVisitor extends NodeVisitorAbstract

private ?string $inFunction = null;

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

public const ATTRIBUTE_NAME = 'variadicFunctions';
Expand All @@ -46,31 +46,16 @@ public function enterNode(Node $node): ?Node

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

foreach ($node->params as $parameter) {
if (!$parameter->variadic) {
continue;
}

if (!array_key_exists($this->inFunction, $this->variadicFunctions)) {
$this->variadicFunctions[$this->inFunction] = TrinaryLogic::createYes();
} else {
$this->variadicFunctions[$this->inFunction]->and(TrinaryLogic::createYes());
}
}
}

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)
) {
if (!array_key_exists($this->inFunction, $this->variadicFunctions)) {
$this->variadicFunctions[$this->inFunction] = TrinaryLogic::createYes();
} else {
$this->variadicFunctions[$this->inFunction]->and(TrinaryLogic::createYes());
}
$this->variadicFunctions[$this->inFunction] = true;
}

return null;
Expand All @@ -83,7 +68,7 @@ public function leaveNode(Node $node): ?Node
}

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

Expand All @@ -93,7 +78,8 @@ public function leaveNode(Node $node): ?Node
public function afterTraverse(array $nodes): ?array
{
if ($this->topNode !== null && $this->variadicFunctions !== []) {
$this->topNode->setAttribute(self::ATTRIBUTE_NAME, $this->variadicFunctions);
$functions = array_filter($this->variadicFunctions, static fn (bool $variadic) => $variadic);
$this->topNode->setAttribute(self::ATTRIBUTE_NAME, $functions);
}

return null;
Expand Down
57 changes: 17 additions & 40 deletions src/Parser/VariadicMethodsVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,37 @@
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\TrinaryLogic;
use function array_key_exists;
use function array_pop;
use function count;
use function implode;
use function in_array;
use function sprintf;
use function str_contains;

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;

private ?string $inClassLike = null;

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

private ?string $inMethod = null;

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

public const ATTRIBUTE_NAME = 'variadicMethods';

private const ANONYMOUS_CLASS_PREFIX = 'class@anonymous';

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

return null;
Expand All @@ -63,31 +57,30 @@ public function enterNode(Node $node): ?Node
if (!$node->name instanceof Node\Identifier) {
$className = sprintf('%s:%s:%s', self::ANONYMOUS_CLASS_PREFIX, $node->getStartLine(), $node->getEndLine());
$this->classStack[] = $className;
$this->inClassLike = $className; // anonymous classes are in global namespace
} else {
$className = $node->name->name;
$this->classStack[] = $className;
$this->inClassLike = $this->inNamespace !== null ? $this->inNamespace . '\\' . implode('\\', $this->classStack) : implode('\\', $this->classStack);
$this->classStack[] = $this->inNamespace !== null ? $this->inNamespace . '\\' . $className : $className;
}

$this->variadicMethods[$this->inClassLike] ??= [];
}

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

if (
$this->inClassLike !== null
&& $this->inMethod !== null
$this->inMethod !== null
&& $node instanceof Node\Expr\FuncCall
&& $node->name instanceof Name
&& in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true)
) {
if (!array_key_exists($this->inMethod, $this->variadicMethods[$this->inClassLike])) {
$this->variadicMethods[$this->inClassLike][$this->inMethod] = TrinaryLogic::createYes();
} else {
$this->variadicMethods[$this->inClassLike][$this->inMethod]->and(TrinaryLogic::createYes());
$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;
}
}

}
Expand All @@ -97,28 +90,12 @@ public function enterNode(Node $node): ?Node

public function leaveNode(Node $node): ?Node
{
if (
$node instanceof ClassMethod
&& $this->inClassLike !== null
) {
$this->variadicMethods[$this->inClassLike][$node->name->name] ??= TrinaryLogic::createNo();
if ($node instanceof ClassMethod) {
$this->inMethod = null;
}

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

if ($this->classStack !== []) {
$lastClass = $this->classStack[count($this->classStack) - 1];

if (str_contains($lastClass, self::ANONYMOUS_CLASS_PREFIX)) {
$this->inClassLike = $lastClass;
} else {
$this->inClassLike = $this->inNamespace !== null ? $this->inNamespace . '\\' . implode('\\', $this->classStack) : implode('\\', $this->classStack);
}
} else {
$this->inClassLike = null;
}
}

if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
Expand Down
2 changes: 1 addition & 1 deletion src/Reflection/Php/PhpFunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ private function isVariadic(): bool
is_array($variadicFunctions)
&& array_key_exists($this->reflection->getName(), $variadicFunctions)
) {
return $this->containsVariadicCalls = !$variadicFunctions[$this->reflection->getName()]->no();
return $this->containsVariadicCalls = $variadicFunctions[$this->reflection->getName()];
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Reflection/Php/PhpMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,15 +256,15 @@ private function isVariadic(): bool

$className = $declaringClass->getName();
if ($declaringClass->isAnonymous()) {
$className = sprintf('class@anonymous:%s:%s', $declaringClass->getNativeReflection()->getStartLine(), $declaringClass->getNativeReflection()->getEndLine());
$className = sprintf('%s:%s:%s', VariadicMethodsVisitor::ANONYMOUS_CLASS_PREFIX, $declaringClass->getNativeReflection()->getStartLine(), $declaringClass->getNativeReflection()->getEndLine());
}

if (
is_array($variadicMethods)
&& array_key_exists($className, $variadicMethods)
&& array_key_exists($this->reflection->getName(), $variadicMethods[$className])
) {
return $this->containsVariadicCalls = !$variadicMethods[$className][$this->reflection->getName()]->no();
return $this->containsVariadicCalls = $variadicMethods[$className][$this->reflection->getName()];
}
}

Expand Down
50 changes: 21 additions & 29 deletions tests/PHPStan/Parser/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace PHPStan\Parser;

use PHPStan\Testing\PHPStanTestCase;
use PHPStan\TrinaryLogic;
use function count;

/**
* @covers \PHPStan\Parser\RichParser
Expand All @@ -18,10 +18,7 @@ public function dataVariadicCallLikes(): iterable
__DIR__ . '/data/variadic-functions.php',
VariadicFunctionsVisitor::ATTRIBUTE_NAME,
[
'VariadicFunctions\variadic_fn1' => TrinaryLogic::createYes(),
'VariadicFunctions\nonvariadic' => TrinaryLogic::createNo(),
'VariadicFunctions\maybe_variadic_fn1' => TrinaryLogic::createNo(),
'VariadicFunctions\implicit_variadic_fn1' => TrinaryLogic::createYes(),
'VariadicFunctions\implicit_variadic_fn1' => true,
],
];

Expand All @@ -30,34 +27,23 @@ public function dataVariadicCallLikes(): iterable
VariadicMethodsVisitor::ATTRIBUTE_NAME,
[
'VariadicMethod\X' => [
'non_variadic_fn1' => TrinaryLogic::createNo(),
'variadic_fn1' => TrinaryLogic::createNo(), // native variadicness later on detected via reflection
'implicit_variadic_fn1' => TrinaryLogic::createYes(),
'implicit_variadic_fn1' => true,
],
'VariadicMethod\Z' => [
'non_variadic_fnZ' => TrinaryLogic::createNo(),
'variadic_fnZ' => TrinaryLogic::createNo(), // native variadicness later on detected via reflection
'implicit_variadic_fnZ' => TrinaryLogic::createYes(),
'implicit_variadic_fnZ' => true,
],
'class@anonymous:20:30' => [
'non_variadic_fn_subZ' => TrinaryLogic::createNo(),
'variadic_fn_subZ' => TrinaryLogic::createNo(), // native variadicness later on detected via reflection
'implicit_variadic_subZ' => TrinaryLogic::createYes(),
'implicit_variadic_subZ' => true,
],
'class@anonymous:42:52' => [
'non_variadic_fn' => TrinaryLogic::createNo(),
'variadic_fn' => TrinaryLogic::createNo(), // native variadicness later on detected via reflection
'implicit_variadic_fn' => TrinaryLogic::createYes(),
'implicit_variadic_fn' => true,
],
'class@anonymous:54:58' => [
'implicit_variadic_fn' => TrinaryLogic::createYes(),
'implicit_variadic_fn' => true,
],
'class@anonymous:54:54' => [],
'class@anonymous:61:68' => [
'nestedClass' => TrinaryLogic::createNo(),
'implicit_variadic_fn' => TrinaryLogic::createYes(),
'implicit_variadic_fn' => true,
],
'class@anonymous:63:63' => [],
],
];

Expand All @@ -66,17 +52,15 @@ public function dataVariadicCallLikes(): iterable
VariadicMethodsVisitor::ATTRIBUTE_NAME,
[
'VariadicMethodEnum\X' => [
'non_variadic_fn1' => TrinaryLogic::createNo(),
'variadic_fn1' => TrinaryLogic::createNo(), // variadicness later on detected via reflection
'implicit_variadic_fn1' => TrinaryLogic::createYes(),
'implicit_variadic_fn1' => true,
],
],
];
}

/**
* @dataProvider dataVariadicCallLikes
* @param array<string, TrinaryLogic>|array<string, array<string, TrinaryLogic>> $expectedVariadics
* @param array<string, true>|array<string, array<string, true>> $expectedVariadics
* @throws ParserErrorsException
*/
public function testSimpleParserVariadicCallLikes(string $file, string $attributeName, array $expectedVariadics): void
Expand All @@ -86,12 +70,16 @@ public function testSimpleParserVariadicCallLikes(string $file, string $attribut
$ast = $parser->parseFile($file);
$variadics = $ast[0]->getAttribute($attributeName);
$this->assertIsArray($variadics);
$this->assertSame($expectedVariadics, $variadics);
$this->assertCount(count($expectedVariadics), $variadics);
foreach ($expectedVariadics as $key => $expectedVariadic) {
$this->assertArrayHasKey($key, $variadics);
$this->assertSame($expectedVariadic, $variadics[$key]);
}
}

/**
* @dataProvider dataVariadicCallLikes
* @param array<string, TrinaryLogic>|array<string, array<string, TrinaryLogic>> $expectedVariadics
* @param array<string, true>|array<string, array<string, true>> $expectedVariadics
* @throws ParserErrorsException
*/
public function testRichParserVariadicCallLikes(string $file, string $attributeName, array $expectedVariadics): void
Expand All @@ -101,7 +89,11 @@ public function testRichParserVariadicCallLikes(string $file, string $attributeN
$ast = $parser->parseFile($file);
$variadics = $ast[0]->getAttribute($attributeName);
$this->assertIsArray($variadics);
$this->assertSame($expectedVariadics, $variadics);
$this->assertCount(count($expectedVariadics), $variadics);
foreach ($expectedVariadics as $key => $expectedVariadic) {
$this->assertArrayHasKey($key, $variadics);
$this->assertSame($expectedVariadic, $variadics[$key]);
}
}

}

0 comments on commit d4d5c1a

Please sign in to comment.