diff --git a/rules-tests/Transform/Rector/StaticCall/StaticCallToMethodCallRector/Fixture/skip_when_parent_has_final_construct.php.inc b/rules-tests/Transform/Rector/StaticCall/StaticCallToMethodCallRector/Fixture/skip_when_parent_has_final_construct.php.inc new file mode 100644 index 00000000000..471e647b0a7 --- /dev/null +++ b/rules-tests/Transform/Rector/StaticCall/StaticCallToMethodCallRector/Fixture/skip_when_parent_has_final_construct.php.inc @@ -0,0 +1,19 @@ + $this->user_id ?? App::get(MissingValue::class), + ]; + } +} diff --git a/rules-tests/Transform/Rector/StaticCall/StaticCallToMethodCallRector/Source/ResourceWithFinalConstruct.php b/rules-tests/Transform/Rector/StaticCall/StaticCallToMethodCallRector/Source/ResourceWithFinalConstruct.php new file mode 100644 index 00000000000..045ff2ce346 --- /dev/null +++ b/rules-tests/Transform/Rector/StaticCall/StaticCallToMethodCallRector/Source/ResourceWithFinalConstruct.php @@ -0,0 +1,15 @@ +resource = $resource; + } +} diff --git a/rules/CodeQuality/Rector/CallLike/AddNameToBooleanArgumentRector.php b/rules/CodeQuality/Rector/CallLike/AddNameToBooleanArgumentRector.php index 97cb901d6a9..57b3cbb35bd 100644 --- a/rules/CodeQuality/Rector/CallLike/AddNameToBooleanArgumentRector.php +++ b/rules/CodeQuality/Rector/CallLike/AddNameToBooleanArgumentRector.php @@ -4,6 +4,7 @@ namespace Rector\CodeQuality\Rector\CallLike; +use PhpParser\Node\Expr; use PhpParser\Node; use PhpParser\Node\Expr\CallLike; use Rector\NodeAnalyzer\CallLikeArgumentNameAdder; @@ -58,7 +59,7 @@ public function refactor(Node $node): ?Node { return $this->callLikeArgumentNameAdder->addNamesToArgs( $node, - fn ($expr): bool => $this->valueResolver->isTrueOrFalse($expr), + fn (Expr $expr): bool => $this->valueResolver->isTrueOrFalse($expr), ); } diff --git a/rules/CodeQuality/Rector/CallLike/AddNameToNullArgumentRector.php b/rules/CodeQuality/Rector/CallLike/AddNameToNullArgumentRector.php index a34b136c5e0..8c4eacd1044 100644 --- a/rules/CodeQuality/Rector/CallLike/AddNameToNullArgumentRector.php +++ b/rules/CodeQuality/Rector/CallLike/AddNameToNullArgumentRector.php @@ -4,6 +4,7 @@ namespace Rector\CodeQuality\Rector\CallLike; +use PhpParser\Node\Expr; use PhpParser\Node; use PhpParser\Node\Expr\CallLike; use Rector\NodeAnalyzer\CallLikeArgumentNameAdder; @@ -58,7 +59,7 @@ public function refactor(Node $node): ?Node { return $this->callLikeArgumentNameAdder->addNamesToArgs( $node, - fn ($expr): bool => $this->valueResolver->isNull($expr), + fn (Expr $expr): bool => $this->valueResolver->isNull($expr), ); } diff --git a/rules/Transform/NodeAnalyzer/FuncCallStaticCallToMethodCallAnalyzer.php b/rules/Transform/NodeAnalyzer/FuncCallStaticCallToMethodCallAnalyzer.php index 2c46e0b5502..c6dec629b46 100644 --- a/rules/Transform/NodeAnalyzer/FuncCallStaticCallToMethodCallAnalyzer.php +++ b/rules/Transform/NodeAnalyzer/FuncCallStaticCallToMethodCallAnalyzer.php @@ -36,7 +36,7 @@ public function matchTypeProvidingExpr( Class_ $class, ClassMethod $classMethod, ObjectType $objectType, - ): MethodCall | PropertyFetch | Variable { + ): MethodCall | PropertyFetch | Variable | null { $expr = $this->typeProvidingExprFromClassResolver->resolveTypeProvidingExprFromClass( $class, $classMethod, @@ -51,6 +51,11 @@ public function matchTypeProvidingExpr( return $expr; } + // Cannot add constructor dependency when nearest parent constructor is final + if ($this->classDependencyManipulator->hasFinalParentConstructor($class)) { + return null; + } + $propertyName = $this->propertyNaming->fqnToVariableName($objectType); $this->classDependencyManipulator->addConstructorDependency( $class, diff --git a/rules/Transform/Rector/FuncCall/FuncCallToMethodCallRector.php b/rules/Transform/Rector/FuncCall/FuncCallToMethodCallRector.php index 08fadf2e746..c00c2c2ad50 100644 --- a/rules/Transform/Rector/FuncCall/FuncCallToMethodCallRector.php +++ b/rules/Transform/Rector/FuncCall/FuncCallToMethodCallRector.php @@ -114,6 +114,10 @@ public function refactor(Node $node): ?Node $funcNameToMethodCallName->getNewObjectType(), ); + if ($expr === null) { + return null; + } + $hasChanged = true; return $this->nodeFactory->createMethodCall( diff --git a/rules/Transform/Rector/StaticCall/StaticCallToMethodCallRector.php b/rules/Transform/Rector/StaticCall/StaticCallToMethodCallRector.php index 5b26e209e8b..06b3effcd34 100644 --- a/rules/Transform/Rector/StaticCall/StaticCallToMethodCallRector.php +++ b/rules/Transform/Rector/StaticCall/StaticCallToMethodCallRector.php @@ -127,6 +127,10 @@ public function refactor(Node $node): ?Node $staticCallToMethodCall->getClassObjectType(), ); + if ($expr === null) { + return null; + } + $methodName = $this->getMethodName($node, $staticCallToMethodCall); $hasChanged = true; diff --git a/src/NodeAnalyzer/CallLikeArgumentNameAdder.php b/src/NodeAnalyzer/CallLikeArgumentNameAdder.php index 503737382c4..53e1fc7ca46 100644 --- a/src/NodeAnalyzer/CallLikeArgumentNameAdder.php +++ b/src/NodeAnalyzer/CallLikeArgumentNameAdder.php @@ -4,6 +4,7 @@ namespace Rector\NodeAnalyzer; +use PhpParser\Node\Expr; use PhpParser\Node\Arg; use PhpParser\Node\Expr\CallLike; use PhpParser\Node\Identifier; @@ -26,22 +27,22 @@ public function __construct( * argument whose value satisfies $shouldNameArgValue. All subsequent positional * arguments receive names too (required by PHP named-arg semantics). * - * @param callable(\PhpParser\Node\Expr): bool $shouldNameArgValue + * @param callable(Expr):bool $shouldNameArgValue */ - public function addNamesToArgs(CallLike $node, callable $shouldNameArgValue): ?CallLike + public function addNamesToArgs(CallLike $callLike, callable $shouldNameArgValue): ?CallLike { - if ($this->shouldSkip($node)) { + if ($this->shouldSkip($callLike)) { return null; } - $reflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($node); + $reflection = $this->reflectionResolver->resolveFunctionLikeReflectionFromCall($callLike); if (! $reflection instanceof FunctionReflection && ! $reflection instanceof MethodReflection) { return null; } - $scope = ScopeFetcher::fetch($node); - $args = $node->getArgs(); - $parameters = ParametersAcceptorSelectorVariantsWrapper::select($reflection, $node, $scope) + $scope = ScopeFetcher::fetch($callLike); + $args = $callLike->getArgs(); + $parameters = ParametersAcceptorSelectorVariantsWrapper::select($reflection, $callLike, $scope) ->getParameters(); $position = $this->resolveFirstPositionToName($args, $parameters, $shouldNameArgValue); @@ -70,7 +71,7 @@ public function addNamesToArgs(CallLike $node, callable $shouldNameArgValue): ?C return null; } - return $node; + return $callLike; } private function shouldSkip(CallLike $callLike): bool @@ -96,7 +97,7 @@ private function shouldSkip(CallLike $callLike): bool /** * @param Arg[] $args * @param ParameterReflection[] $parameters - * @param callable(\PhpParser\Node\Expr): bool $shouldNameArgValue + * @param callable(Expr):bool $shouldNameArgValue */ private function resolveFirstPositionToName(array $args, array $parameters, callable $shouldNameArgValue): ?int { diff --git a/src/NodeManipulator/ClassDependencyManipulator.php b/src/NodeManipulator/ClassDependencyManipulator.php index 9125a347977..6a699fecf38 100644 --- a/src/NodeManipulator/ClassDependencyManipulator.php +++ b/src/NodeManipulator/ClassDependencyManipulator.php @@ -189,6 +189,43 @@ public function addStmtsToConstructorIfNotThereYet(Class_ $class, array $stmts): $classMethod->stmts = array_merge($stmts, (array) $classMethod->stmts); } + public function hasFinalParentConstructor(Class_ $class): bool + { + if ($class->getMethod(MethodName::CONSTRUCT) instanceof ClassMethod) { + return false; + } + + $classReflection = $this->reflectionResolver->resolveClassReflection($class); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + $ancestors = array_filter( + $classReflection->getAncestors(), + static fn (ClassReflection $ancestor): bool => $ancestor->getName() !== $classReflection->getName() + ); + + foreach ($ancestors as $ancestor) { + if (! $ancestor->hasNativeMethod(MethodName::CONSTRUCT)) { + continue; + } + + $parentClass = $this->astResolver->resolveClassFromClassReflection($ancestor); + if (! $parentClass instanceof ClassLike) { + continue; + } + + $parentConstructorMethod = $parentClass->getMethod(MethodName::CONSTRUCT); + if (! $parentConstructorMethod instanceof ClassMethod) { + continue; + } + + return $parentConstructorMethod->isFinal(); + } + + return false; + } + private function resolveConstruct(Class_ $class): ?ClassMethod { $constructorMethod = $class->getMethod(MethodName::CONSTRUCT);