Skip to content

Commit 943e691

Browse files
committed
[type-declaration] Add AddParamTypeFromStrictMethodCallPassRector
1 parent b3d0a37 commit 943e691

10 files changed

Lines changed: 325 additions & 33 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\ClassMethod\AddParamTypeFromStrictMethodCallPassRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class AddParamTypeFromStrictMethodCallPassRectorTest 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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromStrictMethodCallPassRector\Fixture;
4+
5+
final class SomeClass
6+
{
7+
public function setValue($number)
8+
{
9+
$this->checkInt($number);
10+
}
11+
12+
private function checkInt(int $integer)
13+
{
14+
}
15+
}
16+
17+
?>
18+
-----
19+
<?php
20+
21+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromStrictMethodCallPassRector\Fixture;
22+
23+
final class SomeClass
24+
{
25+
public function setValue(int $number)
26+
{
27+
$this->checkInt($number);
28+
}
29+
30+
private function checkInt(int $integer)
31+
{
32+
}
33+
}
34+
35+
?>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromStrictMethodCallPassRector\Fixture;
4+
5+
final class SomeClass
6+
{
7+
public function setValue($number)
8+
{
9+
if (is_string($number)) {
10+
return;
11+
}
12+
13+
$this->checkInt($number);
14+
}
15+
16+
private function checkInt(int $integer)
17+
{
18+
}
19+
}
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\ClassMethod\AddParamTypeFromStrictMethodCallPassRector;
7+
8+
return RectorConfig::configure()
9+
->withRules([AddParamTypeFromStrictMethodCallPassRector::class]);

