diff --git a/rules-tests/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector/Fixture/array_keys_and_values.php.inc b/rules-tests/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector/Fixture/array_keys_and_values.php.inc new file mode 100644 index 00000000000..547eaa288c1 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector/Fixture/array_keys_and_values.php.inc @@ -0,0 +1,37 @@ + $tokens + */ + public function run(array $tokens): void + { + array_map(function ($token, $value) { + echo $token . $value; + }, array_keys($tokens), $tokens); + } +} + +?> +----- + $tokens + */ + public function run(array $tokens): void + { + array_map(function (string $token, string $value) { + echo $token . $value; + }, array_keys($tokens), $tokens); + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector/Fixture/fixture.php.inc b/rules-tests/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector/Fixture/fixture.php.inc index 3b78c8e2daa..71bd9a189bf 100644 --- a/rules-tests/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector/Fixture/fixture.php.inc +++ b/rules-tests/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector/Fixture/fixture.php.inc @@ -12,8 +12,8 @@ class Fixture */ public function run(array $array) { - return array_map(function ($value, $key) { - return $value . $key; + return array_map(function ($value) { + return $value; }, $array); } @@ -23,8 +23,8 @@ class Fixture */ public function runTwo(array $array, array $arrayTwo) { - return array_map(function ($value, $key) { - return get_class($value) . $key; + return array_map(function ($value, $second) { + return get_class($second) . $value; }, $array, $arrayTwo); } @@ -34,30 +34,19 @@ class Fixture */ public function runThree(array $array, array $arrayTwo) { - return array_map(function ($value, $key) { - return get_class($value) . $key; + return array_map(function ($value, $second) { + return get_class($second) . $value; }, $array, $arrayTwo); } /** * @param array $array - * @param array $arrayTwo tested for the missing key + * @param array $arrayTwo */ public function runFour(array $array, array $arrayTwo) { - return array_map(function ($value, $key) { - return get_class($value) . $key; - }, $array, $arrayTwo); - } - - /** - * @param array $array - * @param list $arrayTwo tested for the missing key - */ - public function runFive(array $array, array $arrayTwo) - { - return array_map(function ($value, $key) { - return get_class($value) . $key; + return array_map(function ($value, $second) { + return $second . $value; }, $array, $arrayTwo); } } @@ -78,8 +67,8 @@ class Fixture */ public function run(array $array) { - return array_map(function (string $value, int $key) { - return $value . $key; + return array_map(function (string $value) { + return $value; }, $array); } @@ -89,8 +78,8 @@ class Fixture */ public function runTwo(array $array, array $arrayTwo) { - return array_map(function (\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Foo|string $value, int $key) { - return get_class($value) . $key; + return array_map(function (string $value, \Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Foo $second) { + return get_class($second) . $value; }, $array, $arrayTwo); } @@ -100,30 +89,19 @@ class Fixture */ public function runThree(array $array, array $arrayTwo) { - return array_map(function (\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Bar|\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Foo|string $value, int $key) { - return get_class($value) . $key; + return array_map(function (string $value, \Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Bar|\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Foo $second) { + return get_class($second) . $value; }, $array, $arrayTwo); } /** * @param array $array - * @param array $arrayTwo tested for the missing key + * @param array $arrayTwo */ public function runFour(array $array, array $arrayTwo) { - return array_map(function (string $value, int|string $key) { - return get_class($value) . $key; - }, $array, $arrayTwo); - } - - /** - * @param array $array - * @param list $arrayTwo tested for the missing key - */ - public function runFive(array $array, array $arrayTwo) - { - return array_map(function (string $value, int $key) { - return get_class($value) . $key; + return array_map(function (string $value, string $second) { + return $second . $value; }, $array, $arrayTwo); } } diff --git a/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector.php b/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector.php index 2b16507403d..09aaa734cca 100644 --- a/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector.php +++ b/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector.php @@ -9,14 +9,10 @@ use PhpParser\Node\Expr\Closure; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Param; -use PhpParser\Node\VariadicPlaceholder; use PHPStan\Reflection\Native\NativeFunctionReflection; use PHPStan\Type\ArrayType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; -use PHPStan\Type\UnionType; -use PHPStan\Type\UnionTypeHelper; -use Rector\NodeTypeResolver\TypeComparator\TypeComparator; use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; @@ -30,7 +26,6 @@ final class AddClosureParamTypeForArrayMapRector extends AbstractRector { public function __construct( - private readonly TypeComparator $typeComparator, private readonly StaticTypeMapper $staticTypeMapper, private readonly ReflectionResolver $reflectionResolver, ) { @@ -43,14 +38,14 @@ public function getRuleDefinition(): RuleDefinition [ new CodeSample( <<<'CODE_SAMPLE' -array_map(function ($value, $key): string { - return $value . $key; +array_map(function ($value) { + return strlen($value); }, $strings); CODE_SAMPLE , <<<'CODE_SAMPLE' -array_map(function (string $value, int $key): bool { - return $value . $key; +array_map(function (string $value) { + return strlen($value); }, $strings); CODE_SAMPLE , @@ -89,90 +84,46 @@ public function refactor(Node $node): ?Node return null; } - /** @var ArrayType[] $types */ - $types = array_filter(array_map(function (Arg|VariadicPlaceholder $arg): ?ArrayType { - if (! $arg instanceof Arg) { - return null; - } + // array_map passes the value of each array positionally to the closure: + // the i-th closure param receives values from the i-th array argument. + $arrayArgs = array_slice($node->args, 1); - $type = $this->getType($arg->value); + $closure = $args[0]->value; + $changed = false; - if ($type instanceof ArrayType) { - return $type; + foreach ($arrayArgs as $position => $arg) { + // spread (...$arrays) cannot be mapped positionally, bail out + if (! $arg instanceof Arg || $arg->unpack) { + return null; } - return null; - }, array_slice($node->args, 1))); - - $values = []; - $keys = []; - - foreach ($types as $type) { - $values[] = $type->getIterableValueType(); - $keys[] = $type->getIterableKeyType(); - } - - foreach ($values as $value) { - if ($value instanceof MixedType) { - $values = []; - break; + $param = $closure->params[$position] ?? null; + if (! $param instanceof Param) { + continue; } - if ($value instanceof UnionType) { - $values = [...$values, ...$value->getTypes()]; + $arrayType = $this->getType($arg->value); + if (! $arrayType instanceof ArrayType) { + continue; } - } - foreach ($keys as $key) { - if ($key instanceof MixedType) { - $keys = []; - break; + $valueType = $arrayType->getIterableValueType(); + if ($valueType instanceof MixedType) { + continue; } - if ($key instanceof UnionType) { - $keys = [...$keys, ...$key->getTypes()]; + if ($this->refactorParameter($param, $valueType)) { + $changed = true; } } - $filter = fn (Type $type): bool => ! $type instanceof UnionType; - - $valueType = $this->combineTypes(array_filter($values, $filter)); - $keyType = $this->combineTypes(array_filter($keys, $filter)); - - if (! $keyType instanceof Type && ! $valueType instanceof Type) { - return null; - } - - if ($this->updateClosureWithTypes($args[0]->value, $keyType, $valueType)) { + if ($changed) { return $node; } return null; } - private function updateClosureWithTypes(Closure $closure, ?Type $keyType, ?Type $valueType): bool - { - $changes = false; - $valueParam = $closure->params[0] ?? null; - $keyParam = $closure->params[1] ?? null; - - if ($valueParam instanceof Param && $valueType instanceof Type && $this->refactorParameter( - $closure->params[0], - $valueType - )) { - $changes = true; - } - - if ($keyParam instanceof Param && $keyType instanceof Type && $this->refactorParameter( - $closure->params[1], - $keyType - )) { - return true; - } - - return $changes; - } - private function refactorParameter(Param $param, Type $type): bool { // already set → no change @@ -190,31 +141,4 @@ private function refactorParameter(Param $param, Type $type): bool return true; } - - /** - * @param Type[] $types - */ - private function combineTypes(array $types): ?Type - { - if ($types === []) { - return null; - } - - $types = array_reduce($types, function (array $types, Type $type): array { - foreach ($types as $previousType) { - if ($this->typeComparator->areTypesEqual($type, $previousType)) { - return $types; - } - } - - $types[] = $type; - return $types; - }, []); - - if (count($types) === 1) { - return $types[0]; - } - - return new UnionType(UnionTypeHelper::sortTypes($types)); - } }