diff --git a/build/phpstan.neon b/build/phpstan.neon index 97a076fab0..57c896e08d 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -124,6 +124,9 @@ parameters: - stubs/ReactStreams.stub - stubs/NetteDIContainer.stub - stubs/PhpParserName.stub + - stubs/PhpParserNode.stub + - stubs/PhpParserExpr.stub + - stubs/PhpParserStmt.stub - stubs/Identifier.stub rules: diff --git a/build/stubs/PhpParserExpr.stub b/build/stubs/PhpParserExpr.stub new file mode 100644 index 0000000000..636d8cf5b4 --- /dev/null +++ b/build/stubs/PhpParserExpr.stub @@ -0,0 +1,108 @@ + + */ + abstract public function getRawArgs(): array; + /** + * Assert that this is not a first-class callable and return only ordinary Args. + * + * @return list + */ + public function getArgs(): array { + assert(!$this->isFirstClassCallable()); + return $this->getRawArgs(); + } +} + +class FuncCall extends CallLike { + /** @var list Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr $name Function name + * @param list $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $name, array $args = [], array $attributes = []) {} +} + +class MethodCall extends CallLike { + /** @var list Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param list $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {} +} + +class New_ extends CallLike { + /** @var list Arguments */ + public array $args; + + /** + * Constructs a function call node. + * + * @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes) + * @param list $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $class, array $args = [], array $attributes = []) {} +} + +class NullsafeMethodCall extends CallLike { + /** @var list Arguments */ + public array $args; + + /** + * Constructs a nullsafe method call node. + * + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param list $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {} +} + +class StaticCall extends CallLike { + /** @var list Arguments */ + public array $args; + + /** + * Constructs a static method call node. + * + * @param Node\Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param list $args Arguments + * @param array $attributes Additional attributes + */ + public function __construct(Node $class, $name, array $args = [], array $attributes = []) {} +} + +class Closure extends Expr implements FunctionLike { + /** @var list Statements */ + public array $stmts; +} diff --git a/build/stubs/PhpParserNode.stub b/build/stubs/PhpParserNode.stub new file mode 100644 index 0000000000..57585ed871 --- /dev/null +++ b/build/stubs/PhpParserNode.stub @@ -0,0 +1,10 @@ + Hook body */ + public $body; +} diff --git a/build/stubs/PhpParserStmt.stub b/build/stubs/PhpParserStmt.stub new file mode 100644 index 0000000000..950658805c --- /dev/null +++ b/build/stubs/PhpParserStmt.stub @@ -0,0 +1,83 @@ + Statements */ + public array $stmts; +} + +class Case_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class Catch_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class Do_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class ElseIf_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class Else_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class Finally_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class For_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class Foreach_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class If_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class Namespace_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class TryCatch extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class While_ extends Node\Stmt { + /** @var list Statements */ + public array $stmts; +} + +class ClassMethod extends Node\Stmt implements FunctionLike { + /** @var list|null Statements */ + public ?array $stmts; +} + +class Function_ extends Node\Stmt implements FunctionLike { + /** @var list Statements */ + public array $stmts; +} + diff --git a/src/Analyser/ArgumentsNormalizer.php b/src/Analyser/ArgumentsNormalizer.php index 4a4844cd8c..7b8f4259fa 100644 --- a/src/Analyser/ArgumentsNormalizer.php +++ b/src/Analyser/ArgumentsNormalizer.php @@ -13,8 +13,10 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; +use function array_is_list; use function array_key_exists; use function array_keys; +use function array_values; use function count; use function ksort; use function max; @@ -182,8 +184,8 @@ public static function reorderNewArguments( } /** - * @param Arg[] $callArgs - * @return ?array + * @param list $callArgs + * @return ?list */ public static function reorderArgs(ParametersAcceptor $parametersAcceptor, array $callArgs): ?array { @@ -322,6 +324,10 @@ public static function reorderArgs(ParametersAcceptor $parametersAcceptor, array $reorderedArgs[] = $arg; } + if (!array_is_list($reorderedArgs)) { + $reorderedArgs = array_values($reorderedArgs); + } + return $reorderedArgs; } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 9e1b226d8c..1d833752b0 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4455,7 +4455,7 @@ private function getMethodThrowPoint(MethodReflection $methodReflection, Paramet } /** - * @param Node\Arg[] $args + * @param list $args */ private function getConstructorThrowPoint(MethodReflection $constructorReflection, ParametersAcceptor $parametersAcceptor, ClassReflection $classReflection, New_ $new, Name $className, array $args, MutatingScope $scope): ?ThrowPoint { diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php index c0f4c0b458..382769e3b4 100644 --- a/src/Parser/CleaningVisitor.php +++ b/src/Parser/CleaningVisitor.php @@ -50,8 +50,8 @@ public function enterNode(Node $node): ?Node } /** - * @param Node\Stmt[] $stmts - * @return Node\Stmt[] + * @param list $stmts + * @return list */ private function keepVariadicsAndYields(array $stmts, ?string $hookedPropertyName): array { diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index a8ef3d3f7d..f6c3058cc2 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -43,6 +43,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeTraverser; use PHPStan\Type\UnionType; +use function array_is_list; use function array_key_exists; use function array_key_last; use function array_map; @@ -471,6 +472,11 @@ public static function selectFromArgs( $parameters = null; $singleParametersAcceptor = null; if (count($parametersAcceptors) === 1) { + if (!array_is_list($args)) { + // actually $args parameter should be typed to list but we can't atm, + // because its a BC break. + $args = array_values($args); + } $reorderedArgs = ArgumentsNormalizer::reorderArgs($parametersAcceptors[0], $args); $singleParametersAcceptor = $parametersAcceptors[0]; } diff --git a/src/Rules/Functions/RandomIntParametersRule.php b/src/Rules/Functions/RandomIntParametersRule.php index d2f8c52597..1205283002 100644 --- a/src/Rules/Functions/RandomIntParametersRule.php +++ b/src/Rules/Functions/RandomIntParametersRule.php @@ -14,7 +14,6 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\VerbosityLevel; -use function array_values; use function count; use function sprintf; @@ -49,7 +48,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $args = array_values($node->getArgs()); + $args = $node->getArgs(); if (count($args) < 2) { return []; } diff --git a/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php b/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php index 9b92249486..06235a934e 100644 --- a/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php +++ b/tests/PHPStan/Analyser/ArgumentsNormalizerLegacyTest.php @@ -49,11 +49,9 @@ public function testArgumentReorderAllNamed(): void $reorderedArgs = $funcCall->getArgs(); $this->assertCount(2, $reorderedArgs); - $this->assertArrayHasKey(0, $reorderedArgs); $this->assertNull($reorderedArgs[0]->name, 'named-arg turned into regular numeric arg'); $this->assertInstanceOf(String_::class, $reorderedArgs[0]->value, 'value-arg at the right position'); - $this->assertArrayHasKey(1, $reorderedArgs); $this->assertNull($reorderedArgs[1]->name, 'named-arg turned into regular numeric arg'); $this->assertInstanceOf(LNumber::class, $reorderedArgs[1]->value, 'flags-arg at the right position'); $this->assertSame(0, $reorderedArgs[1]->value->value); @@ -90,17 +88,14 @@ public function testArgumentReorderAllNamedWithSkipped(): void $reorderedArgs = $funcCall->getArgs(); $this->assertCount(3, $reorderedArgs); - $this->assertArrayHasKey(0, $reorderedArgs); $this->assertNull($reorderedArgs[0]->name, 'named-arg turned into regular numeric arg'); $this->assertInstanceOf(String_::class, $reorderedArgs[0]->value, 'value-arg at the right position'); - $this->assertArrayHasKey(1, $reorderedArgs); $this->assertNull($reorderedArgs[1]->name, 'named-arg turned into regular numeric arg'); $this->assertInstanceOf(TypeExpr::class, $reorderedArgs[1]->value, 'flags-arg at the right position'); $this->assertInstanceOf(ConstantIntegerType::class, $reorderedArgs[1]->value->getExprType()); $this->assertSame(0, $reorderedArgs[1]->value->getExprType()->getValue(), 'flags-arg with default value'); - $this->assertArrayHasKey(2, $reorderedArgs); $this->assertNull($reorderedArgs[2]->name, 'named-arg turned into regular numeric arg'); $this->assertInstanceOf(LNumber::class, $reorderedArgs[2]->value, 'depth-arg at the right position'); $this->assertSame(128, $reorderedArgs[2]->value->value); @@ -153,10 +148,7 @@ public function testLeaveRegularCallAsIs(): void $reorderedArgs = $funcCall->getArgs(); $this->assertCount(2, $reorderedArgs); - $this->assertArrayHasKey(0, $reorderedArgs); $this->assertInstanceOf(String_::class, $reorderedArgs[0]->value, 'value-arg at unchanged position'); - - $this->assertArrayHasKey(1, $reorderedArgs); $this->assertInstanceOf(LNumber::class, $reorderedArgs[1]->value, 'flags-arg at unchanged position'); $this->assertSame(0, $reorderedArgs[1]->value->value); }