Skip to content

Commit 1d80ef3

Browse files
authored
[TypeDeclaration] Add external types to ParamTypeFromStrictTypedPropertyRector (#5560)
1 parent 6421107 commit 1d80ef3

17 files changed

Lines changed: 228 additions & 22 deletions

File tree

.github/workflows/php_linter.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
actions:
1212
-
1313
php_version: 7.3
14-
run: vendor/bin/parallel-lint src bin/rector config tests packages rules --colors --exclude packages/rector-generator/templates --exclude rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source --exclude rules/autodiscovery/tests/Rector/FileNode/MoveInterfacesToContractNamespaceDirectoryRector/Expected --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithNativeProps.php --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithNativePropsPhp80.php --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithTypedPropertyTypes.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/Source/ExpectedSomeClassCopyEvent.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedFileManagerUploadEvent.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedDuplicatedEventParamsUploadEvent.php
14+
run: vendor/bin/parallel-lint src bin/rector config tests packages rules --colors --exclude packages/rector-generator/templates --exclude rules/psr4/tests/Rector/Namespace_/MultipleClassFileToPsr4ClassesRector/Source --exclude rules/autodiscovery/tests/Rector/FileNode/MoveInterfacesToContractNamespaceDirectoryRector/Expected --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithNativeProps.php --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithNativePropsPhp80.php --exclude packages/node-type-resolver/tests/PerNodeTypeResolver/PropertyFetchTypeResolver/Source/ClassWithTypedPropertyTypes.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/Source/ExpectedSomeClassCopyEvent.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedFileManagerUploadEvent.php --exclude rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedDuplicatedEventParamsUploadEvent.php --exclude rules/type-declaration/tests/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector/Source/OutOfControlExternalClass.php
1515

1616
-
1717
php_version: 8.0

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"nette/utils": "^3.2",
3838
"nikic/php-parser": "^4.10.4",
3939
"phpstan/phpdoc-parser": "^0.4.9",
40-
"phpstan/phpstan": "^0.12.69, <0.12.70",
40+
"phpstan/phpstan": "^0.12.69",
4141
"phpstan/phpstan-phpunit": "^0.12.17",
4242
"psr/simple-cache": "^1.0",
4343
"sebastian/diff": "^4.0.4",

phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,6 @@ parameters:
541541
# known values
542542
- '#Method Rector\\Testing\\Finder\\RectorsFinder\:\:findClassesInDirectoriesByName\(\) should return array<class\-string\> but returns array<int, \(int\|string\)\>#'
543543
- '#Content of method "collectVariableFromAssign\(\)" is duplicated with method "collectVariableFromAssign\(\)" in "Rector\\NetteToSymfony\\NodeAnalyzer\\ClassMethodRenderAnalyzer" class\. Use unique content or abstract service instead#'
544+
- '#Property PhpParser\\Node\\Param\:\:\$type \(PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType\|null\) does not accept PhpParser\\Node#'
545+
- '#Binary operation "\." between array\|string\|false and (.*?) results in an error#'
546+
- '#Parameter \#3 \.\.\.\$rest of function array_uintersect expects array, Closure\(PhpParser\\Node\\Param, PhpParser\\Node\\Param\)\: int given#'

rector.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
SetList::PHP_72,
5757
SetList::PHP_73,
5858
SetList::EARLY_RETURN,
59+
SetList::TYPE_DECLARATION_STRICT,
5960
]);
6061

