diff --git a/packages/StaticTypeMapper/StaticTypeMapper.php b/packages/StaticTypeMapper/StaticTypeMapper.php index d08cc5383d6..6c0a3f6fb2c 100644 --- a/packages/StaticTypeMapper/StaticTypeMapper.php +++ b/packages/StaticTypeMapper/StaticTypeMapper.php @@ -7,6 +7,8 @@ namespace Rector\StaticTypeMapper; use PhpParser\Node; use PhpParser\Node\Name; use PhpParser\Node\NullableType; +use PhpParser\Node\Param; +use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\UnionType as PhpParserUnionType; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; @@ -19,6 +21,7 @@ use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use Rector\Core\Exception\NotImplementedYetException; +use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper; use Rector\StaticTypeMapper\Mapper\PhpParserNodeMapper; use Rector\StaticTypeMapper\Naming\NameScopeFactory; @@ -36,7 +39,6 @@ final class StaticTypeMapper private PhpDocTypeMapper $phpDocTypeMapper, private PhpParserNodeMapper $phpParserNodeMapper ) { -// $this->nameScopeFactory->setStaticTypeMapper($this); } public function mapPHPStanTypeToPHPStanPhpDocTypeNode(Type $phpStanType): TypeNode @@ -78,7 +80,16 @@ final class StaticTypeMapper public function mapPHPStanPhpDocTypeNodeToPHPStanType(TypeNode $typeNode, Node $node): Type { + if ($node instanceof Param) { + $classMethod = $node->getAttribute(AttributeKey::PARENT_NODE); + if ($classMethod instanceof ClassMethod) { + // param does not hany any clue about template map, but class method has + $node = $classMethod; + } + } + $nameScope = $this->nameScopeFactory->createNameScopeFromNode($node); + return $this->phpDocTypeMapper->mapToPHPStanType($typeNode, $node, $nameScope); } diff --git a/phpstan.neon b/phpstan.neon index 0ea424bcafd..3118fff9a9c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -497,3 +497,6 @@ parameters: - message: '#Assign in loop is not allowed#' path: rules/RemovingStatic/Rector/ClassMethod/LocallyCalledStaticMethodToNonStaticRector.php + + # generics single place + - '#Method Rector\\Php80\\NodeResolver\\ArgumentSorter\:\:sortArgsByExpectedParamOrder\(\) should return array but returns array#' diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayParamDocTypeRector/Fixture/skip_template.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayParamDocTypeRector/Fixture/skip_template.php.inc new file mode 100644 index 00000000000..bdafda22ac1 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayParamDocTypeRector/Fixture/skip_template.php.inc @@ -0,0 +1,20 @@ + $args + * @param array $expectedOrderedParams + * @return array + */ + public function run(array $args): array + { + return $args; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Fixture/add_from_child.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Fixture/add_from_child.php.inc index 575ee8c1d54..50802709609 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Fixture/add_from_child.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Fixture/add_from_child.php.inc @@ -2,54 +2,33 @@ namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddArrayReturnDocTypeRector\Fixture; +use Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddArrayReturnDocTypeRector\Source\NestedGetData; + final class AddFromChild { - public function getData(Nested $nested) + public function getData(NestedGetData $nested) { return $nested->getData(); } } -final class Nested -{ - public function getData() - { - return [ - 'key', - 'value' - ]; - } -} - ?> ----- getData(); } } -final class Nested -{ - /** - * @return string[] - */ - public function getData() - { - return [ - 'key', - 'value' - ]; - } -} - ?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Source/NestedGetData.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Source/NestedGetData.php new file mode 100644 index 00000000000..bb7ea23a0a6 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddArrayReturnDocTypeRector/Source/NestedGetData.php @@ -0,0 +1,16 @@ +getTokens(); + } +} diff --git a/rules/Php80/NodeResolver/ArgumentSorter.php b/rules/Php80/NodeResolver/ArgumentSorter.php index d33cc67e052..dae28851977 100644 --- a/rules/Php80/NodeResolver/ArgumentSorter.php +++ b/rules/Php80/NodeResolver/ArgumentSorter.php @@ -6,13 +6,15 @@ namespace Rector\Php80\NodeResolver; use PhpParser\Node\Arg; use PhpParser\Node\Param; +use PHPStan\Reflection\ParameterReflection; final class ArgumentSorter { /** - * @param array $expectedOrderedParams - * @param Arg[] $args - * @return Arg[] + * @template T as Arg|Param + * @param array $expectedOrderedParams + * @param T[] $args + * @return T[] */ public function sortArgsByExpectedParamOrder(array $args, array $expectedOrderedParams): array { diff --git a/rules/Php80/NodeResolver/RequireOptionalParamResolver.php b/rules/Php80/NodeResolver/RequireOptionalParamResolver.php index 3cf47a0f8dc..9192eb80333 100644 --- a/rules/Php80/NodeResolver/RequireOptionalParamResolver.php +++ b/rules/Php80/NodeResolver/RequireOptionalParamResolver.php @@ -4,25 +4,27 @@ declare(strict_types=1); namespace Rector\Php80\NodeResolver; -use PhpParser\Node\FunctionLike; -use PhpParser\Node\Param; -use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ParameterReflection; final class RequireOptionalParamResolver { /** - * @param ClassMethod $functionLike - * @return Param[] + * @return ParameterReflection[] */ - public function resolve(FunctionLike $functionLike): array + public function resolveFromReflection(MethodReflection | FunctionReflection $functionLikeReflection): array { + $parametersAcceptor = $functionLikeReflection->getVariants()[0]; + $optionalParams = []; $requireParams = []; - foreach ($functionLike->getParams() as $position => $param) { - if ($param->default === null && ! $param->variadic) { - $requireParams[$position] = $param; + + foreach ($parametersAcceptor->getParameters() as $position => $parameterReflection) { + if ($parameterReflection->getDefaultValue() === null && ! $parameterReflection->isVariadic()) { + $requireParams[$position] = $parameterReflection; } else { - $optionalParams[$position] = $param; + $optionalParams[$position] = $parameterReflection; } } diff --git a/rules/Php80/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php b/rules/Php80/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php index 8ea474d279d..15d4819421a 100644 --- a/rules/Php80/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php +++ b/rules/Php80/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php @@ -8,10 +8,12 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\New_; use PhpParser\Node\Stmt\ClassMethod; +use PHPStan\Reflection\MethodReflection; use PHPStan\Type\TypeWithClassName; +use Rector\Core\PHPStan\Reflection\CallReflectionResolver; +use Rector\Core\PHPStan\Reflection\ClassMethodReflectionResolver; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\MethodName; -use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Php80\NodeResolver\ArgumentSorter; use Rector\Php80\NodeResolver\RequireOptionalParamResolver; use Rector\Php80\Reflection\MethodReflectionClassMethodResolver; @@ -28,7 +30,9 @@ final class OptionalParametersAfterRequiredRector extends AbstractRector public function __construct( private RequireOptionalParamResolver $requireOptionalParamResolver, private ArgumentSorter $argumentSorter, - private MethodReflectionClassMethodResolver $methodReflectionClassMethodResolver + private MethodReflectionClassMethodResolver $methodReflectionClassMethodResolver, + private CallReflectionResolver $callReflectionResolver, + private ClassMethodReflectionResolver $classMethodReflectionResolver ) { } @@ -88,12 +92,25 @@ CODE_SAMPLE return null; } - $expectedOrderParams = $this->requireOptionalParamResolver->resolve($classMethod); - if ($classMethod->params === $expectedOrderParams) { + $classMethodReflection = $this->classMethodReflectionResolver->resolve($classMethod); + if (! $classMethodReflection instanceof MethodReflection) { return null; } - $classMethod->params = $expectedOrderParams; + $parametersAcceptor = $classMethodReflection->getVariants()[0]; + + $expectedOrderParameterReflections = $this->requireOptionalParamResolver->resolveFromReflection( + $classMethodReflection + ); + if ($parametersAcceptor->getParameters() === $expectedOrderParameterReflections) { + return null; + } + + $newParams = $this->argumentSorter->sortArgsByExpectedParamOrder( + $classMethod->params, + $expectedOrderParameterReflections + ); + $classMethod->params = $newParams; return $classMethod; } @@ -117,8 +134,17 @@ CODE_SAMPLE return null; } - $expectedOrderedParams = $this->requireOptionalParamResolver->resolve($classMethod); - if ($expectedOrderedParams === $classMethod->getParams()) { + $classMethodReflection = $this->classMethodReflectionResolver->resolve($classMethod); + if (! $classMethodReflection instanceof MethodReflection) { + return null; + } + + $parametersAcceptor = $classMethodReflection->getVariants()[0]; + + $expectedOrderedParameterReflections = $this->requireOptionalParamResolver->resolveFromReflection( + $classMethodReflection + ); + if ($expectedOrderedParameterReflections === $parametersAcceptor->getParameters()) { return null; } @@ -126,7 +152,10 @@ CODE_SAMPLE return null; } - $newArgs = $this->argumentSorter->sortArgsByExpectedParamOrder($new->args, $expectedOrderedParams); + $newArgs = $this->argumentSorter->sortArgsByExpectedParamOrder( + $new->args, + $expectedOrderedParameterReflections + ); if ($new->args === $newArgs) { return null; } @@ -138,32 +167,35 @@ CODE_SAMPLE private function refactorMethodCall(MethodCall $methodCall): ?MethodCall { - $classMethod = $this->nodeRepository->findClassMethodByMethodCall($methodCall); - if (! $classMethod instanceof ClassMethod) { + $callReflection = $this->callReflectionResolver->resolveCall($methodCall); + if ($callReflection === null) { return null; } - // because parameters can be already changed - $originalClassMethod = $classMethod->getAttribute(AttributeKey::ORIGINAL_NODE); - if (! $originalClassMethod instanceof ClassMethod) { + $parametersAcceptor = $callReflection->getVariants()[0]; + + $expectedOrderedParameterReflections = $this->requireOptionalParamResolver->resolveFromReflection( + $callReflection + ); + if ($expectedOrderedParameterReflections === $parametersAcceptor->getParameters()) { return null; } - $expectedOrderedParams = $this->requireOptionalParamResolver->resolve($originalClassMethod); - if ($expectedOrderedParams === $classMethod->getParams()) { + if (count($methodCall->args) !== count($parametersAcceptor->getParameters())) { return null; } - if (count($methodCall->args) !== count($classMethod->getParams())) { - return null; - } + $newArgs = $this->argumentSorter->sortArgsByExpectedParamOrder( + $methodCall->args, + $expectedOrderedParameterReflections + ); - $newArgs = $this->argumentSorter->sortArgsByExpectedParamOrder($methodCall->args, $expectedOrderedParams); if ($methodCall->args === $newArgs) { return null; } $methodCall->args = $newArgs; + return $methodCall; } } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector.php b/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector.php index e37f4ea5529..93d03f1bec3 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictTypedCallRector.php @@ -17,15 +17,15 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Return_; use PhpParser\Node\UnionType as PhpParserUnionType; +use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; -use PHPStan\Type\Type; use PHPStan\Type\UnionType; use Rector\Core\Exception\ShouldNotHappenException; +use Rector\Core\PHPStan\Reflection\CallReflectionResolver; use Rector\Core\Rector\AbstractRector; use Rector\Core\ValueObject\PhpVersionFeature; use Rector\TypeDeclaration\NodeAnalyzer\TypeNodeUnwrapper; -use Rector\TypeDeclaration\Reflection\ReflectionTypeResolver; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -35,8 +35,8 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; final class ReturnTypeFromStrictTypedCallRector extends AbstractRector { public function __construct( - private ReflectionTypeResolver $reflectionTypeResolver, - private TypeNodeUnwrapper $typeNodeUnwrapper + private TypeNodeUnwrapper $typeNodeUnwrapper, + private CallReflectionResolver $callReflectionResolver ) { } @@ -162,12 +162,8 @@ CODE_SAMPLE $returnedExpr = $return->expr; - if ($returnedExpr instanceof MethodCall) { + if ($returnedExpr instanceof MethodCall || $returnedExpr instanceof StaticCall || $returnedExpr instanceof FuncCall) { $returnNode = $this->resolveMethodCallReturnNode($returnedExpr); - } elseif ($returnedExpr instanceof StaticCall) { - $returnNode = $this->resolveStaticCallReturnNode($returnedExpr); - } elseif ($returnedExpr instanceof FuncCall) { - $returnNode = $this->resolveFuncCallReturnNode($returnedExpr); } else { return []; } @@ -182,40 +178,16 @@ CODE_SAMPLE return $this->typeNodeUnwrapper->uniquateNodes($returnedStrictTypeNodes); } - private function resolveMethodCallReturnNode(MethodCall $methodCall): ?Node + private function resolveMethodCallReturnNode(MethodCall | StaticCall | FuncCall $call): ?Node { - $classMethod = $this->nodeRepository->findClassMethodByMethodCall($methodCall); - if ($classMethod instanceof ClassMethod) { - return $classMethod->returnType; - } - - $returnType = $this->reflectionTypeResolver->resolveMethodCallReturnType($methodCall); - if (! $returnType instanceof Type) { + $methodReflection = $this->callReflectionResolver->resolveCall($call); + if ($methodReflection === null) { return null; } - return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType); - } - - private function resolveStaticCallReturnNode(StaticCall $staticCall): ?Node - { - $classMethod = $this->nodeRepository->findClassMethodByStaticCall($staticCall); - if ($classMethod instanceof ClassMethod) { - return $classMethod->returnType; - } - - $returnType = $this->reflectionTypeResolver->resolveStaticCallReturnType($staticCall); - if (! $returnType instanceof Type) { - return null; - } - - return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType); - } - - private function resolveFuncCallReturnNode(FuncCall $funcCall): Name | NullableType | PhpParserUnionType | null - { - $returnType = $this->reflectionTypeResolver->resolveFuncCallReturnType($funcCall); - if (! $returnType instanceof Type) { + $parametersAcceptor = $methodReflection->getVariants()[0]; + $returnType = $parametersAcceptor->getReturnType(); + if ($returnType instanceof MixedType) { return null; } diff --git a/rules/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector.php b/rules/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector.php index 32601a3b667..5e9a482f983 100644 --- a/rules/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector.php +++ b/rules/TypeDeclaration/Rector/FunctionLike/ReturnTypeDeclarationRector.php @@ -246,6 +246,7 @@ CODE_SAMPLE $hasExternalClassOrInterfaceOrTrait = $this->externalFullyQualifiedAnalyzer->hasExternalFullyQualifieds( $classLike ); + return $functionLike->returnType === null && $hasExternalClassOrInterfaceOrTrait && $this->isName( $inferredReturnNode, 'void' diff --git a/rules/TypeDeclaration/Reflection/ReflectionTypeResolver.php b/rules/TypeDeclaration/Reflection/ReflectionTypeResolver.php index b1bb81a4f8f..1ab228d200c 100644 --- a/rules/TypeDeclaration/Reflection/ReflectionTypeResolver.php +++ b/rules/TypeDeclaration/Reflection/ReflectionTypeResolver.php @@ -4,14 +4,7 @@ declare(strict_types=1); namespace Rector\TypeDeclaration\Reflection; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; -use PhpParser\Node\Expr\StaticCall; -use PHPStan\Analyser\Scope; -use PHPStan\Reflection\Php\PhpFunctionReflection; -use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Type; @@ -19,7 +12,6 @@ use PHPStan\Type\TypeWithClassName; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; -use Symplify\PackageBuilder\Reflection\PrivatesCaller; final class ReflectionTypeResolver { @@ -27,40 +19,9 @@ final class ReflectionTypeResolver private NodeTypeResolver $nodeTypeResolver, private ReflectionProvider $reflectionProvider, private NodeNameResolver $nodeNameResolver, - private PrivatesCaller $privatesCaller ) { } - public function resolveMethodCallReturnType(MethodCall $methodCall): ?Type - { - $objectType = $this->nodeTypeResolver->resolve($methodCall->var); - if (! $objectType instanceof TypeWithClassName) { - return null; - } - - $methodName = $this->nodeNameResolver->getName($methodCall->name); - if ($methodName === null) { - return null; - } - - return $this->resolveNativeReturnTypeFromClassAndMethod($objectType->getClassName(), $methodName, $methodCall); - } - - public function resolveStaticCallReturnType(StaticCall $staticCall): ?Type - { - $className = $this->nodeNameResolver->getName($staticCall->class); - if ($className === null) { - return null; - } - - $methodName = $this->nodeNameResolver->getName($staticCall->name); - if ($methodName === null) { - return null; - } - - return $this->resolveNativeReturnTypeFromClassAndMethod($className, $methodName, $staticCall); - } - public function resolvePropertyFetchType(PropertyFetch $propertyFetch): ?Type { $objectType = $this->nodeTypeResolver->resolve($propertyFetch->var); @@ -85,51 +46,4 @@ final class ReflectionTypeResolver return null; } - - public function resolveFuncCallReturnType(FuncCall $funcCall): ?Type - { - $funcCallScope = $funcCall->getAttribute(AttributeKey::SCOPE); - - $funcCallName = $funcCall->name; - if ($funcCallName instanceof Expr) { - return null; - } - - if (! $this->reflectionProvider->hasFunction($funcCallName, $funcCallScope)) { - return null; - } - - $functionReflection = $this->reflectionProvider->getFunction($funcCallName, $funcCallScope); - if (! $functionReflection instanceof PhpFunctionReflection) { - return null; - } - - return $this->privatesCaller->callPrivateMethod($functionReflection, 'getNativeReturnType', []); - } - - private function resolveNativeReturnTypeFromClassAndMethod(string $className, string $methodName, Expr $expr): ?Type - { - if (! $this->reflectionProvider->hasClass($className)) { - return null; - } - - $classReflection = $this->reflectionProvider->getClass($className); - if (! $classReflection->hasMethod($methodName)) { - return null; - } - - $callerScope = $expr->getAttribute(AttributeKey::SCOPE); - - // probably trait - if (! $callerScope instanceof Scope) { - return null; - } - - $methodReflection = $classReflection->getMethod($methodName, $callerScope); - if (! $methodReflection instanceof PhpMethodReflection) { - return null; - } - - return $this->privatesCaller->callPrivateMethod($methodReflection, 'getNativeReturnType', []); - } } diff --git a/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php b/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php index 3d988b0e2d7..e45cf9670c6 100644 --- a/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php +++ b/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer/ReturnedNodesReturnTypeInferer.php @@ -16,11 +16,15 @@ use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Return_; use PhpParser\Node\Stmt\Trait_; use PhpParser\NodeTraverser; +use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\Php\PhpFunctionReflection; use PHPStan\Type\ArrayType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; use PHPStan\Type\VoidType; -use Rector\NodeCollector\NodeCollector\NodeRepository; +use Rector\Core\PhpParser\Printer\BetterStandardPrinter; +use Rector\Core\PHPStan\Reflection\CallReflectionResolver; +use Rector\Core\Reflection\FunctionLikeReflectionParser; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; @@ -37,7 +41,9 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface private SimpleCallableNodeTraverser $simpleCallableNodeTraverser, private TypeFactory $typeFactory, private SplArrayFixedTypeNarrower $splArrayFixedTypeNarrower, - private NodeRepository $nodeRepository + private CallReflectionResolver $callReflectionResolver, + private FunctionLikeReflectionParser $functionLikeReflectionParser, + private BetterStandardPrinter $betterStandardPrinter ) { } @@ -137,17 +143,20 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface return new MixedType(); } - $classMethod = $this->nodeRepository->findClassMethodByMethodCall($return->expr); - if (! $classMethod instanceof ClassMethod) { + $callReflection = $this->callReflectionResolver->resolveCall($return->expr); + if ($callReflection === null) { return new MixedType(); } - // avoid infinite looping over self call - if ($classMethod === $originalFunctionLike) { - return new MixedType(); + if ($callReflection instanceof MethodReflection) { + return $this->resolveClassMethod($callReflection, $originalFunctionLike); } - return $this->inferFunctionLike($classMethod); + if ($callReflection instanceof PhpFunctionReflection) { + return $this->resolveFunction($callReflection, $originalFunctionLike); + } + + return new MixedType(); } private function isArrayTypeMixed(Type $type): bool @@ -167,12 +176,47 @@ final class ReturnedNodesReturnTypeInferer implements ReturnTypeInfererInterface { if ($resolvedType instanceof MixedType || $this->isArrayTypeMixed($resolvedType)) { $correctedType = $this->inferFromReturnedMethodCall($return, $functionLike); + // override only if has some extra value - if (! $correctedType instanceof MixedType) { + if (! $correctedType instanceof MixedType && ! $correctedType instanceof VoidType) { return $correctedType; } } return $resolvedType; } + + private function resolveClassMethod(MethodReflection $methodReflection, FunctionLike $originalFunctionLike): Type + { + $classMethod = $this->functionLikeReflectionParser->parseMethodReflection($methodReflection); + if (! $classMethod instanceof ClassMethod) { + return new MixedType(); + } + + $classMethodCacheKey = $this->betterStandardPrinter->print($classMethod); + $functionLikeCacheKey = $this->betterStandardPrinter->print($originalFunctionLike); + + if ($classMethodCacheKey === $functionLikeCacheKey) { + return new MixedType(); + } + + return $this->inferFunctionLike($classMethod); + } + + private function resolveFunction(PhpFunctionReflection $phpFunctionReflection, FunctionLike $functionLike): Type + { + $function = $this->functionLikeReflectionParser->parseFunctionReflection($phpFunctionReflection); + if (! $function instanceof Function_) { + return new MixedType(); + } + + $classMethodCacheKey = $this->betterStandardPrinter->print($function); + $functionLikeCacheKey = $this->betterStandardPrinter->print($functionLike); + + if ($classMethodCacheKey === $functionLikeCacheKey) { + return new MixedType(); + } + + return $this->inferFunctionLike($function); + } } diff --git a/src/Reflection/FunctionLikeReflectionParser.php b/src/Reflection/FunctionLikeReflectionParser.php index 2b5c4dce083..436f93eda54 100644 --- a/src/Reflection/FunctionLikeReflectionParser.php +++ b/src/Reflection/FunctionLikeReflectionParser.php @@ -10,11 +10,13 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Namespace_; use PhpParser\NodeFinder; use PhpParser\Parser; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\Php\PhpFunctionReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ObjectType; use PHPStan\Type\ThisType; @@ -67,6 +69,38 @@ final class FunctionLikeReflectionParser return $class->getMethod($methodReflection->getName()); } + public function parseFunctionReflection(PhpFunctionReflection $phpFunctionReflection): ?Function_ + { + $fileName = $phpFunctionReflection->getFileName(); + if ($fileName === false) { + return null; + } + + $fileContent = $this->smartFileSystem->readFile($fileName); + if (! is_string($fileContent)) { + return null; + } + + $nodes = (array) $this->parser->parse($fileContent); + + $smartFileInfo = new SmartFileInfo($fileName); + $file = new File($smartFileInfo, $smartFileInfo->getContents()); + + $nodes = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($file, $nodes); + + /** @var Function_[] $functions */ + $functions = $this->nodeFinder->findInstanceOf($nodes, Function_::class); + foreach ($functions as $function) { + if (! $this->nodeNameResolver->isName($function, $phpFunctionReflection->getName())) { + continue; + } + + return $function; + } + + return null; + } + /** * @param MethodCall|StaticCall|Node $node */