From 42e5efc4933a66b5f8e70a436721a4ec5107d6fc Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Fri, 1 Apr 2022 14:29:04 +0200 Subject: [PATCH] Make ParametersAcceptorSelector::selectSingle() return union-ed conditional types See phpstan/phpstan/issues/3853#issuecomment-1082351763 --- src/Reflection/ParametersAcceptorSelector.php | 2 +- src/Reflection/SingleParametersAcceptor.php | 52 +++++++++++++++++++ src/Type/ConditionalType.php | 2 +- src/Type/Traits/ConditionalTypeTrait.php | 2 +- ...icReturnTypeExtensionTypeInferenceTest.php | 1 + .../data/TestDynamicReturnTypeExtensions.php | 20 +++++++ ...ic-method-return-getsingle-conditional.php | 20 +++++++ .../PHPStan/Analyser/dynamic-return-type.neon | 4 ++ 8 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 src/Reflection/SingleParametersAcceptor.php create mode 100644 tests/PHPStan/Analyser/data/dynamic-method-return-getsingle-conditional.php diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 15d13d5f63c..d69697629d7 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -48,7 +48,7 @@ public static function selectSingle( throw new ShouldNotHappenException('Multiple variants - use selectFromArgs() instead.'); } - return $parametersAcceptors[0]; + return new SingleParametersAcceptor($parametersAcceptors[0]); } /** diff --git a/src/Reflection/SingleParametersAcceptor.php b/src/Reflection/SingleParametersAcceptor.php new file mode 100644 index 00000000000..24e5f0408d1 --- /dev/null +++ b/src/Reflection/SingleParametersAcceptor.php @@ -0,0 +1,52 @@ +acceptor->getTemplateTypeMap(); + } + + public function getResolvedTemplateTypeMap(): TemplateTypeMap + { + return $this->acceptor->getResolvedTemplateTypeMap(); + } + + /** + * @return array + */ + public function getParameters(): array + { + return $this->acceptor->getParameters(); + } + + public function isVariadic(): bool + { + return $this->acceptor->isVariadic(); + } + + public function getReturnType(): Type + { + return TypeTraverser::map($this->acceptor->getReturnType(), function (Type $type, callable $traverse) { + while ($type instanceof ConditionalType || $type instanceof ConditionalTypeForParameter) { + $type = $type->getResult(); + } + + return $traverse($type); + }); + } + +} diff --git a/src/Type/ConditionalType.php b/src/Type/ConditionalType.php index 02bb7c31d8a..45d91cb8c8a 100644 --- a/src/Type/ConditionalType.php +++ b/src/Type/ConditionalType.php @@ -91,7 +91,7 @@ private function resolve(): Type } if ($this->isResolved()) { - return TypeCombinator::union($this->if, $this->else); + return $this->getResult(); } return $this; diff --git a/src/Type/Traits/ConditionalTypeTrait.php b/src/Type/Traits/ConditionalTypeTrait.php index 0231649aa59..62cb645557a 100644 --- a/src/Type/Traits/ConditionalTypeTrait.php +++ b/src/Type/Traits/ConditionalTypeTrait.php @@ -288,7 +288,7 @@ public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic return $otherType->isSmallerThanOrEqual($result); } - private function getResult(): Type + public function getResult(): Type { if ($this->result === null) { return $this->result = TypeCombinator::union($this->if, $this->else); diff --git a/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php index 85eebb189f9..f6272e75dc8 100644 --- a/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php +++ b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php @@ -11,6 +11,7 @@ public function dataAsserts(): iterable { yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-types.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-compound-types.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-getsingle-conditional.php'); } /** diff --git a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php index 3fd9f8b0b4e..a86f0eaaf72 100644 --- a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php +++ b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php @@ -189,3 +189,23 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method } } + + +class ConditionalGetSingle implements DynamicMethodReturnTypeExtension { + + public function getClass(): string + { + return \DynamicMethodReturnGetSingleConditional\Foo::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'get'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + +} diff --git a/tests/PHPStan/Analyser/data/dynamic-method-return-getsingle-conditional.php b/tests/PHPStan/Analyser/data/dynamic-method-return-getsingle-conditional.php new file mode 100644 index 00000000000..270658e7ef6 --- /dev/null +++ b/tests/PHPStan/Analyser/data/dynamic-method-return-getsingle-conditional.php @@ -0,0 +1,20 @@ +get(0)); + assertType('bool', $this->get(1)); + assertType('bool', $this->get(2)); + } +} diff --git a/tests/PHPStan/Analyser/dynamic-return-type.neon b/tests/PHPStan/Analyser/dynamic-return-type.neon index b8bd815fb48..6d781ca77ff 100644 --- a/tests/PHPStan/Analyser/dynamic-return-type.neon +++ b/tests/PHPStan/Analyser/dynamic-return-type.neon @@ -27,3 +27,7 @@ services: class: PHPStan\Tests\FooGetSelf tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Tests\ConditionalGetSingle + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension