Skip too wide union types on AddMethodCallBasedStrictParamTypeRector (#1097)

This commit is contained in:
Tomas Votruba 2021-10-29 00:04:55 +02:00 committed by GitHub
parent a5f8d529a9
commit 4c507636dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 248 additions and 264 deletions

View File

@ -1,4 +1,4 @@
# 485 Rules Overview
# 476 Rules Overview
<br>
@ -18,8 +18,6 @@
- [DeadCode](#deadcode) (49)
- [Defluent](#defluent) (9)
- [DependencyInjection](#dependencyinjection) (3)
- [DowngradePhp53](#downgradephp53) (1)
@ -58,7 +56,7 @@
- [Php52](#php52) (2)
- [Php53](#php53) (4)
- [Php53](#php53) (3)
- [Php54](#php54) (2)
@ -76,7 +74,7 @@
- [Php74](#php74) (15)
- [Php80](#php80) (16)
- [Php80](#php80) (17)
- [Php81](#php81) (5)
@ -3949,202 +3947,6 @@ Remove php version checks if they are passed
<br>
## Defluent
### DefluentReturnMethodCallRector
Turns return of fluent, to standalone call line and return of value
- class: [`Rector\Defluent\Rector\Return_\DefluentReturnMethodCallRector`](../rules/Defluent/Rector/Return_/DefluentReturnMethodCallRector.php)
```diff
$someClass = new SomeClass();
-return $someClass->someFunction();
+$someClass->someFunction();
+return $someClass;
```
<br>
### FluentChainMethodCallToNormalMethodCallRector
Turns fluent interface calls to classic ones.
- class: [`Rector\Defluent\Rector\MethodCall\FluentChainMethodCallToNormalMethodCallRector`](../rules/Defluent/Rector/MethodCall/FluentChainMethodCallToNormalMethodCallRector.php)
```diff
$someClass = new SomeClass();
-$someClass->someFunction()
- ->otherFunction();
+$someClass->someFunction();
+$someClass->otherFunction();
```
<br>
### InArgFluentChainMethodCallToStandaloneMethodCallRector
Turns fluent interface calls to classic ones.
- class: [`Rector\Defluent\Rector\MethodCall\InArgFluentChainMethodCallToStandaloneMethodCallRector`](../rules/Defluent/Rector/MethodCall/InArgFluentChainMethodCallToStandaloneMethodCallRector.php)
```diff
class UsedAsParameter
{
public function someFunction(FluentClass $someClass)
{
- $this->processFluentClass($someClass->someFunction()->otherFunction());
+ $someClass->someFunction();
+ $someClass->otherFunction();
+ $this->processFluentClass($someClass);
}
public function processFluentClass(FluentClass $someClass)
{
}
}
```
<br>
### MethodCallOnSetterMethodCallToStandaloneAssignRector
Change method call on setter to standalone assign before the setter
- class: [`Rector\Defluent\Rector\MethodCall\MethodCallOnSetterMethodCallToStandaloneAssignRector`](../rules/Defluent/Rector/MethodCall/MethodCallOnSetterMethodCallToStandaloneAssignRector.php)
```diff
class SomeClass
{
public function some()
{
- $this->anotherMethod(new AnotherClass())
- ->someFunction();
+ $anotherClass = new AnotherClass();
+ $anotherClass->someFunction();
+ $this->anotherMethod($anotherClass);
}
public function anotherMethod(AnotherClass $anotherClass)
{
}
}
```
<br>
### NewFluentChainMethodCallToNonFluentRector
Turns fluent interface calls to classic ones.
- class: [`Rector\Defluent\Rector\MethodCall\NewFluentChainMethodCallToNonFluentRector`](../rules/Defluent/Rector/MethodCall/NewFluentChainMethodCallToNonFluentRector.php)
```diff
-(new SomeClass())->someFunction()
- ->otherFunction();
+$someClass = new SomeClass();
+$someClass->someFunction();
+$someClass->otherFunction();
```
<br>
### NormalToFluentRector
Turns fluent interface calls to classic ones.
:wrench: **configure it!**
- class: [`Rector\Defluent\Rector\ClassMethod\NormalToFluentRector`](../rules/Defluent/Rector/ClassMethod/NormalToFluentRector.php)
```php
use Rector\Defluent\Rector\ClassMethod\NormalToFluentRector;
use Rector\Defluent\ValueObject\NormalToFluent;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\SymfonyPhpConfig\ValueObjectInliner;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(NormalToFluentRector::class)
->call('configure', [[
NormalToFluentRector::CALLS_TO_FLUENT => ValueObjectInliner::inline([
new NormalToFluent('SomeClass', ['someFunction', 'otherFunction']), ]
),
]]);
};
```
```diff
$someObject = new SomeClass();
-$someObject->someFunction();
-$someObject->otherFunction();
+$someObject->someFunction()
+ ->otherFunction();
```
<br>
### ReturnFluentChainMethodCallToNormalMethodCallRector
Turns fluent interface calls to classic ones.
- class: [`Rector\Defluent\Rector\Return_\ReturnFluentChainMethodCallToNormalMethodCallRector`](../rules/Defluent/Rector/Return_/ReturnFluentChainMethodCallToNormalMethodCallRector.php)
```diff
$someClass = new SomeClass();
+$someClass->someFunction();
+$someClass->otherFunction();
-return $someClass->someFunction()
- ->otherFunction();
+return $someClass;
```
<br>
### ReturnNewFluentChainMethodCallToNonFluentRector
Turns fluent interface calls to classic ones.
- class: [`Rector\Defluent\Rector\Return_\ReturnNewFluentChainMethodCallToNonFluentRector`](../rules/Defluent/Rector/Return_/ReturnNewFluentChainMethodCallToNonFluentRector.php)
```diff
-return (new SomeClass())->someFunction()
- ->otherFunction();
+$someClass = new SomeClass();
+$someClass->someFunction();
+$someClass->otherFunction();
+return $someClass;
```
<br>
### ReturnThisRemoveRector
Removes "return `$this;"` from *fluent interfaces* for specified classes.
- class: [`Rector\Defluent\Rector\ClassMethod\ReturnThisRemoveRector`](../rules/Defluent/Rector/ClassMethod/ReturnThisRemoveRector.php)
```diff
class SomeExampleClass
{
public function someFunction()
{
- return $this;
}
public function otherFunction()
{
- return $this;
}
}
```
<br>
## DependencyInjection
### ActionInjectionToConstructorInjectionRector
@ -6367,19 +6169,6 @@ Change property modifier from `var` to `public`
## Php53
### ClearReturnNewByReferenceRector
Remove reference from "$assign = &new Value;"
- class: [`Rector\Php53\Rector\AssignRef\ClearReturnNewByReferenceRector`](../rules/Php53/Rector/AssignRef/ClearReturnNewByReferenceRector.php)
```diff
-$assign = &new Value;
+$assign = new Value;
```
<br>
### DirNameFileConstantToDirConstantRector
Convert dirname(__FILE__) to __DIR__
@ -7973,6 +7762,26 @@ Move required parameters after optional ones
<br>
### Php8ResourceReturnToObjectRector
Change `is_resource()` to instanceof Object
- class: [`Rector\Php80\Rector\FuncCall\Php8ResourceReturnToObjectRector`](../rules/Php80/Rector/FuncCall/Php8ResourceReturnToObjectRector.php)
```diff
class SomeClass
{
public function run()
{
$ch = curl_init();
- is_resource($ch);
+ $ch instanceof \CurlHandle;
}
}
```
<br>
### RemoveUnusedVariableInCatchRector
Remove unused variable in `catch()`
@ -11560,29 +11369,21 @@ Add known return type to functions
### AddMethodCallBasedStrictParamTypeRector
Change param type to strict type of passed expression
Change private method param type to strict type, based on passed strict types
- class: [`Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector`](../rules/TypeDeclaration/Rector/ClassMethod/AddMethodCallBasedStrictParamTypeRector.php)
```diff
class SomeClass
final class SomeClass
{
- public function getById($id)
+ public function getById(int $id)
public function run(int $value)
{
}
}
class CallerClass
{
public function run(SomeClass $someClass)
{
$someClass->getById($this->getId());
$this->resolve($value);
}
public function getId(): int
- private function resolve($value)
+ private function resolve(int $value)
{
return 1000;
}
}
```

View File

@ -7,7 +7,7 @@ namespace Whatever\Foo\Bar;
use MyCLabs\Enum\Enum;
/**
* @template-extends Enum<BrokenEnum::*>
* @template-extends Enum<BrokenEnum>
* @psalm-immutable
*/
final class BrokenEnum extends Enum

View File

@ -0,0 +1,24 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector\Fixture;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
final class SkipTooWideUnion
{
public function run(MethodCall $methodCall, StaticCall $staticCall, String_ $string, LNumber $number)
{
$this->someExpr($methodCall);
$this->someExpr($staticCall);
$this->someExpr($string);
$this->someExpr($number);
}
private function someExpr(Expr $expr)
{
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector\FixtureUnion;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
final class NarrowUnion
{
public function run(MethodCall $methodCall, StaticCall $staticCall, String_ $string)
{
$this->someExpr($methodCall);
$this->someExpr($staticCall);
$this->someExpr($string);
}
private function someExpr(Expr $expr)
{
}
}
?>
-----
<?php
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector\FixtureUnion;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
final class NarrowUnion
{
public function run(MethodCall $methodCall, StaticCall $staticCall, String_ $string)
{
$this->someExpr($methodCall);
$this->someExpr($staticCall);
$this->someExpr($string);
}
private function someExpr(\PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall|\PhpParser\Node\Scalar\String_ $expr)
{
}
}
?>

View File

@ -0,0 +1,24 @@
<?php
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector\FixtureUnion;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
final class SkipTooWideUnion
{
public function run(MethodCall $methodCall, StaticCall $staticCall, String_ $string, LNumber $number)
{
$this->someExpr($methodCall);
$this->someExpr($staticCall);
$this->someExpr($string);
$this->someExpr($number);
}
private function someExpr(Expr $expr)
{
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class UnionTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureUnion');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/union_config.php';
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PHP_VERSION_FEATURES, PhpVersionFeature::UNION_TYPES);
$services = $containerConfigurator->services();
$services->set(AddMethodCallBasedStrictParamTypeRector::class);
};

View File

@ -34,7 +34,7 @@ final class ThisCallOnStaticMethodToStaticCallRector extends AbstractRector impl
public function provideMinPhpVersion(): int
{
return PhpVersionFeature::STATIC_CALL;
return PhpVersionFeature::STATIC_CALL_ON_NON_STATIC;
}
public function getRuleDefinition(): RuleDefinition

View File

@ -10,7 +10,11 @@ use PHPStan\Type\CallableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use Rector\Core\Php\PhpVersionProvider;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeCommonTypeNarrower;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\VendorLocker\NodeVendorLocker\ClassMethodParamVendorLockResolver;
@ -18,19 +22,21 @@ final class ClassMethodParamTypeCompleter
{
public function __construct(
private StaticTypeMapper $staticTypeMapper,
private ClassMethodParamVendorLockResolver $classMethodParamVendorLockResolver
private ClassMethodParamVendorLockResolver $classMethodParamVendorLockResolver,
private UnionTypeCommonTypeNarrower $unionTypeCommonTypeNarrower,
private PhpVersionProvider $phpVersionProvider,
) {
}
/**
* @param array<int, Type> $classParameterTypes
*/
public function complete(ClassMethod $classMethod, array $classParameterTypes): ?ClassMethod
public function complete(ClassMethod $classMethod, array $classParameterTypes, int $maxUnionTypes): ?ClassMethod
{
$hasChanged = false;
foreach ($classParameterTypes as $position => $argumentStaticType) {
if ($this->shouldSkipArgumentStaticType($classMethod, $argumentStaticType, $position)) {
if ($this->shouldSkipArgumentStaticType($classMethod, $argumentStaticType, $position, $maxUnionTypes)) {
continue;
}
@ -58,7 +64,8 @@ final class ClassMethodParamTypeCompleter
private function shouldSkipArgumentStaticType(
ClassMethod $classMethod,
Type $argumentStaticType,
int $position
int $position,
int $maxUnionTypes
): bool {
if ($argumentStaticType instanceof MixedType) {
return true;
@ -82,12 +89,16 @@ final class ClassMethodParamTypeCompleter
return true;
}
// avoid overriding more precise type
if ($argumentStaticType->isSuperTypeOf($currentParameterStaticType)->yes()) {
// narrow union type in case its not supported yet
$argumentStaticType = $this->narrowUnionTypeIfNotSupported($argumentStaticType);
// too many union types
if ($this->isTooDetailedUnionType($currentParameterStaticType, $argumentStaticType, $maxUnionTypes)) {
return true;
}
if ($currentParameterStaticType->equals($argumentStaticType)) {
// avoid overriding more precise type
if ($argumentStaticType->isSuperTypeOf($currentParameterStaticType)->yes()) {
return true;
}
@ -112,4 +123,36 @@ final class ClassMethodParamTypeCompleter
return $type->getClassName() === 'Closure';
}
private function isTooDetailedUnionType(Type $currentType, Type $newType, int $maxUnionTypes): bool
{
if ($currentType instanceof MixedType) {
return false;
}
if (! $newType instanceof UnionType) {
return false;
}
return count($newType->getTypes()) > $maxUnionTypes;
}
private function narrowUnionTypeIfNotSupported(Type $type): Type
{
if (! $type instanceof UnionType) {
return $type;
}
// union is supported, so it's ok
if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::UNION_TYPES)) {
return $type;
}
$narrowedObjectType = $this->unionTypeCommonTypeNarrower->narrowToSharedObjectType($type);
if ($narrowedObjectType instanceof ObjectType) {
return $narrowedObjectType;
}
return $type;
}
}

View File

@ -20,6 +20,11 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
*/
final class AddMethodCallBasedStrictParamTypeRector extends AbstractRector
{
/**
* @var int
*/
private const MAX_UNION_TYPES = 3;
public function __construct(
private CallTypesResolver $callTypesResolver,
private ClassMethodParamTypeCompleter $classMethodParamTypeCompleter,
@ -29,48 +34,32 @@ final class AddMethodCallBasedStrictParamTypeRector extends AbstractRector
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change param type to strict type of passed expression', [
return new RuleDefinition('Change private method param type to strict type, based on passed strict types', [
new CodeSample(
<<<'CODE_SAMPLE'
class SomeClass
final class SomeClass
{
public function getById($id)
public function run(int $value)
{
}
}
class CallerClass
{
public function run(SomeClass $someClass)
{
$someClass->getById($this->getId());
$this->resolve($value);
}
public function getId(): int
private function resolve($value)
{
return 1000;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass
final class SomeClass
{
public function getById(int $id)
public function run(int $value)
{
}
}
class CallerClass
{
public function run(SomeClass $someClass)
{
$someClass->getById($this->getId());
$this->resolve($value);
}
public function getId(): int
private function resolve(int $value)
{
return 1000;
}
}
CODE_SAMPLE
@ -102,6 +91,6 @@ CODE_SAMPLE
$methodCalls = $this->localMethodCallFinder->match($node);
$classMethodParameterTypes = $this->callTypesResolver->resolveStrictTypesFromCalls($methodCalls);
return $this->classMethodParamTypeCompleter->complete($node, $classMethodParameterTypes);
return $this->classMethodParamTypeCompleter->complete($node, $classMethodParameterTypes, self::MAX_UNION_TYPES);
}
}

View File

@ -152,9 +152,12 @@ final class PhpVersionFeature
public const NO_EMPTY_LIST = PhpVersion::PHP_70;
/**
* @see https://php.watch/versions/8.0/non-static-static-call-fatal-error
* Deprecated since PHP 7.0
*
* @var int
*/
public const STATIC_CALL = PhpVersion::PHP_70;
public const STATIC_CALL_ON_NON_STATIC = PhpVersion::PHP_70;
/**
* @var int