On Mon, Jun 8, 2026, at 20:47, Frederik Bosch wrote:
> On Mon, 08 Jun 2026 15:13:11 +0000, Rob Landers wrote:
> > On Sun, May 10, 2026, at 21:02, 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.
> > >
> >
> > Hello internals,
> >
> > For those not in discord, I spent nearly a week attempting to 
> implement reified generics on top of this branch to see how challenging 
> it would be.
> >
> > I have a working implementation: 
> https://github.com/php/php-src/compare/master...bottledcode:php-src:reify
> >
> > Disclosure: AI assisted in tests and memory leak bug hunts.
> >
> > *The Approach*
> >
> > Classes are monomorphized and similar to Gina's substitution approach 
> -- a monomorphized class shares as much memory as the templated class as 
> possible, mainly holding its substitutions. This happens during runtime, 
> only when it cannot be done at compile time (which is mostly already 
> handled by this branch).
> >
> > Functions/methods get bound in call frames and share the substituted 
> types from their parent (class, outer closure, etc).
> >
> > *The Result*
> >
> > Given:
> >
> > class Box<T : object> {
> > public function __construct(public T $value) {}
> > public function get(): T { return $this->value; }
> > }
> >
> > The following work as expected:
> >
> > $b = new Box::<DateTime>(new DateTime());
> > $b instanceof Box<DateTime>; // true
> > $b instanceof Box<Request>; // false
> >
> > Whereas the following will generate errors at runtime:
> >
> > $b = new Box::<DateTime>(new Response);
> > $b = new Box::<int>('password 123');
> >
> > *Performance*
> >
> > Importantly, for code that does not use generics: there is no impact 
> (generics specific code is skipped).
> >
> > For a call (either `new` or a function/method call) that does use 
> generics, the call takes **up to** ~2x a non-generic call. By using 
> Seif's PSL library as a test, and converting it to generics, we were 
> able to show that benchmarks go about 1.3-1.5x slower when comparerd to 
> *no type checking *on the original (ie, `mixed`).
> >
> > Of note, when doing manual type checking with `mixed`, the 
> performance cost of generics is roughly the same. That means:
> >
> > function foo<T>(T $val): T {
> > return $val;
> > }
> >
> > function bar(mixed $val): mixed {
> > if (is_int($val)) { return $val; }
> > throw new Exception();
> > }
> >
> > These two functions will have roughly the same performance, with some 
> jitter depending on the complexity of the type being checked.
> >
> > I think this is important to point out: if checked generics are 
> "about as fast as manual checks", then it behooves us to go for checked 
> generics and not erased generics, which would force everyone to type 
> check manually. Whether that happens in this same RFC or a later one is 
> an open question.
> >
> > *Related Bug*
> >
> > One other issue discovered while working on this branch, the 
> following doesn't behave as written with erased generics:
> >
> > try {
> > do_http_call();
> > } catch(HttpError<NotFound> $e) {
> > // ignore
> > } catch(HttpError<Forbidden> $e) {
> > // alert: api key has been revoked
> > }
> >
> > With erased generics, the latter is never hit as it is erased to just 
> `HttpError` ... no warning, no error ... it doesn't work as written.
> >
> > To me, that is a massive footgun. Checked generics work as written.
> >
> > *Limited Inference*
> >
> > Secondly, I was able to get limited inference "for free" with this 
> approach. This works:
> >
> > class Foo { public string $kind = 'foo'; }
> > class Bar { public string $kind = 'bar'; }
> >
> > function kind<T : object>(T $x): string {
> > return T::class;
> > }
> >
> > echo kind(new Foo) . "\n";
> > echo kind(new Bar) . "\n";
> > // outputs:
> > // Foo
> > // Bar
> >
> > I would appreciate it if others could review the code to verify my 
> results and implementation. Assuming it is soound, I believe this should 
> resolve issues people have been raising with partially erased generics 
> and give us sufficiently complete generics.
> >
> > Whether to write a new RFC or merge into a single RFC is still being 
> discussed in discord and open for discussion here.
> >
> > PS. My normal email address is broken, so this is a new email address 
> on this list. 👋
> >
> > — Rob
> 
> 
> Hi Rob,
> 
> Assuming that both reified and bound-erased generics hit the same lines 
> of code, and thus that the exception example you make will/can be fixed 
> in the bound-erased version, why not offer both versions? Would 
> developers not choose to run tests, CI/CD, web development and cli 
> applications with the reified generics enabled, while in web production 
> they might choose to run with the faster bound-erased mode?
> 
> Regards,
> Frederik

It's not just about "speed" -- if you want speed, you can just run your code 
through a script and delete types altogether and ship it to production today. 
PHP does *some* pattern matching *today *based on the types you write: catching 
exceptions goes to the branch given the specific type. If we want to actually 
implement pattern matching, we need the full type, not the erased one. If you 
ever want typed arrays/structs that actually prevent you from adding "foobars" 
to an array of ints ... you need the full type.

Erased generics don't prevent typos, they don't actually enforce correctness, 
nor do they provide readability. The latter one being the worst offender, in my 
humble opinion. I write code expecting it to do what it says, and PHP has been 
moving in that general direction for over a decade. It seems unreasonable to 
take a step back, for "speed".

— Rob

Reply via email to