Skip to content

Commit

Permalink
feat: handle non-negative integer type
Browse files Browse the repository at this point in the history
Non-negative integer can be used as below. It will accept any value
equal to or greater than zero.

```php
final class SomeClass
{
    /** @var non-negative-int */
    public int $nonNegativeInteger;
}
```
  • Loading branch information
romm committed Oct 19, 2023
1 parent 5cf8ae5 commit d1f387f
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 17 deletions.
3 changes: 3 additions & 0 deletions docs/pages/usage/type-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ final class SomeClass
/** @var negative-int */
private int $negativeInteger,

/** @var non-negative-int */
private int $nonNegativeInteger,

/** @var int<-42, 1337> */
private int $integerRange,

Expand Down
2 changes: 2 additions & 0 deletions src/Definition/Repository/Cache/Compiler/TypeCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use CuyZ\Valinor\Type\Types\NonEmptyArrayType;
use CuyZ\Valinor\Type\Types\NonEmptyListType;
use CuyZ\Valinor\Type\Types\NonEmptyStringType;
use CuyZ\Valinor\Type\Types\NonNegativeIntegerType;
use CuyZ\Valinor\Type\Types\NullType;
use CuyZ\Valinor\Type\Types\NumericStringType;
use CuyZ\Valinor\Type\Types\PositiveIntegerType;
Expand Down Expand Up @@ -58,6 +59,7 @@ public function compile(Type $type): string
case $type instanceof NativeIntegerType:
case $type instanceof PositiveIntegerType:
case $type instanceof NegativeIntegerType:
case $type instanceof NonNegativeIntegerType:
case $type instanceof NativeStringType:
case $type instanceof NonEmptyStringType:
case $type instanceof NumericStringType:
Expand Down
2 changes: 2 additions & 0 deletions src/Type/Parser/Lexer/Token/NativeToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use CuyZ\Valinor\Type\Types\NativeStringType;
use CuyZ\Valinor\Type\Types\NegativeIntegerType;
use CuyZ\Valinor\Type\Types\NonEmptyStringType;
use CuyZ\Valinor\Type\Types\NonNegativeIntegerType;
use CuyZ\Valinor\Type\Types\NullType;
use CuyZ\Valinor\Type\Types\NumericStringType;
use CuyZ\Valinor\Type\Types\PositiveIntegerType;
Expand Down Expand Up @@ -67,6 +68,7 @@ private static function type(string $symbol): ?Type
'float' => NativeFloatType::get(),
'positive-int' => PositiveIntegerType::get(),
'negative-int' => NegativeIntegerType::get(),
'non-negative-int' => NonNegativeIntegerType::get(),
'string' => NativeStringType::get(),
'non-empty-string' => NonEmptyStringType::get(),
'numeric-string' => NumericStringType::get(),
Expand Down
57 changes: 57 additions & 0 deletions src/Type/Types/NonNegativeIntegerType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Types;

use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
use CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder;
use CuyZ\Valinor\Type\IntegerType;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Utility\IsSingleton;

/** @internal */
final class NonNegativeIntegerType implements IntegerType
{
use IsSingleton;

public function accepts(mixed $value): bool
{
return is_int($value) && $value >= 0;
}

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

return $other instanceof self
|| $other instanceof NativeIntegerType
|| $other instanceof MixedType;
}

public function canCast(mixed $value): bool
{
return ! is_bool($value)
&& filter_var($value, FILTER_VALIDATE_INT) !== false
&& $value >= 0;
}

public function cast(mixed $value): int
{
assert($this->canCast($value));

return (int)$value; // @phpstan-ignore-line
}

public function errorMessage(): ErrorMessage
{
return MessageBuilder::newError('Value {source_value} is not a valid non-negative integer.')->build();
}

