diff --git a/src/Reflection/ReflectionType.php b/src/Reflection/ReflectionType.php index 55c84b9b9..df32d74e6 100644 --- a/src/Reflection/ReflectionType.php +++ b/src/Reflection/ReflectionType.php @@ -11,6 +11,10 @@ use PhpParser\Node\UnionType; use Roave\BetterReflection\Reflector\Reflector; +use function array_filter; +use function array_values; +use function count; + abstract class ReflectionType { protected function __construct( @@ -43,6 +47,15 @@ public static function createFromNode( return new ReflectionIntersectionType($reflector, $owner, $type); } + $nonNullTypes = array_values(array_filter( + $type->types, + static fn (Identifier|Name $type): bool => $type->toString() !== 'null', + )); + + if (count($nonNullTypes) === 1) { + return self::createFromNode($reflector, $owner, $nonNullTypes[0], true); + } + return new ReflectionUnionType($reflector, $owner, $type, $allowsNull); } diff --git a/test/unit/Reflection/ReflectionTypeTest.php b/test/unit/Reflection/ReflectionTypeTest.php index 3642cedbf..bf40c3b5d 100644 --- a/test/unit/Reflection/ReflectionTypeTest.php +++ b/test/unit/Reflection/ReflectionTypeTest.php @@ -38,6 +38,18 @@ public function dataProvider(): array [new Node\NullableType(new Node\Identifier('string')), false, ReflectionNamedType::class, true], [new Node\IntersectionType([new Node\Name('A'), new Node\Name('B')]), false, ReflectionIntersectionType::class, false], [new Node\UnionType([new Node\Name('A'), new Node\Name('B')]), false, ReflectionUnionType::class, false], + 'Union types composed of just `null` and a type are simplified into a ReflectionNamedType' => [ + new Node\UnionType([new Node\Name('A'), new Node\Name('null')]), + false, + ReflectionNamedType::class, + true, + ], + 'Union types composed of `null` and more than one type are kept as ReflectionUnionType' => [ + new Node\UnionType([new Node\Name('A'), new Node\Name('B'), new Node\Name('null')]), + false, + ReflectionUnionType::class, + false, + ], ]; }