From 6f7a9cb6cda18ef1ddeb77ac7ec02f7697987092 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 | 6 +++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Mapper/Tree/Shell.php b/src/Mapper/Tree/Shell.php index cec31b4d..2ecdd109 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..75fa57c0 100644 --- a/tests/Integration/Mapping/UnionMappingTest.php +++ b/tests/Integration/Mapping/UnionMappingTest.php @@ -64,6 +64,12 @@ 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' => fn (mixed $result) => self::assertIsFloat($result) && self::assertEquals(42.0, $result), + ]; + yield 'string or list of string, with string' => [ 'type' => 'string|list', 'source' => 'foo',