[VOTE] [RFC] Bound-Erased Generic Types

php.internals

Seifeddine Gmati

79 days ago
Hello internals, Voting is now open on the Bound-Erased Generic Types RFC, as announced in the Intent to Vote message earlier this week. There are two votes on the RFC page: 1. Primary vote, requires a 2/3 majority: accept the RFC as proposed. 2. Secondary vote, simple majority, conditional on the primary passing: variance marker syntax, `+T` / `-T` (Hack, Scala) vs `in T` / `out T` (C#, Kotlin). Semantically identical; only the surface syntax differs. The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at 17:00 UTC. RFC: https://wiki.php.net/rfc/bound_erased_generic_types Discussion thread: https://news-web.php.net/php.internals/130816 Implementation: https://github.com/php/php-src/pull/21969 Cheers::<>, Seifeddine.

Rowan Tommins [IMSoP]

79 days ago
On 14/06/2026 16:53, Seifeddine Gmati wrote:
> Voting is now open on the Bound-Erased Generic Types RFC, as announced > in the Intent to Vote message earlier this week. > > RFC: https://wiki.php.net/rfc/bound_erased_generic_types > Discussion thread: https://news-web.php.net/php.internals/130816 > Implementation: https://github.com/php/php-src/pull/21969
It will come as no surprise to those who followed the discussion that I have voted "No". For those who did not, my position in a nutshell is: I want PHP to have generic types; but I want to be able to trust those types. This RFC does not deliver that. ---- This is not a technical argument about "erased" vs "reified" vs "monomorphized"; it's more philosophical: if there's native syntax, there should be native enforcement of some kind. There are several versions of this RFC I could have supported, for example: - Separating out the parts which *are* enforced, at compile and link time, and leaving more to Future Scope - An official tool which proved types statically, then erased them at run-time - A new syntax for "untrusted" types, as a structured replacement to docblocks In its current form, I think it would be a step backwards for the language's type system. ---- Currently, a reader seeing a native parameter, return, or property type can trust that it will be enforced. Additional unenforced types can be included in docblock annotations, where human readers and tools can choose how much to trust them. For example, the author of a library may document that users *should* pass correct types, but still write defensive code for if they fail to do so. As a quick example, in the below, the language *guarantees* that the is_int() check is redundant: function foo(int $bar) {        if ( ! is_int($bar) ) {        throw new \TypeError('Expected an int');     } } This RFC proposes to *remove that guarantee*, and use native syntax for unenforced types. In the code below, the is_int() call will *not* be redundant, because the language will only guarantee a type of "int|string", despite the native syntax indicating more: interface Foo<T: int|string> {     public function bar(): T; } function test(Foo<int> $foo): void {     $bar = $foo->bar();     if ( ! is_int($bar) ) {        throw new \TypeError('Expected an int');     } } ---- I would like to thank Seifeddine for the hard work, and although I hope this RFC is not accepted, I also hope that others build on its foundations to deliver much-needed generic types to the language. Regards,
-- Rowan Tommins [IMSoP]

Derick Rethans

79 days ago
On 14 June 2026 16:53:44 BST, Seifeddine Gmati <azjezz@carthage.software> wrote:
>2. Secondary vote, simple majority, conditional on the primary >passing: variance marker syntax, `+T` / `-T` (Hack, Scala) vs `in T` / >`out T` (C#, Kotlin). Semantically identical; only the surface syntax >differs.
The second one is missing the Abstain option. cheers Derick

Larry Garfield

78 days ago
On Sun, Jun 14, 2026, at 10:53 AM, Seifeddine Gmati wrote:
> Hello internals, > > Voting is now open on the Bound-Erased Generic Types RFC, as announced > in the Intent to Vote message earlier this week. > > There are two votes on the RFC page: > > 1. Primary vote, requires a 2/3 majority: accept the RFC as proposed. > 2. Secondary vote, simple majority, conditional on the primary > passing: variance marker syntax, `+T` / `-T` (Hack, Scala) vs `in T` / > `out T` (C#, Kotlin). Semantically identical; only the surface syntax > differs. > > The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at 17:00 UTC. > > RFC: https://wiki.php.net/rfc/bound_erased_generic_types > Discussion thread: https://news-web.php.net/php.internals/130816 > Implementation: https://github.com/php/php-src/pull/21969 > > Cheers::<>, > Seifeddine.
I already posted this in the discussion thread, but since many people don't read deep into a thread I'm reposting it here for visibility: --- i strongly urge you to hold off on a vote. Generics would be the biggest PHP feature in *years*, and would generate a lot of very positive buzz, both within the community and externally. OTOH, generics getting voted down (for whatever reason) would be... very bad press, both within the community and externally. I don't feel like Rob's additions have been adequately investigated and evaluated, nor is there a clear consensus on what would be "good enough" for it to be acceptable. That is a problem that should be addressed directly. Additionally, Rob has noted that the current RFC does allow for code that would be broken and change behavior should generics become enforced in the future. That is a landmine we do not want. (And I reiterate, "if you care, use an SA tool" is an insufficient answer.) We basically have three options: 1. Erased generics, with all the limitations and problems that implies (basically Seif's current RFC). 2. Monomorphized generics, with the performance hit that implies (basically Rob's patch, with some further development.) 3. Pass on generics, again. In my mind, option 3 is the absolute worst option. I can think of at least three core features that would benefit from or require generics to be done properly, so having *something* in core is, in my mind, critical. Plus, as previously noted, "Internals rejects generics, even though basically everyone wants it" is going to be the headline (no matter how (in)accurate you feel that is). The only people that win in that scenario are Node.js fanbois. However, my read of the current discussion is that partially-erased generics (this RFC) is not going to pass. I've been struggling with it for a while, and still not sure how I'm going to vote. I really want to support it, but the gaps that Rob pointed out (around catch) are a problem. And again, they're worse than just "oh it's the wrong type so you get a type error." It completely changes the error pathway that gets executed. If partially-erased generics doesn't pass, that will leave us with monomorphized/reified generics. That always runs into the "but performance" problem, yet, there has never been a consensus as to what an acceptable CPU or memory hit would be. Because *there is guaranteed to be one*. If we want generics, then we either accept partial erasure or we accept some performance hit. We're going to have to deal with one or the other, and pretending that some magic free-reified implementation will appear is a fool's errand. So to anyone who plans to vote against the current RFC, please state what you would consider an acceptable performance hit for going all the way. *We need to agree on that*, so that Rob or anyone else can see if we can hit it. That's the step that hasn't been achieved yet (due in part to our current process having no way to handle that). What Rob has expressed so far frankly seems like an acceptable cost to me already, but I know others disagree. But holding out for zero-cost is simply impractical. My recommendation, in fact, would be to get one or more additional people involved to help on the performance front, and put forward an equivalent enforced-generics RFC. (Same syntax and semantics as the current one, which are pretty solid aside from the in/out question.) Let's bang on that and get it right, and then we can pass that. Given the timing, I would suggest such an RFC not target 8.6. Instead, we can plan ahead that 8.6 (this year) will be the end of the 8.x series, PHP 2027 will be PHP 9.0, and will include whatever the result of that further generics work is. That gives us plenty of time to: 1. Ensure it's rock solid, and as performant as we can manage 2. Develop additional features for core that can leverage generics, so when they ship we have stuff already using it. This also helps flush out any edge case bugs before it ships. 3. Gives us a clear path toward a major PR and press win (PHP 9.0, Now with Generics!), which the project desperately needs. I want to reiterate: "Internals votes down generics" is the absolute worst outcome, for literally everyone who cares about PHP. As we say in Chicago, "you don't call the vote until you know you have the votes." Right now, I don't think we have the votes. We need to take the time and do the work to get this *right*, and ensure not just passage, but broad support and endorsement, and the right tooling built on top of it. Voting on the current RFC right now will not get us there. --Larry Garfield

Levi Morrison

78 days ago
> Voting is now open on the Bound-Erased Generic Types RFC, as announced > in the Intent to Vote message earlier this week. > > The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at 17:00 UTC. > > RFC: https://wiki.php.net/rfc/bound_erased_generic_types > Discussion thread: https://news-web.php.net/php.internals/130816 > Implementation: https://github.com/php/php-src/pull/21969
To my own surprise, I am voting yes on this proposal. I still believe that refied generics should be the goal. To that end, I think we should be able to merge this and in minor versions, fix "soundness" and implementation holes as "bug" fixes on that route. If internals can agree on this, and then the community can communicate this through education, promotion, documentation, etc, then this RFC is simply a useful stepping stone. This is based on my own experience playing with the RFC, which included reporting 2 bugs in the implementation on the RFC. To be honest, it's not too useful to me. I very quickly run up against some limitation when trying to build useful things. Most notably, we need to be able to define lower bounds (syntax pending, obviously): class Option<+A> { // B >: A means B is an ancestor of A // Since all As are Bs, you can return the A here function get_or_default<B >: A>(Option $option, B $default): B { return $option->has_some() ? $option->get() : $default; } // todo: the rest of the implementation } You can't take `A $default` and return `A` because this violates the variance laws: a covariant type A cannot be used in parameter position. So alternatively you could solve this with `inout A $default` or similar. This lower bound thing inevitably happens when I try working with iterators too. But if the RFC passed, I would hope we could pass such additional things without too much controversy.

Matthew Brown

78 days ago
On Mon, 15 Jun 2026 at 12:28, Levi Morrison <levi.morrison@datadoghq.com> wrote:
> > Voting is now open on the Bound-Erased Generic Types RFC, as announced > > in the Intent to Vote message earlier this week. > > > > The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at > 17:00 UTC. > > > > RFC: https://wiki.php.net/rfc/bound_erased_generic_types > > Discussion thread: https://news-web.php.net/php.internals/130816 > > Implementation: https://github.com/php/php-src/pull/21969 > > To my own surprise, I am voting yes on this proposal. > > I still believe that refied generics should be the goal. To that end, > I think we should be able to merge this and in minor versions, fix > "soundness" and implementation holes as "bug" fixes on that route. If > internals can agree on this, and then the community can communicate > this through education, promotion, documentation, etc, then this RFC > is simply a useful stepping stone. > > This is based on my own experience playing with the RFC, which > included reporting 2 bugs in the implementation on the RFC. To be > honest, it's not too useful to me. I very quickly run up against some > limitation when trying to build useful things. Most notably, we need > to be able to define lower bounds (syntax pending, obviously): > > class Option<+A> > { > // B >: A means B is an ancestor of A > // Since all As are Bs, you can return the A here > function get_or_default<B >: A>(Option $option, B $default): B > { > return $option->has_some() > ? $option->get() > : $default; > } > // todo: the rest of the implementation > } > > You can't take `A $default` and return `A` because this violates the > variance laws: a covariant type A cannot be used in parameter > position. So alternatively you could solve this with `inout A > $default` or similar. >
Presumably this would work with the current proposal: class Option<+A> { public function getOrDefault<B>(B $default): A|B { return $this->hasSome() ? $this->get() : $default; } } Hack also has `super` type constraints: https://hhvm.com/blog/9215/covariance-contravariance-and-super-type-constraints

Rob Landers

78 days ago
On Mon, Jun 15, 2026, at 20:22, Matthew Brown wrote:
> On Mon, 15 Jun 2026 at 12:28, Levi Morrison <levi.morrison@datadoghq.com> wrote: >> > Voting is now open on the Bound-Erased Generic Types RFC, as announced >> > in the Intent to Vote message earlier this week. >> > >> > The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at 17:00 UTC. >> > >> > RFC: https://wiki.php.net/rfc/bound_erased_generic_types >> > Discussion thread: https://news-web.php.net/php.internals/130816 >> > Implementation: https://github.com/php/php-src/pull/21969 >> >> To my own surprise, I am voting yes on this proposal. >> >> I still believe that refied generics should be the goal. To that end, >> I think we should be able to merge this and in minor versions, fix >> "soundness" and implementation holes as "bug" fixes on that route. If >> internals can agree on this, and then the community can communicate >> this through education, promotion, documentation, etc, then this RFC >> is simply a useful stepping stone. >> >> This is based on my own experience playing with the RFC, which >> included reporting 2 bugs in the implementation on the RFC. To be >> honest, it's not too useful to me. I very quickly run up against some >> limitation when trying to build useful things. Most notably, we need >> to be able to define lower bounds (syntax pending, obviously): >> >> class Option<+A> >> { >> // B >: A means B is an ancestor of A >> // Since all As are Bs, you can return the A here >> function get_or_default<B >: A>(Option $option, B $default): B >> { >> return $option->has_some() >> ? $option->get() >> : $default; >> } >> // todo: the rest of the implementation >> } >> >> You can't take `A $default` and return `A` because this violates the >> variance laws: a covariant type A cannot be used in parameter >> position. So alternatively you could solve this with `inout A >> $default` or similar. > > Presumably this would work with the current proposal: > > class Option<+A> { > public function getOrDefault<B>(B $default): A|B { > return $this->hasSome() ? $this->get() : $default; > } > } > > Hack also has `super` type constraints: https://hhvm.com/blog/9215/covariance-contravariance-and-super-type-constraints
It wouldn’t actually do anything though… — Rob

Rob Landers

78 days ago
On Mon, Jun 15, 2026, at 20:27, Rob Landers wrote:
> > > On Mon, Jun 15, 2026, at 20:22, Matthew Brown wrote: >> On Mon, 15 Jun 2026 at 12:28, Levi Morrison <levi.morrison@datadoghq.com> wrote: >>> > Voting is now open on the Bound-Erased Generic Types RFC, as announced >>> > in the Intent to Vote message earlier this week. >>> > >>> > The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at 17:00 UTC. >>> > >>> > RFC: https://wiki.php.net/rfc/bound_erased_generic_types >>> > Discussion thread: https://news-web.php.net/php.internals/130816 >>> > Implementation: https://github.com/php/php-src/pull/21969 >>> >>> To my own surprise, I am voting yes on this proposal. >>> >>> I still believe that refied generics should be the goal. To that end, >>> I think we should be able to merge this and in minor versions, fix >>> "soundness" and implementation holes as "bug" fixes on that route. If >>> internals can agree on this, and then the community can communicate >>> this through education, promotion, documentation, etc, then this RFC >>> is simply a useful stepping stone. >>> >>> This is based on my own experience playing with the RFC, which >>> included reporting 2 bugs in the implementation on the RFC. To be >>> honest, it's not too useful to me. I very quickly run up against some >>> limitation when trying to build useful things. Most notably, we need >>> to be able to define lower bounds (syntax pending, obviously): >>> >>> class Option<+A> >>> { >>> // B >: A means B is an ancestor of A >>> // Since all As are Bs, you can return the A here >>> function get_or_default<B >: A>(Option $option, B $default): B >>> { >>> return $option->has_some() >>> ? $option->get() >>> : $default; >>> } >>> // todo: the rest of the implementation >>> } >>> >>> You can't take `A $default` and return `A` because this violates the >>> variance laws: a covariant type A cannot be used in parameter >>> position. So alternatively you could solve this with `inout A >>> $default` or similar. >> >> Presumably this would work with the current proposal: >> >> class Option<+A> { >> public function getOrDefault<B>(B $default): A|B { >> return $this->hasSome() ? $this->get() : $default; >> } >> } >> >> Hack also has `super` type constraints: https://hhvm.com/blog/9215/covariance-contravariance-and-super-type-constraints > > It wouldn’t actually do anything though… > > — Rob
Sorry. Sent before I was finished writing. It wouldn’t actually do anything though… Both are “correct” in erased generics. You’d receive no error or feedback what you’d written was “wrong” or “broken”. In both examples, types reduce to mixed. — Rob

Levi Morrison

77 days ago
On Mon, Jun 15, 2026 at 12:28 PM Rob Landers <rob@getswytch.com> wrote:
> > > > On Mon, Jun 15, 2026, at 20:22, Matthew Brown wrote: > > On Mon, 15 Jun 2026 at 12:28, Levi Morrison <levi.morrison@datadoghq.com> wrote: > > > Voting is now open on the Bound-Erased Generic Types RFC, as announced > > in the Intent to Vote message earlier this week. > > > > The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at 17:00 UTC. > > > > RFC: https://wiki.php.net/rfc/bound_erased_generic_types > > Discussion thread: https://news-web.php.net/php.internals/130816 > > Implementation: https://github.com/php/php-src/pull/21969 > > To my own surprise, I am voting yes on this proposal. > > I still believe that refied generics should be the goal. To that end, > I think we should be able to merge this and in minor versions, fix > "soundness" and implementation holes as "bug" fixes on that route. If > internals can agree on this, and then the community can communicate > this through education, promotion, documentation, etc, then this RFC > is simply a useful stepping stone. > > This is based on my own experience playing with the RFC, which > included reporting 2 bugs in the implementation on the RFC. To be > honest, it's not too useful to me. I very quickly run up against some > limitation when trying to build useful things. Most notably, we need > to be able to define lower bounds (syntax pending, obviously): > > class Option<+A> > { > // B >: A means B is an ancestor of A > // Since all As are Bs, you can return the A here > function get_or_default<B >: A>(Option $option, B $default): B > { > return $option->has_some() > ? $option->get() > : $default; > } > // todo: the rest of the implementation > } > > You can't take `A $default` and return `A` because this violates the > variance laws: a covariant type A cannot be used in parameter > position. So alternatively you could solve this with `inout A > $default` or similar. > > > Presumably this would work with the current proposal: > > class Option<+A> { > public function getOrDefault<B>(B $default): A|B { > return $this->hasSome() ? $this->get() : $default; > } > }
The version with a lower bound says it returns an A or an ancestor of A. The version with the union type says it returns an A or a B, and there's no relation between A and B at all. They both allow correct code: you can correctly return an A or ancestor of A in either one. Only the version with the lower bound rejects incorrect code, though, and that's an important duty. But this is tangential to my main point: if we accept that we are moving towards fully reified generics, and this is only a stepping stone, and we document this widely and educate about it, then in future versions we can tighten from "bounds erased" to "fully reified." Yes, there will still be breakages, but far, far fewer breakages. This should be tolerable. I don't want this RFC to be our final form: I only want it to be a stepping stone.

Larry Garfield

77 days ago
On Mon, Jun 15, 2026, at 2:21 PM, Levi Morrison wrote:
> On Mon, Jun 15, 2026 at 12:28 PM Rob Landers <rob@getswytch.com> wrote: >> >> >> >> On Mon, Jun 15, 2026, at 20:22, Matthew Brown wrote: >> >> On Mon, 15 Jun 2026 at 12:28, Levi Morrison <levi.morrison@datadoghq.com> wrote: >> >> > Voting is now open on the Bound-Erased Generic Types RFC, as announced >> > in the Intent to Vote message earlier this week. >> > >> > The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at 17:00 UTC. >> > >> > RFC: https://wiki.php.net/rfc/bound_erased_generic_types >> > Discussion thread: https://news-web.php.net/php.internals/130816 >> > Implementation: https://github.com/php/php-src/pull/21969 >> >> To my own surprise, I am voting yes on this proposal. >> >> I still believe that refied generics should be the goal. To that end, >> I think we should be able to merge this and in minor versions, fix >> "soundness" and implementation holes as "bug" fixes on that route. If >> internals can agree on this, and then the community can communicate >> this through education, promotion, documentation, etc, then this RFC >> is simply a useful stepping stone. >> >> This is based on my own experience playing with the RFC, which >> included reporting 2 bugs in the implementation on the RFC. To be >> honest, it's not too useful to me. I very quickly run up against some >> limitation when trying to build useful things. Most notably, we need >> to be able to define lower bounds (syntax pending, obviously):
> But this is tangential to my main point: if we accept that we are > moving towards fully reified generics, and this is only a stepping > stone, and we document this widely and educate about it, then in > future versions we can tighten from "bounds erased" to "fully > reified." Yes, there will still be breakages, but far, far fewer > breakages. This should be tolerable. > > I don't want this RFC to be our final form: I only want it to be a > stepping stone.
I concur. My concern is, as Rob pointed out, it may not be possible to add enforced generics on top without API changes. Not just "your code was already broken and now we're telling you" changes, but changing the way catch blocks behave. I'm not sure if we're OK with that level of breakage if we incrementally add more enforcement. Is that concern valid? I'm not sure. We didn't get a chance to flesh it out before the vote was called. --Larry Garfield

Seifeddine Gmati

64 days ago
On Sun, 14 Jun 2026 at 16:53, Seifeddine Gmati <azjezz@carthage.software> wrote:
> > Hello internals, > > Voting is now open on the Bound-Erased Generic Types RFC, as announced > in the Intent to Vote message earlier this week. > > There are two votes on the RFC page: > > 1. Primary vote, requires a 2/3 majority: accept the RFC as proposed. > 2. Secondary vote, simple majority, conditional on the primary > passing: variance marker syntax, `+T` / `-T` (Hack, Scala) vs `in T` / > `out T` (C#, Kotlin). Semantically identical; only the surface syntax > differs. > > The vote started on 2026-06-14 at 16:50 UTC and ends on 2026-06-28 at 17:00 UTC. > > RFC: https://wiki.php.net/rfc/bound_erased_generic_types > Discussion thread: https://news-web.php.net/php.internals/130816 > Implementation: https://github.com/php/php-src/pull/21969 > > Cheers::<>, > Seifeddine.
Hello Internals, This RFC closed with a vote of 7 for, 19 against, and 10 abstentions, therefore, it has been declined. Cheers, Seifeddine.