Skip to content

Commit

Permalink
feat: handle literal boolean true / false types
Browse files Browse the repository at this point in the history
Allows the usage of boolean values, as follows:

```php
class Foo
{
    /** @var int|false */
    public readonly int|bool $value;
}
```
  • Loading branch information
danog authored May 9, 2022
1 parent 790df8a commit afcedf9
Show file tree
Hide file tree
Showing 16 changed files with 378 additions and 39 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -782,11 +782,17 @@ final class SomeClass
/** @var array<SomeInterface|AnotherInterface> */
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,
Expand Down
13 changes: 9 additions & 4 deletions src/Definition/Repository/Cache/Compiler/TypeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
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;
Expand All @@ -21,6 +20,8 @@
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;
Expand Down Expand Up @@ -50,7 +51,7 @@ public function compile(Type $type): string

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:
Expand All @@ -60,6 +61,10 @@ public function compile(Type $type): string
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:
Expand Down
11 changes: 11 additions & 0 deletions src/Type/BooleanType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Type;

/** @api */
interface BooleanType extends ScalarType
{
public function cast($value): bool;
}
11 changes: 8 additions & 3 deletions src/Type/Parser/Lexer/Token/NativeToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\ArrayKeyType;
use CuyZ\Valinor\Type\Types\BooleanType;
use CuyZ\Valinor\Type\Types\NativeFloatType;
use CuyZ\Valinor\Type\Types\BooleanValueType;
use CuyZ\Valinor\Type\Types\MixedType;
use CuyZ\Valinor\Type\Types\NativeBooleanType;
use CuyZ\Valinor\Type\Types\NativeFloatType;
use CuyZ\Valinor\Type\Types\NativeStringType;
use CuyZ\Valinor\Type\Types\NegativeIntegerType;
use CuyZ\Valinor\Type\Types\NonEmptyStringType;
Expand Down Expand Up @@ -57,6 +58,10 @@ private static function type(string $symbol): ?Type
switch (strtolower($symbol)) {
case 'null':
return NullType::get();
case 'true':
return BooleanValueType::true();
case 'false':
return BooleanValueType::false();
case 'mixed':
return MixedType::get();
case 'float':
Expand All @@ -71,7 +76,7 @@ private static function type(string $symbol): ?Type
return NonEmptyStringType::get();
case 'bool':
case 'boolean':
return BooleanType::get();
return NativeBooleanType::get();
case 'array-key':
return ArrayKeyType::default();
case 'object':
Expand Down
86 changes: 86 additions & 0 deletions src/Type/Types/BooleanValueType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Types;

use CuyZ\Valinor\Type\BooleanType;
use CuyZ\Valinor\Type\FixedType;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\Exception\CannotCastValue;

/** @api */
final class BooleanValueType implements BooleanType, FixedType
{
private static self $true;

private static self $false;

private bool $value;

/**
* @codeCoverageIgnore
*/
private function __construct(bool $value)
{
$this->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';
}
}
2 changes: 1 addition & 1 deletion src/Type/Types/FloatValueType.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function cast($value): float
return $value;
}

public function value()
public function value(): float
{
return $this->value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use function is_bool;

/** @api */
final class BooleanType implements ScalarType
final class NativeBooleanType implements ScalarType
{
use IsSingleton;

Expand Down
4 changes: 2 additions & 2 deletions tests/Fake/Type/FakeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
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;
Expand Down Expand Up @@ -40,7 +40,7 @@ public static function from(string $raw): Type
}

if ($raw === 'bool') {
return BooleanType::get();
return NativeBooleanType::get();
}

if ($raw === 'array-key') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
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;
Expand All @@ -20,6 +19,8 @@
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;
Expand Down Expand Up @@ -73,7 +74,9 @@ public function test_type_is_compiled_correctly(Type $type): void
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)];
Expand Down
35 changes: 28 additions & 7 deletions tests/Functional/Type/Parser/Lexer/NativeLexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@
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;
Expand All @@ -49,6 +48,8 @@
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;
Expand Down Expand Up @@ -106,6 +107,26 @@ public function parse_valid_types_returns_valid_result_data_provider(): array
'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',
Expand Down Expand Up @@ -304,27 +325,27 @@ public function parse_valid_types_returns_valid_result_data_provider(): array
'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',
Expand Down
16 changes: 15 additions & 1 deletion tests/Integration/Mapping/Fixture/NativeUnionValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,37 @@ 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;
$this->scalarWithInteger = $scalarWithInteger;
$this->scalarWithString = $scalarWithString;
$this->nullableWithString = $nullableWithString;
$this->nullableWithNull = $nullableWithNull;
$this->intOrLiteralTrue = $intOrLiteralTrue;
$this->intOrLiteralFalse = $intOrLiteralFalse;
}
}
Loading

0 comments on commit afcedf9

Please sign in to comment.