|
6 | 6 |
|
7 | 7 | use PhpParser\Node; |
8 | 8 | use PhpParser\Node\Expr; |
| 9 | +use PhpParser\Node\Expr\Assign; |
9 | 10 | use PhpParser\Node\Expr\PropertyFetch; |
10 | 11 | use PhpParser\Node\Expr\Variable; |
11 | 12 | use PhpParser\Node\FunctionLike; |
|
35 | 36 | use Rector\Php80\DocBlock\PropertyPromotionDocBlockMerger; |
36 | 37 | use Rector\Php80\Guard\MakePropertyPromotionGuard; |
37 | 38 | use Rector\Php80\NodeAnalyzer\PromotedPropertyCandidateResolver; |
| 39 | +use Rector\PhpParser\Node\BetterNodeFinder; |
38 | 40 | use Rector\PhpParser\Node\Value\ValueResolver; |
39 | 41 | use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; |
40 | 42 | use Rector\Rector\AbstractRector; |
@@ -98,6 +100,7 @@ public function __construct( |
98 | 100 | private readonly PhpDocInfoFactory $phpDocInfoFactory, |
99 | 101 | private readonly StaticTypeMapper $staticTypeMapper, |
100 | 102 | private readonly ValueResolver $valueResolver, |
| 103 | + private readonly BetterNodeFinder $betterNodeFinder, |
101 | 104 | ) { |
102 | 105 | } |
103 | 106 |
|
@@ -220,7 +223,7 @@ public function refactor(Node $node): ?Node |
220 | 223 | continue; |
221 | 224 | } |
222 | 225 |
|
223 | | - if ($this->shouldSkipWiderPropertyType($property, $param)) { |
| 226 | + if ($this->shouldSkipPropertyAssignedNull($node, $propertyName)) { |
224 | 227 | continue; |
225 | 228 | } |
226 | 229 |
|
@@ -484,31 +487,29 @@ private function shouldUsePropertyTypeForPromotedParam(Property $property, Param |
484 | 487 | return $this->typeComparator->areTypesEqual($type, $paramTypeWithoutNull); |
485 | 488 | } |
486 | 489 |
|
487 | | - /** |
488 | | - * Promotion shares one type. When processUnionType would copy the property type onto the |
489 | | - * param and that type is strictly wider (e.g. ?object vs object), skip — otherwise the |
490 | | - * constructor contract is loosened. Narrowing (object vs ?object) stays allowed. |
491 | | - * Interface vs implementation does not fire here: shouldUsePropertyTypeForPromotedParam |
492 | | - * is false when the non-null base types differ. |
493 | | - */ |
494 | | - private function shouldSkipWiderPropertyType(Property $property, Param $param): bool |
| 490 | + private function shouldSkipPropertyAssignedNull(Class_ $class, string $propertyName): bool |
495 | 491 | { |
496 | | - if (! $this->shouldUsePropertyTypeForPromotedParam($property, $param)) { |
497 | | - return false; |
498 | | - } |
| 492 | + return (bool) $this->betterNodeFinder->findFirst( |
| 493 | + $class, |
| 494 | + function (Node $node) use ($propertyName): bool { |
| 495 | + if (! $node instanceof Assign) { |
| 496 | + return false; |
| 497 | + } |
499 | 498 |
|
500 | | - if (! $property->type instanceof Node || ! $param->type instanceof Node) { |
501 | | - return false; |
502 | | - } |
| 499 | + if (! $node->var instanceof PropertyFetch) { |
| 500 | + return false; |
| 501 | + } |
503 | 502 |
|
504 | | - $propertyType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($property->type); |
505 | | - $paramType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($param->type); |
| 503 | + if (! $this->isName($node->var->var, 'this')) { |
| 504 | + return false; |
| 505 | + } |
506 | 506 |
|
507 | | - if ($param->default instanceof Expr) { |
508 | | - $paramType = TypeCombinator::union($paramType, $this->getType($param->default)); |
509 | | - } |
| 507 | + if (! $this->isName($node->var->name, $propertyName)) { |
| 508 | + return false; |
| 509 | + } |
510 | 510 |
|
511 | | - return $this->typeComparator->isSubtype($paramType, $propertyType) |
512 | | - && ! $this->typeComparator->areTypesEqual($propertyType, $paramType); |
| 511 | + return $this->valueResolver->isNull($node->expr); |
| 512 | + } |
| 513 | + ); |
513 | 514 | } |
514 | 515 | } |
0 commit comments