EVOLUTION-NINJA
Edit File: AssignToPropertyTypeInferer.php
<?php declare (strict_types=1); namespace Rector\TypeDeclaration\TypeInferer; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Stmt\ClassLike; use PHPStan\Type\ArrayType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\Type; use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; use Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector; use Rector\TypeDeclaration\AlreadyAssignDetector\NullTypeAssignDetector; use Rector\TypeDeclaration\AlreadyAssignDetector\PropertyDefaultAssignDetector; use Rector\TypeDeclaration\Matcher\PropertyAssignMatcher; use RectorPrefix20211231\Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser; final class AssignToPropertyTypeInferer { /** * @readonly * @var \Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector */ private $constructorAssignDetector; /** * @readonly * @var \Rector\TypeDeclaration\Matcher\PropertyAssignMatcher */ private $propertyAssignMatcher; /** * @readonly * @var \Rector\TypeDeclaration\AlreadyAssignDetector\PropertyDefaultAssignDetector */ private $propertyDefaultAssignDetector; /** * @readonly * @var \Rector\TypeDeclaration\AlreadyAssignDetector\NullTypeAssignDetector */ private $nullTypeAssignDetector; /** * @readonly * @var \Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser */ private $simpleCallableNodeTraverser; /** * @readonly * @var \Rector\NodeTypeResolver\PHPStan\Type\TypeFactory */ private $typeFactory; /** * @readonly * @var \Rector\NodeTypeResolver\NodeTypeResolver */ private $nodeTypeResolver; public function __construct(\Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector $constructorAssignDetector, \Rector\TypeDeclaration\Matcher\PropertyAssignMatcher $propertyAssignMatcher, \Rector\TypeDeclaration\AlreadyAssignDetector\PropertyDefaultAssignDetector $propertyDefaultAssignDetector, \Rector\TypeDeclaration\AlreadyAssignDetector\NullTypeAssignDetector $nullTypeAssignDetector, \RectorPrefix20211231\Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser $simpleCallableNodeTraverser, \Rector\NodeTypeResolver\PHPStan\Type\TypeFactory $typeFactory, \Rector\NodeTypeResolver\NodeTypeResolver $nodeTypeResolver) { $this->constructorAssignDetector = $constructorAssignDetector; $this->propertyAssignMatcher = $propertyAssignMatcher; $this->propertyDefaultAssignDetector = $propertyDefaultAssignDetector; $this->nullTypeAssignDetector = $nullTypeAssignDetector; $this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser; $this->typeFactory = $typeFactory; $this->nodeTypeResolver = $nodeTypeResolver; } public function inferPropertyInClassLike(string $propertyName, \PhpParser\Node\Stmt\ClassLike $classLike) : ?\PHPStan\Type\Type { $assignedExprTypes = []; $this->simpleCallableNodeTraverser->traverseNodesWithCallable($classLike->stmts, function (\PhpParser\Node $node) use($propertyName, &$assignedExprTypes) { if (!$node instanceof \PhpParser\Node\Expr\Assign) { return null; } $expr = $this->propertyAssignMatcher->matchPropertyAssignExpr($node, $propertyName); if (!$expr instanceof \PhpParser\Node\Expr) { return null; } $assignedExprTypes[] = $this->resolveExprStaticTypeIncludingDimFetch($node); return null; }); if ($this->shouldAddNullType($classLike, $propertyName, $assignedExprTypes)) { $assignedExprTypes[] = new \PHPStan\Type\NullType(); } if ($assignedExprTypes === []) { return null; } return $this->typeFactory->createMixedPassedOrUnionType($assignedExprTypes); } private function resolveExprStaticTypeIncludingDimFetch(\PhpParser\Node\Expr\Assign $assign) : \PHPStan\Type\Type { $exprStaticType = $this->nodeTypeResolver->getType($assign->expr); if ($assign->var instanceof \PhpParser\Node\Expr\ArrayDimFetch) { return new \PHPStan\Type\ArrayType(new \PHPStan\Type\MixedType(), $exprStaticType); } return $exprStaticType; } /** * @param Type[] $assignedExprTypes */ private function shouldAddNullType(\PhpParser\Node\Stmt\ClassLike $classLike, string $propertyName, array $assignedExprTypes) : bool { $hasPropertyDefaultValue = $this->propertyDefaultAssignDetector->detect($classLike, $propertyName); $isAssignedInConstructor = $this->constructorAssignDetector->isPropertyAssigned($classLike, $propertyName); if ($assignedExprTypes === [] && ($isAssignedInConstructor || $hasPropertyDefaultValue)) { return \false; } $shouldAddNullType = $this->nullTypeAssignDetector->detect($classLike, $propertyName); if ($shouldAddNullType) { if ($isAssignedInConstructor) { return \false; } return !$hasPropertyDefaultValue; } if ($assignedExprTypes === []) { return \false; } if ($isAssignedInConstructor) { return \false; } return !$hasPropertyDefaultValue; } }