diff --git a/README.md b/README.md index 3014b57..707c599 100644 --- a/README.md +++ b/README.md @@ -782,11 +782,17 @@ final class SomeClass /** @var array */ private array $unionInsideArray, + /** @var int|true */ + private int|bool $unionWithLiteralTrueType; + + /** @var int|false */ + private int|bool $unionWithLiteralFalseType; + /** @var 404.42|1337.42 */ - private string $unionOfFloatValues, + private float $unionOfFloatValues, /** @var 42|1337 */ - private string $unionOfIntegerValues, + private int $unionOfIntegerValues, /** @var 'foo'|'bar' */ private string $unionOfStringValues, diff --git a/src/Definition/Repository/Cache/Compiler/TypeCompiler.php b/src/Definition/Repository/Cache/Compiler/TypeCompiler.php index fc92e20..1ed3dfe 100644 --- a/src/Definition/Repository/Cache/Compiler/TypeCompiler.php +++ b/src/Definition/Repository/Cache/Compiler/TypeCompiler.php @@ -7,12 +7,11 @@ namespace CuyZ\Valinor\Definition\Repository\Cache\Compiler; use CuyZ\Valinor\Definition\Repository\Cache\Compiler\Exception\TypeCannotBeCompiled; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\ArrayKeyType; -use CuyZ\Valinor\Type\Types\BooleanType; +use CuyZ\Valinor\Type\Types\ArrayType; +use CuyZ\Valinor\Type\Types\BooleanValueType; use CuyZ\Valinor\Type\Types\ClassStringType; use CuyZ\Valinor\Type\Types\ClassType; -use CuyZ\Valinor\Type\Types\ArrayType; use CuyZ\Valinor\Type\Types\EnumType; -use CuyZ\Valinor\Type\Types\NativeFloatType; use CuyZ\Valinor\Type\Types\FloatValueType; use CuyZ\Valinor\Type\Types\IntegerRangeType; use CuyZ\Valinor\Type\Types\IntegerValueType; @@ -21,6 +20,8 @@ use CuyZ\Valinor\Type\Types\IntersectionType; use CuyZ\Valinor\Type\Types\IterableType; use CuyZ\Valinor\Type\Types\ListType; use CuyZ\Valinor\Type\Types\MixedType; +use CuyZ\Valinor\Type\Types\NativeBooleanType; +use CuyZ\Valinor\Type\Types\NativeFloatType; use CuyZ\Valinor\Type\Types\NativeIntegerType; use CuyZ\Valinor\Type\Types\NativeStringType; use CuyZ\Valinor\Type\Types\NegativeIntegerType; @@ -50,7 +51,7 @@ final class TypeCompiler switch (true) { case $type instanceof NullType: - case $type instanceof BooleanType: + case $type instanceof NativeBooleanType: case $type instanceof NativeFloatType: case $type instanceof NativeIntegerType: case $type instanceof PositiveIntegerType: @@ -60,6 +61,10 @@ final class TypeCompiler case $type instanceof UndefinedObjectType: case $type instanceof MixedType: return "$class::get()"; + case $type instanceof BooleanValueType: + return $type->value() === true + ? "$class::true()" + : "$class::false()"; case $type instanceof IntegerRangeType: return "new $class({$type->min()}, {$type->max()})"; case $type instanceof StringValueType: diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php new file mode 100644 index 0000000..a25f494 --- /dev/null +++ b/src/Type/BooleanType.php @@ -0,0 +1,11 @@ +value = $value; + } + + public static function true(): self + { + return self::$true ??= new self(true); + } + + public static function false(): self + { + return self::$false ??= new self(false); + } + + public function accepts($value): bool + { + return $value === $this->value; + } + + public function matches(Type $other): bool + { + if ($other instanceof UnionType) { + return $other->isMatchedBy($this); + } + + return $other === $this + || $other instanceof MixedType + || $other instanceof NativeBooleanType; + } + + public function canCast($value): bool + { + if ($value === $this->value) { + return true; + } + + if ($this->value === true) { + return $value === '1' || $value === 1 || $value === 'true'; + } + + return $value === '0' || $value === 0 || $value === 'false'; + } + + public function cast($value): bool + { + if (! $this->canCast($value)) { + throw new CannotCastValue($value, $this); + } + + return $this->value; + } + + public function value(): bool + { + return $this->value; + } + + public function __toString(): string + { + return $this->value ? 'true' : 'false'; + } +} diff --git a/src/Type/Types/FloatValueType.php b/src/Type/Types/FloatValueType.php index 8febef7..ac95f94 100644 --- a/src/Type/Types/FloatValueType.php +++ b/src/Type/Types/FloatValueType.php @@ -58,7 +58,7 @@ final class FloatValueType implements FloatType, FixedType return $value; } - public function value() + public function value(): float { return $this->value; } diff --git a/src/Type/Types/BooleanType.php b/src/Type/Types/NativeBooleanType.php similarity index 95% rename from src/Type/Types/BooleanType.php rename to src/Type/Types/NativeBooleanType.php index 0d02e96..d92d844 100644 --- a/src/Type/Types/BooleanType.php +++ b/src/Type/Types/NativeBooleanType.php @@ -12,7 +12,7 @@ use CuyZ\Valinor\Utility\IsSingleton; use function is_bool; /** @api */ -final class BooleanType implements ScalarType +final class NativeBooleanType implements ScalarType { use IsSingleton; diff --git a/tests/Fake/Type/FakeType.php b/tests/Fake/Type/FakeType.php index bd0c867..9120cb3 100644 --- a/tests/Fake/Type/FakeType.php +++ b/tests/Fake/Type/FakeType.php @@ -7,7 +7,7 @@ namespace CuyZ\Valinor\Tests\Fake\Type; use CuyZ\Valinor\Tests\Fixture\Object\StringableObject; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\ArrayKeyType; -use CuyZ\Valinor\Type\Types\BooleanType; +use CuyZ\Valinor\Type\Types\NativeBooleanType; use CuyZ\Valinor\Type\Types\ClassType; use CuyZ\Valinor\Type\Types\MixedType; use CuyZ\Valinor\Type\Types\NativeStringType; @@ -40,7 +40,7 @@ final class FakeType implements Type } if ($raw === 'bool') { - return BooleanType::get(); + return NativeBooleanType::get(); } if ($raw === 'array-key') { diff --git a/tests/Functional/Definition/Repository/Cache/Compiler/TypeCompilerTest.php b/tests/Functional/Definition/Repository/Cache/Compiler/TypeCompilerTest.php index 44a97a3..4048786 100644 --- a/tests/Functional/Definition/Repository/Cache/Compiler/TypeCompilerTest.php +++ b/tests/Functional/Definition/Repository/Cache/Compiler/TypeCompilerTest.php @@ -7,11 +7,10 @@ namespace CuyZ\Valinor\Tests\Functional\Definition\Repository\Cache\Compiler; use CuyZ\Valinor\Definition\Repository\Cache\Compiler\TypeCompiler; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\ArrayKeyType; -use CuyZ\Valinor\Type\Types\BooleanType; +use CuyZ\Valinor\Type\Types\ArrayType; +use CuyZ\Valinor\Type\Types\BooleanValueType; use CuyZ\Valinor\Type\Types\ClassStringType; use CuyZ\Valinor\Type\Types\ClassType; -use CuyZ\Valinor\Type\Types\ArrayType; -use CuyZ\Valinor\Type\Types\NativeFloatType; use CuyZ\Valinor\Type\Types\FloatValueType; use CuyZ\Valinor\Type\Types\IntegerRangeType; use CuyZ\Valinor\Type\Types\IntegerValueType; @@ -20,6 +19,8 @@ use CuyZ\Valinor\Type\Types\IntersectionType; use CuyZ\Valinor\Type\Types\IterableType; use CuyZ\Valinor\Type\Types\ListType; use CuyZ\Valinor\Type\Types\MixedType; +use CuyZ\Valinor\Type\Types\NativeBooleanType; +use CuyZ\Valinor\Type\Types\NativeFloatType; use CuyZ\Valinor\Type\Types\NativeIntegerType; use CuyZ\Valinor\Type\Types\NativeStringType; use CuyZ\Valinor\Type\Types\NegativeIntegerType; @@ -73,7 +74,9 @@ final class TypeCompilerTest extends TestCase public function type_is_compiled_correctly_data_provider(): iterable { yield [NullType::get()]; - yield [BooleanType::get()]; + yield [BooleanValueType::true()]; + yield [BooleanValueType::false()]; + yield [NativeBooleanType::get()]; yield [NativeFloatType::get()]; yield [new FloatValueType(1337.42)]; yield [new FloatValueType(-1337.42)]; diff --git a/tests/Functional/Type/Parser/Lexer/NativeLexerTest.php b/tests/Functional/Type/Parser/Lexer/NativeLexerTest.php index b80acbb..a9a7853 100644 --- a/tests/Functional/Type/Parser/Lexer/NativeLexerTest.php +++ b/tests/Functional/Type/Parser/Lexer/NativeLexerTest.php @@ -37,10 +37,9 @@ use CuyZ\Valinor\Type\Parser\TypeParser; use CuyZ\Valinor\Type\StringType; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\ArrayType; -use CuyZ\Valinor\Type\Types\BooleanType; +use CuyZ\Valinor\Type\Types\BooleanValueType; use CuyZ\Valinor\Type\Types\ClassStringType; use CuyZ\Valinor\Type\Types\ClassType; -use CuyZ\Valinor\Type\Types\NativeFloatType; use CuyZ\Valinor\Type\Types\FloatValueType; use CuyZ\Valinor\Type\Types\IntegerRangeType; use CuyZ\Valinor\Type\Types\IntegerValueType; @@ -49,6 +48,8 @@ use CuyZ\Valinor\Type\Types\IntersectionType; use CuyZ\Valinor\Type\Types\IterableType; use CuyZ\Valinor\Type\Types\ListType; use CuyZ\Valinor\Type\Types\MixedType; +use CuyZ\Valinor\Type\Types\NativeBooleanType; +use CuyZ\Valinor\Type\Types\NativeFloatType; use CuyZ\Valinor\Type\Types\NonEmptyArrayType; use CuyZ\Valinor\Type\Types\NonEmptyListType; use CuyZ\Valinor\Type\Types\NonEmptyStringType; @@ -106,6 +107,26 @@ final class NativeLexerTest extends TestCase 'transformed' => 'null', 'type' => NullType::class, ], + 'True type' => [ + 'raw' => 'true', + 'transformed' => 'true', + 'type' => BooleanValueType::class, + ], + 'True type - uppercase' => [ + 'raw' => 'TRUE', + 'transformed' => 'true', + 'type' => BooleanValueType::class, + ], + 'False type' => [ + 'raw' => 'false', + 'transformed' => 'false', + 'type' => BooleanValueType::class, + ], + 'False type - uppercase' => [ + 'raw' => 'FALSE', + 'transformed' => 'false', + 'type' => BooleanValueType::class, + ], 'Mixed type' => [ 'raw' => 'mixed', 'transformed' => 'mixed', @@ -304,27 +325,27 @@ final class NativeLexerTest extends TestCase 'Boolean type' => [ 'raw' => 'bool', 'transformed' => 'bool', - 'type' => BooleanType::class, + 'type' => NativeBooleanType::class, ], 'Boolean type - uppercase' => [ 'raw' => 'BOOL', 'transformed' => 'bool', - 'type' => BooleanType::class, + 'type' => NativeBooleanType::class, ], 'Boolean type (longer version)' => [ 'raw' => 'boolean', 'transformed' => 'bool', - 'type' => BooleanType::class, + 'type' => NativeBooleanType::class, ], 'Boolean type (longer version) - uppercase' => [ 'raw' => 'BOOLEAN', 'transformed' => 'bool', - 'type' => BooleanType::class, + 'type' => NativeBooleanType::class, ], 'Boolean type followed by description' => [ 'raw' => 'bool lorem ipsum', 'transformed' => 'bool', - 'type' => BooleanType::class, + 'type' => NativeBooleanType::class, ], 'Undefined object type' => [ 'raw' => 'object', diff --git a/tests/Integration/Mapping/Fixture/NativeUnionValues.php b/tests/Integration/Mapping/Fixture/NativeUnionValues.php index 27424b3..868d5e2 100644 --- a/tests/Integration/Mapping/Fixture/NativeUnionValues.php +++ b/tests/Integration/Mapping/Fixture/NativeUnionValues.php @@ -18,17 +18,29 @@ class NativeUnionValues public string|null $nullableWithString = 'Schwifty!'; public string|null $nullableWithNull = 'Schwifty!'; + + /** @var int|true */ + public int|bool $intOrLiteralTrue = 42; + + /** @var int|false */ + public int|bool $intOrLiteralFalse = 42; } class NativeUnionValuesWithConstructor extends NativeUnionValues { + /** + * @param int|true $intOrLiteralTrue + * @param int|false $intOrLiteralFalse + */ public function __construct( bool|float|int|string $scalarWithBoolean = 'Schwifty!', bool|float|int|string $scalarWithFloat = 'Schwifty!', bool|float|int|string $scalarWithInteger = 'Schwifty!', bool|float|int|string $scalarWithString = 'Schwifty!', string|null $nullableWithString = 'Schwifty!', - string|null $nullableWithNull = 'Schwifty!' + string|null $nullableWithNull = 'Schwifty!', + int|bool $intOrLiteralTrue = 42, + int|bool $intOrLiteralFalse = 42 ) { $this->scalarWithBoolean = $scalarWithBoolean; $this->scalarWithFloat = $scalarWithFloat; @@ -36,5 +48,7 @@ class NativeUnionValuesWithConstructor extends NativeUnionValues $this->scalarWithString = $scalarWithString; $this->nullableWithString = $nullableWithString; $this->nullableWithNull = $nullableWithNull; + $this->intOrLiteralTrue = $intOrLiteralTrue; + $this->intOrLiteralFalse = $intOrLiteralFalse; } } diff --git a/tests/Integration/Mapping/Object/UnionValuesMappingTest.php b/tests/Integration/Mapping/Object/UnionValuesMappingTest.php index ea6c512..13a4d2a 100644 --- a/tests/Integration/Mapping/Object/UnionValuesMappingTest.php +++ b/tests/Integration/Mapping/Object/UnionValuesMappingTest.php @@ -20,6 +20,8 @@ final class UnionValuesMappingTest extends IntegrationTest 'scalarWithString' => 'foo', 'nullableWithString' => 'bar', 'nullableWithNull' => null, + 'intOrLiteralTrue' => true, + 'intOrLiteralFalse' => false, 'positiveFloatValue' => 1337.42, 'negativeFloatValue' => -1337.42, 'positiveIntegerValue' => 1337, @@ -48,6 +50,8 @@ final class UnionValuesMappingTest extends IntegrationTest self::assertSame('foo', $result->scalarWithString); self::assertSame('bar', $result->nullableWithString); self::assertSame(null, $result->nullableWithNull); + self::assertSame(true, $result->intOrLiteralTrue); + self::assertSame(false, $result->intOrLiteralFalse); if ($result instanceof UnionValues) { self::assertSame(1337.42, $result->positiveFloatValue); @@ -81,6 +85,12 @@ class UnionValues /** @var string|null|float */ public $nullableWithNull = 'Schwifty!'; + /** @var int|true */ + public $intOrLiteralTrue = 42; + + /** @var int|false */ + public $intOrLiteralFalse = 42; + /** @var 404.42|1337.42 */ public float $positiveFloatValue = 404.42; @@ -109,6 +119,8 @@ class UnionValuesWithConstructor extends UnionValues * @param bool|float|int|string $scalarWithString * @param string|null|float $nullableWithString * @param string|null|float $nullableWithNull + * @param int|true $intOrLiteralTrue + * @param int|false $intOrLiteralFalse * @param 404.42|1337.42 $positiveFloatValue * @param -404.42|-1337.42 $negativeFloatValue * @param 42|1337 $positiveIntegerValue @@ -123,6 +135,8 @@ class UnionValuesWithConstructor extends UnionValues $scalarWithString = 'Schwifty!', $nullableWithString = 'Schwifty!', $nullableWithNull = 'Schwifty!', + $intOrLiteralTrue = 42, + $intOrLiteralFalse = 42, float $positiveFloatValue = 404.42, float $negativeFloatValue = -404.42, int $positiveIntegerValue = 42, @@ -136,6 +150,8 @@ class UnionValuesWithConstructor extends UnionValues $this->scalarWithString = $scalarWithString; $this->nullableWithString = $nullableWithString; $this->nullableWithNull = $nullableWithNull; + $this->intOrLiteralTrue = $intOrLiteralTrue; + $this->intOrLiteralFalse = $intOrLiteralFalse; $this->positiveFloatValue = $positiveFloatValue; $this->negativeFloatValue = $negativeFloatValue; $this->positiveIntegerValue = $positiveIntegerValue; diff --git a/tests/Unit/Definition/Repository/Reflection/ReflectionClassDefinitionRepositoryTest.php b/tests/Unit/Definition/Repository/Reflection/ReflectionClassDefinitionRepositoryTest.php index 8556ca4..d88c8c2 100644 --- a/tests/Unit/Definition/Repository/Reflection/ReflectionClassDefinitionRepositoryTest.php +++ b/tests/Unit/Definition/Repository/Reflection/ReflectionClassDefinitionRepositoryTest.php @@ -16,7 +16,7 @@ use CuyZ\Valinor\Tests\Fake\Definition\Repository\FakeAttributesRepository; use CuyZ\Valinor\Tests\Fake\Type\FakeType; use CuyZ\Valinor\Tests\Fake\Type\Parser\Factory\FakeTypeParserFactory; use CuyZ\Valinor\Type\StringType; -use CuyZ\Valinor\Type\Types\BooleanType; +use CuyZ\Valinor\Type\Types\NativeBooleanType; use CuyZ\Valinor\Type\Types\ClassType; use CuyZ\Valinor\Type\Types\MixedType; use CuyZ\Valinor\Type\Types\UnresolvableType; @@ -61,7 +61,7 @@ final class ReflectionClassDefinitionRepositoryTest extends TestCase self::assertTrue($properties->get('propertyWithDefaultValue')->hasDefaultValue()); self::assertSame('Default value for property', $properties->get('propertyWithDefaultValue')->defaultValue()); - self::assertInstanceOf(BooleanType::class, $properties->get('propertyWithDocBlockType')->type()); + self::assertInstanceOf(NativeBooleanType::class, $properties->get('propertyWithDocBlockType')->type()); self::assertInstanceOf(MixedType::class, $properties->get('propertyWithNoType')->type()); @@ -71,7 +71,7 @@ final class ReflectionClassDefinitionRepositoryTest extends TestCase self::assertInstanceOf(StringType::class, $properties->get('protectedProperty')->type()); self::assertFalse($properties->get('protectedProperty')->isPublic()); - self::assertInstanceOf(BooleanType::class, $properties->get('privateProperty')->type()); + self::assertInstanceOf(NativeBooleanType::class, $properties->get('privateProperty')->type()); self::assertFalse($properties->get('privateProperty')->isPublic()); } @@ -140,7 +140,7 @@ final class ReflectionClassDefinitionRepositoryTest extends TestCase self::assertSame($className . '::publicMethod($parameterWithDocBlockType)', $parameterWithDocBlockType->signature()); self::assertSame($className . '::publicMethod($optionalParameter)', $optionalParameter->signature()); - self::assertInstanceOf(BooleanType::class, $mandatoryParameter->type()); + self::assertInstanceOf(NativeBooleanType::class, $mandatoryParameter->type()); self::assertFalse($mandatoryParameter->isOptional()); self::assertInstanceOf(MixedType::class, $parameterWithNoType->type()); diff --git a/tests/Unit/Type/Resolver/Union/UnionScalarNarrowerTest.php b/tests/Unit/Type/Resolver/Union/UnionScalarNarrowerTest.php index 5561318..0223e5f 100644 --- a/tests/Unit/Type/Resolver/Union/UnionScalarNarrowerTest.php +++ b/tests/Unit/Type/Resolver/Union/UnionScalarNarrowerTest.php @@ -10,7 +10,7 @@ use CuyZ\Valinor\Type\Resolver\Exception\CannotResolveTypeFromUnion; use CuyZ\Valinor\Type\Resolver\Union\UnionScalarNarrower; use CuyZ\Valinor\Type\StringType; use CuyZ\Valinor\Type\Type; -use CuyZ\Valinor\Type\Types\BooleanType; +use CuyZ\Valinor\Type\Types\NativeBooleanType; use CuyZ\Valinor\Type\Types\NativeFloatType; use CuyZ\Valinor\Type\Types\NativeIntegerType; use CuyZ\Valinor\Type\Types\NativeStringType; @@ -49,7 +49,7 @@ final class UnionScalarNarrowerTest extends TestCase NativeIntegerType::get(), NativeFloatType::get(), NativeStringType::get(), - BooleanType::get(), + NativeBooleanType::get(), ); return [ @@ -76,7 +76,7 @@ final class UnionScalarNarrowerTest extends TestCase 'int|float|string|bool with boolean value' => [ 'Union type' => $scalarUnion, 'Source' => true, - 'Expected type' => BooleanType::class, + 'Expected type' => NativeBooleanType::class, ], 'int|object with object value' => [ 'Union type' => new UnionType(NativeIntegerType::get(), UndefinedObjectType::get()), @@ -97,7 +97,7 @@ final class UnionScalarNarrowerTest extends TestCase public function test_several_possible_types_throws_exception(): void { - $unionType = new UnionType(BooleanType::get(), NativeIntegerType::get(), NativeFloatType::get()); + $unionType = new UnionType(NativeBooleanType::get(), NativeIntegerType::get(), NativeFloatType::get()); $this->expectException(CannotResolveTypeFromUnion::class); $this->expectExceptionCode(1607027306); diff --git a/tests/Unit/Type/Types/BooleanValueTypeTest.php b/tests/Unit/Type/Types/BooleanValueTypeTest.php new file mode 100644 index 0000000..e3d4e91 --- /dev/null +++ b/tests/Unit/Type/Types/BooleanValueTypeTest.php @@ -0,0 +1,172 @@ +accepts(true)); + self::assertTrue(BooleanValueType::false()->accepts(false)); + } + + public function test_does_not_accept_incorrect_values(): void + { + self::assertFalse(BooleanValueType::true()->accepts('Schwifty!')); + self::assertFalse(BooleanValueType::true()->accepts(42.1337)); + self::assertFalse(BooleanValueType::true()->accepts(404)); + self::assertFalse(BooleanValueType::true()->accepts(['foo' => 'bar'])); + self::assertFalse(BooleanValueType::true()->accepts(false)); + self::assertFalse(BooleanValueType::true()->accepts(null)); + self::assertFalse(BooleanValueType::true()->accepts(new stdClass())); + + self::assertFalse(BooleanValueType::false()->accepts('Schwifty!')); + self::assertFalse(BooleanValueType::false()->accepts(42.1337)); + self::assertFalse(BooleanValueType::false()->accepts(404)); + self::assertFalse(BooleanValueType::false()->accepts(['foo' => 'bar'])); + self::assertFalse(BooleanValueType::false()->accepts(true)); + self::assertFalse(BooleanValueType::false()->accepts(null)); + self::assertFalse(BooleanValueType::false()->accepts(new stdClass())); + } + + public function test_can_cast_boolean_value(): void + { + self::assertTrue(BooleanValueType::true()->canCast(true)); + self::assertTrue(BooleanValueType::false()->canCast(false)); + } + + public function test_can_cast_string_integer_value(): void + { + self::assertTrue(BooleanValueType::true()->canCast('1')); + self::assertTrue(BooleanValueType::false()->canCast('0')); + } + + public function test_can_cast_integer_value(): void + { + self::assertTrue(BooleanValueType::true()->canCast(1)); + self::assertTrue(BooleanValueType::false()->canCast(0)); + } + + public function test_can_cast_string_value(): void + { + self::assertTrue(BooleanValueType::true()->canCast('true')); + self::assertTrue(BooleanValueType::false()->canCast('false')); + } + + public function test_cannot_cast_other_types(): void + { + self::assertFalse(BooleanValueType::true()->canCast(null)); + self::assertFalse(BooleanValueType::true()->canCast(false)); + self::assertFalse(BooleanValueType::true()->canCast(42.1337)); + self::assertFalse(BooleanValueType::true()->canCast(404)); + self::assertFalse(BooleanValueType::true()->canCast('Schwifty!')); + self::assertFalse(BooleanValueType::true()->canCast(['foo' => 'bar'])); + self::assertFalse(BooleanValueType::true()->canCast(new stdClass())); + + self::assertFalse(BooleanValueType::false()->canCast(null)); + self::assertFalse(BooleanValueType::false()->canCast(true)); + self::assertFalse(BooleanValueType::false()->canCast(42.1337)); + self::assertFalse(BooleanValueType::false()->canCast(404)); + self::assertFalse(BooleanValueType::false()->canCast('Schwifty!')); + self::assertFalse(BooleanValueType::false()->canCast(['foo' => 'bar'])); + self::assertFalse(BooleanValueType::false()->canCast(new stdClass())); + } + + public function test_cast_value_returns_correct_result(): void + { + self::assertSame(true, BooleanValueType::true()->cast(true)); + self::assertSame(true, BooleanValueType::true()->cast('1')); + self::assertSame(true, BooleanValueType::true()->cast(1)); + self::assertSame(true, BooleanValueType::true()->cast('true')); + + self::assertSame(false, BooleanValueType::false()->cast(false)); + self::assertSame(false, BooleanValueType::false()->cast('0')); + self::assertSame(false, BooleanValueType::false()->cast(0)); + self::assertSame(false, BooleanValueType::false()->cast('false')); + } + + public function test_cast_invalid_value_to_true_throws_exception(): void + { + $this->expectException(CannotCastValue::class); + $this->expectExceptionCode(1603216198); + $this->expectExceptionMessage('Cannot cast from `string` to `true`.'); + + BooleanValueType::true()->cast('foo'); + } + + public function test_cast_invalid_value_to_false_throws_exception(): void + { + $this->expectException(CannotCastValue::class); + $this->expectExceptionCode(1603216198); + $this->expectExceptionMessage('Cannot cast from `string` to `false`.'); + + BooleanValueType::false()->cast('foo'); + } + + public function test_matches_same_type(): void + { + self::assertTrue(BooleanValueType::true()->matches(BooleanValueType::true())); + self::assertTrue(BooleanValueType::false()->matches(BooleanValueType::false())); + } + + public function test_matches_native_boolean_type(): void + { + self::assertTrue(BooleanValueType::true()->matches(new NativeBooleanType())); + self::assertTrue(BooleanValueType::false()->matches(new NativeBooleanType())); + } + + public function test_matches_mixed_type(): void + { + self::assertTrue(BooleanValueType::true()->matches(new MixedType())); + self::assertTrue(BooleanValueType::false()->matches(new MixedType())); + } + + public function test_matches_union_type_containing_same_type(): void + { + $unionTypeWithTrue = new UnionType( + new FakeType(), + BooleanValueType::true(), + new FakeType(), + ); + + $unionTypeWithFalse = new UnionType( + new FakeType(), + BooleanValueType::false(), + new FakeType(), + ); + + self::assertTrue(BooleanValueType::true()->matches($unionTypeWithTrue)); + self::assertTrue(BooleanValueType::false()->matches($unionTypeWithFalse)); + } + + public function test_does_not_match_union_type_not_containing_same_type(): void + { + $unionType = new UnionType(new FakeType(), new FakeType()); + + self::assertFalse(BooleanValueType::true()->matches($unionType)); + self::assertFalse(BooleanValueType::false()->matches($unionType)); + } +} diff --git a/tests/Unit/Type/Types/BooleanTypeTest.php b/tests/Unit/Type/Types/NativeBooleanTypeTest.php similarity index 93% rename from tests/Unit/Type/Types/BooleanTypeTest.php rename to tests/Unit/Type/Types/NativeBooleanTypeTest.php index c523fa6..4ef570d 100644 --- a/tests/Unit/Type/Types/BooleanTypeTest.php +++ b/tests/Unit/Type/Types/NativeBooleanTypeTest.php @@ -6,24 +6,24 @@ namespace CuyZ\Valinor\Tests\Unit\Type\Types; use CuyZ\Valinor\Tests\Fake\Type\FakeType; use CuyZ\Valinor\Tests\Traits\TestIsSingleton; -use CuyZ\Valinor\Type\Types\BooleanType; +use CuyZ\Valinor\Type\Types\NativeBooleanType; use CuyZ\Valinor\Type\Types\Exception\CannotCastValue; use CuyZ\Valinor\Type\Types\MixedType; use CuyZ\Valinor\Type\Types\UnionType; use PHPUnit\Framework\TestCase; use stdClass; -final class BooleanTypeTest extends TestCase +final class NativeBooleanTypeTest extends TestCase { use TestIsSingleton; - private BooleanType $booleanType; + private NativeBooleanType $booleanType; protected function setUp(): void { parent::setUp(); - $this->booleanType = new BooleanType(); + $this->booleanType = new NativeBooleanType(); } public function test_accepts_correct_values(): void @@ -123,7 +123,7 @@ final class BooleanTypeTest extends TestCase public function test_matches_same_type(): void { - self::assertTrue((new BooleanType())->matches(new BooleanType())); + self::assertTrue((new NativeBooleanType())->matches(new NativeBooleanType())); } public function test_does_not_match_other_type(): void @@ -140,7 +140,7 @@ final class BooleanTypeTest extends TestCase { $unionType = new UnionType( new FakeType(), - new BooleanType(), + new NativeBooleanType(), new FakeType(), );