diff --git a/rules-tests/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector/Fixture/skip_assigned_in_constructor.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector/Fixture/skip_assigned_in_constructor.php.inc new file mode 100644 index 00000000000..f2d3d675d8a --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector/Fixture/skip_assigned_in_constructor.php.inc @@ -0,0 +1,23 @@ +listPath = $listPath; + } + + public function getListPath(): string + { + return $this->listPath; + } + + public function setListPath(string $path): void + { + $this->listPath = $path; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector/Fixture/skip_nested_assigned_in_constructor.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector/Fixture/skip_nested_assigned_in_constructor.php.inc new file mode 100644 index 00000000000..d54b4057cef --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector/Fixture/skip_nested_assigned_in_constructor.php.inc @@ -0,0 +1,25 @@ +listPath = $listPath; + } + } + + public function getListPath(): string + { + return $this->listPath; + } + + public function setListPath(string $path): void + { + $this->listPath = $path; + } +} diff --git a/rules/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector.php b/rules/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector.php index e27356c026e..9e3e68b56c8 100644 --- a/rules/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector.php +++ b/rules/TypeDeclaration/Rector/Class_/PropertyTypeFromStrictSetterGetterRector.php @@ -6,12 +6,15 @@ use PhpParser\Node; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\ConstFetch; +use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Name; use PhpParser\Node\Scalar\Float_; use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\FloatType; @@ -21,12 +24,14 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use Rector\Php74\Guard\MakePropertyTypedGuard; +use Rector\PhpParser\Node\BetterNodeFinder; use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\StaticTypeMapper\StaticTypeMapper; use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\GetterTypeDeclarationPropertyTypeInferer; use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\SetterTypeDeclarationPropertyTypeInferer; +use Rector\ValueObject\MethodName; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; @@ -42,7 +47,8 @@ public function __construct( private readonly SetterTypeDeclarationPropertyTypeInferer $setterTypeDeclarationPropertyTypeInferer, private readonly MakePropertyTypedGuard $makePropertyTypedGuard, private readonly ReflectionResolver $reflectionResolver, - private readonly StaticTypeMapper $staticTypeMapper + private readonly StaticTypeMapper $staticTypeMapper, + private readonly BetterNodeFinder $betterNodeFinder ) { } @@ -113,6 +119,11 @@ public function refactor(Node $node): ?Node continue; } + // constructor assignment can set a different type than the setter/getter → skip + if ($this->isAssignedInConstructor($node, $property)) { + continue; + } + $getterSetterPropertyType = $this->matchGetterSetterIdenticalType($property, $node); if (! $getterSetterPropertyType instanceof Type) { continue; @@ -267,6 +278,33 @@ private function decorateDefaultExpr( $propertyProperty->default = new ConstFetch(new Name('null')); } + private function isAssignedInConstructor(Class_ $class, Property $property): bool + { + $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); + if (! $constructClassMethod instanceof ClassMethod) { + return false; + } + + $propertyName = $this->getName($property); + + foreach ($this->betterNodeFinder->findInstanceOf($constructClassMethod, Assign::class) as $assign) { + if (! $assign->var instanceof PropertyFetch) { + continue; + } + + $propertyFetch = $assign->var; + if (! $this->isName($propertyFetch->var, 'this')) { + continue; + } + + if ($this->isName($propertyFetch->name, $propertyName)) { + return true; + } + } + + return false; + } + private function hasPropertyDefaultNull(Property $property): bool { $defaultExpr = $property->props[0]->default ?? null;