Skip to content

Commit

Permalink
Implement conditional parameter types
Browse files Browse the repository at this point in the history
  • Loading branch information
rvanvelzen committed May 3, 2022
1 parent 6865741 commit 8ad35b3
Show file tree
Hide file tree
Showing 17 changed files with 175 additions and 139 deletions.
16 changes: 1 addition & 15 deletions src/Analyser/MutatingScope.php
Expand Up @@ -90,7 +90,6 @@
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\LateResolvableType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\NonexistentParentClassType;
Expand Down Expand Up @@ -575,7 +574,7 @@ public function getType(Expr $node): Type
$key = $this->getNodeKey($node);

if (!array_key_exists($key, $this->resolvedTypes)) {
$this->resolvedTypes[$key] = $this->resolveLateResolvableTypes($this->resolveType($node));
$this->resolvedTypes[$key] = TypeUtils::resolveLateResolvableTypes($this->resolveType($node));
}
return $this->resolvedTypes[$key];
}
Expand Down Expand Up @@ -6018,17 +6017,4 @@ private function integerRangeMath(Type $range, Expr $node, Type $operand): Type
return IntegerRangeType::fromInterval($min, $max);
}

private function resolveLateResolvableTypes(Type $type): Type
{
return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type {
$type = $traverse($type);

if ($type instanceof LateResolvableType) {
$type = $type->resolve();
}

return $type;
});
}

}
20 changes: 1 addition & 19 deletions src/Reflection/FunctionVariant.php
Expand Up @@ -4,10 +4,9 @@

use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;

/** @api */
class FunctionVariant implements ParametersAcceptor, SingleParametersAcceptor
class FunctionVariant implements ParametersAcceptor
{

/**
Expand Down Expand Up @@ -52,21 +51,4 @@ public function getReturnType(): Type
return $this->returnType;
}

/**
* @return static
*/
public function flattenConditionalsInReturnType(): SingleParametersAcceptor
{
/** @var static $result */
$result = new self(
$this->templateTypeMap,
$this->resolvedTemplateTypeMap,
$this->parameters,
$this->isVariadic,
TypeUtils::flattenConditionals($this->returnType),
);

return $result;
}

}
20 changes: 0 additions & 20 deletions src/Reflection/FunctionVariantWithPhpDocs.php
Expand Up @@ -4,7 +4,6 @@

use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;

/** @api */
class FunctionVariantWithPhpDocs extends FunctionVariant implements ParametersAcceptorWithPhpDocs
Expand Down Expand Up @@ -54,23 +53,4 @@ public function getNativeReturnType(): Type
return $this->nativeReturnType;
}

/**
* @return static
*/
public function flattenConditionalsInReturnType(): SingleParametersAcceptor
{
/** @var static $result */
$result = new self(
$this->getTemplateTypeMap(),
$this->getResolvedTemplateTypeMap(),
$this->getParameters(),
$this->isVariadic(),
TypeUtils::flattenConditionals($this->getReturnType()),
TypeUtils::flattenConditionals($this->phpDocReturnType),
$this->nativeReturnType,
);

return $result;
}

}
8 changes: 1 addition & 7 deletions src/Reflection/ParametersAcceptorSelector.php
Expand Up @@ -50,13 +50,7 @@ public static function selectSingle(
throw new ShouldNotHappenException('Multiple variants - use selectFromArgs() instead.');
}

$parametersAcceptor = $parametersAcceptors[0];

if ($parametersAcceptor instanceof SingleParametersAcceptor) {
$parametersAcceptor = $parametersAcceptor->flattenConditionalsInReturnType();
}

return $parametersAcceptor;
return $parametersAcceptors[0];
}

