Take the State of PHP 2026 Survey →

Nullable intersection types View as Markdown

Declined
Authors
Nicolas Grekas, nicolasgrekas@php.net
Date
2021-07-22
Version
0.1
Implementation
https://github.com/php/php-src/pull/7259
Source
wiki.php.net/rfc/nullable_intersection_types

Votes

Make intersection types nullable closed
Yes
12
No
26
38 voters
alcaeus Yes 2021-08-13 11:22
alec No 2021-08-15 06:38
asgrim No 2021-08-13 12:15
ashnazg Yes 2021-08-19 16:36
beberlei Yes 2021-08-13 17:27
brzuchal Yes 2021-08-16 08:43
bwoebi Yes 2021-08-13 11:02
crell No 2021-08-13 14:43
danack No 2021-08-13 13:51
daverandom No 2021-08-23 19:09
derick No 2021-08-13 10:01
galvao Yes 2021-08-13 22:32
jasny Yes 2021-08-13 10:55
kalle No 2021-08-13 16:35
kelunik No 2021-08-18 09:02
kguest No 2021-08-18 08:11
kocsismate Yes 2021-08-13 11:28
lcobucci No 2021-08-25 17:21
levim No 2021-08-16 13:27
marandall No 2021-08-15 18:49
mauricio Yes 2021-08-26 16:19
mbeccati No 2021-08-23 09:15
nicolasgrekas Yes 2021-08-13 09:37
ocramius No 2021-08-13 11:12
patrickallaert No 2021-08-16 00:50
petk No 2021-08-20 17:03
pollita No 2021-08-20 14:39
ramsey No 2021-08-24 17:37
reywob No 2021-08-13 12:11
salathe No 2021-08-13 10:36
seld Yes 2021-08-13 11:05
sergey Yes 2021-08-13 13:50
stas No 2021-08-19 17:25
svpernova09 No 2021-08-13 14:57
tandre No 2021-08-23 02:21
theodorejb No 2021-08-22 14:22
trowski No 2021-08-20 21:29
weierophinney No 2021-08-18 21:44
Preferred syntax closed
"?" prefix
6
"|null" suffix
29
35 voters
alcaeus "|null" suffix 2021-08-13 11:22
alec "?" prefix 2021-08-15 06:38
asgrim "|null" suffix 2021-08-13 12:15
ashnazg "?" prefix 2021-08-19 16:36
beberlei "|null" suffix 2021-08-13 17:28
brzuchal "|null" suffix 2021-08-16 08:43
bwoebi "|null" suffix 2021-08-13 11:02
crell "|null" suffix 2021-08-13 14:43
danack "|null" suffix 2021-08-13 13:51
daverandom "|null" suffix 2021-08-23 19:09
derick "|null" suffix 2021-08-13 10:01
galvao "|null" suffix 2021-08-13 22:32
jasny "|null" suffix 2021-08-13 10:57
kalle "|null" suffix 2021-08-13 16:35
kelunik "|null" suffix 2021-08-18 09:03
kguest "?" prefix 2021-08-18 08:11
kocsismate "?" prefix 2021-08-13 11:28
lcobucci "|null" suffix 2021-08-25 17:21
levim "|null" suffix 2021-08-16 13:27
marandall "|null" suffix 2021-08-13 22:58
mauricio "|null" suffix 2021-08-26 16:19
mbeccati "|null" suffix 2021-08-23 09:16
nicolasgrekas "?" prefix 2021-08-13 09:37
ocramius "|null" suffix 2021-08-13 11:12
patrickallaert "|null" suffix 2021-08-17 15:29
pollita "|null" suffix 2021-08-20 14:39
ramsey "|null" suffix 2021-08-24 17:37
salathe "|null" suffix 2021-08-13 10:37
sebastian "|null" suffix 2021-08-14 08:26
seld "?" prefix 2021-08-13 11:05
sergey "|null" suffix 2021-08-13 13:50
svpernova09 "|null" suffix 2021-08-13 14:58
theodorejb "|null" suffix 2021-08-13 12:14
trowski "|null" suffix 2021-08-20 21:29
weierophinney "|null" suffix 2021-08-18 21:45
Intersections should be closed
without brackets around
1
with brackets around
27
allow both styles
7
35 voters
alcaeus with brackets around 2021-08-13 11:22
alec allow both styles 2021-08-15 06:38
asgrim with brackets around 2021-08-13 12:16
ashnazg allow both styles 2021-08-19 16:37
beberlei with brackets around 2021-08-13 17:28
brzuchal with brackets around 2021-08-16 08:43
bwoebi with brackets around 2021-08-13 11:02
crell with brackets around 2021-08-13 14:43
danack with brackets around 2021-08-13 13:51
daverandom with brackets around 2021-08-23 19:09
derick with brackets around 2021-08-13 10:02
galvao with brackets around 2021-08-13 22:33
jasny with brackets around 2021-08-13 10:57
kalle with brackets around 2021-08-13 16:36
kelunik without brackets around 2021-08-18 09:03
kguest with brackets around 2021-08-18 08:11
lcobucci with brackets around 2021-08-25 17:21
levim with brackets around 2021-08-16 13:27
marandall with brackets around 2021-08-13 22:58
mauricio with brackets around 2021-08-26 16:20
mbeccati with brackets around 2021-08-23 09:16
nicolasgrekas allow both styles 2021-08-13 09:37
ocramius with brackets around 2021-08-13 11:12
pollita with brackets around 2021-08-20 14:39
ramsey with brackets around 2021-08-24 17:37
reywob with brackets around 2021-08-13 12:11
salathe with brackets around 2021-08-13 10:37
sebastian with brackets around 2021-08-14 08:26
seld allow both styles 2021-08-13 11:06
sergey with brackets around 2021-08-13 13:51
stas allow both styles 2021-08-19 17:25
svpernova09 with brackets around 2021-08-13 14:58
theodorejb allow both styles 2021-08-13 12:15
trowski allow both styles 2021-08-20 21:29
weierophinney with brackets around 2021-08-18 21:45

