Skip to content

Commit e330a18

Browse files
committed
resolveType in FuncCallHandler
1 parent c5f3ce4 commit e330a18

File tree

3 files changed

+152
-134
lines changed

3 files changed

+152
-134
lines changed

src/Analyser/ExprHandler/FuncCallHandler.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
use PhpParser\Node\Expr\PropertyFetch;
1111
use PhpParser\Node\Expr\Variable;
1212
use PhpParser\Node\Name;
13+
use PhpParser\Node\Scalar\String_;
1314
use PhpParser\Node\Stmt;
1415
use PHPStan\Analyser\ArgumentsNormalizer;
1516
use PHPStan\Analyser\ExpressionContext;
1617
use PHPStan\Analyser\ExpressionResult;
1718
use PHPStan\Analyser\ExpressionResultStorage;
1819
use PHPStan\Analyser\ExprHandler;
20+
use PHPStan\Analyser\ExprHandler\Helper\VoidToNullTypeTransfomer;
1921
use PHPStan\Analyser\ImpurePoint;
2022
use PHPStan\Analyser\InternalThrowPoint;
2123
use PHPStan\Analyser\MutatingScope;
@@ -24,6 +26,7 @@
2426
use PHPStan\Analyser\Scope;
2527
use PHPStan\DependencyInjection\AutowiredParameter;
2628
use PHPStan\DependencyInjection\AutowiredService;
29+
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
2730
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
2831
use PHPStan\Node\Expr\NativeTypeExpr;
2932
use PHPStan\Node\Expr\PossiblyImpureCallExpr;
@@ -37,11 +40,13 @@
3740
use PHPStan\Reflection\ReflectionProvider;
3841
use PHPStan\TrinaryLogic;
3942
use PHPStan\Type\Accessory\AccessoryArrayListType;
43+
use PHPStan\Type\Accessory\HasPropertyType;
4044
use PHPStan\Type\Accessory\NonEmptyArrayType;
4145
use PHPStan\Type\ArrayType;
4246
use PHPStan\Type\ClosureType;
4347
use PHPStan\Type\Constant\ConstantArrayType;
4448
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
49+
use PHPStan\Type\ErrorType;
4550
use PHPStan\Type\GeneralizePrecision;
4651
use PHPStan\Type\IntegerRangeType;
4752
use PHPStan\Type\IntegerType;
@@ -76,6 +81,7 @@ final class FuncCallHandler implements ExprHandler
7681
public function __construct(
7782
private ReflectionProvider $reflectionProvider,
7883
private DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
84+
private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider,
7985
#[AutowiredParameter(ref: '%exceptions.implicitThrows%')]
8086
private bool $implicitThrows,
8187
#[AutowiredParameter]
@@ -706,4 +712,123 @@ private function getArraySortDoNotPreserveListFunctionType(Type $type): Type
706712
});
707713
}
708714

