Skip to content

Commit

Permalink
feat: handle float value type
Browse files Browse the repository at this point in the history
Allows the usage of float values, as follows:

```php
class Foo
{
    /** @var 404.42|1337.42 */
    public readonly float $value;
}
```
  • Loading branch information
romm committed May 9, 2022
1 parent 6e6b974 commit 790df8a
Show file tree
Hide file tree
Showing 20 changed files with 407 additions and 36 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,9 @@ final class SomeClass
/** @var array<SomeInterface|AnotherInterface> */
private array $unionInsideArray,

/** @var 404.42|1337.42 */
private string $unionOfFloatValues,

/** @var 42|1337 */
private string $unionOfIntegerValues,

Expand Down
6 changes: 4 additions & 2 deletions src/Definition/Repository/Cache/Compiler/TypeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
use CuyZ\Valinor\Type\Types\ClassType;
use CuyZ\Valinor\Type\Types\ArrayType;
use CuyZ\Valinor\Type\Types\EnumType;
use CuyZ\Valinor\Type\Types\FloatType;
use CuyZ\Valinor\Type\Types\NativeFloatType;
use CuyZ\Valinor\Type\Types\FloatValueType;
use CuyZ\Valinor\Type\Types\IntegerRangeType;
use CuyZ\Valinor\Type\Types\IntegerValueType;
use CuyZ\Valinor\Type\Types\InterfaceType;
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 FloatType:
case $type instanceof NativeFloatType:
case $type instanceof NativeIntegerType:
case $type instanceof PositiveIntegerType:
case $type instanceof NegativeIntegerType:
Expand All @@ -63,6 +64,7 @@ public function compile(Type $type): string
return "new $class({$type->min()}, {$type->max()})";
case $type instanceof StringValueType:
case $type instanceof IntegerValueType:
case $type instanceof FloatValueType:
$value = var_export($type->value(), true);

return "new $class($value)";
Expand Down
11 changes: 11 additions & 0 deletions src/Type/FloatType.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 FloatType extends ScalarType
{
public function cast($value): float;
}
6 changes: 6 additions & 0 deletions src/Type/Parser/Lexer/NativeLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use CuyZ\Valinor\Type\Parser\Lexer\Token\ColonToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\CommaToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\EnumNameToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\FloatValueToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\IntegerToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\IntegerValueToken;
use CuyZ\Valinor\Type\Parser\Lexer\Token\IntersectionToken;
Expand All @@ -33,6 +34,7 @@
use function class_exists;
use function filter_var;
use function interface_exists;
use function is_numeric;
use function strtolower;
use function substr;

Expand Down Expand Up @@ -97,6 +99,10 @@ public function tokenize(string $symbol): Token
return new IntegerValueToken((int)$symbol);
}

if (is_numeric($symbol)) {
return new FloatValueToken((float)$symbol);
}

