Hey Seifeddine,
On 10.5.2026 21:02:32, Seifeddine Gmati wrote:
Hello Internals,
I'd like to start the discussion on a new RFC adding bound-erased
generics types to PHP.
Generic type parameters can be declared on classes, interfaces,
traits, functions, methods, closures, and arrow functions, with
bounds, defaults, and variance markers. Type parameters erase to their
bound at runtime; the pre-erasure form is preserved for Reflection and
consumed by static analyzers.
- RFC:https://wiki.php.net/rfc/bound_erased_generic_types
- Implementation:https://github.com/php/php-src/pull/21969
Thanks,
Seifeddine.
I have a bunch of questions and feedback:
The requirement of ordering seems unnecessary to me - why would we not
want to be able to write <T: Box<U>, U: Box<T>>. Alternatingly recursive
types are not unheard of. Seems like an arbitrary restriction; and for
compilation purposes it only requires collecting all parameter names
before evaluating them.
Your tests also show restrictions around intersection types, e.g. "Type
parameter T with bound mixed cannot be part of an intersection type" for
'class Foo {} function x<T>(): T & Foo {}'. What's the motivation behind
it? This looks fairly natural to me: x() promises to return an instance
of Foo which also fulfills the bound T. Any child class of Foo which
happens to implement T will fulfill that contract.
I would like to plead to skip the arity validation, except for "more
parameters than allowed":
- This inhibits graceful addition of generics - any library adding them
requires callers to immediately update all caller sites.
- It would also make addition of generics to Iterator classes etc.
completely uncontroversial.
- This would be more in line with PHP's general "no type is effectively
the highest possible bound" approach. I.e. "class A extends Box" and
"class A extends Box<mixed>" would be equivalent.
- This would also allow for future incremental runtime generics: you'd
start with <never> and as you call stuff with values, the type becomes
broader.
This is the one thing which makes the whole RFC a non-starter for me if
required:
Typing is optional in PHP!
Your tests show that this specific example is allowed, which strikes me
as odd. Why would we not check the arity here?
class Container {}
function f(Container<int> $x): Container<string> { return $x; }
Diamond checks:
Are these necessarily problematic? if you inherit Box<int> and
Box<string>, it simply means that the generic parameter, when placed in
a contravariant location will accept int|string, when placed into return
or property types it'll evaluate to never.
If you disagree (that's possibly fine), a diamond covariant parameter
should be allowed in any case though, i.e. if Box<+T>, then an interface
shall be able to implement Box<string>, Box<int>. At least at a glance I
don't find such a test - if it already works, nice, then please just add
the test!
Is class ABox implements Box<self> allowed, or do we need to write
implements Box<ABox>?
I'm also not sold on the turbofish syntax. I hate it in Rust, which I
have to write nearly daily. I forget these :: SO often. And then the
Linter yells at me and I correct it.
I understand that there are language limitations, in particular with the
array syntax, but honestly, I'd rather just have the parser shift in
favor of the existing syntax - for these rare conflicting cases forcing
parenthesis around the generic would be nicer, i.e. `[A<B, B>(C)]` would
continue carrying the meaning it has today, and we'd require writing
`[(A<B, B>(C))]` for that case.
I'm not quite sure if + and - are the proper choices. I'm more used to
C# myself with in and out being more obvious to me. I also admit that I
initially assumed "+" to be covariant - the sum of stuff accepted, and
"-" contravariant, subtracting what can be returned. But this particular
bikesheds color is not too important to me.
Otherwise, it's a pretty solid RFC which should be extensible with
runtime generics eventually. (In particular runtime generics on the
class inheritance level should be a no-brainer to add with the existing
syntax.)
Thanks,
Bob