From c7e3eedf891e81011e6435687c18b5911dc4bea5 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Mon, 28 Mar 2022 16:30:25 +0200 Subject: [PATCH] Support conditional return types by union-ing their branches First baby step for phpstan/phpstan#3853 --- src/PhpDoc/TypeNodeResolver.php | 11 ++++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-3853.php | 53 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-3853.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 3c832876b5..9c17658505 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -20,6 +20,8 @@ use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode; +use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode; +use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode; use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; @@ -121,6 +123,9 @@ public function resolve(TypeNode $typeNode, NameScope $nameScope): Type } elseif ($typeNode instanceof IntersectionTypeNode) { return $this->resolveIntersectionTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof ConditionalTypeNode || $typeNode instanceof ConditionalTypeForParameterNode) { + return $this->resolveConditionalTypeNode($typeNode, $nameScope); + } elseif ($typeNode instanceof ArrayTypeNode) { return $this->resolveArrayTypeNode($typeNode, $nameScope); @@ -438,6 +443,12 @@ private function resolveIntersectionTypeNode(IntersectionTypeNode $typeNode, Nam return TypeCombinator::intersect(...$types); } + private function resolveConditionalTypeNode(ConditionalTypeNode|ConditionalTypeForParameterNode $typeNode, NameScope $nameScope): Type + { + $types = $this->resolveMultiple([$typeNode->if, $typeNode->else], $nameScope); + return TypeCombinator::union(...$types); + } + private function resolveArrayTypeNode(ArrayTypeNode $typeNode, NameScope $nameScope): Type { $itemType = $this->resolve($typeNode->type, $nameScope); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 57b414e5cf..2e5d0e4aed 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -830,6 +830,7 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6917.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3853.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-3853.php b/tests/PHPStan/Analyser/data/bug-3853.php new file mode 100644 index 0000000000..54ac3a108f --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-3853.php @@ -0,0 +1,53 @@ + + * + * @param TArray $array + * + * @return (TArray is non-empty-array ? non-empty-list : list) + */ + abstract public function arrayKeys(array $array); + + /** + * @param array $array + * @param non-empty-array $nonEmptyArray + * + * @param array $intArray + * @param non-empty-array $nonEmptyIntArray + */ + public function testArrayKeys(array $array, array $nonEmptyArray, array $intArray, array $nonEmptyIntArray): void + { + assertType('array', $this->arrayKeys($array)); + assertType('array', $this->arrayKeys($intArray)); + + // TODO resolve correctly + //assertType('non-empty-array', $this->arrayKeys($nonEmptyArray)); + //assertType('non-empty-array', $this->arrayKeys($nonEmptyIntArray)); + + assertType('array', $this->arrayKeys($nonEmptyArray)); + assertType('array', $this->arrayKeys($nonEmptyIntArray)); + } + + /** + * @return ($as_float is true ? float : string) + */ + abstract public function microtime(bool $as_float = false); + + public function testMicrotime(): void + { + // TODO resolve correctly + //assertType('float', $this->microtime(true)); + //assertType('string', $this->microtime(false)); + + assertType('float|string', $this->microtime(true)); + assertType('float|string', $this->microtime(false)); + } +}