carusogabriel · GitHub

There are some issues I would like to address. Union types are necessary, but I believe that some improvements must be made to this proposal.

Type aliasing

Let's look at the following example.

use Foo\bar;
function test(bar $bar) {
    // is $bar an object? 
}

If the Foo\bar type exists, then $bar is definitely an object. This is how it's been for years. But now, with the introduction of exported types, $bar could be anything. And this incertitude could prove to be a PITA in the long run.

namespace Foo;
type bar = int|float;
// Usable as \Foo\bar from elsewhere

Proposal

Introduce use type. This way we could no longer make wrong assumptions.

use Foo\bar;
use type Foo\baz;
use type int|float|string|bool as scalar; // local type
function test1(baz $baz, scalar $scalar) {
    // Don't make assumptions, check type definition.
}
function test2(bar $bar) {
    // It's an object
}

Nullable types

Nullable types should not be allowed when defining a union type.

use type ?(T1|T2|T3) as foo;
use type bar;
// What's this? Nullable nullable type?
function test(?foo $val) {
}
// Is bar nullable?
function test2(bar $val) {
   // Should I check if $val is null?
   if (is_null($val)) {
   }
}

Proposal

Keep it simple. No nullable types inside unions

use type T1|T2|T3 as foo;
// Not nullable
function test(foo $val) {
    // $val can't be null
}
// Nullable
function test2(?foo $val) {
   // I should check if $val is null
   if (is_null($val)) {
   }
}

Keep the syntax clean

There should be a single way of declaring a union type. The in place way of declaring a union type should be removed.

use type float|int as number;
// Confusing..
function sum(float|int $a, int|float $b): number{
}
// ??
function example(int|float $value, int $flag = 2|4|8|16|32, string|object $item = null): float|int|false {
}

It's hard to read, hard to follow and the syntax is super ugly.

Proposal

Keep the syntax unchanged by forcing the user to either export a union type or declare it locally with the help of use type.

use type float|int as number;
// Makes sense now..
function sum(number $a, number $b): number{
}

This way the ReflectionUnionType is no longer necessary. Only the ReflectionType needs to be changed.

class ReflectionType {
    isUnionType(): bool;
    getUnionTypes(): array;
}

Putting all together..

type scalar=int|float|string|bool; // export union type
use type int|float as number; // local union type
class Foo
{
    private number $foo; // non-nullable property
    private ?number $bar; // nullable property
    // non-nullable arguments & return type
    function sum(number $a, number b):number {
    }
    // nullable arguments and return type
    function test(?scalar $value): ?scalar {
    }
}

Read the original on github.com ↗