On 20/03/2024 23:05, Robert Landers wrote: > In other > words, I can't think of a case where you'd actually want a Type|null > and you wouldn't have to check for null anyway.
It's not about having to check for null; it's about being able to distinguish between "a null value, which was one of the expected types" and "a value of an unexpected type". That's a distinction which is made everywhere else in the language: parameter types, return types, property types, will all throw an error if you pass a Foo when a ?Bar was expected, they won't silently coerce it to null. > If you think about it, in this proposal, you could use it in a match: > > // $a is TypeA|TypeB|null > > match (true) { > $a as ?TypeA => 'a', > $a as ?TypeB => 'b', > $a === null => 'null', > } That won't work, because match performs a strict comparison, and the as expression won't return a boolean true. You would have to do this: match (true) { (bool)($a as ?TypeA) => 'a', (bool)($a as ?TypeB) => 'b', $a === null => 'null', } Or this: match (true) { ($a as ?TypeA) !== null => 'a', ($a as ?TypeB) !== null => 'b', $a === null => 'null', } Neither of which is particularly readable. What you're really looking for in that case is an "is" operator: match (true) { $a is TypeA => 'a', $a is TypeB => 'b', $a === null => 'null', } Which in the draft pattern matching RFC Ilija linked to can be abbreviated to: match ($a) is { TypeA => 'a', TypeB => 'b', null => 'null', } Of course, in simple cases, you can use "instanceof" in place of "is" already: match (true) { $a instanceof TypeA => 'a', $a instanceof TypeB => 'b', $a === null => 'null', } > Including `null` in that type > seems to be that you would get null if no other type matches, since > any variable can be `null`. > I can't think of any sense in which "any variable can be null" that is not true of any other type you might put in the union. We could interpret Foo|false as meaning "use false as the fallback"; or Foo|int as "use zero as the fallback"; but I don't think that would be sensible. In other words, the "or null on failure" part is an option to the "as" expression, it's not part of the type you're checking against. If we only wanted to support "null on failure", we could have a different keyword, like "?as": $bar = new Bar; $bar as ?Foo; // Error $bar ?as Foo; // null (as fallback) $null = null; $null as ?Foo; // null (because it's an accepted value) $null ?as Foo; // null (as fallback) A similar suggestion was made in a previous discussion around nullable casts - to distinguish between (?int)$foo as "cast to nullable int" and (int?)$foo as "cast to int, with null on error". Note however that combining ?as with ?? is not enough to support "chosen value on failure": $bar = new Bar; $bar ?as ?Foo ?? Foo::createDefault(); // creates default object $null = null; $null ?as ?Foo ?? Foo::createDefault(); // also creates default object, even though null is an expected value That's why my earlier suggestion was to specify the fallback explicitly: $bar = new Bar; $bar as ?Foo else null; // null $bar as ?Foo else Foo::createDefault(); // default object $null = null; $nulll as ?Foo else null; // null $null as ?Foo else Foo::createDefault(); // also null, because it's an accepted value, so the fallback is not evaluated Probably, it should then be an error if the fallback value doesn't meet the constraint: $bar = new Bar; $bar as Foo else null; // error: fallback value null is not of type Foo $bar as ?Foo else 42; // error: fallback value 42 is not of type ?Foo Regards, -- Rowan Tommins [IMSoP]