715+
/**
716+
* @param FuncCall $expr
717+
*/
718+
public function resolveType(MutatingScope $scope, Expr $expr): Type
719+
{
720+
if ($expr->name instanceof Expr) {
721+
$calledOnType = $scope->getType($expr->name);
722+
if ($calledOnType->isCallable()->no()) {
723+
return new ErrorType();
724+
}
725+
726+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
727+
$scope,
728+
$expr->getArgs(),
729+
$calledOnType->getCallableParametersAcceptors($scope),
730+
null,
731+
);
732+
733+
$functionName = null;
734+
if ($expr->name instanceof String_) {
735+
/** @var non-empty-string $name */
736+
$name = $expr->name->value;
737+
$functionName = new Name($name);
738+
} elseif (
739+
$expr->name instanceof FuncCall
740+
&& $expr->name->name instanceof Name
741+
&& $expr->name->isFirstClassCallable()
742+
) {
743+
$functionName = $expr->name->name;
744+
}
745+
746+
$normalizedNode = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr);
747+
if ($normalizedNode !== null && $functionName !== null && $this->reflectionProvider->hasFunction($functionName, $scope)) {
748+
$functionReflection = $this->reflectionProvider->getFunction($functionName, $scope);
749+
$resolvedType = $this->getDynamicFunctionReturnType($scope, $normalizedNode, $functionReflection);
750+
if ($resolvedType !== null) {
751+
return $resolvedType;
752+
}
753+
}
754+
755+
return $parametersAcceptor->getReturnType();
756+
}
757+
758+
if (!$this->reflectionProvider->hasFunction($expr->name, $scope)) {
759+
return new ErrorType();
760+
}
761+
762+
$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope);
763+
if ($scope->nativeTypesPromoted) {
764+
return ParametersAcceptorSelector::combineAcceptors($functionReflection->getVariants())->getNativeReturnType();
765+
}
766+
767+
if ($functionReflection->getName() === 'call_user_func') {
768+
$result = ArgumentsNormalizer::reorderCallUserFuncArguments($expr, $scope);
769+
if ($result !== null) {
770+
[, $innerFuncCall] = $result;
771+
772+
return $scope->getType($innerFuncCall);
773+
}
774+
}
775+
776+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
777+
$scope,
778+
$expr->getArgs(),
779+
$functionReflection->getVariants(),
780+
$functionReflection->getNamedArgumentsVariants(),
781+
);
782+
$normalizedNode = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr);
783+
if ($normalizedNode !== null) {
784+
if ($functionReflection->getName() === 'clone' && count($normalizedNode->getArgs()) > 0) {
785+
$cloneType = $scope->getType(new Expr\Clone_($normalizedNode->getArgs()[0]->value));
786+
if (count($normalizedNode->getArgs()) === 2) {
787+
$propertiesType = $scope->getType($normalizedNode->getArgs()[1]->value);
788+
if ($propertiesType->isConstantArray()->yes()) {
789+
$constantArrays = $propertiesType->getConstantArrays();
790+
if (count($constantArrays) === 1) {
791+
$accessories = [];
792+
foreach ($constantArrays[0]->getKeyTypes() as $keyType) {
793+
$constantKeyTypes = $keyType->getConstantScalarValues();
794+
if (count($constantKeyTypes) !== 1) {
795+
return $cloneType;
796+
}
797+
$accessories[] = new HasPropertyType((string) $constantKeyTypes[0]);
798+
}
799+
if (count($accessories) > 0 && count($accessories) <= 16) {
800+
return TypeCombinator::intersect($cloneType, ...$accessories);
801+
}
802+
}
803+
}
804+
}
805+
806+
return $cloneType;
807+
}
808+
$resolvedType = $this->getDynamicFunctionReturnType($scope, $normalizedNode, $functionReflection);
809+
if ($resolvedType !== null) {
810+
return $resolvedType;
811+
}
812+
}
813+
814+
return VoidToNullTypeTransfomer::transform($parametersAcceptor->getReturnType(), $expr);
815+
}
816+
817+
private function getDynamicFunctionReturnType(MutatingScope $scope, FuncCall $normalizedNode, FunctionReflection $functionReflection): ?Type
818+
{
819+
foreach ($this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicFunctionReturnTypeExtensions($functionReflection) as $dynamicFunctionReturnTypeExtension) {
820+
$resolvedType = $dynamicFunctionReturnTypeExtension->getTypeFromFunctionCall(
821+
$functionReflection,
822+
$normalizedNode,
823+
$scope,
824+
);
825+
826+
if ($resolvedType !== null) {
827+
return $resolvedType;
828+
}
829+
}
830+
831+
return null;
832+
}
833+
709834
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\ExprHandler\Helper;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\MutatingScope;
7+
use PHPStan\Analyser\Traverser\VoidToNullTraverser;
8+
use PHPStan\Type\Type;
9+
use PHPStan\Type\TypeTraverser;
10+
11+
final class VoidToNullTypeTransfomer
12+
{
13+
14+
public static function transform(Type $type, Node $node): Type
15+
{
16+
if ($node->getAttribute(MutatingScope::KEEP_VOID_ATTRIBUTE_NAME) === true) {
17+
return $type;
18+
}
19+
20+
return TypeTraverser::map($type, new VoidToNullTraverser());
21+
}
22+
23+
}

