Updated Rector to commit b75b5d396f0d0c04242a1fb7fb44f03376f4d8e7

b75b5d396f ReturnTypeFromStrictTernaryRector: Support complex ternaries (#4515)
This commit is contained in:
Tomas Votruba 2023-07-18 09:23:51 +00:00
parent 8701fc25cc
commit f6c482799f
7 changed files with 65 additions and 117 deletions

View File

@ -6,18 +6,25 @@ namespace Rector\TypeDeclaration\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ConstantType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;
use Rector\Core\Rector\AbstractScopeAwareRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Rector\TypeDeclaration\ValueObject\TernaryIfElseTypes;
use Rector\VendorLocker\NodeVendorLocker\ClassMethodReturnTypeOverrideGuard;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
@ -33,9 +40,15 @@ final class ReturnTypeFromStrictTernaryRector extends AbstractScopeAwareRector i
* @var \Rector\VendorLocker\NodeVendorLocker\ClassMethodReturnTypeOverrideGuard
*/
private $classMethodReturnTypeOverrideGuard;
public function __construct(ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard)
/**
* @readonly
* @var \Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer
*/
private $returnTypeInferer;
public function __construct(ClassMethodReturnTypeOverrideGuard $classMethodReturnTypeOverrideGuard, ReturnTypeInferer $returnTypeInferer)
{
$this->classMethodReturnTypeOverrideGuard = $classMethodReturnTypeOverrideGuard;
$this->returnTypeInferer = $returnTypeInferer;
}
public function getRuleDefinition() : RuleDefinition
{
@ -64,93 +77,62 @@ CODE_SAMPLE
*/
public function getNodeTypes() : array
{
return [Class_::class];
return [ClassMethod::class, Function_::class, Closure::class];
}
/**
* @param Class_ $node
* @param ClassMethod|Function_|Closure $node
*/
public function refactorWithScope(Node $node, Scope $scope) : ?Node
{
$hasChanged = \false;
foreach ($node->getMethods() as $classMethod) {
if ($classMethod->returnType instanceof Node) {
continue;
}
$onlyStmt = $classMethod->stmts[0] ?? null;
if (!$onlyStmt instanceof Return_) {
continue;
}
if (!$onlyStmt->expr instanceof Ternary) {
continue;
}
$ternary = $onlyStmt->expr;
// has scalar in if/else of ternary
$ternaryIfElseTypes = $this->matchScalarTernaryIfElseTypes($ternary);
if (!$ternaryIfElseTypes instanceof TernaryIfElseTypes) {
continue;
}
$ifType = $ternaryIfElseTypes->getFirstType();
$elseType = $ternaryIfElseTypes->getSecondType();
if (!$this->areTypesEqual($ifType, $elseType)) {
continue;
}
if ($this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($classMethod, $scope)) {
continue;
}
$returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($ifType, TypeKind::RETURN);
if (!$returnTypeNode instanceof Node) {
continue;
}
$classMethod->returnType = $returnTypeNode;
$hasChanged = \true;
if ($this->shouldSkip($node, $scope)) {
return null;
}
if ($hasChanged) {
return $node;
if ($node->stmts === null) {
return null;
}
return null;
$returns = $this->betterNodeFinder->findInstanceOf($node->stmts, Return_::class);
if (\count($returns) !== 1) {
return null;
}
$return = $returns[0];
if (!$return->expr instanceof Ternary) {
return null;
}
$ternary = $return->expr;
$nativeTernaryType = $scope->getNativeType($ternary);
if ($nativeTernaryType instanceof MixedType) {
return null;
}
$ternaryType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($ternary);
$returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($ternaryType, TypeKind::RETURN);
if (!$returnTypeNode instanceof Node) {
return null;
}
$node->returnType = $returnTypeNode;
return $node;
}
public function provideMinPhpVersion() : int
{
return PhpVersionFeature::SCALAR_TYPES;
}
private function isAlwaysScalarExpr(?Expr $expr) : bool
/**
* @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure $node
*/
private function shouldSkip($node, Scope $scope) : bool
{
// check if Scalar node
if ($expr instanceof Scalar) {
if ($node->returnType !== null) {
return \true;
}
// check if constant
if ($expr instanceof ConstFetch) {
$returnType = $this->returnTypeInferer->inferFunctionLike($node);
$returnType = TypeCombinator::removeNull($returnType);
if ($returnType instanceof UnionType) {
return \true;
}
// check if class constant
return $expr instanceof ClassConstFetch;
}
private function areTypesEqual(Type $firstType, Type $secondType) : bool
{
// this is needed to make comparison tolerant to constant values, e.g. 5 and 10 are same only then
if ($firstType instanceof ConstantType) {
$firstType = $firstType->generalize(GeneralizePrecision::lessSpecific());
if ($node instanceof ClassMethod) {
if ($this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($node, $scope)) {
return \true;
}
}
if ($secondType instanceof ConstantType) {
$secondType = $secondType->generalize(GeneralizePrecision::lessSpecific());
}
return $firstType->equals($secondType);
}
private function matchScalarTernaryIfElseTypes(Ternary $ternary) : ?TernaryIfElseTypes
{
if (!$this->isAlwaysScalarExpr($ternary->if)) {
return null;
}
if (!$this->isAlwaysScalarExpr($ternary->else)) {
return null;
}
/** @var Node\Expr $if */
$if = $ternary->if;
/** @var Node\Expr $else */
$else = $ternary->else;
$ifType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($if);
$elseType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($else);
return new TernaryIfElseTypes($ifType, $elseType);
return \false;
}
}

View File

@ -1,32 +0,0 @@
<?php
declare (strict_types=1);
namespace Rector\TypeDeclaration\ValueObject;
use PHPStan\Type\Type;
final class TernaryIfElseTypes
{
/**
* @readonly
* @var \PHPStan\Type\Type
*/
private $firstType;
/**
* @readonly
* @var \PHPStan\Type\Type
*/
private $secondType;
public function __construct(Type $firstType, Type $secondType)
{
$this->firstType = $firstType;
$this->secondType = $secondType;
}
public function getFirstType() : Type
{
return $this->firstType;
}
public function getSecondType() : Type
{
return $this->secondType;
}
}

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = '5a9315aab0e60e7a8086beb1c6891de678b2f003';
public const PACKAGE_VERSION = 'b75b5d396f0d0c04242a1fb7fb44f03376f4d8e7';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2023-07-18 08:44:36';
public const RELEASE_DATE = '2023-07-18 10:19:12';
/**
* @var int
*/

2
vendor/autoload.php vendored
View File

@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) {
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit7c2d1664db3c0cb93d0f0538a851f407::getLoader();
return ComposerAutoloaderInitfb6357a522451a3a2edd8b2727b42b45::getLoader();

View File

@ -2741,7 +2741,6 @@ return array(
'Rector\\TypeDeclaration\\ValueObject\\AddReturnTypeDeclaration' => $baseDir . '/rules/TypeDeclaration/ValueObject/AddReturnTypeDeclaration.php',
'Rector\\TypeDeclaration\\ValueObject\\AssignToVariable' => $baseDir . '/rules/TypeDeclaration/ValueObject/AssignToVariable.php',
'Rector\\TypeDeclaration\\ValueObject\\NestedArrayType' => $baseDir . '/rules/TypeDeclaration/ValueObject/NestedArrayType.php',
'Rector\\TypeDeclaration\\ValueObject\\TernaryIfElseTypes' => $baseDir . '/rules/TypeDeclaration/ValueObject/TernaryIfElseTypes.php',
'Rector\\ValueObject\\ClassMethodWillChangeReturnType' => $vendorDir . '/rector/rector-downgrade-php/src/ValueObject/ClassMethodWillChangeReturnType.php',
'Rector\\VendorLocker\\NodeVendorLocker\\ClassMethodParamVendorLockResolver' => $baseDir . '/packages/VendorLocker/NodeVendorLocker/ClassMethodParamVendorLockResolver.php',
'Rector\\VendorLocker\\NodeVendorLocker\\ClassMethodReturnTypeOverrideGuard' => $baseDir . '/packages/VendorLocker/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php',

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit7c2d1664db3c0cb93d0f0538a851f407
class ComposerAutoloaderInitfb6357a522451a3a2edd8b2727b42b45
{
private static $loader;
@ -22,17 +22,17 @@ class ComposerAutoloaderInit7c2d1664db3c0cb93d0f0538a851f407
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit7c2d1664db3c0cb93d0f0538a851f407', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInitfb6357a522451a3a2edd8b2727b42b45', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit7c2d1664db3c0cb93d0f0538a851f407', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInitfb6357a522451a3a2edd8b2727b42b45', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit7c2d1664db3c0cb93d0f0538a851f407::getInitializer($loader));
call_user_func(\Composer\Autoload\ComposerStaticInitfb6357a522451a3a2edd8b2727b42b45::getInitializer($loader));
$loader->setClassMapAuthoritative(true);
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit7c2d1664db3c0cb93d0f0538a851f407::$files;
$filesToLoad = \Composer\Autoload\ComposerStaticInitfb6357a522451a3a2edd8b2727b42b45::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

View File

@ -4,7 +4,7 @@
namespace Composer\Autoload;
class ComposerStaticInit7c2d1664db3c0cb93d0f0538a851f407
class ComposerStaticInitfb6357a522451a3a2edd8b2727b42b45
{
public static $files = array (
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
@ -2995,7 +2995,6 @@ class ComposerStaticInit7c2d1664db3c0cb93d0f0538a851f407
'Rector\\TypeDeclaration\\ValueObject\\AddReturnTypeDeclaration' => __DIR__ . '/../..' . '/rules/TypeDeclaration/ValueObject/AddReturnTypeDeclaration.php',
'Rector\\TypeDeclaration\\ValueObject\\AssignToVariable' => __DIR__ . '/../..' . '/rules/TypeDeclaration/ValueObject/AssignToVariable.php',
'Rector\\TypeDeclaration\\ValueObject\\NestedArrayType' => __DIR__ . '/../..' . '/rules/TypeDeclaration/ValueObject/NestedArrayType.php',
'Rector\\TypeDeclaration\\ValueObject\\TernaryIfElseTypes' => __DIR__ . '/../..' . '/rules/TypeDeclaration/ValueObject/TernaryIfElseTypes.php',
'Rector\\ValueObject\\ClassMethodWillChangeReturnType' => __DIR__ . '/..' . '/rector/rector-downgrade-php/src/ValueObject/ClassMethodWillChangeReturnType.php',
'Rector\\VendorLocker\\NodeVendorLocker\\ClassMethodParamVendorLockResolver' => __DIR__ . '/../..' . '/packages/VendorLocker/NodeVendorLocker/ClassMethodParamVendorLockResolver.php',
'Rector\\VendorLocker\\NodeVendorLocker\\ClassMethodReturnTypeOverrideGuard' => __DIR__ . '/../..' . '/packages/VendorLocker/NodeVendorLocker/ClassMethodReturnTypeOverrideGuard.php',
@ -3028,9 +3027,9 @@ class ComposerStaticInit7c2d1664db3c0cb93d0f0538a851f407
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit7c2d1664db3c0cb93d0f0538a851f407::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit7c2d1664db3c0cb93d0f0538a851f407::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit7c2d1664db3c0cb93d0f0538a851f407::$classMap;
$loader->prefixLengthsPsr4 = ComposerStaticInitfb6357a522451a3a2edd8b2727b42b45::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitfb6357a522451a3a2edd8b2727b42b45::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitfb6357a522451a3a2edd8b2727b42b45::$classMap;
}, null, ClassLoader::class);
}