Skip to content

Commit

Permalink
Support union type in Type::nullable (#141) [WIP]
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN authored and dg committed Oct 29, 2023
1 parent 0106e52 commit eb06bf1
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 8 deletions.
4 changes: 3 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,9 @@ Each type or union/intersection type can be passed as a string, you can also use
use Nette\PhpGenerator\Type;

$member->setType('array'); // or Type::Array;
$member->setType('array|string'); // or Type::union('array', 'string')
$member->setType('?array'); // or Type::nullable(Type::Array);
$member->setType('array|string'); // or Type::union(Type::Array, Type::String)
$member->setType('array|string|null'); // or Type::nullable(Type::union(Type::Array, Type::String))
$member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class)
$member->setType(null); // removes type
```
Expand Down
14 changes: 13 additions & 1 deletion src/PhpGenerator/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

namespace Nette\PhpGenerator;

use Nette;


/**
* PHP return, property and parameter types.
Expand Down Expand Up @@ -85,7 +87,17 @@ class Type

public static function nullable(string $type, bool $nullable = true): string
{
return ($nullable ? '?' : '') . ltrim($type, '?');
if (str_contains($type, '&')) {
return $nullable
? throw new Nette\InvalidArgumentException('Intersection types cannot be nullable.')
: $type;
}

$nnType = preg_replace('#^\?|^null\||\|null(?=\||$)#i', '', $type);
if ($nullable && $type === $nnType) {
$type = str_contains($type, '|') ? $type . '|null' : '?' . $type;
}
return $nullable ? $type : $nnType;
}


Expand Down
42 changes: 36 additions & 6 deletions tests/PhpGenerator/Type.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,42 @@ use Nette\PhpGenerator\Type;
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';

// Nullable
Assert::same('?int', Type::nullable(Type::Int));
Assert::same('int', Type::nullable(Type::Int, nullable: false));

Assert::same('A|string', Type::union(A::class, Type::String));
Assert::same('?int', Type::nullable('?int'));
Assert::same('int', Type::nullable('?int', nullable: false));

// TODO:
Assert::same('null', Type::nullable('null'));
Assert::same('...', Type::nullable('null', nullable: false));
Assert::same('mixed', Type::nullable('mixed'));
Assert::same('...', Type::nullable('mixed', nullable: false));


Assert::same('int|float|string|null', Type::nullable('int|float|string'));
Assert::same('int|float|string', Type::nullable('int|float|string', nullable: false));

Assert::same('NULL|int|float|string', Type::nullable('NULL|int|float|string'));
Assert::same('int|float|string', Type::nullable('NULL|int|float|string', nullable: false));

Assert::same('?A', Type::nullable(A::class));
Assert::same('?A', Type::nullable(A::class));
Assert::same('A', Type::nullable(A::class, nullable: false));
Assert::same('int|float|string|null', Type::nullable('int|float|string|null'));
Assert::same('int|float|string', Type::nullable('int|float|string|null', nullable: false));

Assert::same('int|float|null|string', Type::nullable('int|float|null|string'));
Assert::same('int|float|string', Type::nullable('int|float|null|string', nullable: false));

Assert::exception(
fn() => Type::nullable('Foo&Bar'),
Nette\InvalidArgumentException::class,
'Intersection types cannot be nullable.',
);
Assert::same('Foo&Bar', Type::nullable('Foo&Bar', nullable: false));


// Union
Assert::same('A|string', Type::union(A::class, Type::String));

Assert::same('?A', Type::nullable('?A'));
Assert::same('A', Type::nullable('?A', nullable: false));
// Intersection
Assert::same('A&string', Type::intersection(A::class, Type::String));

0 comments on commit eb06bf1

Please sign in to comment.