Skip to content

Commit e1283b9

Browse files
authored
[TypeDeclaration] Fix AddClosureParamTypeForArrayMapRector to type closure params from array values, not keys (#8163)
1 parent 299ca8d commit e1283b9

3 files changed

Lines changed: 80 additions & 141 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Fixture;
4+
5+
class ArrayKeysAndValues
6+
{
7+
/**
8+
* @param array<string, string> $tokens
9+
*/
10+
public function run(array $tokens): void
11+
{
12+
array_map(function ($token, $value) {
13+
echo $token . $value;
14+
}, array_keys($tokens), $tokens);
15+
}
16+
}
17+
18+
?>
19+
-----
20+
<?php
21+
22+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Fixture;
23+
24+
class ArrayKeysAndValues
25+
{
26+
/**
27+
* @param array<string, string> $tokens
28+
*/
29+
public function run(array $tokens): void
30+
{
31+
array_map(function (string $token, string $value) {
32+
echo $token . $value;
33+
}, array_keys($tokens), $tokens);
34+
}
35+
}
36+
37+
?>

rules-tests/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector/Fixture/fixture.php.inc

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ class Fixture
1212
*/
1313
public function run(array $array)
1414
{
15-
return array_map(function ($value, $key) {
16-
return $value . $key;
15+
return array_map(function ($value) {
16+
return $value;
1717
}, $array);
1818
}
1919

@@ -23,8 +23,8 @@ class Fixture
2323
*/
2424
public function runTwo(array $array, array $arrayTwo)
2525
{
26-
return array_map(function ($value, $key) {
27-
return get_class($value) . $key;
26+
return array_map(function ($value, $second) {
27+
return get_class($second) . $value;
2828
}, $array, $arrayTwo);
2929
}
3030

@@ -34,30 +34,19 @@ class Fixture
3434
*/
3535
public function runThree(array $array, array $arrayTwo)
3636
{
37-
return array_map(function ($value, $key) {
38-
return get_class($value) . $key;
37+
return array_map(function ($value, $second) {
38+
return get_class($second) . $value;
3939
}, $array, $arrayTwo);
4040
}
4141

4242
/**
4343
* @param array<int, string> $array
44-
* @param array<string> $arrayTwo tested for the missing key
44+
* @param array<string> $arrayTwo
4545
*/
4646
public function runFour(array $array, array $arrayTwo)
4747
{
48-
return array_map(function ($value, $key) {
49-
return get_class($value) . $key;
50-
}, $array, $arrayTwo);
51-
}
52-
53-
/**
54-
* @param array<int, string> $array
55-
* @param list<string> $arrayTwo tested for the missing key
56-
*/
57-
public function runFive(array $array, array $arrayTwo)
58-
{
59-
return array_map(function ($value, $key) {
60-
return get_class($value) . $key;
48+
return array_map(function ($value, $second) {
49+
return $second . $value;
6150
}, $array, $arrayTwo);
6251
}
6352
}
@@ -78,8 +67,8 @@ class Fixture
7867
*/
7968
public function run(array $array)
8069
{
81-
return array_map(function (string $value, int $key) {
82-
return $value . $key;
70+
return array_map(function (string $value) {
71+
return $value;
8372
}, $array);
8473
}
8574

@@ -89,8 +78,8 @@ class Fixture
8978
*/
9079
public function runTwo(array $array, array $arrayTwo)
9180
{
92-
return array_map(function (\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Foo|string $value, int $key) {
93-
return get_class($value) . $key;
81+
return array_map(function (string $value, \Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Foo $second) {
82+
return get_class($second) . $value;
9483
}, $array, $arrayTwo);
9584
}
9685

@@ -100,30 +89,19 @@ class Fixture
10089
*/
10190
public function runThree(array $array, array $arrayTwo)
10291
{
103-
return array_map(function (\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Bar|\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Foo|string $value, int $key) {
104-
return get_class($value) . $key;
92+
return array_map(function (string $value, \Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Bar|\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeForArrayMapRector\Source\Foo $second) {
93+
return get_class($second) . $value;
10594
}, $array, $arrayTwo);
10695
}
10796

10897
/**
10998
* @param array<int, string> $array
110-
* @param array<string> $arrayTwo tested for the missing key
99+
* @param array<string> $arrayTwo
111100
*/
112101
public function runFour(array $array, array $arrayTwo)
113102
{
114-
return array_map(function (string $value, int|string $key) {
115-
return get_class($value) . $key;
116-
}, $array, $arrayTwo);
117-
}
118-
119-
/**
120-
* @param array<int, string> $array
121-
* @param list<string> $arrayTwo tested for the missing key
122-
*/
123-
public function runFive(array $array, array $arrayTwo)
124-
{
125-
return array_map(function (string $value, int $key) {
126-
return get_class($value) . $key;
103+
return array_map(function (string $value, string $second) {
104+
return $second . $value;
127105
}, $array, $arrayTwo);
128106
}
129107
}

rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeForArrayMapRector.php

Lines changed: 25 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,10 @@
99
use PhpParser\Node\Expr\Closure;
1010
use PhpParser\Node\Expr\FuncCall;
1111
use PhpParser\Node\Param;
12-
use PhpParser\Node\VariadicPlaceholder;
1312
use PHPStan\Reflection\Native\NativeFunctionReflection;
1413
use PHPStan\Type\ArrayType;
1514
use PHPStan\Type\MixedType;
1615
use PHPStan\Type\Type;
17-
use PHPStan\Type\UnionType;
18-
use PHPStan\Type\UnionTypeHelper;
19-
use Rector\NodeTypeResolver\TypeComparator\TypeComparator;
2016
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
2117
use Rector\Rector\AbstractRector;
2218
use Rector\Reflection\ReflectionResolver;
@@ -30,7 +26,6 @@
3026
final class AddClosureParamTypeForArrayMapRector extends AbstractRector
3127
{
3228
public function __construct(
33-
private readonly TypeComparator $typeComparator,
3429
private readonly StaticTypeMapper $staticTypeMapper,
3530
private readonly ReflectionResolver $reflectionResolver,
3631
) {
@@ -43,14 +38,14 @@ public function getRuleDefinition(): RuleDefinition
4338
[
4439
new CodeSample(
4540
<<<'CODE_SAMPLE'
46-
array_map(function ($value, $key): string {
47-
return $value . $key;
41+
array_map(function ($value) {
42+
return strlen($value);
4843
}, $strings);
4944
CODE_SAMPLE
5045
,
5146
<<<'CODE_SAMPLE'
52-
array_map(function (string $value, int $key): bool {
53-
return $value . $key;
47+
array_map(function (string $value) {
48+
return strlen($value);
5449
}, $strings);
5550
CODE_SAMPLE
5651
,
@@ -89,90 +84,46 @@ public function refactor(Node $node): ?Node
8984
return null;
9085
}
9186

92-
/** @var ArrayType[] $types */
93-
$types = array_filter(array_map(function (Arg|VariadicPlaceholder $arg): ?ArrayType {
94-
if (! $arg instanceof Arg) {
95-
return null;
96-
}
87+
// array_map passes the value of each array positionally to the closure:
88+
// the i-th closure param receives values from the i-th array argument.
89+
$arrayArgs = array_slice($node->args, 1);
9790

98-
$type = $this->getType($arg->value);
91+
$closure = $args[0]->value;
92+
$changed = false;
9993

100-
if ($type instanceof ArrayType) {
101-
return $type;
94+
foreach ($arrayArgs as $position => $arg) {
95+
// spread (...$arrays) cannot be mapped positionally, bail out
96+
if (! $arg instanceof Arg || $arg->unpack) {
97+
return null;
10298
}
10399

104-
return null;
105-
}, array_slice($node->args, 1)));
106-
107-
$values = [];
108-
$keys = [];
109-
110-
foreach ($types as $type) {
111-
$values[] = $type->getIterableValueType();
112-
$keys[] = $type->getIterableKeyType();
113-
}
114-
115-
foreach ($values as $value) {
116-
if ($value instanceof MixedType) {
117-
$values = [];
118-
break;
100+
$param = $closure->params[$position] ?? null;
101+
if (! $param instanceof Param) {
102+
continue;
119103
}
120104

121-
if ($value instanceof UnionType) {
122-
$values = [...$values, ...$value->getTypes()];
105+
$arrayType = $this->getType($arg->value);
106+
if (! $arrayType instanceof ArrayType) {
107+
continue;
123108
}
124-
}
125109

126-
foreach ($keys as $key) {
127-
if ($key instanceof MixedType) {
128-
$keys = [];
129-
break;
110+
$valueType = $arrayType->getIterableValueType();
111+
if ($valueType instanceof MixedType) {
112+
continue;
130113
}
131114

132-
if ($key instanceof UnionType) {
133-
$keys = [...$keys, ...$key->getTypes()];
115+
if ($this->refactorParameter($param, $valueType)) {
116+
$changed = true;
134117
}
135118
}
136119

137-
$filter = fn (Type $type): bool => ! $type instanceof UnionType;
138-
139-
$valueType = $this->combineTypes(array_filter($values, $filter));
140-
$keyType = $this->combineTypes(array_filter($keys, $filter));
141-
142-
if (! $keyType instanceof Type && ! $valueType instanceof Type) {
143-
return null;
144-
}
145-
146-
if ($this->updateClosureWithTypes($args[0]->value, $keyType, $valueType)) {
120+
if ($changed) {
147121
return $node;
148122
}
149123

150124
return null;
151125
}
152126

153-
private function updateClosureWithTypes(Closure $closure, ?Type $keyType, ?Type $valueType): bool
154-
{
155-
$changes = false;
156-
$valueParam = $closure->params[0] ?? null;
157-
$keyParam = $closure->params[1] ?? null;
158-
159-
if ($valueParam instanceof Param && $valueType instanceof Type && $this->refactorParameter(
160-
$closure->params[0],
161-
$valueType
162-
)) {
163-
$changes = true;
164-
}
165-
166-
if ($keyParam instanceof Param && $keyType instanceof Type && $this->refactorParameter(
167-
$closure->params[1],
168-
$keyType
169-
)) {
170-
return true;
171-
}
172-
173-
return $changes;
174-
}
175-
176127
private function refactorParameter(Param $param, Type $type): bool
177128
{
178129
// already set → no change
@@ -190,31 +141,4 @@ private function refactorParameter(Param $param, Type $type): bool
190141

191142
return true;
192143
}
193-
194-
/**
195-
* @param Type[] $types
196-
*/
197-
private function combineTypes(array $types): ?Type
198-
{
199-
if ($types === []) {
200-
return null;
201-
}
202-
203-
$types = array_reduce($types, function (array $types, Type $type): array {
204-
foreach ($types as $previousType) {
205-
if ($this->typeComparator->areTypesEqual($type, $previousType)) {
206-
return $types;
207-
}
208-
}
209-
210-
$types[] = $type;
211-
return $types;
212-
}, []);
213-
214-
if (count($types) === 1) {
215-
return $types[0];
216-
}
217-
218-
return new UnionType(UnionTypeHelper::sortTypes($types));
219-
}
220144
}

0 commit comments

Comments
 (0)