From ff21394bc2448bae7cfcf7e6b4be462a4bae104f Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Tue, 6 Jun 2023 17:27:10 +0700 Subject: [PATCH] [Php80] Fix add default nullable type on ClassPropertyAssignToConstructorPromotionRector (#4091) --- .../NodeTypeResolver/NodeTypeResolver.php | 14 +++++- .../IdentifierTypeResolver.php | 41 ++++++++++++++--- .../Fixture/add_default_false.php.inc | 28 ++++++++++++ .../Fixture/add_default_nullable.php.inc | 2 +- .../Fixture/add_default_nullable2.php.inc | 28 ++++++++++++ .../Fixture/add_default_nullable3.php.inc | 28 ++++++++++++ .../Fixture/add_default_nullable4.php.inc | 28 ++++++++++++ .../Fixture/add_default_nullable5.php.inc | 28 ++++++++++++ .../Fixture/add_default_nullable6.php.inc | 28 ++++++++++++ .../Fixture/nullable_type.php.inc | 2 +- .../Fixture/string_default_string.php.inc | 28 ++++++++++++ ...ertyAssignToConstructorPromotionRector.php | 44 +++++++++++++++---- src/Kernel/RectorKernel.php | 2 +- .../Issues/Issue6840/Fixture/fixture.php.inc | 2 +- 14 files changed, 283 insertions(+), 20 deletions(-) create mode 100644 rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_false.php.inc create mode 100644 rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable2.php.inc create mode 100644 rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable3.php.inc create mode 100644 rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable4.php.inc create mode 100644 rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable5.php.inc create mode 100644 rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable6.php.inc create mode 100644 rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/string_default_string.php.inc diff --git a/packages/NodeTypeResolver/NodeTypeResolver.php b/packages/NodeTypeResolver/NodeTypeResolver.php index 092949ae9e9..dd7a25dfed8 100644 --- a/packages/NodeTypeResolver/NodeTypeResolver.php +++ b/packages/NodeTypeResolver/NodeTypeResolver.php @@ -16,8 +16,8 @@ use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\NullableType; -use PhpParser\Node\Param; use PhpParser\Node\Stmt\Property; +use PhpParser\Node\UnionType as NodeUnionType; use PHPStan\Analyser\Scope; use PHPStan\Broker\ClassAutoloadingException; use PHPStan\Reflection\ClassReflection; @@ -112,7 +112,7 @@ public function isObjectType(Node $node, ObjectType $requiredObjectType): bool public function getType(Node $node): Type { - if ($node instanceof Property && $node->type instanceof NullableType) { + if ($node instanceof Property && $node->type instanceof Node) { return $this->getType($node->type); } @@ -171,6 +171,16 @@ public function getType(Node $node): Type return new MixedType(); } + if ($node instanceof NodeUnionType) { + $types = []; + + foreach ($node->types as $type) { + $types[] = $this->getType($type); + } + + return new UnionType($types); + } + if (! $node instanceof Expr) { return new MixedType(); } diff --git a/packages/NodeTypeResolver/NodeTypeResolver/IdentifierTypeResolver.php b/packages/NodeTypeResolver/NodeTypeResolver/IdentifierTypeResolver.php index d774c0a7d1d..e6b8a64afaf 100644 --- a/packages/NodeTypeResolver/NodeTypeResolver/IdentifierTypeResolver.php +++ b/packages/NodeTypeResolver/NodeTypeResolver/IdentifierTypeResolver.php @@ -6,10 +6,15 @@ use PhpParser\Node; use PhpParser\Node\Identifier; +use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; +use PHPStan\Type\IterableType; use PHPStan\Type\MixedType; +use PHPStan\Type\NullType; +use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use Rector\NodeTypeResolver\Contract\NodeTypeResolverInterface; @@ -29,23 +34,49 @@ public function getNodeClasses(): array /** * @param Identifier $node - * @return StringType|BooleanType|IntegerType|FloatType|MixedType + * @return StringType|BooleanType|ConstantBooleanType|NullType|ObjectWithoutClassType|ArrayType|IterableType|IntegerType|FloatType|MixedType */ public function resolve(Node $node): Type { - if ($node->toLowerString() === 'string') { + $lowerString = $node->toLowerString(); + + if ($lowerString === 'string') { return new StringType(); } - if ($node->toLowerString() === 'bool') { + if ($lowerString === 'bool') { return new BooleanType(); } - if ($node->toLowerString() === 'int') { + if ($lowerString === 'false') { + return new ConstantBooleanType(false); + } + + if ($lowerString === 'true') { + return new ConstantBooleanType(true); + } + + if ($lowerString === 'null') { + return new NullType(); + } + + if ($lowerString === 'object') { + return new ObjectWithoutClassType(); + } + + if ($lowerString === 'array') { + return new ArrayType(new MixedType(), new MixedType()); + } + + if ($lowerString === 'int') { return new IntegerType(); } - if ($node->toLowerString() === 'float') { + if ($lowerString === 'iterable') { + return new IterableType(new MixedType(), new MixedType()); + } + + if ($lowerString === 'float') { return new FloatType(); } diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_false.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_false.php.inc new file mode 100644 index 00000000000..b7e286f12d0 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_false.php.inc @@ -0,0 +1,28 @@ +name = $name; + } +} + +?> +----- + diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable.php.inc index 46c9767f3ad..6855053e594 100644 --- a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable.php.inc +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable.php.inc @@ -20,7 +20,7 @@ namespace Rector\Tests\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromo final class AddDefaultNullable { - public function __construct(public $name = null) + public function __construct(public ?string $name = null) { } } diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable2.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable2.php.inc new file mode 100644 index 00000000000..982cfd7c136 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable2.php.inc @@ -0,0 +1,28 @@ +name = $name; + } +} + +?> +----- + diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable3.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable3.php.inc new file mode 100644 index 00000000000..478eccd2727 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable3.php.inc @@ -0,0 +1,28 @@ +name = $name; + } +} + +?> +----- + diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable4.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable4.php.inc new file mode 100644 index 00000000000..59a869e1a17 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable4.php.inc @@ -0,0 +1,28 @@ +name = $name; + } +} + +?> +----- + diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable5.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable5.php.inc new file mode 100644 index 00000000000..10cc0468df3 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable5.php.inc @@ -0,0 +1,28 @@ +name = $name; + } +} + +?> +----- + diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable6.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable6.php.inc new file mode 100644 index 00000000000..ed2b5d81896 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/add_default_nullable6.php.inc @@ -0,0 +1,28 @@ +name = $name; + } +} + +?> +----- + diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/nullable_type.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/nullable_type.php.inc index e00366ece65..b69fb8aeb42 100644 --- a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/nullable_type.php.inc +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/nullable_type.php.inc @@ -24,7 +24,7 @@ use DateTimeInterface; class NullableType { - public function __construct(private ?\DateTimeInterface $time = null) + public function __construct(private ?DateTimeInterface $time = null) { } } diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/string_default_string.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/string_default_string.php.inc new file mode 100644 index 00000000000..8fb7cdb6443 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/string_default_string.php.inc @@ -0,0 +1,28 @@ +name = $name; + } +} + +?> +----- + diff --git a/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php b/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php index af1823da39e..7424647817d 100644 --- a/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php +++ b/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php @@ -16,6 +16,8 @@ use PhpParser\Node\UnionType; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use PHPStan\Type\MixedType; +use PHPStan\Type\TypeCombinator; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey; use Rector\Core\Contract\Rector\AllowEmptyConfigurableRectorInterface; @@ -26,6 +28,7 @@ use Rector\DeadCode\PhpDoc\TagRemover\VarTagRemover; use Rector\Naming\VariableRenamer; use Rector\NodeTypeResolver\Node\AttributeKey; +use Rector\NodeTypeResolver\TypeComparator\TypeComparator; use Rector\Php80\Guard\MakePropertyPromotionGuard; use Rector\Php80\NodeAnalyzer\PromotedPropertyCandidateResolver; use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; @@ -62,7 +65,8 @@ public function __construct( private readonly VarTagRemover $varTagRemover, private readonly ParamAnalyzer $paramAnalyzer, private readonly PhpDocTypeChanger $phpDocTypeChanger, - private readonly MakePropertyPromotionGuard $makePropertyPromotionGuard + private readonly MakePropertyPromotionGuard $makePropertyPromotionGuard, + private readonly TypeComparator $typeComparator ) { } @@ -179,7 +183,7 @@ public function refactor(Node $node): ?Node $param->flags = $property->flags; // Copy over attributes of the "old" property $param->attrGroups = array_merge($param->attrGroups, $property->attrGroups); - $this->processNullableType($property, $param); + $this->processUnionType($property, $param); $this->phpDocTypeChanger->copyPropertyDocToParam($constructClassMethod, $property, $param); } @@ -192,17 +196,39 @@ public function provideMinPhpVersion(): int return PhpVersionFeature::PROPERTY_PROMOTION; } - private function processNullableType(Property $property, Param $param): void + private function processUnionType(Property $property, Param $param): void { - if ($this->nodeTypeResolver->isNullableType($property)) { - $objectType = $this->getType($property); - $param->type = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($objectType, TypeKind::PARAM); + if ($property->type instanceof Node) { + $param->type = $property->type; + return; } - if ($param->default instanceof Expr && $this->valueResolver->isNull($param->default)) { - $paramType = $this->getType($param); - $param->type = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($paramType, TypeKind::PARAM); + if (! $param->default instanceof Expr) { + return; } + + if (! $param->type instanceof Node) { + return; + } + + $defaultType = $this->getType($param->default); + $paramType = $this->getType($param->type); + + if ($this->typeComparator->isSubtype($defaultType, $paramType)) { + return; + } + + if ($this->typeComparator->areTypesEqual($defaultType, $paramType)) { + return; + } + + if ($paramType instanceof MixedType) { + return; + } + + $paramType = TypeCombinator::union($paramType, $defaultType); + + $param->type = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($paramType, TypeKind::PARAM); } private function decorateParamWithPropertyPhpDocInfo( diff --git a/src/Kernel/RectorKernel.php b/src/Kernel/RectorKernel.php index ebd09e509a5..4cd9daa6a14 100644 --- a/src/Kernel/RectorKernel.php +++ b/src/Kernel/RectorKernel.php @@ -17,7 +17,7 @@ final class RectorKernel /** * @var string */ - private const CACHE_KEY = 'v57'; + private const CACHE_KEY = 'v58'; private ContainerInterface|null $container = null; diff --git a/tests/Issues/Issue6840/Fixture/fixture.php.inc b/tests/Issues/Issue6840/Fixture/fixture.php.inc index a12e747e0c7..a302c298176 100644 --- a/tests/Issues/Issue6840/Fixture/fixture.php.inc +++ b/tests/Issues/Issue6840/Fixture/fixture.php.inc @@ -23,7 +23,7 @@ class Fixture { protected array $imageSize; - public function __construct(protected $imageId) + public function __construct(protected string $imageId) { $this->imageSize = []; }