src/Analyser/MutatingScope.php

Lines changed: 4 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
use PhpParser\Node\Stmt\Function_;
2828
use PhpParser\NodeFinder;
2929
use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper;
30+
use PHPStan\Analyser\ExprHandler\Helper\VoidToNullTypeTransfomer;
3031
use PHPStan\Analyser\Traverser\TransformStaticTypeTraverser;
31-
use PHPStan\Analyser\Traverser\VoidToNullTraverser;
3232
use PHPStan\DependencyInjection\Container;
3333
use PHPStan\Node\ExecutionEndNode;
3434
use PHPStan\Node\Expr\AlwaysRememberedExpr;
@@ -87,7 +87,6 @@
8787
use PHPStan\TrinaryLogic;
8888
use PHPStan\Type\Accessory\AccessoryArrayListType;
8989
use PHPStan\Type\Accessory\HasOffsetValueType;
90-
use PHPStan\Type\Accessory\HasPropertyType;
9190
use PHPStan\Type\Accessory\NonEmptyArrayType;
9291
use PHPStan\Type\Accessory\OversizedArrayType;
9392
use PHPStan\Type\ArrayType;
@@ -1149,39 +1148,9 @@ private function resolveType(string $exprString, Expr $node): Type
11491148
}
11501149
}
11511150

1152-
if ($node instanceof FuncCall) {
1153-
return $this->getFunctionCallType($node);
1154-
}
1155-
11561151
return new MixedType();
11571152
}
11581153

1159-
private function getDynamicFunctionReturnType(FuncCall $normalizedNode, FunctionReflection $functionReflection): ?Type
1160-
{
1161-
foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicFunctionReturnTypeExtensions($functionReflection) as $dynamicFunctionReturnTypeExtension) {
1162-
$resolvedType = $dynamicFunctionReturnTypeExtension->getTypeFromFunctionCall(
1163-
$functionReflection,
1164-
$normalizedNode,
1165-
$this,
1166-
);
1167-
1168-
if ($resolvedType !== null) {
1169-
return $resolvedType;
1170-
}
1171-
}
1172-
1173-
return null;
1174-
}
1175-
1176-
private function transformVoidToNull(Type $type, Node $node): Type
1177-
{
1178-
if ($node->getAttribute(self::KEEP_VOID_ATTRIBUTE_NAME) === true) {
1179-
return $type;
1180-
}
1181-
1182-
return TypeTraverser::map($type, new VoidToNullTraverser());
1183-
}
1184-
11851154
/**
11861155
* @param callable(Type): ?bool $typeCallback
11871156
*/
@@ -4628,7 +4597,7 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName,
46284597
$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall);
46294598
}
46304599
if ($normalizedMethodCall === null) {
4631-
return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall);
4600+
return VoidToNullTypeTransfomer::transform($parametersAcceptor->getReturnType(), $methodCall);
46324601
}
46334602

46344603
$resolvedTypes = [];
@@ -4667,10 +4636,10 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName,
46674636
}
46684637

46694638
if (count($resolvedTypes) > 0) {
4670-
return $this->transformVoidToNull(TypeCombinator::union(...$resolvedTypes), $methodCall);
4639+
return VoidToNullTypeTransfomer::transform(TypeCombinator::union(...$resolvedTypes), $methodCall);
46714640
}
46724641

4673-
return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall);
4642+
return VoidToNullTypeTransfomer::transform($parametersAcceptor->getReturnType(), $methodCall);
46744643
}
46754644

