Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/Analyser/ArgumentsNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,25 @@ public static function reorderNewArguments(
);
}

/**
* The argument bound to the parameter at $position named $name: the
* positional argument at that index or the one passed by that name.
*
* @param Arg[] $args
* @param 0|positive-int $position
* @param non-empty-string $name
*/
public static function getArgByPositionOrName(array $args, int $position, string $name): ?Arg
Comment thread
staabm marked this conversation as resolved.
Resolved
{
foreach ($args as $i => $arg) {
if ($arg->name === null ? $i === $position : $arg->name->toString() === $name) {
return $arg;
}
}

return null;
}

/**
* @param Arg[] $callArgs
* @return ?list<Arg>
Expand Down
7 changes: 4 additions & 3 deletions src/Parser/ArrayFilterArgVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Override;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Turbo\ShadowedByTurboExtension;

Expand All @@ -21,9 +22,9 @@ public function enterNode(Node $node): ?Node
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name && !$node->isFirstClassCallable()) {
$functionName = $node->name->toLowerString();
if ($functionName === 'array_filter') {
$args = $node->getArgs();
if (isset($args[0])) {
$args[0]->setAttribute(self::ATTRIBUTE_NAME, true);
$arg = ArgumentsNormalizer::getArgByPositionOrName($node->getArgs(), 0, 'array');
if ($arg !== null) {
$arg->setAttribute(self::ATTRIBUTE_NAME, true);
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/Parser/ArrayFindArgVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Override;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Turbo\ShadowedByTurboExtension;
use function in_array;
Expand All @@ -22,9 +23,9 @@ public function enterNode(Node $node): ?Node
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name && !$node->isFirstClassCallable()) {
$functionName = $node->name->toLowerString();
if (in_array($functionName, ['array_all', 'array_any', 'array_find', 'array_find_key'], true)) {
$args = $node->getArgs();
if (isset($args[0])) {
$args[0]->setAttribute(self::ATTRIBUTE_NAME, true);
$arg = ArgumentsNormalizer::getArgByPositionOrName($node->getArgs(), 0, 'array');
if ($arg !== null) {
$arg->setAttribute(self::ATTRIBUTE_NAME, true);
}
}
}
Expand Down
10 changes: 4 additions & 6 deletions src/Parser/ArrayMapArgVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
use Override;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Turbo\ShadowedByTurboExtension;
use function array_slice;
use function count;

#[AutowiredService]
Expand Down Expand Up @@ -35,11 +35,9 @@ public function enterNode(Node $node): ?Node

$arrayArgs[] = $arg;
}
if (isset($args[0])) {
$slicedArgs = array_slice($args, 1);
if (count($slicedArgs) > 0) {
$args[0]->value->setAttribute(self::ATTRIBUTE_NAME, $arrayArgs);
}
$callbackArg = ArgumentsNormalizer::getArgByPositionOrName($args, 0, 'callback');
if ($callbackArg !== null && count($arrayArgs) > 0) {
$callbackArg->value->setAttribute(self::ATTRIBUTE_NAME, $arrayArgs);
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/Parser/ArrayWalkArgVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Override;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Turbo\ShadowedByTurboExtension;

Expand All @@ -21,9 +22,9 @@ public function enterNode(Node $node): ?Node
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name && !$node->isFirstClassCallable()) {
$functionName = $node->name->toLowerString();
if ($functionName === 'array_walk') {
$args = $node->getArgs();
if (isset($args[0])) {
$args[0]->setAttribute(self::ATTRIBUTE_NAME, true);
$arg = ArgumentsNormalizer::getArgByPositionOrName($node->getArgs(), 0, 'array');
if ($arg !== null) {
$arg->setAttribute(self::ATTRIBUTE_NAME, true);
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/Parser/CurlSetOptArgVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Override;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Turbo\ShadowedByTurboExtension;

Expand All @@ -21,9 +22,9 @@ public function enterNode(Node $node): ?Node
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name && !$node->isFirstClassCallable()) {
$functionName = $node->name->toLowerString();
if ($functionName === 'curl_setopt') {
$args = $node->getArgs();
if (isset($args[0])) {
$args[0]->setAttribute(self::ATTRIBUTE_NAME, true);
$arg = ArgumentsNormalizer::getArgByPositionOrName($node->getArgs(), 0, 'handle');
if ($arg !== null) {
$arg->setAttribute(self::ATTRIBUTE_NAME, true);
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/Parser/CurlSetOptArrayArgVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Override;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Turbo\ShadowedByTurboExtension;

Expand All @@ -21,9 +22,9 @@ public function enterNode(Node $node): ?Node
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name && !$node->isFirstClassCallable()) {
$functionName = $node->name->toLowerString();
if ($functionName === 'curl_setopt_array') {
$args = $node->getArgs();
if (isset($args[1])) {
$args[1]->setAttribute(self::ATTRIBUTE_NAME, true);
$arg = ArgumentsNormalizer::getArgByPositionOrName($node->getArgs(), 1, 'options');
if ($arg !== null) {
$arg->setAttribute(self::ATTRIBUTE_NAME, true);
}
}
}
Expand Down
76 changes: 51 additions & 25 deletions src/Reflection/ParametersAcceptorSelector.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,31 @@ public static function selectFromArgs(
return self::selectFromTypes($types, $parametersAcceptors, $unpack);
}

/**
* The intrinsic overrides read sibling arguments by their parameter
* position, so named arguments are put in signature order first.
*
* @param Node\Arg[] $args
* @return Node\Arg[]
*/
private static function reorderIntrinsicArgs(array $args, ParametersAcceptor $parametersAcceptor): array
{
foreach ($args as $arg) {
if ($arg->name === null) {
continue;
}

$reorderedArgs = ArgumentsNormalizer::reorderArgs($parametersAcceptor, array_values($args));
if ($reorderedArgs === null || count($reorderedArgs) === 0) {
return $args;
}

return $reorderedArgs;
}

return $args;
}