Discussions on the mailing list

[RFC] Nullable intersection types
by Nicolas Grekas · 48 messages
[VOTE] Nullable intersection types
by Nicolas Grekas · 21 messages

RFC text

Copied from wiki.php.net, last synchronized Oct 13, 2026 11:52 UTC

Introduction

Intersection types as currently accepted for PHP 8.1 are not nullable. This RFC proposes to make them so.

Proposal

Intersection types as currently accepted for PHP 8.1 are not nullable: when one uses “X&Y” as a type on a property, an argument or a return value, there is no syntax to declare them as also accepting the null value. This means that it is required to use the null-pattern where default values are needed for properties and for optional arguments. For return-types, a null-object must be returned and potentially detected using a custom check.

While useful in some cases, the null-pattern is not common in PHP. One reason might be that it requires quite some boilerplate (the implementation of the null-object's class), but the most likely reason is that the null value works just great instead, with zero extra code to write.

This RFC proposes to add a syntax to the language to declare that a type accepts both an intersection or null. The possible syntax choices are discussed below. Using the longest syntax that has been proposed so far as an example, this PR aims at allowing the following piece of code:

class Foo
{
    public (X&Y)|null $bar;
 
    function setBar((X&Y)|null $bar = null): (X&Y)|null
    {
        return $this->bar = $bar;
    }
}

On the reflection side, ReflectionIntersectionType::allowsNull() will return true/false depending on what the intersection type accepts.

Rationale

When PHP 7.0 introduced scalar types, it was obvious that the special null type was missing as a way to declare that null was a possible return value. PHP 7.1 added the “?foo” syntax to declare their nullability. This lesson from history tells us that the nullable type is special and very much needed in PHP.

As for scalars, nullable intersection types would make optional arguments/properties/return-values trivial to implement. It would also make them consistent with the other type declarations.

For userland, if this nullable capability were added to a later version of PHP, making a parameter nullable later would cause a BC break (or force a major version bump when using semver.) This is of course because of LSP rules.

From an implementation point of view, the linked patch is trivial: the source of PHP already implements all the required logic to deal with variance/covariance rules related to the null type and ReflectionIntersectionType::allowsNull() already exists. The patch is only a matter of adding a syntax to tell the engine about nullability and everything else just works.

For all these reasons, this RFC proposes to make intersection types nullable, and to make them so right away in PHP 8.1.

About reflection, one could imagine a more complex model based on a ReflectionIntersectionType nested inside a ReflectionUnionType. This RFC proposes to rely on ReflectionIntersectionType::allowsNull() instead. This is consistent with how T|null is returned without ReflectionUnionType wrapper, and is also simpler for userland to deal with.

Future Scope

The original intersection types RFC discusses a bit how composite types (i.e. mixing union and intersection types) could happen: https://externals.io/rfc/pure-intersection-types#future_scope

It mentions two main challenges to make them happen (there will likely be others):

  • variance rules and checks
  • Reflection

From a conceptual pov, nullability is a special form of composite type. Yet, nullability doesn't have these concerns, because the engine already deals will null in a special way: MAY_BE_NULL is a flag that any type can carry internally, independently from any other type constraints, and reflection provides the allowsNull() method everywhere needed.

This means we don't need (and shouldn't) wait to solve the generic case to solve the nullability case, which is particular anyway. The very benefits to solve the generic case have even not been discussed yet. The possibility exists that we decide that we don't need to solve it.

