diff --git a/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromStrictConstructorRector/Fixture/default_array_merge.php.inc b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromStrictConstructorRector/Fixture/default_array_merge.php.inc new file mode 100644 index 00000000000..2e0f774937c --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Property/TypedPropertyFromStrictConstructorRector/Fixture/default_array_merge.php.inc @@ -0,0 +1,35 @@ + 'value' + ]; + + public function __construct(array $options) + { + $this->options = array_merge($this->options, $options); + } +} + +?> +----- + 'value' + ]; + + public function __construct(array $options) + { + $this->options = array_merge($this->options, $options); + } +} + +?> diff --git a/rules/TypeDeclaration/AlreadyAssignDetector/ConstructorAssignDetector.php b/rules/TypeDeclaration/AlreadyAssignDetector/ConstructorAssignDetector.php index 2175248e75b..aab0c20c809 100644 --- a/rules/TypeDeclaration/AlreadyAssignDetector/ConstructorAssignDetector.php +++ b/rules/TypeDeclaration/AlreadyAssignDetector/ConstructorAssignDetector.php @@ -7,12 +7,15 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Expr\PropertyFetch; +use PhpParser\Node\Identifier; 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\NodeFinder; use PhpParser\NodeTraverser; use PHPStan\Type\ObjectType; use Rector\Core\NodeAnalyzer\PropertyFetchAnalyzer; @@ -65,6 +68,13 @@ public function isPropertyAssigned(ClassLike $classLike, string $propertyName): /** @var Assign $assign */ $assign = $node; + + // is merged in assign? + if ($this->isPropertyUsedInAssign($assign, $propertyName)) { + $isAssignedInConstructor = false; + return NodeTraverser::STOP_TRAVERSAL; + } + $isFirstLevelStatement = $assign->getAttribute(self::IS_FIRST_LEVEL_STATEMENT); // cannot be nested @@ -178,4 +188,20 @@ private function matchInitializeClassMethod(ClassLike $classLike): array return $initializingClassMethods; } + + private function isPropertyUsedInAssign(Assign $assign, string $propertyName): bool + { + $nodeFinder = new NodeFinder(); + return (bool) $nodeFinder->findFirst($assign->expr, static function (Node $node) use ($propertyName): ?bool { + if (! $node instanceof PropertyFetch) { + return null; + } + + if (! $node->name instanceof Identifier) { + return null; + } + + return $node->name->toString() === $propertyName; + }); + } } diff --git a/rules/TypeDeclaration/TypeAnalyzer/PropertyTypeDefaultValueAnalyzer.php b/rules/TypeDeclaration/TypeAnalyzer/PropertyTypeDefaultValueAnalyzer.php index 7c8393c054a..407a00fff6a 100644 --- a/rules/TypeDeclaration/TypeAnalyzer/PropertyTypeDefaultValueAnalyzer.php +++ b/rules/TypeDeclaration/TypeAnalyzer/PropertyTypeDefaultValueAnalyzer.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Stmt\PropertyProperty; +use PHPStan\Type\ArrayType; use PHPStan\Type\Type; use Rector\StaticTypeMapper\StaticTypeMapper; @@ -24,6 +25,9 @@ public function doesConflictWithDefaultValue(PropertyProperty $propertyProperty, // the defaults can be in conflict $defaultType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($propertyProperty->default); + if ($defaultType instanceof ArrayType && $propertyType instanceof ArrayType) { + return false; + } // type is not matching, skip it return ! $defaultType->isSuperTypeOf($propertyType)