6162
$parameters->set(Option::PATHS, [

rules/code-quality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
final class CompleteDynamicPropertiesRectorTest extends AbstractRectorTestCase
1212
{
1313
/**
14+
* @requires PHP 8.0
1415
* @dataProvider provideData()
1516
*/
1617
public function test(SmartFileInfo $fileInfo): void

rules/dead-code/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/RemoveDefaultArgumentValueRectorTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
final class RemoveDefaultArgumentValueRectorTest extends AbstractRectorTestCase
1313
{
1414
/**
15+
* @requires PHP 8.0
1516
* @dataProvider provideData()
1617
*/
1718
public function test(SmartFileInfo $fileInfo): void

rules/php56/tests/Rector/FunctionLike/AddDefaultValueForUndefinedVariableRector/AddDefaultValueForUndefinedVariableRectorTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
final class AddDefaultValueForUndefinedVariableRectorTest extends AbstractRectorTestCase
1313
{
1414
/**
15+
* @requires PHP 8.0
1516
* @dataProvider provideData()
1617
*/
1718
public function test(SmartFileInfo $fileInfo): void

rules/type-declaration/src/Rector/ClassMethod/ParamTypeFromStrictTypedPropertyRector.php

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@
55
namespace Rector\TypeDeclaration\Rector\ClassMethod;
66

77
use PhpParser\Node;
8+
use PhpParser\Node\Expr\ArrowFunction;
89
use PhpParser\Node\Expr\Assign;
10+
use PhpParser\Node\Expr\Closure;
911
use PhpParser\Node\Expr\PropertyFetch;
12+
use PhpParser\Node\FunctionLike;
1013
use PhpParser\Node\NullableType;
1114
use PhpParser\Node\Param;
1215
use PhpParser\Node\Stmt\ClassMethod;
16+
use PhpParser\Node\Stmt\Function_;
1317
use PhpParser\Node\Stmt\Property;
1418
use PhpParser\Node\UnionType;
1519
use PhpParser\NodeTraverser;
1620
use PHPStan\Type\Type;
1721
use Rector\Core\Rector\AbstractRector;
1822
use Rector\Core\ValueObject\PhpVersionFeature;
23+
use Rector\TypeDeclaration\Reflection\ReflectionTypeResolver;
1924
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
2025
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
2126
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@@ -30,9 +35,17 @@ final class ParamTypeFromStrictTypedPropertyRector extends AbstractRector
3035
*/
3136
private $simpleCallableNodeTraverser;
3237

33-
public function __construct(SimpleCallableNodeTraverser $simpleCallableNodeTraverser)
34-
{
38+
/**
39+
* @var ReflectionTypeResolver
40+
*/
41+
private $reflectionTypeResolver;
42+
43+
public function __construct(
44+
SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
45+
ReflectionTypeResolver $reflectionTypeResolver
46+
) {
3547
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
48+
$this->reflectionTypeResolver = $reflectionTypeResolver;
3649
}
3750

3851
public function getRuleDefinition(): RuleDefinition
@@ -73,11 +86,11 @@ public function setAge(int $age)
7386
*/
7487
public function getNodeTypes(): array
7588
{
76-
return [ClassMethod::class];
89+
return [ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class];
7790
}
7891

7992
/**
80-
* @param ClassMethod $node
93+
* @param ClassMethod|Function_|Closure|ArrowFunction $node
8194
*/
8295
public function refactor(Node $node): ?Node
8396
{
@@ -92,13 +105,16 @@ public function refactor(Node $node): ?Node
92105
return $node;
93106
}
94107

95-
public function decorateParamWithType(ClassMethod $classMethod, Param $param): void
108+
/**
109+
* @param ClassMethod|Function_|Closure|ArrowFunction $functionLike
110+
*/
111+
public function decorateParamWithType(FunctionLike $functionLike, Param $param): void
96112
{
97113
if ($param->type !== null) {
98114
return;
99115
}
100116

101-
$this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $classMethod->stmts, function (
117+
$this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), function (
102118
Node $node
103119
) use ($param): ?int {
104120
if (! $node instanceof Assign) {
@@ -113,22 +129,29 @@ public function decorateParamWithType(ClassMethod $classMethod, Param $param): v
113129
return null;
114130
}
115131

116-
$property = $this->matchPropertyWithSingleType($node->var);
117-
if (! $property instanceof Property) {
132+
$singlePropertyTypeNode = $this->matchPropertySingleTypeNode($node->var);
133+
if (! $singlePropertyTypeNode instanceof Node) {
118134
return null;
119135
}
120136

121-
$param->type = $property->type;
137+
$this->rectorChangeCollector->notifyNodeFileInfo($node);
138+
$param->type = $singlePropertyTypeNode;
122139

123140
return NodeTraverser::STOP_TRAVERSAL;
124141
});
125142
}
126143

127-
private function matchPropertyWithSingleType(PropertyFetch $propertyFetch): ?Property
144+
private function matchPropertySingleTypeNode(PropertyFetch $propertyFetch): ?Node
128145
{
129146
$property = $this->nodeRepository->findPropertyByPropertyFetch($propertyFetch);
130147
if (! $property instanceof Property) {
131-
return null;
148+
// code from /vendor
149+
$propertyFetchType = $this->reflectionTypeResolver->resolvePropertyFetchType($propertyFetch);
150+
if (! $propertyFetchType instanceof Type) {
151+
return null;
152+
}
153+
154+
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($propertyFetchType);
132155
}
133156

134157
if ($property->type === null) {
@@ -144,6 +167,6 @@ private function matchPropertyWithSingleType(PropertyFetch $propertyFetch): ?Pro
144167
return null;
145168
}
146169

147-
return $property;
170+
return $property->type;
148171
}
149172
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\TypeDeclaration\Reflection;
6+
7+
use PhpParser\Node\Expr\PropertyFetch;
8+
use PHPStan\Reflection\Php\PhpPropertyReflection;
9+
use PHPStan\Reflection\ReflectionProvider;
10+
use PHPStan\Type\Type;
11+
use PHPStan\Type\TypeWithClassName;
12+
use Rector\NodeNameResolver\NodeNameResolver;
13+
use Rector\NodeTypeResolver\Node\AttributeKey;
14+
use Rector\NodeTypeResolver\NodeTypeResolver;
15+
16+
final class ReflectionTypeResolver
17+
{
18+
/**
19+
* @var NodeTypeResolver
20+
*/
21+
private $nodeTypeResolver;
22+
23+
/**
24+
* @var ReflectionProvider
25+
*/
26+
private $reflectionProvider;
27+
28+
/**
29+
* @var NodeNameResolver
30+
*/
31+
private $nodeNameResolver;
32+
33+
public function __construct(
34+
NodeTypeResolver $nodeTypeResolver,
35+
ReflectionProvider $reflectionProvider,
36+
NodeNameResolver $nodeNameResolver
37+
) {
38+
$this->nodeTypeResolver = $nodeTypeResolver;
39+
$this->reflectionProvider = $reflectionProvider;
40+
$this->nodeNameResolver = $nodeNameResolver;
41+
}
42+
43+
public function resolvePropertyFetchType(PropertyFetch $propertyFetch): ?Type
44+
{
45+
$objectType = $this->nodeTypeResolver->resolve($propertyFetch->var);
46+
if (! $objectType instanceof TypeWithClassName) {
47+
return null;
48+
}
49+
50+
$classReflection = $this->reflectionProvider->getClass($objectType->getClassName());
51+
$propertyName = $this->nodeNameResolver->getName($propertyFetch);
52+
if ($propertyName === null) {
53+
return null;
54+
}
55+
56+
if ($classReflection->hasProperty($propertyName)) {
57+
$propertyFetchScope = $propertyFetch->getAttribute(AttributeKey::SCOPE);
58+
$propertyReflection = $classReflection->getProperty($propertyName, $propertyFetchScope);
59+
60+
if ($propertyReflection instanceof PhpPropertyReflection) {
61+
return $propertyReflection->getNativeType();
62+
}
63+
}
64+
65+
return null;
66+
}
67+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeFromStrictTypedPropertyRector\Fixture;
4+
5+
final class EvenConstructor
6+
{
7+
private int $age;
8+
9+
public function __construct($age)
10+
{
11+
$this->age = $age;
12+
}
13+
}
14+
15+
?>
16+
-----
17+
<?php
18+
19+
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeFromStrictTypedPropertyRector\Fixture;
20+
21+
final class EvenConstructor
22+
{
23+
private int $age;
24+
25+
public function __construct(int $age)
26+
{
27+
$this->age = $age;
28+
}
29+
}
30+
31+
?>

0 commit comments

Comments
 (0)