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
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Fixture;

class ArrayKeysAndValues
{
/**
* @param array<string, string> $tokens
*/
public function run(array $tokens): void
{
array_map(function ($token, $value) {
echo $token . $value;
}, array_keys($tokens), $tokens);
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Fixture;

class ArrayKeysAndValues
{
/**
* @param array<string, string> $tokens
*/
public function run(array $tokens): void
{
array_map(function (string $token, string $value) {
echo $token . $value;
}, array_keys($tokens), $tokens);
}
}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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<int, string> $array
* @param array<string> $arrayTwo tested for the missing key
* @param array<string> $arrayTwo
*/
public function runFour(array $array, array $arrayTwo)
{
return array_map(function ($value, $key) {
return get_class($value) . $key;
}, $array, $arrayTwo);
}

/**
* @param array<int, string> $array
* @param list<string> $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);
}
}
Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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<int, string> $array
* @param array<string> $arrayTwo tested for the missing key
* @param array<string> $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<int, string> $array
* @param list<string> $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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,7 +26,6 @@
final class AddClosureParamTypeForArrayMapRector extends AbstractRector
{
public function __construct(
private readonly TypeComparator $typeComparator,
private readonly StaticTypeMapper $staticTypeMapper,
private readonly ReflectionResolver $reflectionResolver,
) {
Expand All @@ -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
,
Expand Down Expand Up @@ -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
Expand All @@ -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));
}
}
Loading