diff --git a/composer.json b/composer.json index 52f23c96f93d..79285fc16167 100644 --- a/composer.json +++ b/composer.json @@ -100,7 +100,8 @@ "Rector\\Utils\\RectorGenerator\\": "utils/RectorGenerator/src", "Rector\\StrictCodeQuality\\": "packages/StrictCodeQuality/src", "Rector\\DynamicTypeAnalysis\\": "packages/DynamicTypeAnalysis/src", - "Rector\\PhpDeglobalize\\": "packages/PhpDeglobalize/src" + "Rector\\PhpDeglobalize\\": "packages/PhpDeglobalize/src", + "Rector\\Php80\\": "packages/Php80/src" } }, "autoload-dev": { @@ -157,7 +158,8 @@ "Rector\\ZendToSymfony\\Tests\\": "packages/ZendToSymfony/tests", "Rector\\StrictCodeQuality\\Tests\\": "packages/StrictCodeQuality/tests", "Rector\\DynamicTypeAnalysis\\Tests\\": "packages/DynamicTypeAnalysis/tests", - "Rector\\PhpDeglobalize\\Tests\\": "packages/PhpDeglobalize/tests" + "Rector\\PhpDeglobalize\\Tests\\": "packages/PhpDeglobalize/tests", + "Rector\\Php80\\Tests\\": "packages/Php80/tests" }, "classmap": [ "packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource", diff --git a/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php index 198db9bc6824..7379b4130593 100644 --- a/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php +++ b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/CompleteDynamicPropertiesRectorTest.php @@ -27,4 +27,10 @@ protected function getRectorClass(): string { return CompleteDynamicPropertiesRector::class; } + + protected function getPhpVersion(): string + { + // prevents union types + return '7.4'; + } } diff --git a/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/FixtureUnionTypes/multiple_types.php.inc b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/FixtureUnionTypes/multiple_types.php.inc new file mode 100644 index 000000000000..2dc952643478 --- /dev/null +++ b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/FixtureUnionTypes/multiple_types.php.inc @@ -0,0 +1,36 @@ +value = 5; + + $this->value = 'hey'; + + $this->value = false; + } +} + +?> +----- +value = 5; + + $this->value = 'hey'; + + $this->value = false; + } +} + +?> diff --git a/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/UnionTypeCompleteDynamicPropertiesRectorTest.php b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/UnionTypeCompleteDynamicPropertiesRectorTest.php new file mode 100644 index 000000000000..ac2466dfd2b8 --- /dev/null +++ b/packages/CodeQuality/tests/Rector/Class_/CompleteDynamicPropertiesRector/UnionTypeCompleteDynamicPropertiesRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureUnionTypes'); + } + + protected function getRectorClass(): string + { + return CompleteDynamicPropertiesRector::class; + } +} diff --git a/packages/NodeTypeResolver/src/StaticTypeMapper.php b/packages/NodeTypeResolver/src/StaticTypeMapper.php index 5bdebaa3e430..7cae605695ed 100644 --- a/packages/NodeTypeResolver/src/StaticTypeMapper.php +++ b/packages/NodeTypeResolver/src/StaticTypeMapper.php @@ -12,6 +12,7 @@ use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\NullableType; +use PhpParser\Node\UnionType as PhpParserUnionType; use PHPStan\Analyser\Scope; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; @@ -127,7 +128,7 @@ public function mapPHPStanTypeToPHPStanPhpDocTypeNode(Type $phpStanType): TypeNo } /** - * @return Identifier|Name|NullableType|null + * @return Identifier|Name|NullableType|PhpParserUnionType|null */ public function mapPHPStanTypeToPhpParserNode(Type $phpStanType, ?string $kind = null): ?Node { @@ -260,6 +261,10 @@ public function mapPHPStanTypeToPhpParserNode(Type $phpStanType, ?string $kind = return $nullabledTypeNode; } + if ($nullabledTypeNode instanceof PhpParserUnionType) { + throw new ShouldNotHappenException(); + } + return new NullableType($nullabledTypeNode); } @@ -684,10 +689,15 @@ private function matchTypeForNullableUnionType(UnionType $unionType): ?Type } /** - * @return FullyQualified|null + * @return Name|FullyQualified|PhpParserUnionType|null */ private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node { + $phpParserUnionType = $this->matchPhpParserUnionType($unionType); + if ($phpParserUnionType !== null) { + return $phpParserUnionType; + } + // we need exactly one type foreach ($unionType->getTypes() as $unionedType) { if (! $unionedType instanceof TypeWithClassName) { @@ -751,4 +761,24 @@ private function mapScalarStringToType(string $scalarName): ?Type return null; } + + private function matchPhpParserUnionType(UnionType $unionType): ?PhpParserUnionType + { + if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::UNION_TYPES)) { + return null; + } + + $phpParserUnionedTypes = []; + foreach ($unionType->getTypes() as $unionedType) { + /** @var Identifier|Name|null $phpParserNode */ + $phpParserNode = $this->mapPHPStanTypeToPhpParserNode($unionedType); + if ($phpParserNode === null) { + return null; + } + + $phpParserUnionedTypes[] = $phpParserNode; + } + + return new PhpParserUnionType($phpParserUnionedTypes); + } } diff --git a/packages/Php74/src/Rector/Property/TypedPropertyRector.php b/packages/Php74/src/Rector/Property/TypedPropertyRector.php index 1205df845667..481eb749760e 100644 --- a/packages/Php74/src/Rector/Property/TypedPropertyRector.php +++ b/packages/Php74/src/Rector/Property/TypedPropertyRector.php @@ -36,19 +36,19 @@ public function getDefinition(): RectorDefinition [ new CodeSample( <<<'PHP' -final class SomeClass +final class SomeClass { - /** - * @var int + /** + * @var int */ - private count; + private count; } PHP , <<<'PHP' -final class SomeClass +final class SomeClass { - private int count; + private int count; } PHP ), diff --git a/packages/Php74/tests/Rector/Property/TypedPropertyRector/FixtureUnionTypes/two_types.php.inc b/packages/Php74/tests/Rector/Property/TypedPropertyRector/FixtureUnionTypes/two_types.php.inc new file mode 100644 index 000000000000..f735ed6d5d41 --- /dev/null +++ b/packages/Php74/tests/Rector/Property/TypedPropertyRector/FixtureUnionTypes/two_types.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/packages/Php74/tests/Rector/Property/TypedPropertyRector/UnionTypedPropertyRectorTest.php b/packages/Php74/tests/Rector/Property/TypedPropertyRector/UnionTypedPropertyRectorTest.php new file mode 100644 index 000000000000..fd64801668ba --- /dev/null +++ b/packages/Php74/tests/Rector/Property/TypedPropertyRector/UnionTypedPropertyRectorTest.php @@ -0,0 +1,36 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureUnionTypes'); + } + + protected function getRectorClass(): string + { + return TypedPropertyRector::class; + } + + protected function getPhpVersion(): string + { + return PhpVersionFeature::UNION_TYPES; + } +} diff --git a/packages/Php80/src/Rector/FunctionLike/UnionTypesRector.php b/packages/Php80/src/Rector/FunctionLike/UnionTypesRector.php new file mode 100644 index 000000000000..51d23fe5a0bd --- /dev/null +++ b/packages/Php80/src/Rector/FunctionLike/UnionTypesRector.php @@ -0,0 +1,131 @@ +getPhpDocInfo($node); + if ($phpDocInfo === null) { + return null; + } + + $this->refactorParamTypes($node, $phpDocInfo); + $this->refactorReturnType($node, $phpDocInfo); + + return $node; + } + + /** + * @param ClassMethod|Function_|Node\Expr\Closure|ArrowFunction $functionLike + */ + private function refactorReturnType(FunctionLike $functionLike, PhpDocInfo $phpDocInfo): void + { + // do not override existing return type + if ($functionLike->getReturnType() !== null) { + return; + } + + $returnType = $phpDocInfo->getReturnType(); + if (! $returnType instanceof UnionType) { + return; + } + + $phpParserUnionType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType); + if (! $phpParserUnionType instanceof PhpParserUnionType) { + return; + } + + $functionLike->returnType = $phpParserUnionType; + } + + /** + * @param ClassMethod|Function_|Node\Expr\Closure|ArrowFunction $functionLike + */ + private function refactorParamTypes(FunctionLike $functionLike, PhpDocInfo $phpDocInfo): void + { + foreach ($functionLike->params as $param) { + if ($param->type !== null) { + continue; + } + + /** @var string $paramName */ + $paramName = $this->getName($param->var); + $paramType = $phpDocInfo->getParamType($paramName); + if (! $paramType instanceof UnionType) { + continue; + } + + $phpParserUnionType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($paramType); + if (! $phpParserUnionType instanceof PhpParserUnionType) { + continue; + } + + $param->type = $phpParserUnionType; + } + } +} diff --git a/packages/Php80/tests/Rector/FunctionLike/UnionTypesRector/Fixture/fixture.php.inc b/packages/Php80/tests/Rector/FunctionLike/UnionTypesRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..35d5dd634533 --- /dev/null +++ b/packages/Php80/tests/Rector/FunctionLike/UnionTypesRector/Fixture/fixture.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/packages/Php80/tests/Rector/FunctionLike/UnionTypesRector/UnionTypesRectorTest.php b/packages/Php80/tests/Rector/FunctionLike/UnionTypesRector/UnionTypesRectorTest.php new file mode 100644 index 000000000000..de2b5f5606eb --- /dev/null +++ b/packages/Php80/tests/Rector/FunctionLike/UnionTypesRector/UnionTypesRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return UnionTypesRector::class; + } +} diff --git a/packages/TypeDeclaration/src/Rector/FunctionLike/AbstractTypeDeclarationRector.php b/packages/TypeDeclaration/src/Rector/FunctionLike/AbstractTypeDeclarationRector.php index 0fbac389d506..02a713076331 100644 --- a/packages/TypeDeclaration/src/Rector/FunctionLike/AbstractTypeDeclarationRector.php +++ b/packages/TypeDeclaration/src/Rector/FunctionLike/AbstractTypeDeclarationRector.php @@ -167,7 +167,7 @@ protected function isSubtypeOf(Node $possibleSubtype, Node $type): bool } /** - * @return Name|NullableType|Identifier|null + * @return Name|NullableType|Identifier|UnionType|null */ protected function resolveChildTypeNode(Type $type): ?Node { diff --git a/packages/TypeDeclaration/tests/Rector/ClassMethod/AddMethodCallBasedParamTypeRector/AddMethodCallBasedParamTypeRectorTest.php b/packages/TypeDeclaration/tests/Rector/ClassMethod/AddMethodCallBasedParamTypeRector/AddMethodCallBasedParamTypeRectorTest.php index a147555ba117..8a8c6567ee86 100644 --- a/packages/TypeDeclaration/tests/Rector/ClassMethod/AddMethodCallBasedParamTypeRector/AddMethodCallBasedParamTypeRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/ClassMethod/AddMethodCallBasedParamTypeRector/AddMethodCallBasedParamTypeRectorTest.php @@ -27,4 +27,10 @@ protected function getRectorClass(): string { return AddMethodCallBasedParamTypeRector::class; } + + protected function getPhpVersion(): string + { + // prevents union types + return '7.4'; + } } diff --git a/packages/TypeDeclaration/tests/Rector/Closure/AddClosureReturnTypeRector/AddClosureReturnTypeRectorTest.php b/packages/TypeDeclaration/tests/Rector/Closure/AddClosureReturnTypeRector/AddClosureReturnTypeRectorTest.php index 0ec697b13030..95734b6a4020 100644 --- a/packages/TypeDeclaration/tests/Rector/Closure/AddClosureReturnTypeRector/AddClosureReturnTypeRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/Closure/AddClosureReturnTypeRector/AddClosureReturnTypeRectorTest.php @@ -27,4 +27,10 @@ protected function getRectorClass(): string { return AddClosureReturnTypeRector::class; } + + protected function getPhpVersion(): string + { + // prevent union types + return '7.4'; + } } diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/Fixture/undesired.php.inc b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/Fixture/undesired.php.inc index 97f35aa417fd..f9efb2a4b600 100644 --- a/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/Fixture/undesired.php.inc +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/Fixture/undesired.php.inc @@ -9,6 +9,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe */ function someFunction($a, $b, $c) { } + ?> ----- diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/FixtureUnionType/undesired.php.inc b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/FixtureUnionType/undesired.php.inc new file mode 100644 index 000000000000..1b720f7c26a7 --- /dev/null +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/FixtureUnionType/undesired.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/ParamTypeDeclarationRectorTest.php b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/ParamTypeDeclarationRectorTest.php index 9c44f3eb0db5..082d63fb7ac1 100644 --- a/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/ParamTypeDeclarationRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/ParamTypeDeclarationRectorTest.php @@ -27,4 +27,10 @@ protected function getRectorClass(): string { return ParamTypeDeclarationRector::class; } + + protected function getPhpVersion(): string + { + // prevent union types + return '7.4'; + } } diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/UnionTypeParamTypeDeclarationRectorTest.php b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/UnionTypeParamTypeDeclarationRectorTest.php new file mode 100644 index 000000000000..cdb0afbf7b99 --- /dev/null +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ParamTypeDeclarationRector/UnionTypeParamTypeDeclarationRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureUnionType'); + } + + protected function getRectorClass(): string + { + return ParamTypeDeclarationRector::class; + } +} diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/CorrectionTest.php b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/CorrectionTest.php index ffa4b6bb8133..8591ccbea3d7 100644 --- a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/CorrectionTest.php +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/CorrectionTest.php @@ -27,4 +27,10 @@ protected function getRectorClass(): string { return ReturnTypeDeclarationRector::class; } + + protected function getPhpVersion(): string + { + // to prevent union types + return '7.4'; + } } diff --git a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php index a99781522a51..56d369523e3d 100644 --- a/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/FunctionLike/ReturnTypeDeclarationRector/ReturnTypeDeclarationRectorTest.php @@ -27,4 +27,10 @@ protected function getRectorClass(): string { return ReturnTypeDeclarationRector::class; } + + protected function getPhpVersion(): string + { + // to prevent union types + return '7.4'; + } } diff --git a/phpstan.neon b/phpstan.neon index 3860f480a5ec..26acc0956f21 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -224,3 +224,5 @@ parameters: # known value - '#Parameter \#1 \$name of method Rector\\CodingStyle\\Naming\\ClassNaming\:\:getShortName\(\) expects PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|string, PhpParser\\Node\\Identifier\|null given#' + + - '#Parameter \#1 \$type of method PhpParser\\Builder\\Param\:\:setType\(\) expects PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|string, PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType given#' diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index d91ea514808a..f411a5f02470 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -35,4 +35,9 @@ final class PhpVersionFeature * @var string */ public const OBJECT_TYPE = '7.2'; + + /** + * @var string + */ + public const UNION_TYPES = '8.0'; } diff --git a/utils/RectorGenerator/src/Configuration/ConfigurationFactory.php b/utils/RectorGenerator/src/Configuration/ConfigurationFactory.php index a3fdcc9293bd..615bd7d9b4f0 100644 --- a/utils/RectorGenerator/src/Configuration/ConfigurationFactory.php +++ b/utils/RectorGenerator/src/Configuration/ConfigurationFactory.php @@ -7,6 +7,7 @@ use Nette\Utils\Strings; use PhpParser\Node; use Rector\Exception\FileSystem\FileNotFoundException; +use Rector\Exception\ShouldNotHappenException; use Rector\Set\Set; use Rector\Utils\RectorGenerator\Exception\ConfigurationException; use Rector\Utils\RectorGenerator\Node\NodeClassProvider; @@ -114,6 +115,8 @@ private function resolveFullyQualifiedNodeTypes(array $nodeTypes): array continue 2; } } + + throw new ShouldNotHappenException(sprintf('Node endings with "%s" was not found', $nodeType)); } return array_unique($fqnNodeTypes);