diff --git a/packages/NodeTypeResolver/PHPStan/Type/TypeFactory.php b/packages/NodeTypeResolver/PHPStan/Type/TypeFactory.php index 16ff8076e88..d02376d60a0 100644 --- a/packages/NodeTypeResolver/PHPStan/Type/TypeFactory.php +++ b/packages/NodeTypeResolver/PHPStan/Type/TypeFactory.php @@ -4,6 +4,8 @@ namespace Rector\NodeTypeResolver\PHPStan\Type; +use Rector\NodeTypeResolver\PHPStan\ObjectWithoutClassTypeWithParentTypes; +use PHPStan\Type\ObjectType; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantArrayType; @@ -58,8 +60,14 @@ public function uniquateTypes(array $types, bool $keepConstant = false): array { $constantTypeHashes = []; $uniqueTypes = []; + $totalTypes = count($types); foreach ($types as $type) { + if ($totalTypes > 1 && $type instanceof ObjectWithoutClassTypeWithParentTypes) { + $parents = $type->getParentTypes(); + $type = new ObjectType($parents[0]->getClassName()); + } + $removedConstantType = $this->removeValueFromConstantType($type); $removedConstantTypeHash = $this->typeHasher->createTypeHash($removedConstantType); diff --git a/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/anonymous_extends_existing_class_in_union.php.inc b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/anonymous_extends_existing_class_in_union.php.inc index 42b162ea3c6..245f9aad009 100644 --- a/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/anonymous_extends_existing_class_in_union.php.inc +++ b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/anonymous_extends_existing_class_in_union.php.inc @@ -24,7 +24,7 @@ namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsR final class AnonymousExtendsExistingClassInUnion { - private ?\DateTime $x = null; + private \DateTime $x; public function __construct() { diff --git a/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/if_else_assign.php.inc b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/if_else_assign.php.inc new file mode 100644 index 00000000000..000d769fa6d --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/if_else_assign.php.inc @@ -0,0 +1,39 @@ +apiUrl = 'https://example.com/'; + } else { + $this->apiUrl = 'https://another.example.com/'; + } + } +} + +?> +----- +apiUrl = 'https://example.com/'; + } else { + $this->apiUrl = 'https://another.example.com/'; + } + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/if_else_assign_complex.php.inc b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/if_else_assign_complex.php.inc new file mode 100644 index 00000000000..d1cf51f3baa --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/Fixture/if_else_assign_complex.php.inc @@ -0,0 +1,43 @@ +apiUrl = 'https://example.com/'; + } + } else { + $this->apiUrl = 'https://another.example.com/'; + } + } +} + +?> +----- +apiUrl = 'https://example.com/'; + } + } else { + $this->apiUrl = 'https://another.example.com/'; + } + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/FixtureComplexTypes/anonymous_extends_existing_class.php.inc b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/FixtureComplexTypes/anonymous_extends_existing_class.php.inc index ef9e8cfec4c..6bc65847edb 100644 --- a/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/FixtureComplexTypes/anonymous_extends_existing_class.php.inc +++ b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/FixtureComplexTypes/anonymous_extends_existing_class.php.inc @@ -2,7 +2,7 @@ namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector\FixtureComplexTypes; -final class AnonymousExtendsExistingClassInUnion +final class AnonymousExtendsExistingClassInUnion2 { private $x; @@ -22,9 +22,9 @@ final class AnonymousExtendsExistingClassInUnion namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector\FixtureComplexTypes; -final class AnonymousExtendsExistingClassInUnion +final class AnonymousExtendsExistingClassInUnion2 { - private ?\DateTime $x = null; + private \DateTime $x; public function __construct() { diff --git a/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/FixtureComplexTypes/anonymous_extends_existing_class_docblock_exists.php.inc b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/FixtureComplexTypes/anonymous_extends_existing_class_docblock_exists.php.inc index 938c84279fd..d6c5ee62dc9 100644 --- a/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/FixtureComplexTypes/anonymous_extends_existing_class_docblock_exists.php.inc +++ b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromAssignsRector/FixtureComplexTypes/anonymous_extends_existing_class_docblock_exists.php.inc @@ -28,7 +28,7 @@ namespace Rector\Tests\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsR final class AnonymousExtendsExistingClassDocblockExists { - private ?\DateTime $x = null; + private \DateTime $x; public function __construct() { diff --git a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php index 2291f54334c..14681a9a70e 100644 --- a/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php +++ b/rules/DeadCode/PhpDoc/DeadVarTagValueNodeAnalyzer.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt\Property; use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\Type\IntersectionType; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use Rector\NodeTypeResolver\TypeComparator\TypeComparator; use Rector\StaticTypeMapper\StaticTypeMapper; @@ -37,10 +38,17 @@ public function isDead(VarTagValueNode $varTagValueNode, Property $property): bo return ! $docType instanceof IntersectionType; } - return $this->typeComparator->arePhpParserAndPhpStanPhpDocTypesEqual( + if ($this->typeComparator->arePhpParserAndPhpStanPhpDocTypesEqual( $property->type, $varTagValueNode->type, $property + )) { + return true; + } + + return $docType instanceof UnionType && $this->typeComparator->areTypesEqual( + TypeCombinator::removeNull($docType), + $propertyType ); } } diff --git a/rules/TypeDeclaration/AlreadyAssignDetector/ConstructorAssignDetector.php b/rules/TypeDeclaration/AlreadyAssignDetector/ConstructorAssignDetector.php index b7054000a67..2175248e75b 100644 --- a/rules/TypeDeclaration/AlreadyAssignDetector/ConstructorAssignDetector.php +++ b/rules/TypeDeclaration/AlreadyAssignDetector/ConstructorAssignDetector.php @@ -7,9 +7,12 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Else_; use PhpParser\Node\Stmt\Expression; +use PhpParser\Node\Stmt\If_; use PhpParser\NodeTraverser; use PHPStan\Type\ObjectType; use Rector\Core\NodeAnalyzer\PropertyFetchAnalyzer; @@ -50,6 +53,11 @@ public function isPropertyAssigned(ClassLike $classLike, string $propertyName): $this->simpleCallableNodeTraverser->traverseNodesWithCallable((array) $initializeClassMethod->stmts, function ( Node $node ) use ($propertyName, &$isAssignedInConstructor): ?int { + if ($this->isIfElseAssign($node, $propertyName)) { + $isAssignedInConstructor = true; + return NodeTraverser::STOP_TRAVERSAL; + } + $expr = $this->matchAssignExprToPropertyName($node, $propertyName); if (! $expr instanceof Expr) { return null; @@ -77,6 +85,39 @@ public function isPropertyAssigned(ClassLike $classLike, string $propertyName): return $isAssignedInConstructor; } + /** + * @param Stmt[] $stmts + */ + private function isAssignedInStmts(array $stmts, string $propertyName): bool + { + $isAssigned = false; + foreach ($stmts as $stmt) { + // non Expression can be on next stmt + if (! $stmt instanceof Expression) { + $isAssigned = false; + break; + } + + if ($this->matchAssignExprToPropertyName($stmt->expr, $propertyName) instanceof Expr) { + $isAssigned = true; + } + } + + return $isAssigned; + } + + private function isIfElseAssign(Node $node, string $propertyName): bool + { + if (! $node instanceof If_ || $node->elseifs !== [] || ! $node->else instanceof Else_) { + return false; + } + + return $this->isAssignedInStmts($node->stmts, $propertyName) && $this->isAssignedInStmts( + $node->else->stmts, + $propertyName + ); + } + private function matchAssignExprToPropertyName(Node $node, string $propertyName): ?Expr { if (! $node instanceof Assign) {