rules/DeadCode/Rector/Stmt/RemoveConditionExactReturnRector.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
use PhpParser\Node\Expr;
99
use PhpParser\Node\Expr\BinaryOp\Equal;
1010
use PhpParser\Node\Expr\BinaryOp\Identical;
11-
use PhpParser\Node\Expr\MethodCall;
12-
use PhpParser\Node\Expr\StaticCall;
1311
use PhpParser\Node\Stmt\If_;
1412
use PhpParser\Node\Stmt\Return_;
1513
use Rector\Contract\PhpParser\Node\StmtsAwareInterface;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclaration\NodeTypeAnalyzer;
6+
7+
use PhpParser\Node\Expr\MethodCall;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Reflection\ParametersAcceptorSelector;
10+
use PHPStan\Type\Type;
11+
use Rector\Reflection\ReflectionResolver;
12+
13+
final readonly class MethodCallParamTypeResolver
14+
{
15+
public function __construct(
16+
private ReflectionResolver $reflectionResolver,
17+
) {
18+
}
19+
20+
/**
21+
* @return array<int, Type>
22+
*/
23+
public function resolve(MethodCall $methodCall): array
24+
{
25+
$methodReflection = $this->reflectionResolver->resolveMethodReflectionFromMethodCall($methodCall);
26+
if (! $methodReflection instanceof MethodReflection) {
27+
return [];
28+
}
29+
30+
$extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants());
31+
32+
$typeByPosition = [];
33+
foreach ($extendedParametersAcceptor->getParameters() as $position => $parameterReflection) {
34+
$typeByPosition[$position] = $parameterReflection->getNativeType();
35+
}
36+
37+
return $typeByPosition;
38+
}
39+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclaration\Rector\ClassMethod;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PhpParser\Node\Expr\Variable;
10+
use PhpParser\Node\Stmt\ClassMethod;
11+
use PhpParser\Node\Stmt\Function_;
12+
use PHPStan\Type\Type;
13+
use Rector\PhpParser\Node\BetterNodeFinder;
14+
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
15+
use Rector\Rector\AbstractRector;
16+
use Rector\StaticTypeMapper\StaticTypeMapper;
17+
use Rector\TypeDeclaration\NodeTypeAnalyzer\MethodCallParamTypeResolver;
18+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
19+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
20+
21+
/**
22+
* @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromStrictMethodCallPassRector\AddParamTypeFromStrictMethodCallPassRectorTest
23+
*/
24+
final class AddParamTypeFromStrictMethodCallPassRector extends AbstractRector
25+
{
26+
public function __construct(
27+
private readonly BetterNodeFinder $betterNodeFinder,
28+
private readonly MethodCallParamTypeResolver $methodCallParamTypeResolver,
29+
private readonly StaticTypeMapper $staticTypeMapper,
30+
) {
31+
}
32+
33+
public function getRuleDefinition(): RuleDefinition
34+
{
35+
return new RuleDefinition(
36+
'Change param type from strict type of passed-to method call',
37+
[
38+
new CodeSample(
39+
<<<'CODE_SAMPLE'
40+
final class SomeClass
41+
{
42+
public function run($value)
43+
{
44+
$this->resolve($value);
45+
}
46+
47+
private function resolve(int $value)
48+
{
49+
}
50+
}
51+
CODE_SAMPLE
52+
,
53+
<<<'CODE_SAMPLE'
54+
final class SomeClass
55+
{
56+
public function run(int $value)
57+
{
58+
$this->resolve($value);
59+
}
60+
61+
private function resolve(int $value)
62+
{
63+
}
64+
}
65+
CODE_SAMPLE
66+
),
67+
68+
]
69+
);
70+
}
71+
72+
/**
73+
* @return array<class-string<Node>>
74+
*/
75+
public function getNodeTypes(): array
76+
{
77+
return [ClassMethod::class, Function_::class, Node\Expr\Closure::class];
78+
}
79+
80+
/**
81+
* @param ClassMethod|Function_|Node\Expr\Closure $node
82+
*/
83+
public function refactor(Node $node): ?Node
84+
{
85+
if ($node->getParams() === []) {
86+
return null;
87+
}
88+
89+
if ($node instanceof ClassMethod && $node->stmts === null) {
90+
return null;
91+
}
92+
93+
$hasChanged = false;
94+
95+
$usedVariables = $this->betterNodeFinder->findInstancesOfScoped((array) $node->stmts, Variable::class);
96+
97+
foreach ($node->getParams() as $param) {
98+
// already known type
99+
if ($param->type instanceof Node) {
100+
continue;
101+
}
102+
103+
/** @var string $paramName */
104+
$paramName = $this->getName($param->var);
105+
106+
$paramVariables = array_filter(
107+
$usedVariables,
108+
fn (Variable $variable): bool => $this->isName($variable, $paramName)
109+
);
110+
111+
if (count($paramVariables) >= 2) {
112+
// skip for now, as we look for sole use
113+
continue;
114+
}
115+
116+
$methodCalls = $this->betterNodeFinder->findInstancesOfScoped((array) $node->stmts, MethodCall::class);
117+
foreach ($methodCalls as $methodCall) {
118+
if ($methodCall->isFirstClassCallable()) {
119+
continue;
120+
}
121+
122+
$typesByPosition = $this->methodCallParamTypeResolver->resolve($methodCall);
123+
124+
$usedPosition = $this->matchParamMethodCallUsedPosition($methodCall, $paramName);
125+
if (! is_int($usedPosition)) {
126+
continue;
127+
}
128+
129+
$paramType = $typesByPosition[$usedPosition] ?? null;
130+
if (! $paramType instanceof Type) {
131+
continue;
132+
}
133+
134+
$paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($paramType, TypeKind::PARAM);
135+
if (! $paramTypeNode instanceof Node) {
136+
continue;
137+
}
138+
139+
$param->type = $paramTypeNode;
140+
$hasChanged = true;
141+
142+
// go to next param
143+
continue 2;
144+
}
145+
}
146+
147+
return null;
148+
}
149+
150+
private function matchParamMethodCallUsedPosition(MethodCall $methodCall, string $paramName): int|null
151+
{
152+
foreach ($methodCall->getArgs() as $position => $arg) {
153+
if (! $arg->value instanceof Variable) {
154+
continue;
155+
}
156+
157+
if (! $this->isName($arg->value, $paramName)) {
158+
continue;
159+
}
160+
161+
return $position;
162+
}
163+
164+
return null;
165+
}
166+
}

rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromIterableMethodCallRector.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,11 @@ public function refactor(Node $node): ?Node
9595
}
9696

9797
$varType = $this->getType($node->var);
98-
9998
if (! $varType instanceof IntersectionType || ! $varType->isIterable()->yes()) {
10099
return null;
101100
}
102101

103102
$className = $varType->getObjectClassNames()[0] ?? null;
104-
105103
if ($className === null) {
106104
return null;
107105
}

src/Config/Level/TypeDeclarationLevel.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamStringTypeFromSprintfUseRector;
2121
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeBasedOnPHPUnitDataProviderRector;
2222
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromPropertyTypeRector;
23+
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromStrictMethodCallPassRector;
2324
use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeDeclarationBasedOnParentClassMethodRector;
2425
use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeFromTryCatchTypeRector;
2526
use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector;
@@ -149,6 +150,7 @@ final class TypeDeclarationLevel
149150
// array parameter from dim fetch assign inside
150151
StrictArrayParamDimFetchRector::class,
151152
AddParamFromDimFetchKeyUseRector::class,
153+
AddParamTypeFromStrictMethodCallPassRector::class,
152154
AddParamStringTypeFromSprintfUseRector::class,
153155

154156
// possibly based on docblocks, but also helpful, intentionally last

0 commit comments

Comments
 (0)