/**
Expand Down
35 changes: 14 additions & 21 deletions src/Reflection/ResolvedFunctionVariant.php
Expand Up @@ -14,7 +14,7 @@
use function array_key_exists;
use function array_map;

class ResolvedFunctionVariant implements ParametersAcceptor, SingleParametersAcceptor
class ResolvedFunctionVariant implements ParametersAcceptor
{

/** @var ParameterReflection[]|null */
Expand Down Expand Up @@ -57,7 +57,13 @@ public function getParameters(): array
if ($parameters === null) {
$parameters = array_map(fn (ParameterReflection $param): ParameterReflection => new DummyParameter(
$param->getName(),
TemplateTypeHelper::resolveTemplateTypes($param->getType(), $this->resolvedTemplateTypeMap),
TypeUtils::resolveLateResolvableTypes(
TemplateTypeHelper::resolveTemplateTypes(
$this->resolveConditionalTypesForParameter($param->getType()),
$this->resolvedTemplateTypeMap,
),
false,
),
$param->isOptional(),
$param->passedByReference(),
$param->isVariadic(),
Expand Down Expand Up @@ -88,9 +94,12 @@ public function getReturnType(): Type
$type = $this->returnType;

if ($type === null) {
$type = TemplateTypeHelper::resolveTemplateTypes(
$this->getReturnTypeWithUnresolvableTemplateTypes(),
$this->resolvedTemplateTypeMap,
$type = TypeUtils::resolveLateResolvableTypes(
TemplateTypeHelper::resolveTemplateTypes(
$this->getReturnTypeWithUnresolvableTemplateTypes(),
$this->resolvedTemplateTypeMap,
),
false,
);

$this->returnType = $type;
Expand All @@ -99,22 +108,6 @@ public function getReturnType(): Type
return $type;
}

/**
* @return static
*/
public function flattenConditionalsInReturnType(): SingleParametersAcceptor
{
/** @var static $result */
$result = new self(
$this->parametersAcceptor,
$this->resolvedTemplateTypeMap,
$this->passedArgs,
);
$result->returnType = TypeUtils::flattenConditionals($this->getReturnType());

return $result;
}

private function resolveResolvableTemplateTypes(Type $type): Type
{
return TypeTraverser::map($type, function (Type $type, callable $traverse): Type {
Expand Down
13 changes: 0 additions & 13 deletions src/Reflection/SingleParametersAcceptor.php

This file was deleted.

2 changes: 1 addition & 1 deletion src/Rules/FunctionCallParametersCheck.php
Expand Up @@ -248,7 +248,7 @@ public function check(
continue;
}

$parameterType = $parameter->getType();
$parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType());
if (
$this->checkArgumentTypes
&& !$parameter->passedByReference()->createsNewVariable()
Expand Down
3 changes: 3 additions & 0 deletions src/Rules/FunctionReturnTypeCheck.php
Expand Up @@ -9,6 +9,7 @@
use PHPStan\Type\GenericTypeVariableResolver;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\VerbosityLevel;
use PHPStan\Type\VoidType;
Expand Down Expand Up @@ -36,6 +37,8 @@ public function checkReturnType(
bool $isGenerator,
): array
{
$returnType = TypeUtils::resolveLateResolvableTypes($returnType);

if ($returnType instanceof NeverType && $returnType->isExplicit()) {
return [
RuleErrorBuilder::message($neverMessage)
Expand Down
36 changes: 16 additions & 20 deletions src/Type/ConditionalType.php
Expand Up @@ -3,7 +3,7 @@
namespace PHPStan\Type;

use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\Traits\ConditionalTypeTrait;
use PHPStan\Type\Traits\LateResolvableTypeTrait;
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
use function array_merge;
use function sprintf;
Expand All @@ -12,19 +12,17 @@
final class ConditionalType implements CompoundType, LateResolvableType
{

use ConditionalTypeTrait;
use LateResolvableTypeTrait;
use NonGeneralizableTypeTrait;

public function __construct(
private Type $subject,
private Type $target,
Type $if,
Type $else,
private Type $if,
private Type $else,
private bool $negated,
)
{
$this->if = $if;
$this->else = $else;
}

public function getSubject(): Type
Expand Down Expand Up @@ -83,26 +81,24 @@ public function describe(VerbosityLevel $level): string
);
}

public function resolve(): Type
public function isResolvable(): bool
{
return $this->getResult();
return !TypeUtils::containsTemplateType($this->subject) && !TypeUtils::containsTemplateType($this->target);
}

public function getResult(): Type
protected function getResult(): Type
{
if ($this->result === null) {
$isSuperType = $this->target->isSuperTypeOf($this->subject);

if ($isSuperType->yes()) {
$this->result = !$this->negated ? $this->if : $this->else;
} elseif ($isSuperType->no()) {
$this->result = !$this->negated ? $this->else : $this->if;
} else {
$this->result = TypeCombinator::union($this->if, $this->else);
}
$isSuperType = $this->target->isSuperTypeOf($this->subject);

if ($isSuperType->yes()) {
return !$this->negated ? $this->if : $this->else;
}

if ($isSuperType->no()) {
return !$this->negated ? $this->else : $this->if;
}

return $this->result;
return TypeCombinator::union($this->if, $this->else);
}

public function traverse(callable $cb): Type
Expand Down
22 changes: 15 additions & 7 deletions src/Type/ConditionalTypeForParameter.php
Expand Up @@ -3,28 +3,26 @@
namespace PHPStan\Type;

use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\Traits\ConditionalTypeTrait;
use PHPStan\Type\Traits\LateResolvableTypeTrait;
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
use function array_merge;
use function sprintf;

/** @api */
final class ConditionalTypeForParameter implements CompoundType
final class ConditionalTypeForParameter implements CompoundType, LateResolvableType
{

use ConditionalTypeTrait;
use LateResolvableTypeTrait;
use NonGeneralizableTypeTrait;

public function __construct(
private string $parameterName,
private Type $target,
Type $if,
Type $else,
private Type $if,
private Type $else,
private bool $negated,
)
{
$this->if = $if;
$this->else = $else;
}

public function getParameterName(): string
Expand Down Expand Up @@ -103,6 +101,16 @@ public function describe(VerbosityLevel $level): string
);
}

public function isResolvable(): bool
{
return false;
}

protected function getResult(): Type
{
return TypeCombinator::union($this->if, $this->else);
}

/**
* @param callable(Type): Type $cb
*/
Expand Down
2 changes: 2 additions & 0 deletions src/Type/LateResolvableType.php
Expand Up @@ -8,4 +8,6 @@ interface LateResolvableType

public function resolve(): Type;

public function isResolvable(): bool;

}
Expand Up @@ -13,15 +13,10 @@
use PHPStan\Type\CompoundType;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

trait ConditionalTypeTrait
trait LateResolvableTypeTrait
{

private Type $if;

private Type $else;

private ?Type $result = null;

public function accepts(Type $type, bool $strictTypes): TrinaryLogic
Expand Down Expand Up @@ -288,13 +283,15 @@ public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic
return $otherType->isSmallerThanOrEqual($result);
}

public function getResult(): Type
public function resolve(): Type
{
if ($this->result === null) {
return $this->result = TypeCombinator::union($this->if, $this->else);
return $this->result = $this->getResult();
}

return $this->result;
}

abstract protected function getResult(): Type;

}

0 comments on commit 8ad35b3

Please sign in to comment.