diff --git a/src/Library/Container.php b/src/Library/Container.php index 6cd01fae..346aa509 100644 --- a/src/Library/Container.php +++ b/src/Library/Container.php @@ -56,8 +56,6 @@ use CuyZ\Valinor\Type\Parser\Template\BasicTemplateParser; use CuyZ\Valinor\Type\Parser\Template\TemplateParser; use CuyZ\Valinor\Type\Parser\TypeParser; -use CuyZ\Valinor\Type\Resolver\Union\UnionNullNarrower; -use CuyZ\Valinor\Type\Resolver\Union\UnionScalarNarrower; use CuyZ\Valinor\Type\ScalarType; use CuyZ\Valinor\Type\Types\ArrayType; use CuyZ\Valinor\Type\Types\IterableType; @@ -102,7 +100,7 @@ public function __construct(Settings $settings) ScalarType::class => new ScalarNodeBuilder($settings->flexible), ]); - $builder = new UnionNodeBuilder($builder, new UnionNullNarrower(new UnionScalarNarrower())); + $builder = new UnionNodeBuilder($builder, $settings->flexible); $builder = new ClassNodeBuilder( $builder, diff --git a/src/Mapper/Tree/Builder/ObjectImplementations.php b/src/Mapper/Tree/Builder/ObjectImplementations.php index 835e59d7..c5412ef5 100644 --- a/src/Mapper/Tree/Builder/ObjectImplementations.php +++ b/src/Mapper/Tree/Builder/ObjectImplementations.php @@ -6,6 +6,7 @@ use CuyZ\Valinor\Definition\FunctionDefinition; use CuyZ\Valinor\Definition\FunctionsContainer; +use CuyZ\Valinor\Mapper\Tree\Exception\CannotResolveObjectType; use CuyZ\Valinor\Mapper\Tree\Exception\InvalidAbstractObjectName; use CuyZ\Valinor\Mapper\Tree\Exception\InvalidResolvedImplementationValue; use CuyZ\Valinor\Mapper\Tree\Exception\MissingObjectImplementationRegistration; @@ -14,7 +15,6 @@ use CuyZ\Valinor\Mapper\Tree\Exception\ResolvedImplementationIsNotAccepted; use CuyZ\Valinor\Type\Parser\Exception\InvalidType; use CuyZ\Valinor\Type\Parser\TypeParser; -use CuyZ\Valinor\Type\Resolver\Exception\CannotResolveObjectType; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\ClassStringType; use CuyZ\Valinor\Type\Types\ClassType; diff --git a/src/Mapper/Tree/Builder/UnionNodeBuilder.php b/src/Mapper/Tree/Builder/UnionNodeBuilder.php index 12e723aa..89651b83 100644 --- a/src/Mapper/Tree/Builder/UnionNodeBuilder.php +++ b/src/Mapper/Tree/Builder/UnionNodeBuilder.php @@ -4,21 +4,27 @@ namespace CuyZ\Valinor\Mapper\Tree\Builder; +use CuyZ\Valinor\Mapper\Tree\Exception\CannotResolveTypeFromUnion; use CuyZ\Valinor\Mapper\Tree\Shell; -use CuyZ\Valinor\Type\Resolver\Union\UnionNarrower; +use CuyZ\Valinor\Type\EnumType; +use CuyZ\Valinor\Type\ScalarType; +use CuyZ\Valinor\Type\Type; +use CuyZ\Valinor\Type\Types\NullType; use CuyZ\Valinor\Type\Types\UnionType; +use function count; + /** @internal */ final class UnionNodeBuilder implements NodeBuilder { private NodeBuilder $delegate; - private UnionNarrower $unionNarrower; + private bool $flexible; - public function __construct(NodeBuilder $delegate, UnionNarrower $unionNarrower) + public function __construct(NodeBuilder $delegate, bool $flexible) { $this->delegate = $delegate; - $this->unionNarrower = $unionNarrower; + $this->flexible = $flexible; } public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode @@ -29,8 +35,40 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode return $this->delegate->build($shell, $rootBuilder); } - $narrowedType = $this->unionNarrower->narrow($type, $shell->value()); + $narrowedType = $this->narrow($type, $shell->value()); return $rootBuilder->build($shell->withType($narrowedType)); } + + /** + * @param mixed $source + */ + private function narrow(UnionType $type, $source): Type + { + $subTypes = $type->types(); + + if ($source !== null && count($subTypes) === 2) { + if ($subTypes[0] instanceof NullType) { + return $subTypes[1]; + } elseif ($subTypes[1] instanceof NullType) { + return $subTypes[0]; + } + } + + foreach ($subTypes as $subType) { + if (! $subType instanceof ScalarType) { + continue; + } + + if (! $this->flexible && ! $subType instanceof EnumType) { + continue; + } + + if ($subType->canCast($source)) { + return $subType; + } + } + + throw new CannotResolveTypeFromUnion($source, $type); + } } diff --git a/src/Type/Resolver/Exception/CannotResolveObjectType.php b/src/Mapper/Tree/Exception/CannotResolveObjectType.php similarity index 87% rename from src/Type/Resolver/Exception/CannotResolveObjectType.php rename to src/Mapper/Tree/Exception/CannotResolveObjectType.php index 8202da16..7b35fc6b 100644 --- a/src/Type/Resolver/Exception/CannotResolveObjectType.php +++ b/src/Mapper/Tree/Exception/CannotResolveObjectType.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace CuyZ\Valinor\Type\Resolver\Exception; +namespace CuyZ\Valinor\Mapper\Tree\Exception; use RuntimeException; diff --git a/src/Type/Resolver/Exception/CannotResolveTypeFromUnion.php b/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php similarity index 63% rename from src/Type/Resolver/Exception/CannotResolveTypeFromUnion.php rename to src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php index 2e84e430..a2635284 100644 --- a/src/Type/Resolver/Exception/CannotResolveTypeFromUnion.php +++ b/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace CuyZ\Valinor\Type\Resolver\Exception; +namespace CuyZ\Valinor\Mapper\Tree\Exception; use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage; use CuyZ\Valinor\Mapper\Tree\Message\HasParameters; @@ -22,7 +22,10 @@ final class CannotResolveTypeFromUnion extends RuntimeException implements Error /** @var array */ private array $parameters; - public function __construct(UnionType $unionType) + /** + * @param mixed $source + */ + public function __construct($source, UnionType $unionType) { $this->parameters = [ 'allowed_types' => implode( @@ -32,9 +35,15 @@ public function __construct(UnionType $unionType) ), ]; - $this->body = TypeHelper::containsObject($unionType) - ? 'Invalid value {source_value}.' - : 'Value {source_value} does not match any of {allowed_types}.'; + if ($source === null) { + $this->body = TypeHelper::containsObject($unionType) + ? 'Cannot be empty.' + : 'Cannot be empty and must be filled with a value matching any of {allowed_types}.'; + } else { + $this->body = TypeHelper::containsObject($unionType) + ? 'Invalid value {source_value}.' + : 'Value {source_value} does not match any of {allowed_types}.'; + } parent::__construct(StringFormatter::for($this), 1607027306); } diff --git a/src/Mapper/Tree/Message/DefaultMessage.php b/src/Mapper/Tree/Message/DefaultMessage.php index 9a02712a..33b2e080 100644 --- a/src/Mapper/Tree/Message/DefaultMessage.php +++ b/src/Mapper/Tree/Message/DefaultMessage.php @@ -14,6 +14,9 @@ interface DefaultMessage 'Value {source_value} does not match any of {allowed_types}.' => [ 'en' => 'Value {source_value} does not match any of {allowed_types}.', ], + 'Cannot be empty and must be filled with a value matching any of {allowed_types}.' => [ + 'en' => 'Cannot be empty and must be filled with a value matching any of {allowed_types}.', + ], 'Value {source_value} does not match type {expected_type}.' => [ 'en' => 'Value {source_value} does not match type {expected_type}.', ], diff --git a/src/Type/Resolver/Exception/UnionTypeDoesNotAllowNull.php b/src/Type/Resolver/Exception/UnionTypeDoesNotAllowNull.php deleted file mode 100644 index e1563fd8..00000000 --- a/src/Type/Resolver/Exception/UnionTypeDoesNotAllowNull.php +++ /dev/null @@ -1,44 +0,0 @@ - */ - private array $parameters; - - public function __construct(UnionType $unionType) - { - $this->parameters = [ - 'expected_type' => TypeHelper::dump($unionType), - ]; - - $this->body = TypeHelper::containsObject($unionType) - ? 'Cannot be empty.' - : 'Cannot be empty and must be filled with a value matching type {expected_type}.'; - - parent::__construct(StringFormatter::for($this), 1618742357); - } - - public function body(): string - { - return $this->body; - } - - public function parameters(): array - { - return $this->parameters; - } -} diff --git a/src/Type/Resolver/Union/UnionNarrower.php b/src/Type/Resolver/Union/UnionNarrower.php deleted file mode 100644 index f472c20e..00000000 --- a/src/Type/Resolver/Union/UnionNarrower.php +++ /dev/null @@ -1,15 +0,0 @@ -delegate = $delegate; - } - - public function narrow(UnionType $unionType, $source): Type - { - $allowsNull = $this->findNullType($unionType); - - if ($source === null) { - if (! $allowsNull) { - throw new UnionTypeDoesNotAllowNull($unionType); - } - - return NullType::get(); - } - - $subTypes = $unionType->types(); - - if ($allowsNull && count($subTypes) === 2) { - return $subTypes[0] instanceof NullType - ? $subTypes[1] - : $subTypes[0]; - } - - return $this->delegate->narrow($unionType, $source); - } - - private function findNullType(UnionType $type): bool - { - foreach ($type->types() as $subType) { - if ($subType instanceof NullType) { - return true; - } - } - - return false; - } -} diff --git a/src/Type/Resolver/Union/UnionScalarNarrower.php b/src/Type/Resolver/Union/UnionScalarNarrower.php deleted file mode 100644 index 14cc8300..00000000 --- a/src/Type/Resolver/Union/UnionScalarNarrower.php +++ /dev/null @@ -1,43 +0,0 @@ -types() as $subType) { - if ($subType->accepts($source)) { - $accepts[] = $subType; - } elseif ($subType instanceof ScalarType && $subType->canCast($source)) { - $canCast[] = $subType; - } - } - - if (count($accepts) === 1) { - return $accepts[0]; - } - - if (count($canCast) === 1) { - return $canCast[0]; - } - - throw new CannotResolveTypeFromUnion($unionType); - } -} diff --git a/tests/Fake/Type/Resolver/Union/FakeUnionNarrower.php b/tests/Fake/Type/Resolver/Union/FakeUnionNarrower.php deleted file mode 100644 index 3a40cfe8..00000000 --- a/tests/Fake/Type/Resolver/Union/FakeUnionNarrower.php +++ /dev/null @@ -1,25 +0,0 @@ -type ?? new FakeType(); - } - - public function willReturn(Type $type): void - { - $this->type = $type; - } -} diff --git a/tests/Integration/Mapping/Fixture/NativeUnionValues.php b/tests/Integration/Mapping/Fixture/NativeUnionValues.php index 3ca9c3b3..6703b23f 100644 --- a/tests/Integration/Mapping/Fixture/NativeUnionValues.php +++ b/tests/Integration/Mapping/Fixture/NativeUnionValues.php @@ -6,6 +6,7 @@ // @PHP8.0 move inside \CuyZ\Valinor\Tests\Integration\Mapping\UnionValuesMappingTest use CuyZ\Valinor\Tests\Fixture\Object\ObjectWithConstants; +use DateTimeInterface; class NativeUnionValues { @@ -27,6 +28,10 @@ class NativeUnionValues /** @var int|false */ public int|bool $intOrLiteralFalse = 42; + public DateTimeInterface|null $dateTimeOrNull = null; + + public null|DateTimeInterface $nullOrDateTime = null; + /** @var ObjectWithConstants::CONST_WITH_STRING_VALUE_A|ObjectWithConstants::CONST_WITH_INTEGER_VALUE_A */ public string|int $constantWithStringValue = 1653398288; @@ -51,6 +56,8 @@ public function __construct( string|null $nullableWithNull = 'Schwifty!', int|bool $intOrLiteralTrue = 42, int|bool $intOrLiteralFalse = 42, + DateTimeInterface|null $dateTimeOrNull = null, + null|DateTimeInterface $nullOrDateTime = null, string|int $constantWithStringValue = 1653398288, string|int $constantWithIntegerValue = 'some string value' ) { @@ -62,6 +69,8 @@ public function __construct( $this->nullableWithNull = $nullableWithNull; $this->intOrLiteralTrue = $intOrLiteralTrue; $this->intOrLiteralFalse = $intOrLiteralFalse; + $this->dateTimeOrNull = $dateTimeOrNull; + $this->nullOrDateTime = $nullOrDateTime; $this->constantWithStringValue = $constantWithStringValue; $this->constantWithIntegerValue = $constantWithIntegerValue; } diff --git a/tests/Integration/Mapping/InterfaceInferringMappingTest.php b/tests/Integration/Mapping/InterfaceInferringMappingTest.php index b390a72f..96071273 100644 --- a/tests/Integration/Mapping/InterfaceInferringMappingTest.php +++ b/tests/Integration/Mapping/InterfaceInferringMappingTest.php @@ -5,6 +5,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping; use CuyZ\Valinor\Mapper\MappingError; +use CuyZ\Valinor\Mapper\Tree\Exception\CannotResolveObjectType; use CuyZ\Valinor\Mapper\Tree\Exception\InvalidAbstractObjectName; use CuyZ\Valinor\Mapper\Tree\Exception\InvalidResolvedImplementationValue; use CuyZ\Valinor\Mapper\Tree\Exception\MissingObjectImplementationRegistration; @@ -21,7 +22,6 @@ use CuyZ\Valinor\Tests\Fixture\Object\InterfaceWithDifferentNamespaces\InterfaceB; use CuyZ\Valinor\Tests\Fixture\Object\InterfaceWithDifferentNamespaces\InterfaceBInferer; use CuyZ\Valinor\Tests\Integration\IntegrationTest; -use CuyZ\Valinor\Type\Resolver\Exception\CannotResolveObjectType; use DateTime; use DateTimeInterface; use DomainException; diff --git a/tests/Integration/Mapping/Object/UnionValuesMappingTest.php b/tests/Integration/Mapping/Object/UnionValuesMappingTest.php index 2c7fa4f0..5637795e 100644 --- a/tests/Integration/Mapping/Object/UnionValuesMappingTest.php +++ b/tests/Integration/Mapping/Object/UnionValuesMappingTest.php @@ -6,10 +6,14 @@ use CuyZ\Valinor\Mapper\MappingError; use CuyZ\Valinor\MapperBuilder; +use CuyZ\Valinor\Tests\Fixture\Enum\PureEnum; use CuyZ\Valinor\Tests\Fixture\Object\ObjectWithConstants; +use CuyZ\Valinor\Tests\Fixture\Object\StringableObject; use CuyZ\Valinor\Tests\Integration\IntegrationTest; use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\NativeUnionValues; use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\NativeUnionValuesWithConstructor; +use DateTimeImmutable; +use DateTimeInterface; final class UnionValuesMappingTest extends IntegrationTest { @@ -24,6 +28,8 @@ public function test_values_are_mapped_properly(): void 'nullableWithNull' => null, 'intOrLiteralTrue' => true, 'intOrLiteralFalse' => false, + 'dateTimeOrNull' => 1667754013, + 'nullOrDateTime' => 1667754014, 'constantWithStringValue' => 'some string value', 'constantWithIntegerValue' => 1653398288, ]; @@ -50,6 +56,10 @@ public function test_values_are_mapped_properly(): void self::assertSame(null, $result->nullableWithNull); self::assertSame(true, $result->intOrLiteralTrue); self::assertSame(false, $result->intOrLiteralFalse); + self::assertInstanceOf(DateTimeInterface::class, $result->dateTimeOrNull); + self::assertInstanceOf(DateTimeInterface::class, $result->nullOrDateTime); + self::assertSame((new DateTimeImmutable('@1667754013'))->format('U'), $result->dateTimeOrNull->format('U')); + self::assertSame((new DateTimeImmutable('@1667754014'))->format('U'), $result->nullOrDateTime->format('U')); self::assertSame('some string value', $result->constantWithStringValue); self::assertSame(1653398288, $result->constantWithIntegerValue); } @@ -78,6 +88,60 @@ public function test_values_are_mapped_properly(): void self::assertSame('fiz', $result->stringValueWithDoubleQuote); } } + + public function test_filled_source_value_is_casted_when_union_contains_three_types_including_null(): void + { + try { + $result = (new MapperBuilder()) + ->flexible() + ->mapper() + ->map('null|int|string', new StringableObject('foo')); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame('foo', $result); + } + + /** + * @requires PHP >= 8.1 + */ + public function test_enum_in_union_type_is_casted_properly(): void + { + try { + $result = (new MapperBuilder())->mapper()->map('int|' . PureEnum::class, 'FOO'); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame(PureEnum::FOO, $result); + } + + public function test_source_value_is_casted_when_other_type_cannot_be_caster(): void + { + try { + $result = (new MapperBuilder()) + ->flexible() + ->mapper() + ->map('string[]|string', new StringableObject('foo')); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame('foo', $result); + } + + public function test_invalid_value_is_not_casted_when_casting_mode_is_disabled(): void + { + try { + (new MapperBuilder())->mapper()->map('string|float', 42); + } catch (MappingError $exception) { + $error = $exception->node()->messages()[0]; + + self::assertSame('1607027306', $error->code()); + self::assertSame('Value 42 does not match any of `string`, `float`.', (string)$error); + } + } } class UnionValues @@ -106,6 +170,12 @@ class UnionValues /** @var int|false */ public $intOrLiteralFalse = 42; + /** @var DateTimeInterface|null */ + public $dateTimeOrNull; + + /** @var null|DateTimeInterface */ + public $nullOrDateTime; + /** @var ObjectWithConstants::CONST_WITH_STRING_VALUE_A|ObjectWithConstants::CONST_WITH_INTEGER_VALUE_A */ public $constantWithStringValue = 1653398288; @@ -145,6 +215,8 @@ class UnionValuesWithConstructor extends UnionValues * @param string|null|float $nullableWithNull * @param int|true $intOrLiteralTrue * @param int|false $intOrLiteralFalse + * @param DateTimeInterface|null $dateTimeOrNull + * @param null|DateTimeInterface $nullOrDateTime * @param ObjectWithConstants::CONST_WITH_STRING_VALUE_A|ObjectWithConstants::CONST_WITH_INTEGER_VALUE_A $constantWithStringValue * @param ObjectWithConstants::CONST_WITH_STRING_VALUE_A|ObjectWithConstants::CONST_WITH_INTEGER_VALUE_A $constantWithIntegerValue */ @@ -157,6 +229,8 @@ public function __construct( $nullableWithNull = 'Schwifty!', $intOrLiteralTrue = 42, $intOrLiteralFalse = 42, + $dateTimeOrNull = null, + $nullOrDateTime = null, $constantWithStringValue = 1653398288, $constantWithIntegerValue = 'some string value' ) { @@ -168,6 +242,8 @@ public function __construct( $this->nullableWithNull = $nullableWithNull; $this->intOrLiteralTrue = $intOrLiteralTrue; $this->intOrLiteralFalse = $intOrLiteralFalse; + $this->dateTimeOrNull = $dateTimeOrNull; + $this->nullOrDateTime = $nullOrDateTime; $this->constantWithStringValue = $constantWithStringValue; $this->constantWithIntegerValue = $constantWithIntegerValue; } diff --git a/tests/Integration/Mapping/Other/FlexibleMappingTest.php b/tests/Integration/Mapping/Other/FlexibleMappingTest.php index a7f3df52..b46c3ee5 100644 --- a/tests/Integration/Mapping/Other/FlexibleMappingTest.php +++ b/tests/Integration/Mapping/Other/FlexibleMappingTest.php @@ -368,8 +368,8 @@ public function test_null_in_union_value_throws_exception(): void } catch (MappingError $exception) { $error = $exception->node()->messages()[0]; - self::assertSame('1618742357', $error->code()); - self::assertSame("Cannot be empty and must be filled with a value matching type `bool|int|float`.", (string)$error); + self::assertSame('1607027306', $error->code()); + self::assertSame('Cannot be empty and must be filled with a value matching any of `bool`, `int`, `float`.', (string)$error); } } diff --git a/tests/Unit/Type/Resolver/Union/UnionNullNarrowerTest.php b/tests/Unit/Type/Resolver/Union/UnionNullNarrowerTest.php deleted file mode 100644 index f11e9f49..00000000 --- a/tests/Unit/Type/Resolver/Union/UnionNullNarrowerTest.php +++ /dev/null @@ -1,92 +0,0 @@ -delegate = new FakeUnionNarrower(); - - $this->unionNullNarrower = new UnionNullNarrower($this->delegate); - } - - public function test_null_value_with_union_type_allowing_null_returns_null_type(): void - { - $unionType = new UnionType(new FakeType(), NullType::get()); - - $type = $this->unionNullNarrower->narrow($unionType, null); - - self::assertInstanceOf(NullType::class, $type); - } - - public function test_union_not_containing_null_type_is_narrowed_by_delegate(): void - { - $type = new FakeType(); - $this->delegate->willReturn($type); - $unionType = new UnionType(new FakeType(), new FakeType()); - - $narrowedType = $this->unionNullNarrower->narrow($unionType, 'foo'); - - self::assertSame($type, $narrowedType); - } - - public function test_null_value_not_allowed_by_union_type_throws_exception(): void - { - $unionType = new UnionType(new FakeType(), new FakeType()); - - $this->expectException(UnionTypeDoesNotAllowNull::class); - $this->expectExceptionCode(1618742357); - $this->expectExceptionMessage("Cannot be empty and must be filled with a value matching type `{$unionType->toString()}`."); - - $this->unionNullNarrower->narrow($unionType, null); - } - - public function test_null_value_not_allowed_by_union_type_containing_object_type_throws_exception(): void - { - $unionType = new UnionType(new FakeType(), new FakeObjectType()); - - $this->expectException(UnionTypeDoesNotAllowNull::class); - $this->expectExceptionCode(1618742357); - $this->expectExceptionMessage('Cannot be empty.'); - - $this->unionNullNarrower->narrow($unionType, null); - } - - public function test_non_null_value_for_union_type_with_null_type_at_left_returns_type_at_right(): void - { - $type = new FakeType(); - $unionType = new UnionType($type, new NullType()); - - $narrowedType = $this->unionNullNarrower->narrow($unionType, 'foo'); - - self::assertSame($type, $narrowedType); - } - - public function test_non_null_value_for_union_type_with_null_type_at_right_returns_type_at_left(): void - { - $type = new FakeType(); - $unionType = new UnionType(new NullType(), $type); - - $narrowedType = $this->unionNullNarrower->narrow($unionType, 'foo'); - - self::assertSame($type, $narrowedType); - } -} diff --git a/tests/Unit/Type/Resolver/Union/UnionScalarNarrowerTest.php b/tests/Unit/Type/Resolver/Union/UnionScalarNarrowerTest.php deleted file mode 100644 index 0fbd00eb..00000000 --- a/tests/Unit/Type/Resolver/Union/UnionScalarNarrowerTest.php +++ /dev/null @@ -1,95 +0,0 @@ -unionScalarNarrower = new UnionScalarNarrower(); - } - - /** - * @dataProvider matching_types_are_resolved_data_provider - * - * @param mixed $source - * @param class-string $expectedType - */ - public function test_matching_types_are_resolved(UnionType $unionType, $source, string $expectedType): void - { - $type = $this->unionScalarNarrower->narrow($unionType, $source); - - self::assertInstanceOf($expectedType, $type); - } - - public function matching_types_are_resolved_data_provider(): iterable - { - $scalarUnion = new UnionType( - NativeIntegerType::get(), - NativeFloatType::get(), - NativeStringType::get(), - NativeBooleanType::get(), - ); - - return [ - 'int|float|string|bool with integer value' => [ - 'Union type' => $scalarUnion, - 'Source' => 42, - 'Expected type' => IntegerType::class, - ], - 'int|float|string|bool with float value' => [ - 'Union type' => $scalarUnion, - 'Source' => 1337.42, - 'Expected type' => NativeFloatType::class, - ], - 'int|float with stringed-float value' => [ - 'Union type' => new UnionType(NativeIntegerType::get(), NativeFloatType::get()), - 'Source' => '1337.42', - 'Expected type' => NativeFloatType::class, - ], - 'int|float|string|bool with string value' => [ - 'Union type' => $scalarUnion, - 'Source' => 'foo', - 'Expected type' => StringType::class, - ], - 'int|float|string|bool with boolean value' => [ - 'Union type' => $scalarUnion, - 'Source' => true, - 'Expected type' => NativeBooleanType::class, - ], - 'int|object with object value' => [ - 'Union type' => new UnionType(NativeIntegerType::get(), UndefinedObjectType::get()), - 'Source' => new stdClass(), - 'Expected type' => UndefinedObjectType::class, - ], - ]; - } - - public function test_integer_type_is_narrowed_over_float_when_an_integer_value_is_given(): void - { - $unionType = new UnionType(NativeFloatType::get(), NativeIntegerType::get()); - - $type = $this->unionScalarNarrower->narrow($unionType, 42); - - self::assertInstanceOf(IntegerType::class, $type); - } -}