Skip to content

Commit

Permalink
Downgrade PHP 7.4 contravariant argument type (#4770)
Browse files Browse the repository at this point in the history
Co-authored-by: Tomas Votruba <tomas.vot@gmail.com>
  • Loading branch information
leoloso and TomasVotruba authored Dec 4, 2020
1 parent 1bb4e06 commit 5257f3a
Show file tree
Hide file tree
Showing 18 changed files with 568 additions and 6 deletions.
2 changes: 2 additions & 0 deletions config/set/downgrade-php74.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Rector\DowngradePhp74\Rector\Array_\DowngradeArraySpreadRector;
use Rector\DowngradePhp74\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector;
use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeCovariantReturnTypeRector;
use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector;
use Rector\DowngradePhp74\Rector\Coalesce\DowngradeNullCoalescingOperatorRector;
use Rector\DowngradePhp74\Rector\FuncCall\DowngradeArrayMergeCallWithoutArgumentsRector;
use Rector\DowngradePhp74\Rector\FuncCall\DowngradeStripTagsCallWithArrayRector;
Expand All @@ -18,6 +19,7 @@
$services->set(DowngradeTypedPropertyRector::class);
$services->set(ArrowFunctionToAnonymousFunctionRector::class);
$services->set(DowngradeCovariantReturnTypeRector::class);
$services->set(DowngradeContravariantArgumentTypeRector::class);
$services->set(DowngradeNullCoalescingOperatorRector::class);
$services->set(DowngradeNumericLiteralSeparatorRector::class);
$services->set(DowngradeStripTagsCallWithArrayRector::class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

namespace Rector\DowngradePhp71\Contract\Rector;

use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;

interface DowngradeParamDeclarationRectorInterface
{
/**
* Indicate if the parameter must be removed
*/
public function shouldRemoveParamDeclaration(Param $param): bool;
public function shouldRemoveParamDeclaration(Param $param, FunctionLike $functionLike): bool;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?php

declare(strict_types=1);

namespace Rector\DowngradePhp74\Rector\ClassMethod;

use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\NullableType;
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;
use ReflectionNamedType;
use ReflectionParameter;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see https://www.php.net/manual/en/language.oop5.variance.php#language.oop5.variance.contravariance
*
* @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 ConfiguredCodeSample(
<<<'CODE_SAMPLE'
class ParentType {}
class ChildType extends ParentType {}
class A
{
public function contraVariantArguments(ChildType $type)
{ /* … */ }
}
class B extends A
{
public function contraVariantArguments(ParentType $type)
{ /* … */ }
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class ParentType {}
class ChildType extends ParentType {}
class A
{
public function contraVariantArguments(ChildType $type)
{ /* … */ }
}
class B extends A
{
/**
* @param ParentType $type
*/
public function contraVariantArguments($type)
{ /* … */ }
}
CODE_SAMPLE
,
[
self::ADD_DOC_BLOCK => true,
]
),
]);
}

public function shouldRemoveParamDeclaration(Param $param, FunctionLike $functionLike): bool
{
if ($param->variadic) {
return false;
}

if ($param->type === null) {
return false;
}

// Don't consider for Union types
if ($param->type instanceof UnionType) {
return false;
}

// Check if the type is different from the one declared in some ancestor
return $this->getDifferentParamTypeFromAncestorClass($param, $functionLike) !== null;
}

private function getDifferentParamTypeFromAncestorClass(Param $param, FunctionLike $functionLike): ?string
{
/** @var Scope|null $scope */
$scope = $functionLike->getAttribute(AttributeKey::SCOPE);
if ($scope === null) {
// possibly trait
return null;
}

$classReflection = $scope->getClassReflection();
if ($classReflection === null) {
return null;
}

$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 = $paramType;
$paramTypeName = $this->getName($nullableType->type);
} else {
$paramTypeName = $this->getName($paramType);
}
if ($paramTypeName === null) {
return null;
}

/** @var string $methodName */
$methodName = $this->getName($functionLike);

// 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;
}

// Find the param we're looking for
$parentReflectionMethod = new ReflectionMethod($parentClassName, $methodName);
$differentAncestorParamTypeName = $this->getDifferentParamTypeFromReflectionMethod(
$parentReflectionMethod,
$paramName,
$paramTypeName
);
if ($differentAncestorParamTypeName !== null) {
return $differentAncestorParamTypeName;
}
}

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();
}
}
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector;

use Iterator;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

final class DowngradeContravariantArgumentTypeRectorTest extends AbstractRectorTestCase
{
/**
* @requires PHP 7.4
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector\Fixture;

class ParentType {}
class ChildType extends ParentType {}

class A
{
public function contraVariantArguments(ChildType $type)
{ /* … */ }
}

class B extends A
{
public function contraVariantArguments(ParentType $type)
{ /* … */ }
}

?>
-----
<?php

namespace Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector\Fixture;

class ParentType {}
class ChildType extends ParentType {}

class A
{
public function contraVariantArguments(ChildType $type)
{ /* … */ }
}

class B extends A
{
/**
* @param \Rector\DowngradePhp74\Tests\Rector\ClassMethod\DowngradeContravariantArgumentTypeRector\Fixture\ParentType $type
*/
public function contraVariantArguments($type)
{ /* … */ }
}

?>
Loading

0 comments on commit 5257f3a

Please sign in to comment.