Syntax choices

PHP already has two syntax to express nullability:

  • for simple types, the “?” prefix is required, as in “?array”
  • for union types, “null” should be added to the union, as in “string|array|null”

This means that we have two main options for the syntax of nullable intersection types:

  1. use the “?” prefix, as in “?X&Y”
  2. use the “|null” suffix (or similar prefix), as in “X&Y|null”

Since both possibilities contain several operators (“?” and “&” / “|” and “&” respectively), operator precedence should be taken into account to resolve any possible ambiguity when interpreting the type expression. PHP already defines “|” as having a lower precedence than the “&” operators. This means that “X&Y|null” can only be interpreted as “(X&Y) | null”, which is what we want to express here.

The precedence of the “?” type-operator is not defined yet. Looking at the precedence of null-related operators (see this table), they are all below the “&” and “|” operators, which is what we need to unambiguously interpret “?X&Y” as “? (X&Y)”. Another consideration related to composite types backs this interpretation up: whatever the nesting level of an hypothetical composite type definition, nullability can always we expressed as a single flag that sits next to the non-null constraints of the type. This is because any intersections that contain the null type are identical to the never type.

Taking all these elements into account, the preference of the author of this RFC is to define “?” as having a lower precedence than any other type-operator, and thus to use the “?X&Y” syntax. This reads quickly from left to right as: 1. the type is nullable 2. here are the constraints that apply to any non-null values.

Using the “?X&Y” syntax has also the benefit of not colliding with any of the envisioned language extensions (them being composite types or even generics).

Here is how this would look like in practice:

class Foo
{
    public ?X&Y $bar;
 
    function setBar(?X&Y $bar = null): ?X&Y
    {
        return $this->bar = $bar;
    }
}

That being said and because it's kinda hard to gather a broad consensus on syntax choices, this RFC proposes various possible options for the community to decide. Using “null|X&Y” is not offered as an option because it would be “over-delivering syntax that hasn't been entirely thought through” (using sgolemon's words) and that should be introduced by a potential future RFC that would extend to composite types.

It is also the author's opinion that introducing brackets would be over-delivering syntax. Precedence rules + “nullability is a flag” arguments make them unnecessary. Not using brackets also eases with visual reading, to quickly spot eg the end of the signature of a function declaration. This is still offered as a possible vote option.

Proposed PHP Version(s)

PHP 8.1.

Proposed Voting Choices

As per the voting RFC, the first question requires a 2/3 majority for this proposal to be accepted. The other choices require simple majority.

  • Make intersection types nullable: yes / no
  • Preferred syntax: “?” prefix / “|null” suffix
  • Intersections should be: without brackets around / with brackets around / allow both styles

Vote

Voting starts 2021-08-13 09:30 UTC and ends 2021-08-27 17:00 UTC.

Vote: Make intersection types nullable — Yes: 12 · No: 26 (closed)


Vote: Preferred syntax — "?" prefix: 6 · "|null" suffix: 29 (closed)


Vote: Intersections should be — without brackets around: 1 · with brackets around: 27 · allow both styles: 7 (closed)

Patches and Tests

See https://github.com/php/php-src/pull/7259

Patch will be updated according to the syntax decided by the vote.

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged into
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature
  4. a link to the language specification section (if any)

References

https://externals.io/rfc/pure-intersection-types