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

Reply via email to