diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index b9506fc93b..a1dfd2a34d 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -453,7 +453,7 @@ private function resolveIntersectionTypeNode(IntersectionTypeNode $typeNode, Nam private function resolveConditionalTypeNode(ConditionalTypeNode $typeNode, NameScope $nameScope): Type { - return ConditionalType::create( + return new ConditionalType( $this->resolve($typeNode->subjectType, $nameScope), $this->resolve($typeNode->targetType, $nameScope), $this->resolve($typeNode->if, $nameScope), diff --git a/src/Reflection/ResolvedFunctionVariant.php b/src/Reflection/ResolvedFunctionVariant.php index c15f218c6e..eb6d5aaa76 100644 --- a/src/Reflection/ResolvedFunctionVariant.php +++ b/src/Reflection/ResolvedFunctionVariant.php @@ -3,13 +3,10 @@ namespace PHPStan\Reflection; use PHPStan\Reflection\Php\DummyParameter; -use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Type; -use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; -use function array_key_exists; use function array_map; class ResolvedFunctionVariant implements ParametersAcceptor, SingleParametersAcceptor @@ -76,13 +73,12 @@ public function getReturnType(): Type $type = $this->returnType; if ($type === null) { - $type = TemplateTypeHelper::resolveTemplateTypes( + $type = TypeUtils::resolveTypes( $this->parametersAcceptor->getReturnType(), $this->resolvedTemplateTypeMap, + $this->passedArgs, ); - $type = $this->resolveConditionalTypes($type); - $this->returnType = $type; } @@ -105,15 +101,4 @@ public function flattenConditionalsInReturnType(): SingleParametersAcceptor return $result; } - private function resolveConditionalTypes(Type $type): Type - { - return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { - while ($type instanceof ConditionalTypeForParameter && array_key_exists($type->getParameterName(), $this->passedArgs)) { - $type = $type->toConditional($this->passedArgs[$type->getParameterName()]); - } - - return $traverse($type); - }); - } - } diff --git a/src/Type/ConditionalType.php b/src/Type/ConditionalType.php index 45d91cb8c8..5270c27914 100644 --- a/src/Type/ConditionalType.php +++ b/src/Type/ConditionalType.php @@ -16,7 +16,7 @@ final class ConditionalType implements CompoundType use ConditionalTypeTrait; use NonGeneralizableTypeTrait; - private function __construct( + public function __construct( private Type $subject, private Type $target, Type $if, @@ -69,18 +69,7 @@ public function describe(VerbosityLevel $level): string ); } - public static function create( - Type $subject, - Type $target, - Type $if, - Type $else, - bool $negated, - ): Type - { - return (new self($subject, $target, $if, $else, $negated))->resolve(); - } - - private function resolve(): Type + public function resolve(): Type { $isSuperType = $this->target->isSuperTypeOf($this->subject); @@ -108,7 +97,7 @@ public function traverse(callable $cb): Type return $this; } - return self::create($subject, $target, $if, $else, $this->negated); + return new self($subject, $target, $if, $else, $this->negated); } private function isResolved(): bool diff --git a/src/Type/ConditionalTypeForParameter.php b/src/Type/ConditionalTypeForParameter.php index e780ad115d..77b0893d54 100644 --- a/src/Type/ConditionalTypeForParameter.php +++ b/src/Type/ConditionalTypeForParameter.php @@ -34,7 +34,7 @@ public function getParameterName(): string public function toConditional(Type $subject): Type { - return ConditionalType::create( + return new ConditionalType( $subject, $this->target, $this->if, diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 0f3bcfd30d..2e474dc9a0 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -7,6 +7,9 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Generic\TemplateType; +use PHPStan\Type\Generic\TemplateTypeMap; +use function array_key_exists; use function array_merge; /** @api */ @@ -348,4 +351,43 @@ public static function flattenConditionals(Type $type): Type }); } + /** + * Replaces template types with standin types, and conditional types with their resolved types + * + * @param array $passedArgs + */ + public static function resolveTypes(Type $type, TemplateTypeMap $standins, array $passedArgs, bool $keepErrorTypes = false): Type + { + return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($standins, $passedArgs, $keepErrorTypes): Type { + if ($type instanceof TemplateType && !$type->isArgument()) { + $newType = $standins->getType($type->getName()); + if ($newType === null) { + return $traverse($type); + } + + if ($newType instanceof ErrorType && !$keepErrorTypes) { + return $traverse($type->getBound()); + } + + return $newType; + } + + if ($type instanceof ConditionalTypeForParameter || $type instanceof ConditionalType) { + $type = $traverse($type); + + if ($type instanceof ConditionalTypeForParameter && array_key_exists($type->getParameterName(), $passedArgs)) { + $type = $type->toConditional($passedArgs[$type->getParameterName()]); + } + + if ($type instanceof ConditionalType) { + return $type->resolve(); + } + + return $type; + } + + return $traverse($type); + }); + } + } diff --git a/tests/PHPStan/Analyser/data/conditional-types.php b/tests/PHPStan/Analyser/data/conditional-types.php index 8e1c605fa7..05fb314ed9 100644 --- a/tests/PHPStan/Analyser/data/conditional-types.php +++ b/tests/PHPStan/Analyser/data/conditional-types.php @@ -112,9 +112,9 @@ public function testDeterministicReturnValue(): void */ public function testDeterministicParameter($foo, $bar, $baz): void { - assertType('string', $foo); - assertType('string', $bar); - assertType('string', $baz); + assertType('(true is true ? string : bool)', $foo); + assertType('(5 is int<4, 6> ? string : bool)', $bar); + assertType('(5 is not int<0, 4> ? (4 is bool ? float : string) : bool)', $baz); } /**