Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Downgrade PHP 7.4 contravariant argument type #4770

Merged
merged 17 commits into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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