Skip to content

Commit f840277

Browse files
ximki-vinkiTomasVotrubaILJA MANYUTIN
authored
[Php80] Skip promotion when property type is wider than constructor param on ClassPropertyAssignToConstructorPromotionRector (#8170)
Co-authored-by: Tomas Votruba <tomas.vot@gmail.com> Co-authored-by: ILJA MANYUTIN <i.manytuin@auchan.ru>
1 parent c607ef0 commit f840277

4 files changed

Lines changed: 123 additions & 1 deletion

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Rector\Tests\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
4+
5+
final class NarrowerPropertyType
6+
{
7+
private object $entity;
8+
9+
public function __construct(?object $entity)
10+
{
11+
$this->entity = $entity;
12+
}
13+
}
14+
15+
?>
16+
-----
17+
<?php
18+
19+
namespace Rector\Tests\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
20+
21+
final class NarrowerPropertyType
22+
{
23+
public function __construct(private object $entity)
24+
{
25+
}
26+
}
27+
28+
?>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Rector\Tests\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
4+
5+
final class SkipWiderPropertyType
6+
{
7+
private(set) ?object $entity;
8+
9+
public function __construct(object $entity)
10+
{
11+
$this->entity = $entity;
12+
}
13+
14+
public function clear(): void
15+
{
16+
$this->entity = null;
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Rector\Tests\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
4+
5+
final class WiderPropertyTypeWithoutNullAssign
6+
{
7+
private(set) ?object $entity;
8+
9+
public function __construct(object $entity)
10+
{
11+
$this->entity = $entity;
12+
}
13+
14+
}
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace Rector\Tests\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector\Fixture;
21+
22+
final class WiderPropertyTypeWithoutNullAssign
23+
{
24+
public function __construct(private(set) object $entity)
25+
{
26+
}
27+
28+
}
29+
30+
?>

rules/Php80/Rector/Class_/ClassPropertyAssignToConstructorPromotionRector.php

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PhpParser\Node;
88
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Expr\Assign;
910
use PhpParser\Node\Expr\PropertyFetch;
1011
use PhpParser\Node\Expr\Variable;
1112
use PhpParser\Node\FunctionLike;
@@ -35,6 +36,7 @@
3536
use Rector\Php80\DocBlock\PropertyPromotionDocBlockMerger;
3637
use Rector\Php80\Guard\MakePropertyPromotionGuard;
3738
use Rector\Php80\NodeAnalyzer\PromotedPropertyCandidateResolver;
39+
use Rector\PhpParser\Node\BetterNodeFinder;
3840
use Rector\PhpParser\Node\Value\ValueResolver;
3941
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
4042
use Rector\Rector\AbstractRector;
@@ -98,6 +100,7 @@ public function __construct(
98100
private readonly PhpDocInfoFactory $phpDocInfoFactory,
99101
private readonly StaticTypeMapper $staticTypeMapper,
100102
private readonly ValueResolver $valueResolver,
103+
private readonly BetterNodeFinder $betterNodeFinder,
101104
) {
102105
}
103106

@@ -220,6 +223,10 @@ public function refactor(Node $node): ?Node
220223
continue;
221224
}
222225

226+
if ($this->shouldSkipPropertyAssignedNull($node, $propertyName)) {
227+
continue;
228+
}
229+
223230
$hasChanged = true;
224231

225232
// remove property from class
@@ -477,6 +484,45 @@ private function shouldUsePropertyTypeForPromotedParam(Property $property, Param
477484
$paramType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($param->type);
478485
$paramTypeWithoutNull = TypeCombinator::removeNull($paramType);
479486

480-
return $this->typeComparator->areTypesEqual($type, $paramTypeWithoutNull);
487+
if (! $this->typeComparator->areTypesEqual($type, $paramTypeWithoutNull)) {
488+
return false;
489+
}
490+
491+
if ($param->default instanceof Expr) {
492+
$paramType = TypeCombinator::union($paramType, $this->getType($param->default));
493+
}
494+
495+
if ($this->typeComparator->isSubtype($paramType, $propertyType)
496+
&& ! $this->typeComparator->areTypesEqual($propertyType, $paramType)) {
497+
return false;
498+
}
499+
500+
return true;
501+
}
502+
503+
private function shouldSkipPropertyAssignedNull(Class_ $class, string $propertyName): bool
504+
{
505+
return (bool) $this->betterNodeFinder->findFirst(
506+
$class,
507+
function (Node $node) use ($propertyName): bool {
508+
if (! $node instanceof Assign) {
509+
return false;
510+
}
511+
512+
if (! $node->var instanceof PropertyFetch) {
513+
return false;
514+
}
515+
516+
if (! $this->isName($node->var->var, 'this')) {
517+
return false;
518+
}
519+
520+
if (! $this->isName($node->var->name, $propertyName)) {
521+
return false;
522+
}
523+
524+
return $this->valueResolver->isNull($node->expr);
525+
}
526+
);
481527
}
482528
}

0 commit comments

Comments
 (0)