/**
* @internal
* @param Node\Arg[] $args
Expand All @@ -204,7 +229,8 @@ public static function applyIntrinsicArgOverrides(
count($args) > 0
&& count($parametersAcceptors) > 0
) {
$arrayMapArgs = $args[0]->value->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
$positionalArgs = self::reorderIntrinsicArgs($args, $namedArgumentsVariants[0] ?? $parametersAcceptors[0]);
$arrayMapArgs = $positionalArgs[0]->value->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
if ($arrayMapArgs !== null) {
$callbackValueTypes = [];
$callbackArrayTypes = [];
Expand Down Expand Up @@ -267,8 +293,8 @@ public static function applyIntrinsicArgOverrides(
}
}

if (count($args) >= 3 && (bool) $args[0]->getAttribute(CurlSetOptArgVisitor::ATTRIBUTE_NAME)) {
$optType = ($typeGetter)($args[1]->value);
if (count($positionalArgs) >= 3 && (bool) $positionalArgs[0]->getAttribute(CurlSetOptArgVisitor::ATTRIBUTE_NAME)) {
$optType = ($typeGetter)($positionalArgs[1]->value);

$valueTypes = [];
foreach ($optType->getConstantScalarValues() as $scalarValue) {
Expand Down Expand Up @@ -311,8 +337,8 @@ public static function applyIntrinsicArgOverrides(
}
}

if (count($args) >= 2 && (bool) $args[1]->getAttribute(CurlSetOptArrayArgVisitor::ATTRIBUTE_NAME)) {
$optArrayType = ($typeGetter)($args[1]->value);
if (count($positionalArgs) >= 2 && (bool) $positionalArgs[1]->getAttribute(CurlSetOptArrayArgVisitor::ATTRIBUTE_NAME)) {
$optArrayType = ($typeGetter)($positionalArgs[1]->value);

$hasTypes = false;
$builder = ConstantArrayTypeBuilder::createEmpty();
Expand Down Expand Up @@ -363,27 +389,27 @@ public static function applyIntrinsicArgOverrides(
}
}

if ((bool) $args[0]->getAttribute(ArrayFilterArgVisitor::ATTRIBUTE_NAME)) {
if ((bool) $positionalArgs[0]->getAttribute(ArrayFilterArgVisitor::ATTRIBUTE_NAME)) {
$arrayFilterParameters = null;
$nativeArrayFilterParameters = null;
if (isset($args[2])) {
$mode = ($typeGetter)($args[2]->value);
if (isset($positionalArgs[2])) {
$mode = ($typeGetter)($positionalArgs[2]->value);
if ($mode instanceof ConstantIntegerType) {
if ($mode->getValue() === ARRAY_FILTER_USE_KEY) {
$arrayFilterParameters = [
new DummyParameter('key', ($iterableKeyTypeGetter)(($typeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
new DummyParameter('key', ($iterableKeyTypeGetter)(($typeGetter)($positionalArgs[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
];
$nativeArrayFilterParameters = [
new DummyParameter('key', ($iterableKeyTypeGetter)(($nativeTypeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
new DummyParameter('key', ($iterableKeyTypeGetter)(($nativeTypeGetter)($positionalArgs[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
];
} elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) {
$arrayFilterParameters = [
new DummyParameter('item', ($iterableValueTypeGetter)(($typeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
new DummyParameter('key', ($iterableKeyTypeGetter)(($typeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
new DummyParameter('item', ($iterableValueTypeGetter)(($typeGetter)($positionalArgs[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
new DummyParameter('key', ($iterableKeyTypeGetter)(($typeGetter)($positionalArgs[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
];
$nativeArrayFilterParameters = [
new DummyParameter('item', ($iterableValueTypeGetter)(($nativeTypeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
new DummyParameter('key', ($iterableKeyTypeGetter)(($nativeTypeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
new DummyParameter('item', ($iterableValueTypeGetter)(($nativeTypeGetter)($positionalArgs[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
new DummyParameter('key', ($iterableKeyTypeGetter)(($nativeTypeGetter)($positionalArgs[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
];
}
}
Expand All @@ -392,7 +418,7 @@ public static function applyIntrinsicArgOverrides(
$acceptor = $parametersAcceptors[0];
$parameters = $acceptor->getParameters();
if (isset($parameters[1])) {
$arrayArgType = ($typeGetter)($args[0]->value);
$arrayArgType = ($typeGetter)($positionalArgs[0]->value);
$callableType = new UnionType([
new CallableType(
$arrayFilterParameters ?? [
Expand All @@ -403,7 +429,7 @@ public static function applyIntrinsicArgOverrides(
),
new NullType(),
]);
$nativeArrayArgType = ($nativeTypeGetter)($args[0]->value);
$nativeArrayArgType = ($nativeTypeGetter)($positionalArgs[0]->value);
$nativeCallableType = new UnionType([
new CallableType(
$nativeArrayFilterParameters ?? [
Expand Down Expand Up @@ -448,9 +474,9 @@ public static function applyIntrinsicArgOverrides(
];
}

if ((bool) $args[0]->getAttribute(ArrayWalkArgVisitor::ATTRIBUTE_NAME)) {
$arrayArgType = ($typeGetter)($args[0]->value);
$nativeArrayArgType = ($nativeTypeGetter)($args[0]->value);
if ((bool) $positionalArgs[0]->getAttribute(ArrayWalkArgVisitor::ATTRIBUTE_NAME)) {
$arrayArgType = ($typeGetter)($positionalArgs[0]->value);
$nativeArrayArgType = ($nativeTypeGetter)($positionalArgs[0]->value);
$arrayWalkParameters = [
new DummyParameter('item', ($iterableValueTypeGetter)($arrayArgType), optional: false, passedByReference: PassedByReference::createReadsArgument(), variadic: false, defaultValue: null),
new DummyParameter('key', ($iterableKeyTypeGetter)($arrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
Expand All @@ -459,9 +485,9 @@ public static function applyIntrinsicArgOverrides(
new DummyParameter('item', ($iterableValueTypeGetter)($nativeArrayArgType), optional: false, passedByReference: PassedByReference::createReadsArgument(), variadic: false, defaultValue: null),
new DummyParameter('key', ($iterableKeyTypeGetter)($nativeArrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
];
if (isset($args[2])) {
$arrayWalkParameters[] = new DummyParameter('arg', ($typeGetter)($args[2]->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
$nativeArrayWalkParameters[] = new DummyParameter('arg', ($nativeTypeGetter)($args[2]->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
if (isset($positionalArgs[2])) {
$arrayWalkParameters[] = new DummyParameter('arg', ($typeGetter)($positionalArgs[2]->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
$nativeArrayWalkParameters[] = new DummyParameter('arg', ($nativeTypeGetter)($positionalArgs[2]->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
}

$acceptor = $parametersAcceptors[0];
Expand All @@ -474,11 +500,11 @@ public static function applyIntrinsicArgOverrides(
}
}

if ((bool) $args[0]->getAttribute(ArrayFindArgVisitor::ATTRIBUTE_NAME)) {
if ((bool) $positionalArgs[0]->getAttribute(ArrayFindArgVisitor::ATTRIBUTE_NAME)) {
$acceptor = $parametersAcceptors[0];
$parameters = $acceptor->getParameters();
if (isset($parameters[1])) {
$argType = ($typeGetter)($args[0]->value);
$argType = ($typeGetter)($positionalArgs[0]->value);
$callableType = new CallableType(
[
new DummyParameter('value', ($iterableValueTypeGetter)($argType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
Expand All @@ -487,7 +513,7 @@ public static function applyIntrinsicArgOverrides(
new BooleanType(),
false,
);
$nativeArgType = ($nativeTypeGetter)($args[0]->value);
$nativeArgType = ($nativeTypeGetter)($positionalArgs[0]->value);
$nativeCallableType = new CallableType(
[
new DummyParameter('value', ($iterableValueTypeGetter)($nativeArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
Expand Down
3 changes: 2 additions & 1 deletion src/Type/Php/ArrayFilterParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\FunctionReflection;
Expand All @@ -28,7 +29,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Para

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $functionCall->getArgs();
$args = ArgumentsNormalizer::reorderArgs($functionReflection->getNamedArgumentsVariants()[0] ?? $functionReflection->getVariants()[0], $functionCall->getArgs()) ?? [];
if (!isset($args[0])) {
return null;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Type/Php/ArrayFindParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\FunctionReflection;
Expand All @@ -27,7 +28,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Para

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $functionCall->getArgs();
$args = ArgumentsNormalizer::reorderArgs($functionReflection->getNamedArgumentsVariants()[0] ?? $functionReflection->getVariants()[0], $functionCall->getArgs()) ?? [];
if (!isset($args[0])) {
return null;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Type/Php/ArrayMapParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\ArrayMapCallbackParameterTypes;
Expand All @@ -27,7 +28,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Para

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $functionCall->getArgs();
$args = ArgumentsNormalizer::reorderArgs($functionReflection->getNamedArgumentsVariants()[0] ?? $functionReflection->getVariants()[0], $functionCall->getArgs()) ?? [];
if (count($args) < 2) {
return null;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Type/Php/ArrayWalkParameterClosureTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\FunctionReflection;
Expand All @@ -25,7 +26,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Para

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type
{
$args = $functionCall->getArgs();
$args = ArgumentsNormalizer::reorderArgs($functionReflection->getNamedArgumentsVariants()[0] ?? $functionReflection->getVariants()[0], $functionCall->getArgs()) ?? [];
if (!isset($args[0])) {
return null;
}
Expand Down
Loading
Loading