Conversation
Implement reified generics with type parameter declarations on classes, interfaces, traits, and functions. Includes type constraint enforcement, variance checking, type inference, reflection API support, and comprehensive test suite.
Refcount zend_generic_args to eliminate per-object alloc/dealloc — new Box<int>() now adds a refcount instead of deep-copying, removing 4 allocator round-trips per object lifecycle. Inline resolved_masks into the args struct (single contiguous allocation). Fix crash when creating generic objects inside generic methods (new Box<T>() inside Factory<int> ::create()) by resolving type param refs from the enclosing context.
DanielEScherzer
left a comment
There was a problem hiding this comment.
Please have this target the master branch of PHP, patch-specific branches like 8.5.4 are for release management, and older version branches like 8.5 are for bugfixes or security fixes, new feature are added to the master branch
|
Hi @php-generics 👋 May I ask two things:
|
Won't this break existing serialized strings in case of an upgrade? I think of having existing serialized strings in a database and upgrading it would break it. Also, usually isn't there an RFC for these kind of things? |
|
This requires an RFC and discussion on the internals mailing list. My initial impression is this was coded 100% by an AI agent, and perhaps the @php-generics account itself was created by an AI agent. All code in this PR should be highly scrutinized to ensure it's not introducing vulnerabilities. |
|
The only tests for generic functions which I see are simple identity functions. Can we also have tests which show proper behaviour for generics in return types (e.g. |
If it's any indication the first line of the summary contains an em-dash, there are 12 of them in the relatively short description and they are used throughout the code comments. I'd guess at 100% of it. |
Not only dashes. Whole structure of PR description, benchmark results, comments in Code, tests structure. A lot of indicators of AI. I'm not against AI, but it's sad that author didn't even invest time to learn how such changes should be performed in PHP - through RFC. It's just one more prompt to learn it. |
Add reified generics to PHP
Summary
This PR adds reified generics to the Zend Engine — generic type parameters that are preserved at runtime and enforced through the type system. Unlike type erasure (Java/TypeScript), generic type arguments are bound per-instance and checked at every type boundary.
Syntax
Generic classes
Multiple type params, constraints, defaults
Variance annotations
Wildcard types
Generic traits
Generic functions and closures
Nested generics
Static method calls with generics
instanceof with generics
Inheritance
Runtime enforcement
All type boundaries are checked at runtime:
new Box<int>("x")throws TypeError$box->set("x")throws TypeError when T = intreturn "x"from a method declared(): Twith T = int throws TypeError$box->value = "x"throws TypeErrorCannot assign string to property Box<int>::$value of type intEcosystem integration
ReflectionClass::isGeneric(),::getGenericParameters(),ReflectionObject::getGenericArguments(),ReflectionGenericParameter(name, constraint, default, variance)serialize(new Box<int>(42))producesO:8:"Box<int>":1:{...},unserialize()restores generic argsvar_dumpshowsobject(Box<int>)#1, stack traces showBox<int>->method()Edge cases covered
new class extends Box<int> {})Collection<int>triggers autoload forCollection)class_aliasinherits generic paramsnew Box<T>()inside generic methods/factories resolves T from context)void/neveras type argsPerformance
Benchmarked at 1M, 10M, and 100M iterations on arm64, both master (PHP 8.5.3 NTS) and generics branch (PHP 8.5.4-dev NTS) built as release binaries.
Generic args use refcounted sharing —
new Box<int>()adds a refcount instead of deep-copying, eliminating 4 allocator round-trips per object lifecycle. Pre-computed scalar bitmasks are stored inline (no separate allocation).Generic vs non-generic overhead (same binary)
Results stabilize at higher iteration counts as warmup and noise are amortized.
Absolute throughput (generics branch, JIT, ops/sec)
new GenericBox<int>(42)GenericBox<int>->set+getGenericBox<int>->value = Nnew GenericBox<GenericBox<int>>Non-generic regression check
Non-generic classes (
PlainBox,UntypedBox) were benchmarked on both master and the generics branch under identical conditions (release NTS, same image). Results at 100M iterations (most stable) confirm zero performance regression on existing code paths:The +8 bytes per object is the
generic_argspointer field added tozend_object(NULL for non-generic objects). Throughput deltas are within cross-build variance (+-5%); no systematic code path slowdown was observed.Memory overhead
Generic objects with explicit type args have zero memory overhead vs non-generic typed objects — refcounted args are shared with the compiled literal. Inferred args (+20 bytes) allocate a new args struct. Nested generics benefit most from sharing (90 bytes vs 210 bytes before optimization).