46764645
/**
@@ -5227,105 +5196,6 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
52275196
);
52285197
}
52295198

5230-
private function getFunctionCallType(FuncCall $node): Type
5231-
{
5232-
if ($node->name instanceof Expr) {
5233-
$calledOnType = $this->getType($node->name);
5234-
if ($calledOnType->isCallable()->no()) {
5235-
return new ErrorType();
5236-
}
5237-
5238-
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
5239-
$this,
5240-
$node->getArgs(),
5241-
$calledOnType->getCallableParametersAcceptors($this),
5242-
null,
5243-
);
5244-
5245-
$functionName = null;
5246-
if ($node->name instanceof String_) {
5247-
/** @var non-empty-string $name */
5248-
$name = $node->name->value;
5249-
$functionName = new Name($name);
5250-
} elseif (
5251-
$node->name instanceof FuncCall
5252-
&& $node->name->name instanceof Name
5253-
&& $node->name->isFirstClassCallable()
5254-
) {
5255-
$functionName = $node->name->name;
5256-
}
5257-
5258-
$normalizedNode = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);
5259-
if ($normalizedNode !== null && $functionName !== null && $this->reflectionProvider->hasFunction($functionName, $this)) {
5260-
$functionReflection = $this->reflectionProvider->getFunction($functionName, $this);
5261-
$resolvedType = $this->getDynamicFunctionReturnType($normalizedNode, $functionReflection);
5262-
if ($resolvedType !== null) {
5263-
return $resolvedType;
5264-
}
5265-
}
5266-
5267-
return $parametersAcceptor->getReturnType();
5268-
}
5269-
5270-
if (!$this->reflectionProvider->hasFunction($node->name, $this)) {
5271-
return new ErrorType();
5272-
}
5273-
5274-
$functionReflection = $this->reflectionProvider->getFunction($node->name, $this);
5275-
if ($this->nativeTypesPromoted) {
5276-
return ParametersAcceptorSelector::combineAcceptors($functionReflection->getVariants())->getNativeReturnType();
5277-
}
5278-
5279-
if ($functionReflection->getName() === 'call_user_func') {
5280-
$result = ArgumentsNormalizer::reorderCallUserFuncArguments($node, $this);
5281-
if ($result !== null) {
5282-
[, $innerFuncCall] = $result;
5283-
5284-
return $this->getType($innerFuncCall);
5285-
}
5286-
}
5287-
5288-
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
5289-
$this,
5290-
$node->getArgs(),
5291-
$functionReflection->getVariants(),
5292-
$functionReflection->getNamedArgumentsVariants(),
5293-
);
5294-
$normalizedNode = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);
5295-
if ($normalizedNode !== null) {
5296-
if ($functionReflection->getName() === 'clone' && count($normalizedNode->getArgs()) > 0) {
5297-
$cloneType = $this->getType(new Expr\Clone_($normalizedNode->getArgs()[0]->value));
5298-
if (count($normalizedNode->getArgs()) === 2) {
5299-
$propertiesType = $this->getType($normalizedNode->getArgs()[1]->value);
5300-
if ($propertiesType->isConstantArray()->yes()) {
5301-
$constantArrays = $propertiesType->getConstantArrays();
5302-
if (count($constantArrays) === 1) {
5303-
$accessories = [];
5304-
foreach ($constantArrays[0]->getKeyTypes() as $keyType) {
5305-
$constantKeyTypes = $keyType->getConstantScalarValues();
5306-
if (count($constantKeyTypes) !== 1) {
5307-
return $cloneType;
5308-
}
5309-
$accessories[] = new HasPropertyType((string) $constantKeyTypes[0]);
5310-
}
5311-
if (count($accessories) > 0 && count($accessories) <= 16) {
5312-
return TypeCombinator::intersect($cloneType, ...$accessories);
5313-
}
5314-
}
5315-
}
5316-
}
5317-
5318-
return $cloneType;
5319-
}
5320-
$resolvedType = $this->getDynamicFunctionReturnType($normalizedNode, $functionReflection);
5321-
if ($resolvedType !== null) {
5322-
return $resolvedType;
5323-
}
5324-
}
5325-
5326-
return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $node);
5327-
}
5328-
53295199
private function getStaticCallType(Expr\StaticCall $node): ?Type
53305200
{
53315201
if ($node->name instanceof Node\Identifier) {

0 commit comments

Comments
 (0)