Skip to content

Commit

Permalink
Support union type in Type::nullable (#141)
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 c92f8de
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 8 deletions.
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,8 @@ 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('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class)
$member->setType(null); // removes type
```
Expand Down
23 changes: 22 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,26 @@ 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);
$spec = (bool) preg_match('#^(null|mixed)$#i', $nnType);
if ($nullable && $type === $nnType) {
return match (true) {
$spec => $type,
str_contains($type, '|') => $type . '|null',
default => '?' . $type,
};

} elseif (!$nullable && $spec) {
throw new Nette\InvalidArgumentException("Type $type cannot be not nullable.");
}

return $nullable ? $type : $nnType;
}


Expand Down
50 changes: 44 additions & 6 deletions tests/PhpGenerator/Type.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,50 @@ 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));

Assert::same('null', Type::nullable('null'));
Assert::same('NULL', Type::nullable('NULL'));
Assert::exception(
fn() => Type::nullable('null', nullable: false),
Nette\InvalidArgumentException::class,
'Type null cannot be not nullable.',
);

Assert::same('mixed', Type::nullable('mixed'));
Assert::exception(
fn() => Type::nullable('mixed', nullable: false),
Nette\InvalidArgumentException::class,
'Type mixed cannot be not nullable.',
);

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 c92f8de

Please sign in to comment.