Skip to content

Commit

Permalink
feat(type): add uint type (#393)
Browse files Browse the repository at this point in the history
  • Loading branch information
azjezz committed Jan 15, 2023
1 parent fedb2e3 commit 3ae09a0
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/component/type.md
Expand Up @@ -38,11 +38,12 @@
- [num](./../../src/Psl/Type/num.php#L10)
- [object](./../../src/Psl/Type/object.php#L10)
- [optional](./../../src/Psl/Type/optional.php#L14)
- [positive_int](./../../src/Psl/Type/positive_int.php#L10)
- [positive_int](./../../src/Psl/Type/positive_int.php#L12) ( deprecated )
- [resource](./../../src/Psl/Type/resource.php#L12)
- [scalar](./../../src/Psl/Type/scalar.php#L10)
- [shape](./../../src/Psl/Type/shape.php#L15)
- [string](./../../src/Psl/Type/string.php#L10)
- [uint](./../../src/Psl/Type/uint.php#L12)
- [union](./../../src/Psl/Type/union.php#L16)
- [unit_enum](./../../src/Psl/Type/unit_enum.php#L16)
- [vec](./../../src/Psl/Type/vec.php#L14)
Expand Down
2 changes: 2 additions & 0 deletions src/Psl/Internal/Loader.php
Expand Up @@ -330,6 +330,7 @@ final class Loader
'Psl\\Type\\non_empty_vec' => 'Psl/Type/non_empty_vec.php',
'Psl\\Type\\scalar' => 'Psl/Type/scalar.php',
'Psl\\Type\\shape' => 'Psl/Type/shape.php',
'Psl\\Type\\uint' => 'Psl/Type/uint.php',
'Psl\\Type\\union' => 'Psl/Type/union.php',
'Psl\\Type\\vec' => 'Psl/Type/vec.php',
'Psl\\Type\\dict' => 'Psl/Type/dict.php',
Expand Down Expand Up @@ -636,6 +637,7 @@ final class Loader
'Psl\\Type\\Internal\\NonEmptyDictType' => 'Psl/Type/Internal/NonEmptyDictType.php',
'Psl\\Type\\Internal\\NonEmptyStringType' => 'Psl/Type/Internal/NonEmptyStringType.php',
'Psl\\Type\\Internal\\NonEmptyVecType' => 'Psl/Type/Internal/NonEmptyVecType.php',
'Psl\\Type\\Internal\\UIntType' => 'Psl/Type/Internal/UIntType.php',
'Psl\\Type\\Internal\\UnionType' => 'Psl/Type/Internal/UnionType.php',
'Psl\\Type\\Internal\\VecType' => 'Psl/Type/Internal/VecType.php',
'Psl\\Type\\Internal\\DictType' => 'Psl/Type/Internal/DictType.php',
Expand Down
94 changes: 94 additions & 0 deletions src/Psl/Type/Internal/UIntType.php
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace Psl\Type\Internal;

use Psl\Type;
use Psl\Type\Exception\AssertException;
use Psl\Type\Exception\CoercionException;
use Stringable;

use function is_float;
use function is_int;
use function is_string;
use function ltrim;

/**
* @ara-extends Type\Type<uint>
*
* @extends Type\Type<int<0, max>>
*
* @internal
*/
final class UIntType extends Type\Type
{
/**
* @ara-assert-if-true unit $value
*
* @psalm-assert-if-true int<0, max> $value
*/
public function matches(mixed $value): bool
{
return is_int($value) && $value >= 0;
}

/**
* @throws CoercionException
*
* @ara-return uint
*
* @return int<0, max>
*/
public function coerce(mixed $value): int
{
if (is_int($value) && $value >= 0) {
return $value;
} elseif (is_float($value)) {
$integer_value = (int) $value;
if (((float) $integer_value) === $value && $integer_value >= 0) {
return $integer_value;
}
} elseif (is_string($value) || $value instanceof Stringable) {
$str = (string)$value;
$int = (int)$str;
if ($str === (string) $int && $int >= 0) {
return $int;
}

$trimmed = ltrim($str, '0');
$int = (int) $trimmed;
if ($trimmed === (string) $int && $int >= 0) {
return $int;
}

// Exceptional case "000" -(trim)-> "", but we want to return 0
if ('' === $trimmed && '' !== $str) {
return 0;
}
}

throw CoercionException::withValue($value, $this->toString(), $this->getTrace());
}

/**
* @ara-assert uint $value
*
* @psalm-assert int<0, max> $value
*
* @throws AssertException
*/
public function assert(mixed $value): int
{
if (is_int($value) && $value >= 0) {
return $value;
}

throw AssertException::withValue($value, $this->toString(), $this->getTrace());
}

public function toString(): string
{
return 'uint';
}
}
2 changes: 2 additions & 0 deletions src/Psl/Type/positive_int.php
Expand Up @@ -6,6 +6,8 @@

/**
* @return TypeInterface<positive-int>
*
* @deprecated use {@see uint} instead.
*/
function positive_int(): TypeInterface
{
Expand Down
15 changes: 15 additions & 0 deletions src/Psl/Type/uint.php
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Psl\Type;

/**
* @ara-return TypeInterface<uint>
*
* @return TypeInterface<int<0, max>>
*/
function uint(): TypeInterface
{
return new Internal\UIntType();
}
63 changes: 63 additions & 0 deletions tests/unit/Type/UIntTypeTest.php
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Psl\Tests\Unit\Type;

use Psl\Math;
use Psl\Type;

final class UIntTypeTest extends TypeTest
{
public function getType(): Type\TypeInterface
{
return Type\uint();
}

public function getValidCoercions(): iterable
{
yield [123, 123];
yield [0, 0];
yield ['0', 0];
yield ['123', 123];
yield [$this->stringable('123'), 123];
yield [$this->stringable((string) Math\INT16_MAX), Math\INT16_MAX];
yield [$this->stringable((string) Math\INT64_MAX), Math\INT64_MAX];
yield [(string) Math\INT64_MAX, Math\INT64_MAX];
yield [Math\INT64_MAX, Math\INT64_MAX];
yield ['7', 7];
yield ['07', 7];
yield ['007', 7];
yield ['000', 0];
yield [1.0, 1];
}

public function getInvalidCoercions(): iterable
{
yield [1.23];
yield ['1.23'];
yield ['1e123'];
yield [''];
yield [[]];
yield [[123]];
yield [null];
yield [false];
yield [$this->stringable('1.23')];
yield [$this->stringable('-007')];
yield ['-007'];
yield ['9223372036854775808'];
yield [$this->stringable('9223372036854775808')];
yield ['-9223372036854775809'];
yield [$this->stringable('-9223372036854775809')];
yield ['0xFF'];
yield [''];
yield [$this->stringable('-321')];
yield ['-321'];
yield [-321];
}

public function getToStringExamples(): iterable
{
yield [$this->getType(), 'uint'];
}
}

0 comments on commit 3ae09a0

Please sign in to comment.