Skip to content

Commit eb3eb21

Browse files
authored
[TypeDeclaration] Add AddClosureParamTypeFromVariableCallRector (#8142)
* [TypeDeclaration] Add AddClosureParamTypeFromVariableCallRector Infer a closure param's object type from the argument the assigned closure variable is invoked with, e.g. $cb($formView) where $formView is a FormView adds a FormView type hint to the closure param. Only concrete object types are added; scalar/array/conflicting args are skipped. * Register AddClosureParamTypeFromVariableCallRector in TypeDeclarationLevel * Order AddClosureParamTypeFromVariableCallRector above StrictArrayParamDimFetchRector Infer the closure param object type before the array-dim-fetch rule runs, so a typed param is already present and the array fallback is skipped.
1 parent 62e4045 commit eb3eb21

12 files changed

Lines changed: 439 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class AddClosureParamTypeFromVariableCallRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Fixture;
4+
5+
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source\Item;
6+
7+
final class ObjectParam
8+
{
9+
public function run(): void
10+
{
11+
$printItem = function ($item) {
12+
echo $item->name;
13+
};
14+
15+
$printItem(new Item());
16+
}
17+
}
18+
19+
?>
20+
-----
21+
<?php
22+
23+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Fixture;
24+
25+
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source\Item;
26+
27+
final class ObjectParam
28+
{
29+
public function run(): void
30+
{
31+
$printItem = function (\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source\Item $item) {
32+
echo $item->name;
33+
};
34+
35+
$printItem(new Item());
36+
}
37+
}
38+
39+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Fixture;
4+
5+
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source\FormView;
6+
7+
final class RecursiveFormView
8+
{
9+
public function setFormTheme(FormView $formView): void
10+
{
11+
$findThemes = function ($formView) use (&$findThemes): void {
12+
$child = $formView['child'];
13+
$findThemes($child);
14+
};
15+
16+
$findThemes($formView);
17+
}
18+
}
19+
20+
?>
21+
-----
22+
<?php
23+
24+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Fixture;
25+
26+
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source\FormView;
27+
28+
final class RecursiveFormView
29+
{
30+
public function setFormTheme(FormView $formView): void
31+
{
32+
$findThemes = function (\Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source\FormView $formView) use (&$findThemes): void {
33+
$child = $formView['child'];
34+
$findThemes($child);
35+
};
36+
37+
$findThemes($formView);
38+
}
39+
}
40+
41+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Fixture;
4+
5+
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source\Item;
6+
7+
final class SkipAlreadyTyped
8+
{
9+
public function run(): void
10+
{
11+
$printItem = function (Item $item) {
12+
echo $item->name;
13+
};
14+
15+
$printItem(new Item());
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Fixture;
4+
5+
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source\Item;
6+
use Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source\Other;
7+
8+
final class SkipConflictingTypes
9+
{
10+
public function run(): void
11+
{
12+
$printItem = function ($item) {
13+
var_dump($item);
14+
};
15+
16+
$printItem(new Item());
17+
$printItem(new Other());
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Fixture;
4+
5+
final class SkipScalarArg
6+
{
7+
public function run(): void
8+
{
9+
$printValue = function ($value) {
10+
echo $value;
11+
};
12+
13+
$printValue('hello');
14+
}
15+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source;
6+
7+
/**
8+
* @implements \ArrayAccess<string, self>
9+
*/
10+
final class FormView implements \ArrayAccess
11+
{
12+
public function offsetExists($offset): bool
13+
{
14+
return true;
15+
}
16+
17+
public function offsetGet($offset): self
18+
{
19+
return $this;
20+
}
21+
22+
public function offsetSet($offset, $value): void
23+
{
24+
}
25+
26+
public function offsetUnset($offset): void
27+
{
28+
}
29+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source;
6+
7+
final class Item
8+
{
9+
public string $name = 'item';
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector\Source;
6+
7+
final class Other
8+
{
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\TypeDeclaration\Rector\FunctionLike\AddClosureParamTypeFromVariableCallRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([AddClosureParamTypeFromVariableCallRector::class]);

0 commit comments

Comments
 (0)