diff --git a/src/Reflection/Domain/Entities/Type/IntersectionTypeReflection.php b/src/Reflection/Domain/Entities/Type/IntersectionTypeReflection.php index a840db7..dbdc8ef 100644 --- a/src/Reflection/Domain/Entities/Type/IntersectionTypeReflection.php +++ b/src/Reflection/Domain/Entities/Type/IntersectionTypeReflection.php @@ -18,6 +18,10 @@ private function __construct( public static function create( array $types, ): static { + if (count($types) < 2) { + throw new \InvalidArgumentException('Intersection type must have at least two types.'); + } + return new static($types); } diff --git a/src/Reflection/Domain/Entities/Type/NamedTypeReflection.php b/src/Reflection/Domain/Entities/Type/NamedTypeReflection.php index 0b59019..2a83f01 100644 --- a/src/Reflection/Domain/Entities/Type/NamedTypeReflection.php +++ b/src/Reflection/Domain/Entities/Type/NamedTypeReflection.php @@ -7,9 +7,9 @@ final class NamedTypeReflection extends TypeReflection { public const IS_BUILT_IN = 1; - public const IS_CLASS = 2; - public const IS_INTERFACE = 4; - public const IS_ABSTRACT = 8; + public const IS_INTERFACE = 2; + public const IS_ABSTRACT = 4; + public const IS_CLASS = 8; public const IS_ENUM = 16; private function __construct( diff --git a/src/Reflection/Domain/Entities/Type/UnionTypeReflection.php b/src/Reflection/Domain/Entities/Type/UnionTypeReflection.php index 418e322..56ff2bb 100644 --- a/src/Reflection/Domain/Entities/Type/UnionTypeReflection.php +++ b/src/Reflection/Domain/Entities/Type/UnionTypeReflection.php @@ -18,6 +18,10 @@ private function __construct( public static function create( array $types, ): static { + if (count($types) < 2) { + throw new \InvalidArgumentException('Union type must have at least two types.'); + } + return new static($types); } diff --git a/src/Reflection/Domain/Factories/TypeReflectionFactory.php b/src/Reflection/Domain/Factories/TypeReflectionFactory.php index fb7db9c..519d358 100644 --- a/src/Reflection/Domain/Factories/TypeReflectionFactory.php +++ b/src/Reflection/Domain/Factories/TypeReflectionFactory.php @@ -38,7 +38,8 @@ public function createForParameter(\ReflectionParameter $reflectionParameter): T { return $this->create( $reflectionParameter->getType(), - $this->getDocBlockReflectionTypeFromParamTag($reflectionParameter) + $this->getDocBlockReflectionTypeFromParamTag($reflectionParameter), + $reflectionParameter->getDeclaringClass() ); } @@ -46,7 +47,8 @@ public function createForProperty(\ReflectionProperty $reflectionProperty): Type { return $this->create( $reflectionProperty->getType(), - $this->getDocBlockReflectionTypeFromVarTag($reflectionProperty) + $this->getDocBlockReflectionTypeFromVarTag($reflectionProperty), + $reflectionProperty->getDeclaringClass() ); } @@ -54,18 +56,66 @@ public function createForMethod(\ReflectionMethod $reflectionMethod): TypeReflec { return $this->create( $reflectionMethod->getReturnType(), - $this->getDocBlockReflectionTypeFromReturnTag($reflectionMethod) + $this->getDocBlockReflectionTypeFromReturnTag($reflectionMethod), + $reflectionMethod->getDeclaringClass() ); } - public function create(?\ReflectionType $ref, ?PhpDocumentorReflectionType $docCommentRef): TypeReflection + public function create(?\ReflectionType $ref, ?PhpDocumentorReflectionType $docCommentRef, ?\ReflectionClass $declarationClass): TypeReflection { - // collection - // compound - // intersection - // named + $reflectionType = $this->createTypeReflectionBasedOnReflectionType($ref); + if ($declarationClass) { + $phpDocumentatorType = $this->createTypeReflectionBasedOnPhpDocumentatorReflectionType($docCommentRef, $declarationClass); + } + + if (!isset($phpDocumentatorType) || $reflectionType === $phpDocumentatorType) { + return $reflectionType; + } + + if ($reflectionType instanceof NamedTypeReflection && $phpDocumentatorType instanceof NamedTypeReflection) { + return $this->mergeNamedTypeReflections($reflectionType, $phpDocumentatorType); + } + + // todo + return $reflectionType; + } + + private function mergeNamedTypeReflections(?NamedTypeReflection $reflectionType, ?NamedTypeReflection $phpDocumentatorType): NamedTypeReflection + { + // if there is no reflection type and no phpDocumentator type + if (null === $reflectionType && null === $phpDocumentatorType) { + return NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN); + + // if there is no reflection type or no phpDocumentator type + } elseif (null === $reflectionType || null === $phpDocumentatorType) { + return $reflectionType ?? $phpDocumentatorType; + + // if the reflection type and phpDocumentator type are the same + } elseif ($reflectionType->name() === $phpDocumentatorType->name()) { + return $reflectionType; + + // if the php documentator reflection type is mixed then it does not matter what is the reflection type + } elseif ('mixed' === $phpDocumentatorType->name()) { + return $reflectionType; + + // if the reflection type is mixed then it does not matter what is the php documentator reflection type + } elseif ('mixed' === $reflectionType->name()) { + return $phpDocumentatorType; + + // if the php documentator reflection type is advanced object and the reflection type is not at least object or mixed + } elseif ($phpDocumentatorType->flags() > 1 && 1 == $reflectionType->flags() && !in_array($reflectionType->name(), ['mixed', 'object'])) { + throw new \LogicException('\ReflectionType and phpDocumentator reflection type are not compatible.', 16); + } + + /* Class should be more important thant abstract class */ + [$updatedReflectionType, $updatedPhpDocumentatorType] = array_map( + fn (NamedTypeReflection $type) => $type->isClass() && !$type->isAbstractClass() ? NamedTypeReflection::recreate($type->name(), $type->flags() | 32) : $type, + [$reflectionType, $phpDocumentatorType] + ); - return $this->createTypeReflectionBasedOnReflectionType($ref); + return $updatedReflectionType->flags() >= $updatedPhpDocumentatorType->flags() + ? $reflectionType + : $phpDocumentatorType; } private function createTypeReflectionBasedOnReflectionType(?\ReflectionType $ref): TypeReflection @@ -203,11 +253,17 @@ private function createTypeReflectionBasedOnPhpDocumentatorReflectionType(?PhpDo // if the reflection is a collection if ($ref instanceof AbstractList || in_array(get_class($ref), [Array_::class, Iterable_::class])) { + $flags = 0; + if (method_exists($ref, 'getFqsen') && null !== $ref->getFqsen()) { + $class = $this->resolveReflectionClass($ref->getFqsen()->__toString(), $declarationClass); + $flags = $this->setFlagsForClass($class); + } + return CollectionTypeReflection::create( match (get_class($ref)) { Array_::class => NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), Iterable_::class => NamedTypeReflection::create('iterable', NamedTypeReflection::IS_BUILT_IN), - Collection::class => NamedTypeReflection::create($ref->getFqsen()?->__toString() ?? 'object', NamedTypeReflection::IS_CLASS), + Collection::class => NamedTypeReflection::create(@$class->getName() ?? $ref->getFqsen()?->__toString() ?? 'object', $flags), }, $this->createTypeReflectionBasedOnPhpDocumentatorReflectionType($ref->getKeyType(), $declarationClass), $this->createTypeReflectionBasedOnPhpDocumentatorReflectionType($ref->getValueType(), $declarationClass) @@ -226,23 +282,10 @@ private function createTypeReflectionBasedOnPhpDocumentatorReflectionType(?PhpDo $flags = 0; // if the reflection is class, interface or enum if ($ref instanceof Object_ && null !== $ref->getFqsen()) { - $class = (new \ReflectionClass($ref->getFqsen()->__toString())); - $isClass = true; + $class = $this->resolveReflectionClass($ref->getFqsen()->__toString(), $declarationClass); + $isCollection = $class->implementsInterface(\Traversable::class) || $class->implementsInterface(\ArrayAccess::class); - if ($class->isAbstract()) { - $flags |= NamedTypeReflection::IS_ABSTRACT; - } - if ($class->isInterface()) { - $flags |= NamedTypeReflection::IS_INTERFACE; - $isClass = false; - } - if ($class->isEnum()) { - $flags |= NamedTypeReflection::IS_ENUM; - $isClass = false; - } - if ($isClass) { - $flags |= NamedTypeReflection::IS_CLASS; - } + $flags = $this->setFlagsForClass($class); return $isCollection ? CollectionTypeReflection::create( @@ -266,12 +309,108 @@ private function createTypeReflectionBasedOnPhpDocumentatorReflectionType(?PhpDo ]); } + // if the reflection is not recognized intersection or compound + if (str_contains($ref->__toString(), '|') || str_contains($ref->__toString(), '&')) { + $docBlock = '/** @var '.$ref->__toString().' */'; + $factory = DocBlockFactory::createInstance(); + $context = (new ContextFactory())->createFromReflector($declarationClass); + $docBlock = $factory->create($docBlock, $context); + + /** @var Var_[] $tags */ + $tags = $docBlock->getTagsByName('var'); + + $type = $tags[0]->getType(); + + return $this->createTypeReflectionBasedOnPhpDocumentatorReflectionType($type, $declarationClass); + } + // if the reflection is built-in $flags |= NamedTypeReflection::IS_BUILT_IN; return NamedTypeReflection::create($ref->__toString(), $flags); } + private function resolveReflectionClass(string $fqsen, \ReflectionClass $declarationClass): \ReflectionClass + { + $returnIfExists = function (string $fqsen): ?\ReflectionClass { + if (class_exists($fqsen, false)) { + return new \ReflectionClass($fqsen); + } + + if (interface_exists($fqsen, false)) { + return new \ReflectionClass($fqsen); + } + + if (enum_exists($fqsen, false)) { + return new \ReflectionEnum($fqsen); + } + + return null; + }; + + $possibleClasses = array_filter([ + $fqsen, + '\\'.ltrim($fqsen, '\\'), + '\\'.ltrim($declarationClass->getNamespaceName(), '\\').'\\'.$fqsen, + '\\'.ltrim($declarationClass->getNamespaceName(), '\\').'\\'.ltrim($fqsen, '\\'), + $this->findMatchingImport($declarationClass->getFileName(), $fqsen), + $this->findMatchingImport($declarationClass->getFileName(), $fqsen) ? '\\'.ltrim($this->findMatchingImport($declarationClass->getFileName(), $fqsen), '\\') : null, + ]); + + foreach ($possibleClasses as $possibleClass) { + if (null !== $possibleClass && null !== $returnIfExists($possibleClass)) { + return $returnIfExists($possibleClass); + } + } + + throw new \LogicException("Class, interface or enum $fqsen (should be full name with namespace) not found. ".'Maybe the package require an update. On this stage the Object_ has to be a class, interface or enum.'); + } + + private function findMatchingImport(false|string $fileName, string $fqsen): ?string + { + if (false === $fileName) { + throw new \LogicException('The file name is not valid.'); + } + + /** @var string[] $fileLines */ + $fileLines = file($fileName); + + $useStatements = array_filter($fileLines, function ($line) { + return 0 === strpos(trim($line), 'use '); + }); + + $useStatements = array_map(function ($line) { + return trim(str_replace(['use ', ';'], '', $line)); + }, $useStatements); + + $matchingUses = array_filter($useStatements, function ($useStatement) use ($fqsen) { + return false !== strpos($useStatement, ltrim($fqsen, '\\')); + }); + + $matchingUses = array_map(function ($useStatement) { + return explode(' as ', $useStatement); + }, $matchingUses); + + return $matchingUses ? array_values($matchingUses)[0][0] : null; + } + + private function setFlagsForClass(\ReflectionClass $ref): int + { + $flags = 0; + + if ($ref->isInterface()) { + $flags |= NamedTypeReflection::IS_INTERFACE; + } elseif ($ref->isAbstract()) { + $flags |= NamedTypeReflection::IS_ABSTRACT | NamedTypeReflection::IS_CLASS; + } elseif ($ref->isEnum()) { + $flags |= NamedTypeReflection::IS_ENUM; + } else { + $flags |= NamedTypeReflection::IS_CLASS; + } + + return $flags; + } + private function getDocBlockReflectionTypeFromVarTag(\ReflectionProperty $ref): ?PhpDocumentorReflectionType { $docBlock = $ref->getDocComment(); diff --git a/trash/tests/Assets/Dummy.php b/tests/Support/Assets/DTO/Dummy.php similarity index 95% rename from trash/tests/Assets/Dummy.php rename to tests/Support/Assets/DTO/Dummy.php index 9825b26..e17677b 100644 --- a/trash/tests/Assets/Dummy.php +++ b/tests/Support/Assets/DTO/Dummy.php @@ -1,6 +1,6 @@ assertEquals($expected, $result); } + + public static function createTypeReflectionBasedOnPhpDocumentatorReflectionTypeDataProvider(): array + { + return [ + 'none' => [ + new \ReflectionProperty(get_class(new class() { + public $property; + }), 'property'), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN), + ], + 'mixed' => [ + new \ReflectionProperty(get_class(new class() { + public $property; + }), 'property'), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN), + ], + 'null' => [ + new \ReflectionProperty(get_class(new class() { + /** @var null */ + public $property; + }), 'property'), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ], + 'bool' => [ + new \ReflectionProperty(get_class(new class() { + /** @var bool */ + public $property; + }), 'property'), + NamedTypeReflection::create('bool', NamedTypeReflection::IS_BUILT_IN), + ], + 'int' => [ + new \ReflectionProperty(get_class(new class() { + /** @var int */ + public $property; + }), 'property'), + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + ], + 'float' => [ + new \ReflectionProperty(get_class(new class() { + /** @var float */ + public $property; + }), 'property'), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ], + 'string' => [ + new \ReflectionProperty(get_class(new class() { + /** @var string */ + public $property; + }), 'property'), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ], + 'array' => [ + new \ReflectionProperty(get_class(new class() { + /** @var array */ + public $property; + }), 'property'), + CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN), + ), + ], + 'iterable' => [ + new \ReflectionProperty(get_class(new class() { + /** @var iterable */ + public $property; + }), 'property'), + CollectionTypeReflection::create( + NamedTypeReflection::create('iterable', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN), + ), + ], + 'object' => [ + new \ReflectionProperty(get_class(new class() { + /** @var object */ + public $property; + }), 'property'), + NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN), + ], + 'nullable' => [ + new \ReflectionProperty(get_class(new class() { + /** @var ?string */ + public $property; + }), 'property'), + UnionTypeReflection::create([ + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'union' => [ + new \ReflectionProperty(get_class(new class() { + /** @var int|string */ + public $property; + }), 'property'), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'nullable union' => [ + new \ReflectionProperty(get_class(new class() { + /** @var int|string|null */ + public $property; + }), 'property'), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'intersection' => [ + new \ReflectionProperty(get_class(new class() { + /** @var \DateTime&\DateTimeInterface */ + public $property; + }), 'property'), + IntersectionTypeReflection::create([ + NamedTypeReflection::create('DateTime', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('DateTimeInterface', NamedTypeReflection::IS_INTERFACE), + ]), + ], + 'nullable intersection' => [ + new \ReflectionProperty(get_class(new class() { + /** @var (\DateTime&\DateTimeInterface)|null */ + public $property; + }), 'property'), + UnionTypeReflection::create([ + IntersectionTypeReflection::create([ + NamedTypeReflection::create('DateTime', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('DateTimeInterface', NamedTypeReflection::IS_INTERFACE), + ]), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'class' => [ + new \ReflectionProperty(get_class(new class() { + /** @var \DateTime */ + public $property; + }), 'property'), + NamedTypeReflection::create('DateTime', NamedTypeReflection::IS_CLASS), + ], + 'abstract class' => [ + new \ReflectionProperty(get_class(new class() { + /** @var \ReflectionType */ + public $property; + }), 'property'), + NamedTypeReflection::create('ReflectionType', NamedTypeReflection::IS_CLASS | NamedTypeReflection::IS_ABSTRACT), + ], + 'array access' => [ + new \ReflectionProperty(get_class(new class() { + /** @var \ArrayAccess */ + public $property; + }), 'property'), + CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayAccess', NamedTypeReflection::IS_INTERFACE), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN), + ), + ], + 'traversable' => [ + new \ReflectionProperty(get_class(new class() { + /** @var \Traversable */ + public $property; + }), 'property'), + CollectionTypeReflection::create( + NamedTypeReflection::create('Traversable', NamedTypeReflection::IS_INTERFACE), + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN), + ), + ], + 'enum' => [ + new \ReflectionProperty(get_class(new class() { + /** @var TestEnum */ + public $property; + }), 'property'), + NamedTypeReflection::create(TestEnum::class, NamedTypeReflection::IS_ENUM), + ], + 'interface' => [ + new \ReflectionProperty(get_class(new class() { + /** @var \DateTimeInterface */ + public $property; + }), 'property'), + NamedTypeReflection::create('DateTimeInterface', NamedTypeReflection::IS_INTERFACE), + ], + 'spl object storage' => [ + new \ReflectionProperty(get_class(new class() { + /** @var \SplObjectStorage */ + public $property; + }), 'property'), + CollectionTypeReflection::create( + NamedTypeReflection::create('SplObjectStorage', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + ]; + } + + #[Test] + #[DataProvider('createTypeReflectionBasedOnPhpDocumentatorReflectionTypeDataProvider')] + public function testCreateTypeReflectionBasedOnPhpDocumentatorReflectionType(\ReflectionProperty $ref, TypeReflection $expected): void + { + $factory = new TypeReflectionFactory(); + $docBlockResolver = new \ReflectionMethod(TypeReflectionFactory::class, 'getDocBlockReflectionTypeFromVarTag'); + $input = $docBlockResolver->invokeArgs($factory, [$ref]); + $method = new \ReflectionMethod(TypeReflectionFactory::class, 'createTypeReflectionBasedOnPhpDocumentatorReflectionType'); + + $result = $method->invokeArgs($factory, [$input, new \ReflectionClass(__CLASS__)]); + + $this->assertEquals($expected, $result); + } + + public static function mergeNamedTypeReflectionsDataProvider(): array + { + return [ + [ + null, + null, + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN), + ], + [ + null, + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + ], + [ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + null, + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + ], + [ + NamedTypeReflection::recreate('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::recreate('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::recreate('int', NamedTypeReflection::IS_BUILT_IN), + ], + [ + NamedTypeReflection::recreate('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::recreate('mixed', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::recreate('int', NamedTypeReflection::IS_BUILT_IN), + ], + [ + NamedTypeReflection::recreate('mixed', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::recreate('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::recreate('int', NamedTypeReflection::IS_BUILT_IN), + ], + [ + NamedTypeReflection::recreate(\DateTimeInterface::class, NamedTypeReflection::IS_INTERFACE), + NamedTypeReflection::recreate(\DateTime::class, NamedTypeReflection::IS_CLASS), + NamedTypeReflection::recreate(\DateTime::class, NamedTypeReflection::IS_CLASS), + ], + [ + NamedTypeReflection::recreate(\ReflectionType::class, NamedTypeReflection::IS_CLASS | NamedTypeReflection::IS_ABSTRACT), + NamedTypeReflection::recreate(\ReflectionNamedType::class, NamedTypeReflection::IS_CLASS), + NamedTypeReflection::recreate(\ReflectionNamedType::class, NamedTypeReflection::IS_CLASS), + ], + [ + NamedTypeReflection::recreate(\ReflectionNamedType::class, NamedTypeReflection::IS_CLASS), + NamedTypeReflection::recreate(\ReflectionType::class, NamedTypeReflection::IS_CLASS | NamedTypeReflection::IS_ABSTRACT), + NamedTypeReflection::recreate(\ReflectionNamedType::class, NamedTypeReflection::IS_CLASS), + ], + ]; + } + + #[Test] + #[DataProvider('mergeNamedTypeReflectionsDataProvider')] + public function shouldCorrectMergeNamedTypeReflections(?NamedTypeReflection $phpRef, ?NamedTypeReflection $docRef, NamedTypeReflection $expected): void + { + $factory = new TypeReflectionFactory(); + $input = [$phpRef, $docRef]; + $method = new \ReflectionMethod(TypeReflectionFactory::class, 'mergeNamedTypeReflections'); + + $result = $method->invokeArgs($factory, $input); + + $this->assertEquals($expected, $result); + } + + #[Test] + public function shouldThrowLogicExceptionBecausePhpDocumentatorReflectionIsNotCompatibleWithReflectionType(): void + { + $factory = new TypeReflectionFactory(); + $input = [ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create(\DateTime::class, NamedTypeReflection::IS_CLASS), + ]; + $method = new \ReflectionMethod(TypeReflectionFactory::class, 'mergeNamedTypeReflections'); + + $this->expectException(\LogicException::class); + $this->expectExceptionCode(16); + $method->invokeArgs($factory, $input); + } + + public static function dataProvider(): array + { + return [ + // (simple types + nullable simple types) x (based on docblock + based on reflection + based on docblock and reflection) + 'null' => [ + 'obj' => new class() { + public null $property; + }, + 'expected' => NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ], + 'nullBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var null */ + public $property; + }, + 'expected' => NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ], + 'nullBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public null $property; + }, + 'expected' => NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ], + 'false' => [ + 'obj' => new class() { + public false $property; + }, + 'expected' => NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + ], + 'falseBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var false */ + public $property; + }, + 'expected' => NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + ], + 'falseBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public false $property; + }, + 'expected' => NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + ], + '?false' => [ + 'obj' => new class() { + public ?false $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?falseBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var false|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?falseBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public ?false $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'true' => [ + 'obj' => new class() { + public true $property; + }, + 'expected' => NamedTypeReflection::create('true', NamedTypeReflection::IS_BUILT_IN), + ], + 'trueBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var true */ + public $property; + }, + 'expected' => NamedTypeReflection::create('true', NamedTypeReflection::IS_BUILT_IN), + ], + 'trueBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public true $property; + }, + 'expected' => NamedTypeReflection::create('true', NamedTypeReflection::IS_BUILT_IN), + ], + '?true' => [ + 'obj' => new class() { + public ?true $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('true', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?trueBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var true|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('true', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?trueBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public ?true $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('true', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'bool' => [ + 'obj' => new class() { + public bool $property; + }, + 'expected' => NamedTypeReflection::create('bool', NamedTypeReflection::IS_BUILT_IN), + ], + 'boolBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var bool */ + public $property; + }, + 'expected' => NamedTypeReflection::create('bool', NamedTypeReflection::IS_BUILT_IN), + ], + 'boolBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public bool $property; + }, + 'expected' => NamedTypeReflection::create('bool', NamedTypeReflection::IS_BUILT_IN), + ], + '?bool' => [ + 'obj' => new class() { + public ?bool $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('bool', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?boolBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var bool|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('bool', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?boolBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public ?bool $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('bool', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'int' => [ + 'obj' => new class() { + public int $property; + }, + 'expected' => NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + ], + 'intBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var int */ + public $property; + }, + 'expected' => NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + ], + 'intBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public int $property; + }, + 'expected' => NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + ], + '?int' => [ + 'obj' => new class() { + public ?int $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?intBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var ?int */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?intBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public ?int $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'float' => [ + 'obj' => new class() { + public float $property; + }, + 'expected' => NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ], + 'floatBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var float */ + public $property; + }, + 'expected' => NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ], + 'floatBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public float $property; + }, + 'expected' => NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ], + '?float' => [ + 'obj' => new class() { + public ?float $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?floatBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var float|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?floatBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public ?float $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'string' => [ + 'obj' => new class() { + public string $property; + }, + 'expected' => NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ], + 'stringBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var string */ + public $property; + }, + 'expected' => NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ], + 'stringBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public string $property; + }, + 'expected' => NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ], + '?string' => [ + 'obj' => new class() { + public ?string $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?stringBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var string|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?stringBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public ?string $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'object' => [ + 'obj' => new class() { + public object $property; + }, + 'expected' => NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN), + ], + 'objectBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var object */ + public $property; + }, + 'expected' => NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN), + ], + 'objectBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public object $property; + }, + 'expected' => NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN), + ], + '?object' => [ + 'obj' => new class() { + public ?object $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?objectBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var object|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?objectBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public ?object $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'DateTime' => [ + 'obj' => new class() { + public \DateTime $property; + }, + 'expected' => NamedTypeReflection::create('DateTime', NamedTypeReflection::IS_CLASS), + ], + '?DateTime' => [ + 'obj' => new class() { + public ?\DateTime $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('DateTime', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '\\'.Dummy::class => [ + 'obj' => new class() { + public Dummy $property; + }, + 'expected' => NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + ], + '\\'.Dummy::class.'BasedOnDocBlock' => [ + 'obj' => new class() { + /** @var Dummy */ + public $property; + }, + 'expected' => NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + ], + '\\'.Dummy::class.'BasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public Dummy $property; + }, + 'expected' => NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + ], + '?\\'.Dummy::class => [ + 'obj' => new class() { + public ?Dummy $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?\\'.Dummy::class.'BasedOnDocBlock' => [ + 'obj' => new class() { + /** @var Dummy|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?\\'.Dummy::class.'BasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public ?Dummy $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '\\'.TestEnum::class => [ + 'obj' => new class() { + public TestEnum $property; + }, + 'expected' => NamedTypeReflection::create(TestEnum::class, NamedTypeReflection::IS_ENUM), + ], + '\\'.TestEnum::class.'BasedOnDocBlock' => [ + 'obj' => new class() { + /** @var TestEnum */ + public $property; + }, + 'expected' => NamedTypeReflection::create(TestEnum::class, NamedTypeReflection::IS_ENUM), + ], + '\\'.TestEnum::class.'BasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public TestEnum $property; + }, + 'expected' => NamedTypeReflection::create(TestEnum::class, NamedTypeReflection::IS_ENUM), + ], + '?\\'.TestEnum::class => [ + 'obj' => new class() { + public ?TestEnum $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create(TestEnum::class, NamedTypeReflection::IS_ENUM), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?\\'.TestEnum::class.'BasedOnDocBlock' => [ + 'obj' => new class() { + /** @var TestEnum|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create(TestEnum::class, NamedTypeReflection::IS_ENUM), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?\\'.TestEnum::class.'BasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public ?TestEnum $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create(TestEnum::class, NamedTypeReflection::IS_ENUM), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'mixed' => [ + 'obj' => new class() { + public mixed $property; + }, + 'expected' => NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN), + ], + 'mixedBasedOnDocBlock' => [ + 'obj' => new class() { + public $property; + }, + 'expected' => NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN), + ], + 'mixedBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public mixed $property; + }, + 'expected' => NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN), + ], + + // special simple types + 'class-stringBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var class-string */ + public $property; + }, + 'expected' => NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ], + 'class-stringBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var class-string */ + public string $property; + }, + 'expected' => NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ], + + // intersection types + \JsonSerializable::class.'&\\'.Dummy::class => [ + 'obj' => new class() { + public \JsonSerializable&Dummy $property; + }, + 'expected' => IntersectionTypeReflection::create([ + NamedTypeReflection::create('JsonSerializable', NamedTypeReflection::IS_INTERFACE), + NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + ]), + ], + \JsonSerializable::class.'&\\'.Dummy::class.'BasedOnDocBlock' => [ + 'obj' => new class() { + /** @var \JsonSerializable&Dummy */ + public $property; + }, + 'expected' => IntersectionTypeReflection::create([ + NamedTypeReflection::create('JsonSerializable', NamedTypeReflection::IS_INTERFACE), + NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + ]), + ], + \JsonSerializable::class.'&\\'.Dummy::class.'BasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public \JsonSerializable&Dummy $property; + }, + 'expected' => IntersectionTypeReflection::create([ + NamedTypeReflection::create('JsonSerializable', NamedTypeReflection::IS_INTERFACE), + NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + ]), + ], + '?'.\JsonSerializable::class.'&\\'.Dummy::class => [ + 'obj' => new class() { + public (\JsonSerializable&Dummy)|null $property; + }, + 'expected' => UnionTypeReflection::create([ + IntersectionTypeReflection::create([ + NamedTypeReflection::create('JsonSerializable', NamedTypeReflection::IS_INTERFACE), + NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + ]), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + + // union types + 'false|int' => [ + 'obj' => new class() { + public int|false $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'false|intBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var int|false */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'false|intBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public int|false $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?false|int' => [ + 'obj' => new class() { + public int|false|null $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?false|intBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var int|false|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?false|intBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public int|false|null $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('false', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'int|float' => [ + 'obj' => new class() { + public int|float $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'int|floatBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var int|float */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'int|floatBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public int|float $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?int|float' => [ + 'obj' => new class() { + public int|float|null $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?int|floatBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var int|float|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?int|floatBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public int|float|null $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'int|float|string' => [ + 'obj' => new class() { + public int|float|string $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'int|float|stringBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var int|float|string */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + 'int|float|stringBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public int|float|string $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?int|float|string' => [ + 'obj' => new class() { + public int|float|string|null $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?int|float|stringBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var int|float|string|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?int|float|stringBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public int|float|string|null $property; + }, + 'expected' => UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + + // collection types + 'array' => [ + 'obj' => new class() { + public array $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + 'arrayBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var array */ + public $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + 'arrayBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + public array $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + '?array' => [ + 'obj' => new class() { + public ?array $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?arrayBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var array|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?arrayBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var ?array */ + public ?array $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '\\ArrayObject' => [ + 'obj' => new class() { + public \ArrayObject $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + '\\ArrayObjectBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var \ArrayObject */ + public $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + '\\ArrayObjectBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var \ArrayObject */ + public \ArrayObject $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + '?\\ArrayObject' => [ + 'obj' => new class() { + public ?\ArrayObject $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?\\ArrayObjectBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var \ArrayObject|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?\\ArrayObjectBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var ?\ArrayObject */ + public ?\ArrayObject $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + + // collection with string key + 'arrayBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var array */ + public $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + 'arrayBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var array */ + public array $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + '?arrayBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var array|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?arrayBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var array|null */ + public ?array $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '\\ArrayObjectBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var \ArrayObject */ + public $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + '\\ArrayObjectBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var \ArrayObject */ + public \ArrayObject $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + ], + '?\\ArrayObjectBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var \ArrayObject|null */ + public $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + '?\\ArrayObjectBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var \ArrayObject|null */ + public ?\ArrayObject $property; + }, + 'expected' => UnionTypeReflection::create([ + CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN) + ), + NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN), + ]), + ], + + // collection of collection + 'array>BasedOnDocBlock' => [ + 'obj' => new class() { + /** @var array> */ + public $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN) + ) + ), + ], + 'array>BasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var array> */ + public array $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN) + ) + ), + ], + + // collection of union + 'arrayBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var array */ + public $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ]) + ), + ], + 'arrayBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var array */ + public array $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ]) + ), + ], + '\\ArrayObjectBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var \ArrayObject */ + public $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + ]), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ]) + ), + ], + '\\ArrayObjectBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var \ArrayObject */ + public \ArrayObject $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('ArrayObject', NamedTypeReflection::IS_CLASS), + UnionTypeReflection::create([ + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + ]), + UnionTypeReflection::create([ + NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('float', NamedTypeReflection::IS_BUILT_IN), + ]) + ), + ], + + // collection of intersection + 'arrayBasedOnDocBlock' => [ + 'obj' => new class() { + /** @var array */ + public $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + IntersectionTypeReflection::create([ + NamedTypeReflection::create('JsonSerializable', NamedTypeReflection::IS_INTERFACE), + NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + ]) + ), + ], + 'arrayBasedOnDocBlockAndReflection' => [ + 'obj' => new class() { + /** @var array */ + public array $property; + }, + 'expected' => CollectionTypeReflection::create( + NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN), + NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN), + IntersectionTypeReflection::create([ + NamedTypeReflection::create('JsonSerializable', NamedTypeReflection::IS_INTERFACE), + NamedTypeReflection::create(Dummy::class, NamedTypeReflection::IS_CLASS), + ]) + ), + ], + ]; + } + + #[Test] + #[DataProvider('dataProvider')] + public function testOnDataFromDataProvider(object $obj, TypeReflection $expected): void + { + $ref = new \ReflectionProperty($obj, 'property'); + $factory = new TypeReflectionFactory(); + + $result = $factory->createForProperty($ref); + + $this->assertEquals($expected, $result); + } } diff --git a/tools/phpstan/fpm-baseline.neon b/tools/phpstan/fpm-baseline.neon index dd00392..8f54056 100644 --- a/tools/phpstan/fpm-baseline.neon +++ b/tools/phpstan/fpm-baseline.neon @@ -315,19 +315,9 @@ parameters: count: 1 path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php - - - message: "#^Method PBaszak\\\\UltraMapper\\\\Reflection\\\\Domain\\\\Factories\\\\TypeReflectionFactory\\:\\:createTypeReflectionBasedOnPhpDocumentatorReflectionType\\(\\) is unused\\.$#" - count: 1 - path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php - - - - message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#" - count: 1 - path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php - - message: "#^Variable \\$class might not be defined\\.$#" - count: 1 + count: 2 path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php -