From 9dd83fc7e45365f654e90efd0106a4acd9ed89a8 Mon Sep 17 00:00:00 2001 From: NanoSector Date: Mon, 9 Sep 2024 09:32:10 +0200 Subject: [PATCH] Fix int and float compatibility in union types This was broken in commit https://github.com/CuyZ/Valinor/commit/0479532fbc96fca35dcbfb4c1f5a9ef63e7625c5 as withType may be called in union type context. First calling withValue() to set an int value and then updating the type with withType() does not trigger the float cast, in turn causing a mapping error. Developed by: Social Deal (@socialdeal) --- src/Mapper/Tree/Shell.php | 24 ++++++++++++------- .../Integration/Mapping/UnionMappingTest.php | 9 +++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Mapper/Tree/Shell.php b/src/Mapper/Tree/Shell.php index 150083cd..10831820 100644 --- a/src/Mapper/Tree/Shell.php +++ b/src/Mapper/Tree/Shell.php @@ -80,6 +80,7 @@ public function withType(Type $newType): self { $clone = clone $this; $clone->type = $newType; + $clone->value = self::castCompatibleValue($newType, $this->value); return $clone; } @@ -91,17 +92,9 @@ public function type(): Type public function withValue(mixed $value): self { - // When the value is an integer and the type is a float, the value is - // cast to float, to follow the rule of PHP regarding acceptance of an - // integer value in a float type. Note that PHPStan/Psalm analysis - // applies the same rule. - if ($this->type instanceof FloatType && is_int($value)) { - $value = (float)$value; - } - $clone = clone $this; $clone->hasValue = true; - $clone->value = $value; + $clone->value = self::castCompatibleValue($clone->type, $value); return $clone; } @@ -173,4 +166,17 @@ public function path(): string return implode('.', $path); } + + private static function castCompatibleValue(Type $type, mixed $value): mixed + { + // When the value is an integer and the type is a float, the value is + // cast to float, to follow the rule of PHP regarding acceptance of an + // integer value in a float type. Note that PHPStan/Psalm analysis + // applies the same rule. + if ($type instanceof FloatType && is_int($value)) { + return (float)$value; + } + + return $value; + } } diff --git a/tests/Integration/Mapping/UnionMappingTest.php b/tests/Integration/Mapping/UnionMappingTest.php index 8612d507..e840a98c 100644 --- a/tests/Integration/Mapping/UnionMappingTest.php +++ b/tests/Integration/Mapping/UnionMappingTest.php @@ -64,6 +64,15 @@ public static function union_mapping_works_properly_data_provider(): iterable 'assertion' => fn (mixed $result) => self::assertNull($result), ]; + yield 'nullable float with integer value' => [ + 'type' => 'float|null', + 'source' => 42, + 'assertion' => function (mixed $result) { + self::assertIsFloat($result); + self::assertEquals(42.0, $result); + }, + ]; + yield 'string or list of string, with string' => [ 'type' => 'string|list', 'source' => 'foo',