From f2b9ea635dbfab4ada1f1ad4bda273ed9bf0b429 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Fri, 29 Apr 2022 10:43:35 +0200 Subject: [PATCH] Refactor late resolvable types --- src/Reflection/FunctionVariant.php | 20 +--------- src/Reflection/FunctionVariantWithPhpDocs.php | 20 ---------- src/Reflection/ParametersAcceptorSelector.php | 8 +--- src/Reflection/ResolvedFunctionVariant.php | 18 +-------- src/Reflection/SingleParametersAcceptor.php | 13 ------- src/Rules/FunctionCallParametersCheck.php | 2 +- src/Rules/FunctionReturnTypeCheck.php | 3 ++ src/Type/ConditionalType.php | 37 +++++++------------ src/Type/ConditionalTypeForParameter.php | 22 +++++++---- ...eTrait.php => LateResolvableTypeTrait.php} | 13 +++---- src/Type/TypeUtils.php | 17 +-------- .../Analyser/AnalyserIntegrationTest.php | 10 ++++- .../Analyser/data/conditional-types.php | 2 +- .../Rules/Methods/MethodSignatureRuleTest.php | 2 +- 14 files changed, 54 insertions(+), 133 deletions(-) delete mode 100644 src/Reflection/SingleParametersAcceptor.php rename src/Type/Traits/{ConditionalTypeTrait.php => LateResolvableTypeTrait.php} (97%) diff --git a/src/Reflection/FunctionVariant.php b/src/Reflection/FunctionVariant.php index 4970bb6ce3a..9936ae92440 100644 --- a/src/Reflection/FunctionVariant.php +++ b/src/Reflection/FunctionVariant.php @@ -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 { /** @@ -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; - } - } diff --git a/src/Reflection/FunctionVariantWithPhpDocs.php b/src/Reflection/FunctionVariantWithPhpDocs.php index 2de7029adeb..aae15cb864a 100644 --- a/src/Reflection/FunctionVariantWithPhpDocs.php +++ b/src/Reflection/FunctionVariantWithPhpDocs.php @@ -4,7 +4,6 @@ use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Type; -use PHPStan\Type\TypeUtils; /** @api */ class FunctionVariantWithPhpDocs extends FunctionVariant implements ParametersAcceptorWithPhpDocs @@ -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; - } - } diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 348be666668..8b2241c8b82 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -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]; } /** diff --git a/src/Reflection/ResolvedFunctionVariant.php b/src/Reflection/ResolvedFunctionVariant.php index 5b3ca47e953..092570c6cdd 100644 --- a/src/Reflection/ResolvedFunctionVariant.php +++ b/src/Reflection/ResolvedFunctionVariant.php @@ -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 */ @@ -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 { diff --git a/src/Reflection/SingleParametersAcceptor.php b/src/Reflection/SingleParametersAcceptor.php deleted file mode 100644 index ccc938b34ba..00000000000 --- a/src/Reflection/SingleParametersAcceptor.php +++ /dev/null @@ -1,13 +0,0 @@ -getType(); + $parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType()); if ( $this->checkArgumentTypes && !$parameter->passedByReference()->createsNewVariable() diff --git a/src/Rules/FunctionReturnTypeCheck.php b/src/Rules/FunctionReturnTypeCheck.php index 20995fbc976..76f67a3d9b7 100644 --- a/src/Rules/FunctionReturnTypeCheck.php +++ b/src/Rules/FunctionReturnTypeCheck.php @@ -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; @@ -36,6 +37,8 @@ public function checkReturnType( bool $isGenerator, ): array { + $returnType = TypeUtils::resolveLateResolvableTypes($returnType); + if ($returnType instanceof NeverType && $returnType->isExplicit()) { return [ RuleErrorBuilder::message($neverMessage) diff --git a/src/Type/ConditionalType.php b/src/Type/ConditionalType.php index e1944d548f6..2c8a7aed4f4 100644 --- a/src/Type/ConditionalType.php +++ b/src/Type/ConditionalType.php @@ -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; @@ -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 @@ -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 diff --git a/src/Type/ConditionalTypeForParameter.php b/src/Type/ConditionalTypeForParameter.php index 54c79ac426e..aea7056ac17 100644 --- a/src/Type/ConditionalTypeForParameter.php +++ b/src/Type/ConditionalTypeForParameter.php @@ -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 @@ -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 */ diff --git a/src/Type/Traits/ConditionalTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php similarity index 97% rename from src/Type/Traits/ConditionalTypeTrait.php rename to src/Type/Traits/LateResolvableTypeTrait.php index 62cb645557a..792205db84f 100644 --- a/src/Type/Traits/ConditionalTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -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 @@ -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; + } diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index fff35e1aee1..ba2e249fc6a 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -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; diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 71c365858e7..a19cec0047e 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -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|Bug6896\XIterator|(iterable&LimitIterator)|IteratorIterator but returns TRandList of array|Traversable.', $errors[3]->getMessage()); + $this->assertSame(42, $errors[3]->getLine()); } public function testBug6940(): void diff --git a/tests/PHPStan/Analyser/data/conditional-types.php b/tests/PHPStan/Analyser/data/conditional-types.php index 78fa77d2132..427155ef5bf 100644 --- a/tests/PHPStan/Analyser/data/conditional-types.php +++ b/tests/PHPStan/Analyser/data/conditional-types.php @@ -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()); } /** diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 468afac0e1f..a310822516b 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -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, ], ]);