Skip to content

Commit

Permalink
Don't auto-resolve conditional types
Browse files Browse the repository at this point in the history
Auto-resolving seems very useful, but doesn't allow inspecting the original type
  • Loading branch information
rvanvelzen authored and ondrejmirtes committed Apr 7, 2022
1 parent 69a3007 commit 86b5a51
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/PhpDoc/TypeNodeResolver.php
Expand Up @@ -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),
Expand Down
19 changes: 2 additions & 17 deletions src/Reflection/ResolvedFunctionVariant.php
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
});
}

}
17 changes: 3 additions & 14 deletions src/Type/ConditionalType.php
Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Type/ConditionalTypeForParameter.php
Expand Up @@ -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,
Expand Down
42 changes: 42 additions & 0 deletions src/Type/TypeUtils.php
Expand Up @@ -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 */
Expand Down Expand Up @@ -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<string, Type> $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);
});
}

}
6 changes: 3 additions & 3 deletions tests/PHPStan/Analyser/data/conditional-types.php
Expand Up @@ -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);
}

/**
Expand Down

0 comments on commit 86b5a51

Please sign in to comment.