public function toString(): string
{
return 'non-negative-int';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use CuyZ\Valinor\Type\Types\NonEmptyArrayType;
use CuyZ\Valinor\Type\Types\NonEmptyListType;
use CuyZ\Valinor\Type\Types\NonEmptyStringType;
use CuyZ\Valinor\Type\Types\NonNegativeIntegerType;
use CuyZ\Valinor\Type\Types\NullType;
use CuyZ\Valinor\Type\Types\NumericStringType;
use CuyZ\Valinor\Type\Types\PositiveIntegerType;
Expand Down Expand Up @@ -84,6 +85,7 @@ public function type_is_compiled_correctly_data_provider(): iterable
yield [NativeIntegerType::get()];
yield [PositiveIntegerType::get()];
yield [NegativeIntegerType::get()];
yield [NonNegativeIntegerType::get()];
yield [new IntegerValueType(1337)];
yield [new IntegerValueType(-1337)];
yield [new IntegerRangeType(42, 1337)];
Expand Down
44 changes: 33 additions & 11 deletions tests/Functional/Type/Parser/Lexer/NativeLexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,15 @@
use CuyZ\Valinor\Type\Types\NativeBooleanType;
use CuyZ\Valinor\Type\Types\EnumType;
use CuyZ\Valinor\Type\Types\NativeFloatType;
use CuyZ\Valinor\Type\Types\NativeIntegerType;
use CuyZ\Valinor\Type\Types\NegativeIntegerType;
use CuyZ\Valinor\Type\Types\NonEmptyArrayType;
use CuyZ\Valinor\Type\Types\NonEmptyListType;
use CuyZ\Valinor\Type\Types\NonEmptyStringType;
use CuyZ\Valinor\Type\Types\NonNegativeIntegerType;
use CuyZ\Valinor\Type\Types\NullType;
use CuyZ\Valinor\Type\Types\NumericStringType;
use CuyZ\Valinor\Type\Types\PositiveIntegerType;
use CuyZ\Valinor\Type\Types\ShapedArrayType;
use CuyZ\Valinor\Type\Types\StringValueType;
use CuyZ\Valinor\Type\Types\UndefinedObjectType;
Expand Down Expand Up @@ -208,67 +212,85 @@ public function parse_valid_types_returns_valid_result_data_provider(): iterable
yield 'Integer type' => [
'raw' => 'int',
'transformed' => 'int',
'type' => IntegerType::class,
'type' => NativeIntegerType::class,
];

yield 'Integer type - uppercase' => [
'raw' => 'INT',
'transformed' => 'int',
'type' => IntegerType::class,
'type' => NativeIntegerType::class,
];

yield 'Integer type followed by description' => [
'raw' => 'int lorem ipsum',
'transformed' => 'int',
'type' => IntegerType::class,
'type' => NativeIntegerType::class,
];

yield 'Integer type (longer version)' => [
'raw' => 'integer',
'transformed' => 'int',
'type' => IntegerType::class,
'type' => NativeIntegerType::class,
];

yield 'Integer type (longer version) - uppercase' => [
'raw' => 'INTEGER',
'transformed' => 'int',
'type' => IntegerType::class,
'type' => NativeIntegerType::class,
];

yield 'Positive integer type' => [
'raw' => 'positive-int',
'transformed' => 'positive-int',
'type' => IntegerType::class,
'type' => PositiveIntegerType::class,
];

yield 'Positive integer type - uppercase' => [
'raw' => 'POSITIVE-INT',
'transformed' => 'positive-int',
'type' => IntegerType::class,
'type' => PositiveIntegerType::class,
];

yield 'Positive integer type followed by description' => [
'raw' => 'positive-int lorem ipsum',
'transformed' => 'positive-int',
'type' => IntegerType::class,
'type' => PositiveIntegerType::class,
];

yield 'Negative integer type' => [
'raw' => 'negative-int',
'transformed' => 'negative-int',
'type' => IntegerType::class,
'type' => NegativeIntegerType::class,
];

yield 'Negative integer type - uppercase' => [
'raw' => 'NEGATIVE-INT',
'transformed' => 'negative-int',
'type' => IntegerType::class,
'type' => NegativeIntegerType::class,
];

yield 'Negative integer type followed by description' => [
'raw' => 'negative-int lorem ipsum',
'transformed' => 'negative-int',
'type' => IntegerType::class,
'type' => NegativeIntegerType::class,
];

yield 'Non-negative integer type' => [
'raw' => 'non-negative-int',
'transformed' => 'non-negative-int',
'type' => NonNegativeIntegerType::class,
];

yield 'Non-negative integer type - uppercase' => [
'raw' => 'NON-NEGATIVE-INT',
'transformed' => 'non-negative-int',
'type' => NonNegativeIntegerType::class,
];

yield 'Non-negative integer type followed by description' => [
'raw' => 'non-negative-int lorem ipsum',
'transformed' => 'non-negative-int',
'type' => NonNegativeIntegerType::class,
];

yield 'Positive integer value' => [
Expand Down
8 changes: 8 additions & 0 deletions tests/Integration/Mapping/Object/ScalarValuesMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function test_values_are_mapped_properly(): void
'integer' => 1337,
'positiveInteger' => 1337,
'negativeInteger' => -1337,
'nonNegativeInteger' => 1337,
'integerRangeWithPositiveValue' => 1337,
'integerRangeWithNegativeValue' => -1337,
'integerRangeWithMinAndMax' => 42,
Expand Down Expand Up @@ -61,6 +62,7 @@ public function test_values_are_mapped_properly(): void
self::assertSame(1337, $result->integer);
self::assertSame(1337, $result->positiveInteger);
self::assertSame(-1337, $result->negativeInteger);
self::assertSame(1337, $result->nonNegativeInteger);
self::assertSame(1337, $result->integerRangeWithPositiveValue);
self::assertSame(-1337, $result->integerRangeWithNegativeValue);
self::assertSame(42, $result->integerRangeWithMinAndMax);
Expand Down Expand Up @@ -115,6 +117,9 @@ class ScalarValues
/** @var negative-int */
public int $negativeInteger = -1;

/** @var non-negative-int */
public int $nonNegativeInteger = 1;

/** @var int<-1337, 1337> */
public int $integerRangeWithPositiveValue = -1;

Expand Down Expand Up @@ -173,6 +178,7 @@ class ScalarValuesWithConstructor extends ScalarValues
* @param -42.404 $negativeFloatValue
* @param positive-int $positiveInteger
* @param negative-int $negativeInteger
* @param non-negative-int $nonNegativeInteger
* @param int<-1337, 1337> $integerRangeWithPositiveValue
* @param int<-1337, 1337> $integerRangeWithNegativeValue
* @param int<min, max> $integerRangeWithMinAndMax
Expand All @@ -199,6 +205,7 @@ public function __construct(
int $integer,
int $positiveInteger,
int $negativeInteger,
int $nonNegativeInteger,
int $integerRangeWithPositiveValue,
int $integerRangeWithNegativeValue,
int $integerRangeWithMinAndMax,
Expand All @@ -225,6 +232,7 @@ public function __construct(
$this->integer = $integer;
$this->positiveInteger = $positiveInteger;
$this->negativeInteger = $negativeInteger;
$this->nonNegativeInteger = $nonNegativeInteger;
$this->integerRangeWithPositiveValue = $integerRangeWithPositiveValue;
$this->integerRangeWithNegativeValue = $integerRangeWithNegativeValue;
$this->integerRangeWithMinAndMax = $integerRangeWithMinAndMax;
Expand Down
6 changes: 0 additions & 6 deletions tests/Integration/Mapping/Object/UnionValuesMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,6 @@ class NativeUnionValuesWithConstructor extends NativeUnionValues
{
/**
* PHP8.2 native `true` and `false`
* @param int|true $intOrLiteralTrue
* @param int|false $intOrLiteralFalse
* @param ObjectWithConstants::CONST_WITH_STRING_VALUE_A|ObjectWithConstants::CONST_WITH_INTEGER_VALUE_A $constantWithStringValue
* @param ObjectWithConstants::CONST_WITH_STRING_VALUE_A|ObjectWithConstants::CONST_WITH_INTEGER_VALUE_A $constantWithIntegerValue
*/
Expand Down Expand Up @@ -275,10 +273,6 @@ class UnionOfFixedValues
class UnionOfFixedValuesWithConstructor extends UnionOfFixedValues
{
/**
* @param 404.42|1337.42 $positiveFloatValue
* @param -404.42|-1337.42 $negativeFloatValue
* @param 42|1337 $positiveIntegerValue
* @param -42|-1337 $negativeIntegerValue
* @param 'foo'|'bar' $stringValueWithSingleQuote
* @param "baz"|"fiz" $stringValueWithDoubleQuote
*/
Expand Down
Loading

0 comments on commit d1f387f

Please sign in to comment.