Skip to content

Commit

Permalink
Refactor late resolvable types
Browse files Browse the repository at this point in the history
  • Loading branch information
rvanvelzen committed Apr 29, 2022
1 parent 0be51b2 commit be17f0a
Show file tree
Hide file tree
Showing 14 changed files with 54 additions and 133 deletions.
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
18 changes: 1 addition & 17 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 @@ -108,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
37 changes: 14 additions & 23 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,31 +81,24 @@ public function describe(VerbosityLevel $level): string
);
}

public function resolve(): Type
{
return $this->getResult();
}

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

public function getResult(): Type
private 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;
}

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

/**
* @param callable(Type): Type $cb
*/
Expand Down
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 private function getResult(): Type;

}
17 changes: 2 additions & 15 deletions src/Type/TypeUtils.php
Expand Up @@ -352,26 +352,13 @@ public static function containsTemplateType(Type $type): bool
return $containsTemplateType;
}

public static function flattenConditionals(Type $type): Type
{
return TypeTraverser::map($type, static function (Type $type, callable $traverse) {
while ($type instanceof ConditionalType || $type instanceof ConditionalTypeForParameter) {
$type = $type->getResult();
}

return $traverse($type);
});
}

public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type
{
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($resolveUnresolvableTypes): Type {
$type = $traverse($type);

if ($type instanceof LateResolvableType) {
if ($resolveUnresolvableTypes || !self::containsTemplateType($type)) {
$type = $type->resolve();
}
if ($type instanceof LateResolvableType && ($resolveUnresolvableTypes || $type->isResolvable())) {
$type = $type->resolve();
}

return $type;
Expand Down
10 changes: 9 additions & 1 deletion tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Expand Up @@ -628,7 +628,15 @@ public function testBug6896(): void
}

$errors = $this->runAnalyse(__DIR__ . '/data/bug-6896.php');
$this->assertCount(2, $errors);
$this->assertCount(4, $errors);
$this->assertSame('Generic type IteratorIterator<(int|string), mixed> in PHPDoc tag @return does not specify all template types of class IteratorIterator: TKey, TValue, TIterator', $errors[0]->getMessage());
$this->assertSame(38, $errors[0]->getLine());
$this->assertSame('Method Bug6896\RandHelper::getPseudoRandomWithUrl() return type has no value type specified in iterable type array.', $errors[1]->getMessage());
$this->assertSame(38, $errors[1]->getLine());
$this->assertSame('Method Bug6896\RandHelper::getPseudoRandomWithUrl() return type with generic class Bug6896\XIterator does not specify its types: TKey, TValue', $errors[2]->getMessage());
$this->assertSame(38, $errors[2]->getLine());
$this->assertSame('Method Bug6896\RandHelper::getPseudoRandomWithUrl() should return array<TRandKey of (int|string), TRandVal>|Bug6896\XIterator<TRandKey of (int|string), TRandVal>|(iterable<TRandKey of (int|string), TRandVal>&LimitIterator)|IteratorIterator<TRandKey of (int|string), TRandVal> but returns TRandList of array<TRandKey of (int|string), TRandVal>|Traversable<TRandKey of (int|string), TRandVal>.', $errors[3]->getMessage());
$this->assertSame(42, $errors[3]->getLine());
}

public function testBug6940(): void
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/conditional-types.php
Expand Up @@ -92,7 +92,7 @@ abstract public function missingParameter();

public function testMissingParameter(): void
{
assertType('($parameter is true ? int : string)', $this->missingParameter());
assertType('int|string', $this->missingParameter());
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php
Expand Up @@ -373,7 +373,7 @@ public function testOverridenMethodWithConditionalReturnType(): void
$this->reportStatic = true;
$this->analyse([__DIR__ . '/data/overriden-method-with-conditional-return-type.php'], [
[
'Return type (stdClass|string) of method OverridenMethodWithConditionalReturnType\Bar2::doFoo() should be covariant with return type (int|string) of method OverridenMethodWithConditionalReturnType\Foo::doFoo()',
'Return type (($p is int ? stdClass : string)) of method OverridenMethodWithConditionalReturnType\Bar2::doFoo() should be covariant with return type (($p is int ? int : string)) of method OverridenMethodWithConditionalReturnType\Foo::doFoo()',
37,
],
]);
Expand Down

0 comments on commit be17f0a

Please sign in to comment.