From 78cde706187f501d17ecd2ee508abdecf57435ab Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 21 Aug 2025 09:52:59 +0700 Subject: [PATCH 1/5] [CodeQuality] Skip return createMock() on TypeWillReturnCallableArrowFunctionRector --- .../Fixture/skip_return_create_mock.php.inc | 20 +++++++++++++++++++ .../Symfony/Component/Form/FormInterface.php | 14 +++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector/Fixture/skip_return_create_mock.php.inc create mode 100644 stubs/Symfony/Component/Form/FormInterface.php diff --git a/rules-tests/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector/Fixture/skip_return_create_mock.php.inc b/rules-tests/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector/Fixture/skip_return_create_mock.php.inc new file mode 100644 index 00000000..4169671f --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector/Fixture/skip_return_create_mock.php.inc @@ -0,0 +1,20 @@ +createMock(FormInterface::class); + + $form->method('add') + ->willReturnCallback(function ($name, $type, $options) use (&$calls) { + $calls[] = compact('name', 'type', 'options'); + return $this->createMock(FormInterface::class); + }); + } +} diff --git a/stubs/Symfony/Component/Form/FormInterface.php b/stubs/Symfony/Component/Form/FormInterface.php new file mode 100644 index 00000000..af0df243 --- /dev/null +++ b/stubs/Symfony/Component/Form/FormInterface.php @@ -0,0 +1,14 @@ + Date: Thu, 21 Aug 2025 11:13:50 +0700 Subject: [PATCH 2/5] Fix --- ...eWillReturnCallableArrowFunctionRector.php | 31 +++++++++++-------- ...MethodParametersAndReturnTypesResolver.php | 28 ++++++++++++----- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php b/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php index 9766aea0..868ff06d 100644 --- a/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php +++ b/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php @@ -26,6 +26,7 @@ use Rector\PHPUnit\CodeQuality\ValueObject\ParamTypesAndReturnType; use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; use Rector\Rector\AbstractRector; +use Rector\Reflection\ReflectionResolver; use Rector\StaticTypeMapper\StaticTypeMapper; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -45,6 +46,7 @@ public function __construct( private readonly StaticTypeMapper $staticTypeMapper, private readonly SetUpAssignedMockTypesResolver $setUpAssignedMockTypesResolver, private readonly MethodParametersAndReturnTypesResolver $methodParametersAndReturnTypesResolver, + private readonly ReflectionResolver $reflectionResolver ) { } @@ -127,11 +129,17 @@ public function refactor(Node $node): ?Class_ $hasChanged = false; + $currentClassReflection = $this->reflectionResolver->resolveClassReflection($node); + if (! $currentClassReflection instanceof \PHPStan\Reflection\ClassReflection) { + return null; + } + $propertyNameToMockedTypes = $this->setUpAssignedMockTypesResolver->resolveFromClass($node); $this->traverseNodesWithCallable($node->getMethods(), function (Node $node) use ( &$hasChanged, - $propertyNameToMockedTypes + $propertyNameToMockedTypes, + $currentClassReflection ) { if (! $node instanceof MethodCall || $node->isFirstClassCallable()) { return null; @@ -188,7 +196,8 @@ public function refactor(Node $node): ?Class_ $parameterTypesAndReturnType = $this->methodParametersAndReturnTypesResolver->resolveFromReflection( $callerType, - $methodName + $methodName, + $currentClassReflection ); if (! $parameterTypesAndReturnType instanceof ParamTypesAndReturnType) { @@ -229,19 +238,15 @@ public function refactor(Node $node): ?Class_ $hasChanged = true; } - if (! $innerArg->returnType instanceof Node) { - $returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode( - $parameterTypesAndReturnType->getReturnType(), - TypeKind::RETURN - ); + $returnTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode( + $parameterTypesAndReturnType->getReturnType(), + TypeKind::RETURN + ); - if ($returnTypeNode instanceof Node) { - $innerArg->returnType = $returnTypeNode; - $hasChanged = true; - } + if ($returnTypeNode instanceof Node) { + $innerArg->returnType = $returnTypeNode; + $hasChanged = true; } - - $hasChanged = true; }); if (! $hasChanged) { diff --git a/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php b/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php index 57d8f66c..49bde84f 100644 --- a/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php +++ b/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\IntersectionType; +use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use Rector\Enum\ClassName; @@ -17,7 +18,8 @@ final class MethodParametersAndReturnTypesResolver { public function resolveFromReflection( IntersectionType $intersectionType, - string $methodName + string $methodName, + ClassReflection $currentClassReflection ): ?ParamTypesAndReturnType { foreach ($intersectionType->getTypes() as $intersectionedType) { if (! $intersectionedType instanceof ObjectType) { @@ -39,8 +41,8 @@ public function resolveFromReflection( $mockedMethodReflection = $classReflection->getNativeMethod($methodName); - $parameterTypes = $this->resolveParameterTypes($mockedMethodReflection); - $returnType = $this->resolveReturnType($mockedMethodReflection); + $parameterTypes = $this->resolveParameterTypes($mockedMethodReflection, $currentClassReflection); + $returnType = $this->resolveReturnType($mockedMethodReflection, $currentClassReflection); return new ParamTypesAndReturnType($parameterTypes, $returnType); } @@ -51,7 +53,7 @@ public function resolveFromReflection( /** * @return Type[] */ - private function resolveParameterTypes(ExtendedMethodReflection $extendedMethodReflection): array + private function resolveParameterTypes(ExtendedMethodReflection $extendedMethodReflection, ClassReflection $currentClassReflection): array { $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors( $extendedMethodReflection->getVariants() @@ -59,18 +61,30 @@ private function resolveParameterTypes(ExtendedMethodReflection $extendedMethodR $parameterTypes = []; foreach ($extendedParametersAcceptor->getParameters() as $parameterReflection) { - $parameterTypes[] = $parameterReflection->getType(); + $parameterType = $parameterReflection->getType(); + + if ($parameterType->isObject()->yes() && $currentClassReflection->getName() !== $parameterType->getClassReflection()->getName()) { + return []; + } + + $parameterTypes[] = $parameterType; } return $parameterTypes; } - private function resolveReturnType(ExtendedMethodReflection $extendedMethodReflection): Type + private function resolveReturnType(ExtendedMethodReflection $extendedMethodReflection, ClassReflection $currentClassReflection): Type { $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors( $extendedMethodReflection->getVariants() ); - return $extendedParametersAcceptor->getReturnType(); + $returnType = $extendedParametersAcceptor->getReturnType(); + + if ($returnType->isObject()->yes() && $currentClassReflection->getName() !== $returnType->getClassReflection()->getName()) { + return new MixedType(); + } + + return $returnType; } } From b940a4ab2f754e19c84620822122d794d81c87ec Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 21 Aug 2025 04:14:32 +0000 Subject: [PATCH 3/5] [ci-review] Rector Rectify --- .../TypeWillReturnCallableArrowFunctionRector.php | 3 ++- .../MethodParametersAndReturnTypesResolver.php | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php b/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php index 868ff06d..ba8c8df4 100644 --- a/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php +++ b/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php @@ -13,6 +13,7 @@ use PhpParser\Node\Expr\Variable; use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\Class_; +use PHPStan\Reflection\ClassReflection; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; @@ -130,7 +131,7 @@ public function refactor(Node $node): ?Class_ $hasChanged = false; $currentClassReflection = $this->reflectionResolver->resolveClassReflection($node); - if (! $currentClassReflection instanceof \PHPStan\Reflection\ClassReflection) { + if (! $currentClassReflection instanceof ClassReflection) { return null; } diff --git a/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php b/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php index 49bde84f..74aaeb32 100644 --- a/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php +++ b/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php @@ -53,8 +53,10 @@ public function resolveFromReflection( /** * @return Type[] */ - private function resolveParameterTypes(ExtendedMethodReflection $extendedMethodReflection, ClassReflection $currentClassReflection): array - { + private function resolveParameterTypes( + ExtendedMethodReflection $extendedMethodReflection, + ClassReflection $currentClassReflection + ): array { $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors( $extendedMethodReflection->getVariants() ); @@ -73,13 +75,15 @@ private function resolveParameterTypes(ExtendedMethodReflection $extendedMethodR return $parameterTypes; } - private function resolveReturnType(ExtendedMethodReflection $extendedMethodReflection, ClassReflection $currentClassReflection): Type - { + private function resolveReturnType( + ExtendedMethodReflection $extendedMethodReflection, + ClassReflection $currentClassReflection + ): Type { $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors( $extendedMethodReflection->getVariants() ); - $returnType = $extendedParametersAcceptor->getReturnType(); + $returnType = $extendedParametersAcceptor->getReturnType(); if ($returnType->isObject()->yes() && $currentClassReflection->getName() !== $returnType->getClassReflection()->getName()) { return new MixedType(); From b067f02dce580f15672d7394b080bdb0e270d481 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 21 Aug 2025 11:23:46 +0700 Subject: [PATCH 4/5] Fix phpstan --- phpstan.neon | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index c8ad29f2..b1199e35 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -57,3 +57,7 @@ parameters: identifier: assign.propertyType - '#::provideMinPhpVersion\(\) never returns \d+ so it can be removed from the return type#' + + - + message: '#Call to an undefined method PHPStan\\Type\\Type\:\:getClassReflection\(\)#' + path: rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php From f1899d77c07a7402ece1c6dcb3371f316c7d2b42 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 21 Aug 2025 11:25:09 +0700 Subject: [PATCH 5/5] use native --- .../Reflection/MethodParametersAndReturnTypesResolver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php b/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php index 74aaeb32..9b4f5ea0 100644 --- a/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php +++ b/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php @@ -63,7 +63,7 @@ private function resolveParameterTypes( $parameterTypes = []; foreach ($extendedParametersAcceptor->getParameters() as $parameterReflection) { - $parameterType = $parameterReflection->getType(); + $parameterType = $parameterReflection->getNativeType(); if ($parameterType->isObject()->yes() && $currentClassReflection->getName() !== $parameterType->getClassReflection()->getName()) { return []; @@ -83,7 +83,7 @@ private function resolveReturnType( $extendedMethodReflection->getVariants() ); - $returnType = $extendedParametersAcceptor->getReturnType(); + $returnType = $extendedParametersAcceptor->getNativeReturnType(); if ($returnType->isObject()->yes() && $currentClassReflection->getName() !== $returnType->getClassReflection()->getName()) { return new MixedType();