Reified generics by php-generics · Pull Request #21317 · php/php-src

3 min read Original article ↗

DanielEScherzer

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.

@php-generics

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.
- Fix optimizer type inference: add MAY_BE_OBJECT for generic class types
  (ZEND_TYPE_IS_GENERIC_CLASS) in zend_convert_type(), fixing memory leaks
  and incorrect type inference when JIT compiles functions with generic
  class type parameters like Box<int>
- Fix JIT trace recording: handle ZEND_INIT_STATIC_METHOD_CALL generic
  args in trace entry and properly skip ZEND_GENERIC_ASSIGN_TYPE_ARG
  opcodes during tracing
- Fix JIT IR: handle generic_args in INIT_STATIC_METHOD_CALL compilation
- Update reflection tests for generic class entries (ZendTestGenericClass)
- Fix optimizer: handle ZEND_GENERIC_ASSIGN_TYPE_ARG in SSA/optimizer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@php-generics @claude

…review feedback

- Fix opcache SHM persistence for interface_bound_generic_args and
  trait_bound_generic_args HashTables (zend_persist.c, zend_persist_calc.c,
  zend_file_cache.c)
- Fix object type fast-path bug in zend_check_type that skipped instanceof
- Optimize generic type checking with inline mask checks to avoid
  zend_check_type_slow() calls on the hot path
- Optimize ZEND_NEW handler to use inlined i_zend_get_current_generic_args()
  and skip context resolution when not in a generic context
- Apply PR review feedback: convert ZEND_GENERIC_VARIANCE_* and
  ZEND_GENERIC_BOUND_* defines to C23_ENUM, remove underscore prefixes
  from struct names, use proper enum types for variance/bound fields,
  use typedefs instead of struct tags in globals

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@php php locked as too heated and limited conversation to collaborators

Mar 1, 2026

@php php unlocked this conversation

Mar 1, 2026
…ak, compile warning

- Fix segfault with opcache.protect_memory=1: add zend_accel_in_shm()
  checks before calling zend_shared_memdup_put_free() on generic type
  refs, class refs, wildcard bounds, generic args, params info, and
  bound generic args hash tables that may already be in read-only SHM
  when inheriting from previously-persisted interfaces.

- Fix 28-byte memory leak in static generic calls with opcache optimizer:
  when pass 4 (func_calls) + pass 16 (inline) combine to inline a
  Box<int>::hello() call, the INIT_STATIC_METHOD_CALL opcode is NOPed
  but the generic args literal at op1.constant+2 was never released.
  Release it before zend_delete_call_instructions NOPs the opcode.

- Fix -Werror compile failure: remove unused 'key' variable in
  zend_verify_generic_variance() by switching to ZEND_HASH_MAP_FOREACH_PTR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… cache

Progressive inference: when a generic class is instantiated without type
args (e.g., `new Collection()`), the object enters progressive mode that
tracks min-type (widened from never) and max-type (narrowed from mixed).
Values widen the lower bound; passing to typed functions narrows the upper
bound. When min equals max, the object auto-freezes to regular generic_args.

Generic args interning: deduplicate identical generic_args via a global
HashTable keyed by type content hash, reducing memory for monomorphic
usage patterns.

Inline cache for generic class type hints: cache CE lookup and last-seen
generic_args in run_time_cache slots, turning O(n*m) compatibility checks
into O(1) for the common monomorphic case.

Also fixes subtype compatibility so Box<int> is accepted where
Box<int|string> is expected (subset check instead of exact equality).

92/92 generics tests, 5356/5356 Zend tests, 891/891 opcache tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>