From c6f56fb977b078c1c401d5a12740df7ad1dbbd8a Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Thu, 3 Dec 2020 18:41:51 +0800 Subject: [PATCH 01/15] Set-up rector structure --- ...wngradeContravariantArgumentTypeRector.php | 166 ++++++++++++++++++ ...adeContravariantArgumentTypeRectorTest.php | 38 ++++ .../Fixture/fixture.php.inc | 44 +++++ src/ValueObject/PhpVersionFeature.php | 6 + 4 files changed, 254 insertions(+) create mode 100644 rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php create mode 100644 rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/DowngradeContravariantArgumentTypeRectorTest.php create mode 100644 rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/fixture.php.inc diff --git a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php new file mode 100644 index 000000000000..fe214ec1297e --- /dev/null +++ b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php @@ -0,0 +1,166 @@ +shouldRefactor($node)) { + return null; + } + + /** @var string */ + $parentReflectionMethodClassname = $this->getDifferentReturnTypeClassnameFromAncestorClass($node); + $newType = new FullyQualified($parentReflectionMethodClassname); + + // Make it nullable? + if ($node->returnType instanceof NullableType) { + $newType = new NullableType($newType); + } + + // Add the docblock before changing the type + $this->addDocBlockReturn($node); + + $node->returnType = $newType; + + return $node; + } + + private function shouldRefactor(ClassMethod $classMethod): bool + { + return $this->getDifferentReturnTypeClassnameFromAncestorClass($classMethod) !== null; + } + + private function getDifferentReturnTypeClassnameFromAncestorClass(ClassMethod $classMethod): ?string + { + /** @var Scope|null $scope */ + $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + if ($scope === null) { + // possibly trait + return null; + } + + $classReflection = $scope->getClassReflection(); + if ($classReflection === null) { + return null; + } + + $nodeReturnType = $classMethod->returnType; + if ($nodeReturnType === null || $nodeReturnType instanceof UnionType) { + return null; + } + $nodeReturnTypeName = $this->getName($nodeReturnType); + + /** @var string $methodName */ + $methodName = $this->getName($classMethod->name); + + foreach ($classReflection->getParentClassesNames() as $parentClassName) { + if (! method_exists($parentClassName, $methodName)) { + continue; + } + + $parentReflectionMethod = new ReflectionMethod($parentClassName, $methodName); + /** @var ReflectionNamedType|null */ + $parentReflectionMethodReturnType = $parentReflectionMethod->getReturnType(); + if ($parentReflectionMethodReturnType === null || $parentReflectionMethodReturnType->getName() === $nodeReturnTypeName) { + continue; + } + + // This is an ancestor class with a different return type + return $parentReflectionMethodReturnType->getName(); + } + + return null; + } + + private function addDocBlockReturn(ClassMethod $classMethod): void + { + /** @var PhpDocInfo|null */ + $phpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { + $phpDocInfo = $this->phpDocInfoFactory->createEmpty($classMethod); + } + + /** @var Node */ + $returnType = $classMethod->returnType; + $type = $this->staticTypeMapper->mapPhpParserNodePHPStanType($returnType); + $phpDocInfo->changeReturnType($type); + } +} diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/DowngradeContravariantArgumentTypeRectorTest.php b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/DowngradeContravariantArgumentTypeRectorTest.php new file mode 100644 index 000000000000..1b3bc61ebc89 --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/DowngradeContravariantArgumentTypeRectorTest.php @@ -0,0 +1,38 @@ +doTestFileInfo($fileInfo); + } + + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return DowngradeContravariantArgumentTypeRector::class; + } + + protected function getPhpVersion(): int + { + return PhpVersionFeature::CONTRAVARIANT_ARGUMENT - 1; + } +} diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/fixture.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..839cffea4721 --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/fixture.php.inc @@ -0,0 +1,44 @@ + +----- + diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index 303f81285b4d..c5c707ac01c8 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -142,6 +142,12 @@ final class PhpVersionFeature */ public const COVARIANT_RETURN = 70400; + /** + * @see https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters + * @var int + */ + public const CONTRAVARIANT_ARGUMENT = 70400; + /** * @var int */ From 98986ab1c15a26a3cfbd061df8d7338287fccf07 Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 12:50:29 +0800 Subject: [PATCH 02/15] Added param functionLike --- .../Rector/DowngradeParamDeclarationRectorInterface.php | 3 ++- .../FunctionLike/AbstractDowngradeParamDeclarationRector.php | 2 +- .../DowngradeIterablePseudoTypeParamDeclarationRector.php | 3 ++- .../DowngradeNullableTypeParamDeclarationRector.php | 3 ++- .../AbstractDowngradeParamTypeDeclarationRector.php | 3 ++- .../FunctionLike/DowngradeUnionTypeParamDeclarationRector.php | 3 ++- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/rules/downgrade-php71/src/Contract/Rector/DowngradeParamDeclarationRectorInterface.php b/rules/downgrade-php71/src/Contract/Rector/DowngradeParamDeclarationRectorInterface.php index 709711773497..33c9af1c92cf 100644 --- a/rules/downgrade-php71/src/Contract/Rector/DowngradeParamDeclarationRectorInterface.php +++ b/rules/downgrade-php71/src/Contract/Rector/DowngradeParamDeclarationRectorInterface.php @@ -4,6 +4,7 @@ namespace Rector\DowngradePhp71\Contract\Rector; +use PhpParser\Node\FunctionLike; use PhpParser\Node\Param; interface DowngradeParamDeclarationRectorInterface @@ -11,5 +12,5 @@ interface DowngradeParamDeclarationRectorInterface /** * Indicate if the parameter must be removed */ - public function shouldRemoveParamDeclaration(Param $param): bool; + public function shouldRemoveParamDeclaration(Param $param, FunctionLike $functionLike): bool; } diff --git a/rules/downgrade-php71/src/Rector/FunctionLike/AbstractDowngradeParamDeclarationRector.php b/rules/downgrade-php71/src/Rector/FunctionLike/AbstractDowngradeParamDeclarationRector.php index 67137c5b0031..3663aad110bf 100644 --- a/rules/downgrade-php71/src/Rector/FunctionLike/AbstractDowngradeParamDeclarationRector.php +++ b/rules/downgrade-php71/src/Rector/FunctionLike/AbstractDowngradeParamDeclarationRector.php @@ -49,7 +49,7 @@ public function refactor(Node $node): ?Node */ private function refactorParam(Param $param, FunctionLike $functionLike): void { - if (! $this->shouldRemoveParamDeclaration($param)) { + if (! $this->shouldRemoveParamDeclaration($param, $functionLike)) { return; } diff --git a/rules/downgrade-php71/src/Rector/FunctionLike/DowngradeIterablePseudoTypeParamDeclarationRector.php b/rules/downgrade-php71/src/Rector/FunctionLike/DowngradeIterablePseudoTypeParamDeclarationRector.php index 4d4e8786cdc4..abb732ef1757 100644 --- a/rules/downgrade-php71/src/Rector/FunctionLike/DowngradeIterablePseudoTypeParamDeclarationRector.php +++ b/rules/downgrade-php71/src/Rector/FunctionLike/DowngradeIterablePseudoTypeParamDeclarationRector.php @@ -4,6 +4,7 @@ namespace Rector\DowngradePhp71\Rector\FunctionLike; +use PhpParser\Node\FunctionLike; use PhpParser\Node\Identifier; use PhpParser\Node\Param; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; @@ -55,7 +56,7 @@ public function run($iterator) ); } - public function shouldRemoveParamDeclaration(Param $param): bool + public function shouldRemoveParamDeclaration(Param $param, FunctionLike $functionLike): bool { if ($param->type === null) { return false; diff --git a/rules/downgrade-php71/src/Rector/FunctionLike/DowngradeNullableTypeParamDeclarationRector.php b/rules/downgrade-php71/src/Rector/FunctionLike/DowngradeNullableTypeParamDeclarationRector.php index 326f65413f76..e8d387c77b98 100644 --- a/rules/downgrade-php71/src/Rector/FunctionLike/DowngradeNullableTypeParamDeclarationRector.php +++ b/rules/downgrade-php71/src/Rector/FunctionLike/DowngradeNullableTypeParamDeclarationRector.php @@ -4,6 +4,7 @@ namespace Rector\DowngradePhp71\Rector\FunctionLike; +use PhpParser\Node\FunctionLike; use PhpParser\Node\NullableType; use PhpParser\Node\Param; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; @@ -55,7 +56,7 @@ public function run($input) ); } - public function shouldRemoveParamDeclaration(Param $param): bool + public function shouldRemoveParamDeclaration(Param $param, FunctionLike $functionLike): bool { if ($param->variadic) { return false; diff --git a/rules/downgrade-php72/src/Rector/FunctionLike/AbstractDowngradeParamTypeDeclarationRector.php b/rules/downgrade-php72/src/Rector/FunctionLike/AbstractDowngradeParamTypeDeclarationRector.php index 96918cb37c57..334c71d71c81 100644 --- a/rules/downgrade-php72/src/Rector/FunctionLike/AbstractDowngradeParamTypeDeclarationRector.php +++ b/rules/downgrade-php72/src/Rector/FunctionLike/AbstractDowngradeParamTypeDeclarationRector.php @@ -4,6 +4,7 @@ namespace Rector\DowngradePhp72\Rector\FunctionLike; +use PhpParser\Node\FunctionLike; use PhpParser\Node\Identifier; use PhpParser\Node\NullableType; use PhpParser\Node\Param; @@ -12,7 +13,7 @@ abstract class AbstractDowngradeParamTypeDeclarationRector extends AbstractDowngradeParamDeclarationRector implements DowngradeTypeRectorInterface { - public function shouldRemoveParamDeclaration(Param $param): bool + public function shouldRemoveParamDeclaration(Param $param, FunctionLike $functionLike): bool { if ($param->variadic) { return false; diff --git a/rules/downgrade-php80/src/Rector/FunctionLike/DowngradeUnionTypeParamDeclarationRector.php b/rules/downgrade-php80/src/Rector/FunctionLike/DowngradeUnionTypeParamDeclarationRector.php index 9d8f458daa24..448b30878cc6 100644 --- a/rules/downgrade-php80/src/Rector/FunctionLike/DowngradeUnionTypeParamDeclarationRector.php +++ b/rules/downgrade-php80/src/Rector/FunctionLike/DowngradeUnionTypeParamDeclarationRector.php @@ -4,6 +4,7 @@ namespace Rector\DowngradePhp80\Rector\FunctionLike; +use PhpParser\Node\FunctionLike; use PhpParser\Node\Param; use PhpParser\Node\UnionType; use Rector\DowngradePhp71\Rector\FunctionLike\AbstractDowngradeParamDeclarationRector; @@ -56,7 +57,7 @@ public function echoInput($input) ); } - public function shouldRemoveParamDeclaration(Param $param): bool + public function shouldRemoveParamDeclaration(Param $param, FunctionLike $functionLike): bool { if ($param->variadic) { return false; From 33a558d3ce66f5fdd89e03cbcf70c2c584468218 Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 12:50:45 +0800 Subject: [PATCH 03/15] Implemented rector --- ...wngradeContravariantArgumentTypeRector.php | 119 ++++++++---------- 1 file changed, 51 insertions(+), 68 deletions(-) diff --git a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php index fe214ec1297e..bf1178e0cb8e 100644 --- a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php +++ b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php @@ -4,26 +4,26 @@ namespace Rector\DowngradePhp74\Rector\ClassMethod; -use PhpParser\Node; -use PhpParser\Node\Name\FullyQualified; +use ReflectionMethod; +use ReflectionNamedType; +use ReflectionParameter; +use PhpParser\Node\Param; +use PHPStan\Analyser\Scope; +use PhpParser\Node\UnionType; +use PhpParser\Node\FunctionLike; use PhpParser\Node\NullableType; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\UnionType; -use PHPStan\Analyser\Scope; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; -use Rector\Core\Rector\AbstractRector; use Rector\NodeTypeResolver\Node\AttributeKey; -use ReflectionMethod; -use ReflectionNamedType; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Rector\DowngradePhp71\Rector\FunctionLike\AbstractDowngradeParamDeclarationRector; /** * @see https://www.php.net/manual/en/language.oop5.variance.php#language.oop5.variance.contravariance * * @see \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravarianArgumentTypeRector\DowngradeContravarianArgumentTypeRectorTest */ -final class DowngradeContravariantArgumentTypeRector extends AbstractRector +final class DowngradeContravariantArgumentTypeRector extends AbstractDowngradeParamDeclarationRector { public function getRuleDefinition(): RuleDefinition { @@ -69,49 +69,32 @@ public function contraVariantArguments($type) ]); } - /** - * @return string[] - */ - public function getNodeTypes(): array + public function shouldRemoveParamDeclaration(Param $param, FunctionLike $functionLike): bool { - return [ClassMethod::class]; - } - - /** - * @param ClassMethod $node - */ - public function refactor(Node $node): ?Node - { - if (! $this->shouldRefactor($node)) { - return null; + if ($param->variadic) { + return false; } - /** @var string */ - $parentReflectionMethodClassname = $this->getDifferentReturnTypeClassnameFromAncestorClass($node); - $newType = new FullyQualified($parentReflectionMethodClassname); - - // Make it nullable? - if ($node->returnType instanceof NullableType) { - $newType = new NullableType($newType); + if ($param->type === null) { + return false; } - // Add the docblock before changing the type - $this->addDocBlockReturn($node); - - $node->returnType = $newType; + // Don't consider for Union types + if ($param->type instanceof UnionType) { + return false; + } - return $node; + // Check if the type is different from the one declared in some ancestor + return $this->getDifferentParamTypeFromAncestorClass($param, $functionLike) !== null; } - private function shouldRefactor(ClassMethod $classMethod): bool - { - return $this->getDifferentReturnTypeClassnameFromAncestorClass($classMethod) !== null; - } - - private function getDifferentReturnTypeClassnameFromAncestorClass(ClassMethod $classMethod): ?string + /** + * @param ClassMethod|Function_ $functionLike + */ + private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLike $functionLike): ?string { /** @var Scope|null $scope */ - $scope = $classMethod->getAttribute(AttributeKey::SCOPE); + $scope = $functionLike->getAttribute(AttributeKey::SCOPE); if ($scope === null) { // possibly trait return null; @@ -122,45 +105,45 @@ private function getDifferentReturnTypeClassnameFromAncestorClass(ClassMethod $c return null; } - $nodeReturnType = $classMethod->returnType; - if ($nodeReturnType === null || $nodeReturnType instanceof UnionType) { - return null; + $paramName = $this->getName($param); + + // If it is the NullableType, extract the name from its inner type + $isNullableType = $param->type instanceof NullableType; + if ($isNullableType) { + /** @var NullableType */ + $nullableType = $param->type; + $paramTypeName = $this->getName($nullableType->type); + } else { + $paramTypeName = $this->getName($param->type); } - $nodeReturnTypeName = $this->getName($nodeReturnType); /** @var string $methodName */ - $methodName = $this->getName($classMethod->name); + $methodName = $this->getName($functionLike->name); foreach ($classReflection->getParentClassesNames() as $parentClassName) { if (! method_exists($parentClassName, $methodName)) { continue; } + // Find the param we're looking for $parentReflectionMethod = new ReflectionMethod($parentClassName, $methodName); - /** @var ReflectionNamedType|null */ - $parentReflectionMethodReturnType = $parentReflectionMethod->getReturnType(); - if ($parentReflectionMethodReturnType === null || $parentReflectionMethodReturnType->getName() === $nodeReturnTypeName) { + /** @var ReflectionParameter[] */ + $parentReflectionMethodParams = $parentReflectionMethod->getParameters(); + if ($parentReflectionMethodParams === null) { continue; } - - // This is an ancestor class with a different return type - return $parentReflectionMethodReturnType->getName(); + foreach ($parentReflectionMethodParams as $reflectionParameter) { + if ($reflectionParameter->name == $paramName) { + /** @var ReflectionNamedType */ + $reflectionParamType = $reflectionParameter->getType(); + if ($reflectionParamType->getName() != $paramTypeName) { + // We found it: a different param type in some ancestor + return $reflectionParamType->getName(); + } + } + } } return null; } - - private function addDocBlockReturn(ClassMethod $classMethod): void - { - /** @var PhpDocInfo|null */ - $phpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); - if ($phpDocInfo === null) { - $phpDocInfo = $this->phpDocInfoFactory->createEmpty($classMethod); - } - - /** @var Node */ - $returnType = $classMethod->returnType; - $type = $this->staticTypeMapper->mapPhpParserNodePHPStanType($returnType); - $phpDocInfo->changeReturnType($type); - } } From ca9c7384e95d85f44bb45892ff08510ce691aec6 Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 12:51:01 +0800 Subject: [PATCH 04/15] Use fully qualified type --- .../Fixture/fixture.php.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/fixture.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/fixture.php.inc index 839cffea4721..5600f5978ee7 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/fixture.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/fixture.php.inc @@ -35,7 +35,7 @@ class A class B extends A { /** - * @param ParentType $type + * @param \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector\Fixture\ParentType $type */ public function contraVariantArguments($type) { /* … */ } From 05ce23817ecc9a72cb76824e4461548bea2ffd4d Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 13:10:39 +0800 Subject: [PATCH 05/15] Fixed phpstan errors --- ...wngradeContravariantArgumentTypeRector.php | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php index bf1178e0cb8e..5876532956c6 100644 --- a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php +++ b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php @@ -7,28 +7,30 @@ use ReflectionMethod; use ReflectionNamedType; use ReflectionParameter; +use PhpParser\Node; use PhpParser\Node\Param; use PHPStan\Analyser\Scope; use PhpParser\Node\UnionType; use PhpParser\Node\FunctionLike; use PhpParser\Node\NullableType; use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; use Rector\NodeTypeResolver\Node\AttributeKey; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Rector\DowngradePhp71\Rector\FunctionLike\AbstractDowngradeParamDeclarationRector; /** * @see https://www.php.net/manual/en/language.oop5.variance.php#language.oop5.variance.contravariance * - * @see \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravarianArgumentTypeRector\DowngradeContravarianArgumentTypeRectorTest + * @see \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector\DowngradeContravariantArgumentTypeRectorTest */ final class DowngradeContravariantArgumentTypeRector extends AbstractDowngradeParamDeclarationRector { public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Remove contravariant argument type declarations', [ - new CodeSample( + new ConfiguredCodeSample( <<<'CODE_SAMPLE' class ParentType {} class ChildType extends ParentType {} @@ -65,6 +67,10 @@ public function contraVariantArguments($type) { /* … */ } } CODE_SAMPLE +, + [ + self::ADD_DOC_BLOCK => true, + ] ), ]); } @@ -88,9 +94,6 @@ public function shouldRemoveParamDeclaration(Param $param, FunctionLike $functio return $this->getDifferentParamTypeFromAncestorClass($param, $functionLike) !== null; } - /** - * @param ClassMethod|Function_ $functionLike - */ private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLike $functionLike): ?string { /** @var Scope|null $scope */ @@ -108,17 +111,22 @@ private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLi $paramName = $this->getName($param); // If it is the NullableType, extract the name from its inner type + /** @var Node */ + $paramType = $param->type; $isNullableType = $param->type instanceof NullableType; if ($isNullableType) { /** @var NullableType */ - $nullableType = $param->type; + $nullableType = $paramType; $paramTypeName = $this->getName($nullableType->type); } else { - $paramTypeName = $this->getName($param->type); + $paramTypeName = $this->getName($paramType); + } + if ($paramTypeName === null) { + return null; } /** @var string $methodName */ - $methodName = $this->getName($functionLike->name); + $methodName = $this->getName($functionLike); foreach ($classReflection->getParentClassesNames() as $parentClassName) { if (! method_exists($parentClassName, $methodName)) { @@ -127,19 +135,26 @@ private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLi // Find the param we're looking for $parentReflectionMethod = new ReflectionMethod($parentClassName, $methodName); - /** @var ReflectionParameter[] */ - $parentReflectionMethodParams = $parentReflectionMethod->getParameters(); - if ($parentReflectionMethodParams === null) { - continue; + $differentAncestorParamTypeName = $this->getDifferentParamTypeFromReflectionMethod($parentReflectionMethod, $paramName, $paramTypeName); + if ($differentAncestorParamTypeName !== null) { + return $differentAncestorParamTypeName; } - foreach ($parentReflectionMethodParams as $reflectionParameter) { - if ($reflectionParameter->name == $paramName) { - /** @var ReflectionNamedType */ - $reflectionParamType = $reflectionParameter->getType(); - if ($reflectionParamType->getName() != $paramTypeName) { - // We found it: a different param type in some ancestor - return $reflectionParamType->getName(); - } + } + + return null; + } + + private function getDifferentParamTypeFromReflectionMethod(ReflectionMethod $parentReflectionMethod, string $paramName, string $paramTypeName): ?string + { + /** @var ReflectionParameter[] */ + $parentReflectionMethodParams = $parentReflectionMethod->getParameters(); + foreach ($parentReflectionMethodParams as $reflectionParameter) { + if ($reflectionParameter->name == $paramName) { + /** @var ReflectionNamedType */ + $reflectionParamType = $reflectionParameter->getType(); + if ($reflectionParamType->getName() != $paramTypeName) { + // We found it: a different param type in some ancestor + return $reflectionParamType->getName(); } } } From b2fe160d4f44e0b51809dfe82f59c000d57155cb Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 13:13:31 +0800 Subject: [PATCH 06/15] Test passing object type as parent --- .../Fixture/using_object_type.php.inc | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_object_type.php.inc diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_object_type.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_object_type.php.inc new file mode 100644 index 000000000000..229d5b506b74 --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_object_type.php.inc @@ -0,0 +1,38 @@ + +----- + From a13e5db0b3e1f7d92bd8f891f0ee27abc2c891d5 Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 13:15:03 +0800 Subject: [PATCH 07/15] Fixed cs --- ...wngradeContravariantArgumentTypeRector.php | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php index 5876532956c6..9df05cbe000b 100644 --- a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php +++ b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php @@ -4,21 +4,19 @@ namespace Rector\DowngradePhp74\Rector\ClassMethod; -use ReflectionMethod; -use ReflectionNamedType; -use ReflectionParameter; use PhpParser\Node; -use PhpParser\Node\Param; -use PHPStan\Analyser\Scope; -use PhpParser\Node\UnionType; use PhpParser\Node\FunctionLike; use PhpParser\Node\NullableType; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Function_; +use PhpParser\Node\Param; +use PhpParser\Node\UnionType; +use PHPStan\Analyser\Scope; +use Rector\DowngradePhp71\Rector\FunctionLike\AbstractDowngradeParamDeclarationRector; use Rector\NodeTypeResolver\Node\AttributeKey; -use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +use ReflectionMethod; +use ReflectionNamedType; +use ReflectionParameter; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; -use Rector\DowngradePhp71\Rector\FunctionLike\AbstractDowngradeParamDeclarationRector; +use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * @see https://www.php.net/manual/en/language.oop5.variance.php#language.oop5.variance.contravariance @@ -135,7 +133,11 @@ private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLi // Find the param we're looking for $parentReflectionMethod = new ReflectionMethod($parentClassName, $methodName); - $differentAncestorParamTypeName = $this->getDifferentParamTypeFromReflectionMethod($parentReflectionMethod, $paramName, $paramTypeName); + $differentAncestorParamTypeName = $this->getDifferentParamTypeFromReflectionMethod( + $parentReflectionMethod, + $paramName, + $paramTypeName + ); if ($differentAncestorParamTypeName !== null) { return $differentAncestorParamTypeName; } @@ -144,15 +146,18 @@ private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLi return null; } - private function getDifferentParamTypeFromReflectionMethod(ReflectionMethod $parentReflectionMethod, string $paramName, string $paramTypeName): ?string - { + private function getDifferentParamTypeFromReflectionMethod( + ReflectionMethod $parentReflectionMethod, + string $paramName, + string $paramTypeName + ): ?string { /** @var ReflectionParameter[] */ $parentReflectionMethodParams = $parentReflectionMethod->getParameters(); foreach ($parentReflectionMethodParams as $reflectionParameter) { - if ($reflectionParameter->name == $paramName) { + if ($reflectionParameter->name === $paramName) { /** @var ReflectionNamedType */ $reflectionParamType = $reflectionParameter->getType(); - if ($reflectionParamType->getName() != $paramTypeName) { + if ($reflectionParamType->getName() !== $paramTypeName) { // We found it: a different param type in some ancestor return $reflectionParamType->getName(); } From 103a5809e658f644a76fb31e186d4cd0d9e7bb55 Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 13:17:02 +0800 Subject: [PATCH 08/15] Test with gap in class hierarchy --- .../Fixture/gap_level.php.inc | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/gap_level.php.inc diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/gap_level.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/gap_level.php.inc new file mode 100644 index 000000000000..2d4365c6c715 --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/gap_level.php.inc @@ -0,0 +1,52 @@ + +----- + From 69678d39dae64f8b9572a11e194cbceb0cfce4d8 Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 13:18:40 +0800 Subject: [PATCH 09/15] Test that nothing happens --- .../Fixture/nothing_happens.php.inc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/nothing_happens.php.inc diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/nothing_happens.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/nothing_happens.php.inc new file mode 100644 index 000000000000..02749e5a012f --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/nothing_happens.php.inc @@ -0,0 +1,19 @@ + From dd2da8cd5d4048c3b7a1d438614b531c6bb1afdf Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 13:20:14 +0800 Subject: [PATCH 10/15] Test nullable object type --- .../using_nullable_object_type.php.inc | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_object_type.php.inc diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_object_type.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_object_type.php.inc new file mode 100644 index 000000000000..0f08fcb9b099 --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_object_type.php.inc @@ -0,0 +1,38 @@ + +----- + From 91d8358a3f5d60c34e07ff5cdb6be38acc9f0ed2 Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 13:22:41 +0800 Subject: [PATCH 11/15] Test nullable type --- .../Fixture/using_nullable_type.php.inc | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_type.php.inc diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_type.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_type.php.inc new file mode 100644 index 000000000000..c18299ba9bdb --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_type.php.inc @@ -0,0 +1,44 @@ + +----- + From 90047c60d8cf1c713df9502bd0b2553d68784b36 Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 13:27:29 +0800 Subject: [PATCH 12/15] Added rule to downgrade set --- config/set/downgrade-php74.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/set/downgrade-php74.php b/config/set/downgrade-php74.php index 177b6c0a679e..d1ae32e763af 100644 --- a/config/set/downgrade-php74.php +++ b/config/set/downgrade-php74.php @@ -6,6 +6,7 @@ use Rector\DowngradePhp74\Rector\Array_\DowngradeArraySpreadRector; use Rector\DowngradePhp74\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector; use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeCovarianReturnTypeRector; +use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector; use Rector\DowngradePhp74\Rector\Coalesce\DowngradeNullCoalescingOperatorRector; use Rector\DowngradePhp74\Rector\FuncCall\DowngradeArrayMergeCallWithoutArgumentsRector; use Rector\DowngradePhp74\Rector\FuncCall\DowngradeStripTagsCallWithArrayRector; @@ -18,6 +19,7 @@ $services->set(DowngradeTypedPropertyRector::class); $services->set(ArrowFunctionToAnonymousFunctionRector::class); $services->set(DowngradeCovarianReturnTypeRector::class); + $services->set(DowngradeContravariantArgumentTypeRector::class); $services->set(DowngradeNullCoalescingOperatorRector::class); $services->set(DowngradeNumericLiteralSeparatorRector::class); $services->set(DowngradeStripTagsCallWithArrayRector::class); From cbab06afdcff889fa7cca7b85be3fe36670e252e Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 13:55:30 +0800 Subject: [PATCH 13/15] Explicitly added class for testing in CI --- .../Fixture/using_object_type.php.inc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_object_type.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_object_type.php.inc index 229d5b506b74..027fd82cb91d 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_object_type.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_object_type.php.inc @@ -2,9 +2,11 @@ namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector\Fixture; +class UsingObjectType {} + class UsingObjectTypeA { - public function contraVariantArguments(ChildType $type) + public function contraVariantArguments(UsingObjectType $type) { /* … */ } } @@ -20,9 +22,11 @@ class UsingObjectTypeB extends UsingObjectTypeA namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector\Fixture; +class UsingObjectType {} + class UsingObjectTypeA { - public function contraVariantArguments(ChildType $type) + public function contraVariantArguments(UsingObjectType $type) { /* … */ } } From 6214d1e8f3d2d7c0609a2dff8240580ea63b55ef Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 14:02:43 +0800 Subject: [PATCH 14/15] Explicitly add class for testing in CI --- .../Fixture/using_nullable_object_type.php.inc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_object_type.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_object_type.php.inc index 0f08fcb9b099..63f66674328b 100644 --- a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_object_type.php.inc +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/using_nullable_object_type.php.inc @@ -2,9 +2,11 @@ namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector\Fixture; +class UsingNullableObjectType {} + class UsingNullableObjectTypeA { - public function contraVariantArguments(?ChildType $type) + public function contraVariantArguments(?UsingNullableObjectType $type) { /* … */ } } @@ -20,9 +22,11 @@ class UsingNullableObjectTypeB extends UsingNullableObjectTypeA namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector\Fixture; +class UsingNullableObjectType {} + class UsingNullableObjectTypeA { - public function contraVariantArguments(?ChildType $type) + public function contraVariantArguments(?UsingNullableObjectType $type) { /* … */ } } From 6e59ac3042c5c1e974d48266749e03bb4c6a337c Mon Sep 17 00:00:00 2001 From: Leonardo Losoviz Date: Fri, 4 Dec 2020 18:32:24 +0800 Subject: [PATCH 15/15] Handle interfaces --- ...wngradeContravariantArgumentTypeRector.php | 13 ++++- .../Fixture/gap_level_interface.php.inc | 48 +++++++++++++++++++ .../Fixture/with_interface.php.inc | 40 ++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/gap_level_interface.php.inc create mode 100644 rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/with_interface.php.inc diff --git a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php index 9df05cbe000b..f292dd950986 100644 --- a/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php +++ b/rules/downgrade-php74/src/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector.php @@ -10,6 +10,7 @@ use PhpParser\Node\Param; use PhpParser\Node\UnionType; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ClassReflection; use Rector\DowngradePhp71\Rector\FunctionLike\AbstractDowngradeParamDeclarationRector; use Rector\NodeTypeResolver\Node\AttributeKey; use ReflectionMethod; @@ -126,7 +127,17 @@ private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLi /** @var string $methodName */ $methodName = $this->getName($functionLike); - foreach ($classReflection->getParentClassesNames() as $parentClassName) { + // Either Ancestor classes or implemented interfaces + $parentClassNames = array_merge( + $classReflection->getParentClassesNames(), + array_map( + function (ClassReflection $interfaceReflection) : string { + return $interfaceReflection->getName(); + }, + $classReflection->getInterfaces() + ) + ); + foreach ($parentClassNames as $parentClassName) { if (! method_exists($parentClassName, $methodName)) { continue; } diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/gap_level_interface.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/gap_level_interface.php.inc new file mode 100644 index 000000000000..9f21ee806133 --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/gap_level_interface.php.inc @@ -0,0 +1,48 @@ + +----- + diff --git a/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/with_interface.php.inc b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/with_interface.php.inc new file mode 100644 index 000000000000..4abd7e02d68f --- /dev/null +++ b/rules/downgrade-php74/tests/Rector/ClassMethod/DowngradeContravariantArgumentTypeRector/Fixture/with_interface.php.inc @@ -0,0 +1,40 @@ + +----- +