Skip to content

Commit

Permalink
Merge branch 'master' into feat/normalizer
Browse files Browse the repository at this point in the history
  • Loading branch information
romm authored Dec 26, 2023
2 parents 7ea600a + f000c10 commit 5351c78
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 24 deletions.
4 changes: 3 additions & 1 deletion qa/PHPStan/Extension/ApiAndInternalAnnotationCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

if (str_starts_with($reflection->getName(), 'CuyZ\Valinor\Tests')) {
if (str_starts_with($reflection->getName(), 'CuyZ\Valinor\Tests')
|| str_starts_with($reflection->getName(), 'SimpleNamespace')
) {
return [];
}

Expand Down
17 changes: 6 additions & 11 deletions src/Mapper/Tree/Builder/UnionNodeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,13 @@ private function narrow(UnionType $type, mixed $source): Type

private function tryToBuildClassNode(UnionType $type, Shell $shell, RootNodeBuilder $rootBuilder): ?TreeNode
{
$classTypes = [];
$classTypes = array_filter(
$type->types(),
fn (Type $type) => $type instanceof ClassType,
);

foreach ($type->types() as $subType) {
if ($subType instanceof NullType) {
continue;
}

if (! $subType instanceof ClassType) {
return null;
}

$classTypes[] = $subType;
if (count($classTypes) === 0) {
return null;
}

$objectBuilder = $this->objectBuilder($shell->value(), ...$classTypes);
Expand Down
12 changes: 1 addition & 11 deletions src/Type/Parser/Lexer/AliasLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,7 @@ private function resolveAlias(string $symbol): string

private function resolveNamespaced(string $symbol): string
{
$reflection = $this->reflection;

if ($reflection instanceof ReflectionFunction) {
$reflection = $reflection->getClosureScopeClass();
}

if (! $reflection) {
return $symbol;
}

$namespace = $reflection->getNamespaceName();
$namespace = $this->reflection->getNamespaceName();

if (! $namespace) {
return $symbol;
Expand Down
9 changes: 9 additions & 0 deletions src/Type/Types/IntegerRangeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
use CuyZ\Valinor\Type\Parser\Exception\Scalar\SameValueForIntegerRange;
use CuyZ\Valinor\Type\Type;

use function is_string;
use function ltrim;
use function preg_match;
use function sprintf;

/** @internal */
Expand Down Expand Up @@ -79,6 +82,12 @@ public function matches(Type $other): bool

public function canCast(mixed $value): bool
{
if (is_string($value)) {
$value = preg_match('/^0+$/', $value)
? '0'
: ltrim($value, '0');
}

return ! is_bool($value)
&& filter_var($value, FILTER_VALIDATE_INT) !== false
&& $value >= $this->min
Expand Down
9 changes: 9 additions & 0 deletions src/Type/Types/IntegerValueType.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
use function assert;
use function filter_var;
use function is_bool;
use function is_string;
use function ltrim;
use function preg_match;

/** @internal */
final class IntegerValueType implements IntegerType, FixedType
Expand Down Expand Up @@ -55,6 +58,12 @@ public function matches(Type $other): bool

public function canCast(mixed $value): bool
{
if (is_string($value)) {
$value = preg_match('/^0+$/', $value)
? '0'
: ltrim($value, '0');
}

return ! is_bool($value)
&& filter_var($value, FILTER_VALIDATE_INT) !== false
&& (int)$value === $this->value; // @phpstan-ignore-line;
Expand Down
6 changes: 6 additions & 0 deletions src/Type/Types/NativeIntegerType.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use function filter_var;
use function is_bool;
use function is_int;
use function is_string;
use function ltrim;

/** @internal */
final class NativeIntegerType implements IntegerType
Expand All @@ -37,6 +39,10 @@ public function matches(Type $other): bool

public function canCast(mixed $value): bool
{
if (is_string($value)) {
$value = ltrim($value, '0') . '0';
}

return ! is_bool($value) && filter_var($value, FILTER_VALIDATE_INT) !== false;
}

Expand Down
7 changes: 7 additions & 0 deletions src/Type/Types/NonNegativeIntegerType.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Utility\IsSingleton;

use function is_string;
use function ltrim;

/** @internal */
final class NonNegativeIntegerType implements IntegerType
{
Expand All @@ -33,6 +36,10 @@ public function matches(Type $other): bool

public function canCast(mixed $value): bool
{
if (is_string($value)) {
$value = ltrim($value, '0') . '0';
}

return ! is_bool($value)
&& filter_var($value, FILTER_VALIDATE_INT) !== false
&& $value >= 0;
Expand Down
6 changes: 6 additions & 0 deletions src/Type/Types/PositiveIntegerType.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use function filter_var;
use function is_bool;
use function is_int;
use function is_string;
use function ltrim;

/** @internal */
final class PositiveIntegerType implements IntegerType
Expand All @@ -38,6 +40,10 @@ public function matches(Type $other): bool

public function canCast(mixed $value): bool
{
if (is_string($value)) {
$value = ltrim($value, '0');
}

return ! is_bool($value)
&& filter_var($value, FILTER_VALIDATE_INT) !== false
&& $value > 0;
Expand Down
2 changes: 1 addition & 1 deletion src/Utility/Reflection/TokenParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private function next(): ?PhpToken
private function parseNamespace(): string
{
while ($token = $this->next()) {
if ($token->is([T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED])) {
if ($token->is([T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED, T_STRING])) {
return (string)$token;
}
}
Expand Down
13 changes: 13 additions & 0 deletions tests/Integration/Mapping/Fixture/SimpleNamespacedObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace SimpleNamespace;

final class SimpleNamespacedObject
{
public function __construct(
/** @var list<string> */
public array $value,
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;

use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use SimpleNamespace\SimpleNamespacedObject;

final class SimpleNamespacedObjectMappingTest extends IntegrationTest
{
public function test_simple_namespaced_object_can_be_mapped(): void
{
require_once(__DIR__ . '/../Fixture/SimpleNamespacedObject.php');

try {
$object = (new MapperBuilder())->mapper()->map(SimpleNamespacedObject::class, ['foo']);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame(['foo'], $object->value);
}
}
65 changes: 65 additions & 0 deletions tests/Integration/Mapping/Other/FlexibleCastingMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,71 @@ protected function setUp(): void
$this->mapper = (new MapperBuilder())->enableFlexibleCasting()->mapper();
}

public function test_leading_zero_in_numeric_is_mapped_properly(): void
{
$source = ['000', '040', '00040', '0001337.404'];

try {
$result = $this->mapper->map('array<int|float>', $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame([0, 40, 40, 1337.404], $result);
}

public function test_leading_zero_in_integer_range_is_mapped_properly(): void
{
$source = ['060', '042', '000404'];

try {
$result = $this->mapper->map('array<int<1, 500>>', $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame([60, 42, 404], $result);
}

public function test_leading_zero_in_integer_value_is_mapped_properly(): void
{
$source = ['000', '040', '000404'];

try {
$result = $this->mapper->map('array<0|40|404>', $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame([0, 40, 404], $result);
}

public function test_leading_zero_in_positive_integer_is_mapped_properly(): void
{
$source = ['040', '000404'];

try {
$result = $this->mapper->map('array<positive-int>', $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame([40, 404], $result);
}

public function test_leading_zero_in_non_negative_integer_is_mapped_properly(): void
{
$source = ['000', '040', '000404'];

try {
$result = $this->mapper->map('array<non-negative-int>', $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame([0, 40, 404], $result);
}

public function test_array_of_scalars_is_mapped_properly(): void
{
$source = ['foo', 42, 1337.404];
Expand Down
72 changes: 72 additions & 0 deletions tests/Integration/Mapping/UnionMappingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Tests\Integration\Mapping;

use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\City;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;

final class UnionMappingTest extends IntegrationTest
{
public function test_union_with_int_or_object(): void
{
try {
$array = (new MapperBuilder())->mapper()->map("list<int|" . SimpleObject::class . ">", [123, "foo"]);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame(123, $array[0]);
self::assertInstanceOf(SimpleObject::class, $array[1]);
}

public function test_union_with_string_or_object_prioritizes_string(): void
{
try {
$array = (new MapperBuilder())
->mapper()
->map("list<string|" . SimpleObject::class . ">", ["foo", "bar", "baz"]);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame(["foo", "bar", "baz"], $array);
}

public function test_union_with_string_literal_or_object_prioritizes_string_literal(): void
{
try {
$array = (new MapperBuilder())
->mapper()
->map("list<'foo'|" . SimpleObject::class . "|'bar'>", ["foo", "bar", "baz"]);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame("foo", $array[0]);
self::assertSame("bar", $array[1]);
self::assertInstanceOf(SimpleObject::class, $array[2]);
}

public function test_union_of_objects(): void
{
try {
$array = (new MapperBuilder())
->mapper()
->map(
"list<" . SimpleObject::class . "|" . City::class . ">",
["foo", ["name" => "foobar", "timeZone" => "UTC"], "baz"],
);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertInstanceOf(SimpleObject::class, $array[0]);
self::assertInstanceOf(City::class, $array[1]);
self::assertInstanceOf(SimpleObject::class, $array[2]);
}
}

0 comments on commit 5351c78

Please sign in to comment.