/** @infection-ignore-all */
if (PHP_VERSION_ID >= 8_01_00 && enum_exists($symbol)) {
/** @var class-string<UnitEnum> $symbol */
Expand Down
25 changes: 25 additions & 0 deletions src/Type/Parser/Lexer/Token/FloatValueToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Parser\Lexer\Token;

use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\FloatValueType;

/** @internal */
final class FloatValueToken implements TraversingToken
{
private float $value;

public function __construct(float $value)
{
$this->value = $value;
}

public function traverse(TokenStream $stream): Type
{
return new FloatValueType($this->value);
}
}
4 changes: 2 additions & 2 deletions src/Type/Parser/Lexer/Token/NativeToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\ArrayKeyType;
use CuyZ\Valinor\Type\Types\BooleanType;
use CuyZ\Valinor\Type\Types\FloatType;
use CuyZ\Valinor\Type\Types\NativeFloatType;
use CuyZ\Valinor\Type\Types\MixedType;
use CuyZ\Valinor\Type\Types\NativeStringType;
use CuyZ\Valinor\Type\Types\NegativeIntegerType;
Expand Down Expand Up @@ -60,7 +60,7 @@ private static function type(string $symbol): ?Type
case 'mixed':
return MixedType::get();
case 'float':
return FloatType::get();
return NativeFloatType::get();
case 'positive-int':
return PositiveIntegerType::get();
case 'negative-int':
Expand Down
19 changes: 19 additions & 0 deletions src/Type/Types/Exception/InvalidFloatValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Types\Exception;

use RuntimeException;

/** @api */
final class InvalidFloatValue extends RuntimeException implements CastError
{
public function __construct(float $value, float $expected)
{
parent::__construct(
"Value `$value` does not match expected value `$expected`.",
1652110115
);
}
}
25 changes: 25 additions & 0 deletions src/Type/Types/Exception/InvalidFloatValueType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Types\Exception;

use CuyZ\Valinor\Utility\Polyfill;
use RuntimeException;

/** @api */
final class InvalidFloatValueType extends RuntimeException implements CastError
{
/**
* @param mixed $value
*/
public function __construct($value, float $floatValue)
{
$baseType = Polyfill::get_debug_type($value);

parent::__construct(
"Value of type `$baseType` does not match float value `$floatValue`.",
1652110003
);
}
}
70 changes: 70 additions & 0 deletions src/Type/Types/FloatValueType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Types;

use CuyZ\Valinor\Type\FixedType;
use CuyZ\Valinor\Type\FloatType;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\Exception\InvalidFloatValue;
use CuyZ\Valinor\Type\Types\Exception\InvalidFloatValueType;

/** @api */
final class FloatValueType implements FloatType, FixedType
{
private float $value;

public function __construct(float $value)
{
$this->value = $value;
}

public function accepts($value): bool
{
return $value === $this->value;
}

public function matches(Type $other): bool
{
if ($other instanceof UnionType) {
return $other->isMatchedBy($this);
}

if ($other instanceof self) {
return $this->value === $other->value;
}

return $other instanceof NativeFloatType || $other instanceof MixedType;
}

public function canCast($value): bool
{
return is_numeric($value);
}

public function cast($value): float
{
if (! $this->canCast($value)) {
throw new InvalidFloatValueType($value, $this->value);
}

$value = (float)$value; // @phpstan-ignore-line

if (! $this->accepts($value)) {
throw new InvalidFloatValue($value, $this->value);
}

return $value;
}

public function value()
{
return $this->value;
}

public function __toString(): string
{
return (string)$this->value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace CuyZ\Valinor\Type\Types;

use CuyZ\Valinor\Type\ScalarType;
use CuyZ\Valinor\Type\FloatType;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\Exception\CannotCastValue;
use CuyZ\Valinor\Utility\IsSingleton;
Expand All @@ -13,7 +13,7 @@
use function is_numeric;

/** @api */
final class FloatType implements ScalarType
final class NativeFloatType implements FloatType
{
use IsSingleton;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
use CuyZ\Valinor\Type\Types\ClassStringType;
use CuyZ\Valinor\Type\Types\ClassType;
use CuyZ\Valinor\Type\Types\ArrayType;
use CuyZ\Valinor\Type\Types\FloatType;
use CuyZ\Valinor\Type\Types\NativeFloatType;
use CuyZ\Valinor\Type\Types\FloatValueType;
use CuyZ\Valinor\Type\Types\IntegerRangeType;
use CuyZ\Valinor\Type\Types\IntegerValueType;
use CuyZ\Valinor\Type\Types\InterfaceType;
Expand Down Expand Up @@ -73,10 +74,14 @@ public function type_is_compiled_correctly_data_provider(): iterable
{
yield [NullType::get()];
yield [BooleanType::get()];
yield [FloatType::get()];
yield [NativeFloatType::get()];
yield [new FloatValueType(1337.42)];
yield [new FloatValueType(-1337.42)];
yield [NativeIntegerType::get()];
yield [PositiveIntegerType::get()];
yield [NegativeIntegerType::get()];
yield [new IntegerValueType(1337)];
yield [new IntegerValueType(-1337)];
yield [new IntegerRangeType(42, 1337)];
yield [new IntegerRangeType(-1337, -42)];
yield [new IntegerRangeType(PHP_INT_MIN, PHP_INT_MAX)];
Expand All @@ -87,28 +92,28 @@ public function type_is_compiled_correctly_data_provider(): iterable
yield [new InterfaceType(DateTimeInterface::class, ['Template' => NativeStringType::get()])];
yield [new ClassType(stdClass::class, ['Template' => NativeStringType::get()])];
yield [new IntersectionType(new InterfaceType(DateTimeInterface::class), new ClassType(DateTime::class))];
yield [new UnionType(NativeStringType::get(), NativeIntegerType::get(), FloatType::get())];
yield [new UnionType(NativeStringType::get(), NativeIntegerType::get(), NativeFloatType::get())];
yield [ArrayType::native()];
yield [new ArrayType(ArrayKeyType::default(), FloatType::get())];
yield [new ArrayType(ArrayKeyType::default(), NativeFloatType::get())];
yield [new ArrayType(ArrayKeyType::integer(), NativeIntegerType::get())];
yield [new ArrayType(ArrayKeyType::string(), NativeStringType::get())];
yield [NonEmptyArrayType::native()];
yield [new NonEmptyArrayType(ArrayKeyType::default(), FloatType::get())];
yield [new NonEmptyArrayType(ArrayKeyType::default(), NativeFloatType::get())];
yield [new NonEmptyArrayType(ArrayKeyType::integer(), NativeIntegerType::get())];
yield [new NonEmptyArrayType(ArrayKeyType::string(), NativeStringType::get())];
yield [ListType::native()];
yield [new ListType(FloatType::get())];
yield [new ListType(NativeFloatType::get())];
yield [new ListType(NativeIntegerType::get())];
yield [new ListType(NativeStringType::get())];
yield [NonEmptyListType::native()];
yield [new NonEmptyListType(FloatType::get())];
yield [new NonEmptyListType(NativeFloatType::get())];
yield [new NonEmptyListType(NativeIntegerType::get())];
yield [new NonEmptyListType(NativeStringType::get())];
yield [new ShapedArrayType(
new ShapedArrayElement(new StringValueType('foo'), NativeStringType::get()),
new ShapedArrayElement(new IntegerValueType(1337), NativeIntegerType::get(), true)
)];
yield [new IterableType(ArrayKeyType::default(), FloatType::get())];
yield [new IterableType(ArrayKeyType::default(), NativeFloatType::get())];
yield [new IterableType(ArrayKeyType::integer(), NativeIntegerType::get())];
yield [new IterableType(ArrayKeyType::string(), NativeStringType::get())];
yield [new ClassStringType()];
Expand Down
31 changes: 26 additions & 5 deletions tests/Functional/Type/Parser/Lexer/NativeLexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
use CuyZ\Valinor\Type\Types\BooleanType;
use CuyZ\Valinor\Type\Types\ClassStringType;
use CuyZ\Valinor\Type\Types\ClassType;
use CuyZ\Valinor\Type\Types\FloatType;
use CuyZ\Valinor\Type\Types\NativeFloatType;
use CuyZ\Valinor\Type\Types\FloatValueType;
use CuyZ\Valinor\Type\Types\IntegerRangeType;
use CuyZ\Valinor\Type\Types\IntegerValueType;
use CuyZ\Valinor\Type\Types\InterfaceType;
Expand Down Expand Up @@ -123,17 +124,37 @@ public function parse_valid_types_returns_valid_result_data_provider(): array
'Float type' => [
'raw' => 'float',
'transformed' => 'float',
'type' => FloatType::class,
'type' => NativeFloatType::class,
],
'Float type - uppercase' => [
'raw' => 'FLOAT',
'transformed' => 'float',
'type' => FloatType::class,
'type' => NativeFloatType::class,
],
'Float type followed by description' => [
'raw' => 'float lorem ipsum',
'transformed' => 'float',
'type' => FloatType::class,
'type' => NativeFloatType::class,
],
'Positive float value' => [
'raw' => '1337.42',
'transformed' => '1337.42',
'type' => FloatValueType::class,
],
'Positive float value followed by description' => [
'raw' => '1337.42 lorem ipsum',
'transformed' => '1337.42',
'type' => FloatValueType::class,
],
'Negative float value' => [
'raw' => '-1337.42',
'transformed' => '-1337.42',
'type' => FloatValueType::class,
],
'Negative float value followed by description' => [
'raw' => '-1337.42 lorem ipsum',
'transformed' => '-1337.42',
'type' => FloatValueType::class,
],
'Integer type' => [
'raw' => 'int',
Expand Down Expand Up @@ -659,7 +680,7 @@ public function test_multiple_union_types_are_parsed(): void
$types = $unionType->types();

self::assertInstanceOf(IntegerType::class, $types[0]);
self::assertInstanceOf(FloatType::class, $types[1]);
self::assertInstanceOf(NativeFloatType::class, $types[1]);
self::assertInstanceOf(StringType::class, $types[2]);
}

Expand Down
Loading

0 comments on commit 790df8a

Please sign in to comment.