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 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/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php b/rules/CodeQuality/Rector/Class_/TypeWillReturnCallableArrowFunctionRector.php index 9766aea0..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; @@ -26,6 +27,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 +47,7 @@ public function __construct( private readonly StaticTypeMapper $staticTypeMapper, private readonly SetUpAssignedMockTypesResolver $setUpAssignedMockTypesResolver, private readonly MethodParametersAndReturnTypesResolver $methodParametersAndReturnTypesResolver, + private readonly ReflectionResolver $reflectionResolver ) { } @@ -127,11 +130,17 @@ public function refactor(Node $node): ?Class_ $hasChanged = false; + $currentClassReflection = $this->reflectionResolver->resolveClassReflection($node); + if (! $currentClassReflection instanceof 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 +197,8 @@ public function refactor(Node $node): ?Class_ $parameterTypesAndReturnType = $this->methodParametersAndReturnTypesResolver->resolveFromReflection( $callerType, - $methodName + $methodName, + $currentClassReflection ); if (! $parameterTypesAndReturnType instanceof ParamTypesAndReturnType) { @@ -229,19 +239,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..9b4f5ea0 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,26 +53,42 @@ public function resolveFromReflection( /** * @return Type[] */ - private function resolveParameterTypes(ExtendedMethodReflection $extendedMethodReflection): array - { + private function resolveParameterTypes( + ExtendedMethodReflection $extendedMethodReflection, + ClassReflection $currentClassReflection + ): array { $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors( $extendedMethodReflection->getVariants() ); $parameterTypes = []; foreach ($extendedParametersAcceptor->getParameters() as $parameterReflection) { - $parameterTypes[] = $parameterReflection->getType(); + $parameterType = $parameterReflection->getNativeType(); + + 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->getNativeReturnType(); + + if ($returnType->isObject()->yes() && $currentClassReflection->getName() !== $returnType->getClassReflection()->getName()) { + return new MixedType(); + } + + return $returnType; } } 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 @@ +