diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/narrower_property_type.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/narrower_property_type.php.inc new file mode 100644 index 00000000000..570e1a2f252 --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/narrower_property_type.php.inc @@ -0,0 +1,28 @@ +entity = $entity; + } +} + +?> +----- + diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/skip_wider_property_type.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/skip_wider_property_type.php.inc new file mode 100644 index 00000000000..086a63a54ea --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/skip_wider_property_type.php.inc @@ -0,0 +1,18 @@ +entity = $entity; + } + + public function clear(): void + { + $this->entity = null; + } +} diff --git a/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/wider_property_type_without_null_assign.php.inc b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/wider_property_type_without_null_assign.php.inc new file mode 100644 index 00000000000..35672d1c57b --- /dev/null +++ b/rules-tests/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector/Fixture/wider_property_type_without_null_assign.php.inc @@ -0,0 +1,30 @@ +entity = $entity; + } + +} + +?> +----- + diff --git a/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php b/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php index 7e26ea9b871..079948aaaae 100644 --- a/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php +++ b/rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\FunctionLike; @@ -35,6 +36,7 @@ use Rector\Php80\DocBlock\PropertyPromotionDocBlockMerger; use Rector\Php80\Guard\MakePropertyPromotionGuard; use Rector\Php80\NodeAnalyzer\PromotedPropertyCandidateResolver; +use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PhpParser\Node\Value\ValueResolver; use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; use Rector\Rector\AbstractRector; @@ -98,6 +100,7 @@ public function __construct( private readonly PhpDocInfoFactory $phpDocInfoFactory, private readonly StaticTypeMapper $staticTypeMapper, private readonly ValueResolver $valueResolver, + private readonly BetterNodeFinder $betterNodeFinder, ) { } @@ -220,6 +223,10 @@ public function refactor(Node $node): ?Node continue; } + if ($this->shouldSkipPropertyAssignedNull($node, $propertyName)) { + continue; + } + $hasChanged = true; // remove property from class @@ -477,6 +484,45 @@ private function shouldUsePropertyTypeForPromotedParam(Property $property, Param $paramType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($param->type); $paramTypeWithoutNull = TypeCombinator::removeNull($paramType); - return $this->typeComparator->areTypesEqual($type, $paramTypeWithoutNull); + if (! $this->typeComparator->areTypesEqual($type, $paramTypeWithoutNull)) { + return false; + } + + if ($param->default instanceof Expr) { + $paramType = TypeCombinator::union($paramType, $this->getType($param->default)); + } + + if ($this->typeComparator->isSubtype($paramType, $propertyType) + && ! $this->typeComparator->areTypesEqual($propertyType, $paramType)) { + return false; + } + + return true; + } + + private function shouldSkipPropertyAssignedNull(Class_ $class, string $propertyName): bool + { + return (bool) $this->betterNodeFinder->findFirst( + $class, + function (Node $node) use ($propertyName): bool { + if (! $node instanceof Assign) { + return false; + } + + if (! $node->var instanceof PropertyFetch) { + return false; + } + + if (! $this->isName($node->var->var, 'this')) { + return false; + } + + if (! $this->isName($node->var->name, $propertyName)) { + return false; + } + + return $this->valueResolver->isNull($node->expr); + } + ); } }