Syntax tracks PHP RFC: bound-erased generic types. "Per RFC" anywhere below resolves there. Generics are the first substantial chunk of work; many more capabilities are queued up behind them.
See the comparison with TypeScript / Kotlin / Rust for the strategic view, or the syntax tour for a hands-on look at every feature listed under Shipped.
timeline
section Shipped
Core compiler
: six-stage pipeline
: arbitrarily nested generics
: depth cap plus cycle detection
Generic templates
: classes and interfaces and traits
: methods on static and instance receivers
: free functions at any scope
: closures and arrow functions
Bounds
: single upper bound
: intersection multi-bound
: DNF disjunctive normal form
: F-bounded recursion
Defaults
: class level
: method and function level
: closure and arrow level
: forward references
Variance
: covariant and contravariant markers
: position rules
: real subtype edges between specializations
: inner template composition
Pseudo-types
: self static parent in type positions
: constructor turbofish forms
Reified T
: runtime instanceof T
: marker interface per template
Developer experience
: PSR-4 fixtures
: RFC-aligned call-site syntax
: empty turbofish for all-defaults templates
section Next
Editor and tooling
: PhpStorm syntax highlighting
: Language Server Protocol
: Composer plugin for autoload
: Live transpilation via stream wrapper
Compiler ergonomics
: Migration hint for bare call sites
: PHPDoc substitution in generated bodies
: Source maps back to xphp lines
Generic surface
: Generic type aliases
: Variance edges on trait-owned templates
: Branching narrowing precision
section Discovery
Module surface
: internal visibility modifier
: composer-package boundary
: friend declarations
Type system breadth
: Wildcards via marker methods
: Literal types
: Mapped types
: Conditional types
: Discriminated unions
: Generic enums and sum types
: Variadic type parameters
: Per-arg specialization
Ecosystem
: phpstan bridge
: REPL and playground
: Migration tooling from PHPDoc
Explorations
: AST macros and metaprogramming
: Decorators interop
: Community-driven explorations
The diagram above is a glance view; the sections below give each shipped feature with its actual syntax and the rationale behind each upcoming one.
- Six-stage pipeline: parse → hierarchy → method-scope specialize → fixed-point class specialization → rewrite + emit → bound validate.
- Single-param, multi-param, arbitrarily nested generics.
- Type-hint positions everywhere (return, params, properties,
new,extends,implements). - Fixed-point transitive specialization with a 16-iteration depth cap.
- Real cycle detection as a backstop against pathological recursion.
- Generic classes.
- Generic interfaces.
- Generic traits (template only — dropped after specialization
because PHP can't
instanceofa trait).
- Generic methods on static and instance receivers.
- Generic free functions at namespace scope and bare top-level.
- Nullsafe instance turbofish (
$obj?->m::<T>()). - Receiver-type analysis for
$this, typed parameters, typed properties, and local$x = new Foo()assignments. - Conservative branching: post-branch calls de-specialize on reassignment rather than risking a wrong dispatch.
- Same-class merge: post-branch type kept when every reachable arm assigns the same class.
- Generic closures:
function<T>(...) {...}. - Generic arrow functions:
fn<T>(...) => .... - Implicit captures on arrows lifted into the dispatch shape.
- Explicit
use (...)clauses including by-refuse (&$y). - Empty-turbofish
$f::<>()for all-defaults templates.
- Single upper bound (e.g.
T : Stringable) checked at compile time. - Multi-bound intersection (e.g.
T : A & B). - DNF bound (e.g.
T : (A & B) | C). - F-bounded recursion (e.g.
T : Comparable<T>). - Built-in interface whitelist (Stringable / Countable / Iterator / ...).
- Error messages reference the source-level instantiation, not the generated hash.
- Class-level defaults.
- Method and free-function defaults.
- Anonymous closure and arrow defaults.
- Forward references (e.g.
Pair<A, B = A>). - Declaration-time bound check on fully-concrete defaults.
+Tand-Tmarkers on type parameters.- Position rules enforced at parse time (covariant in return, contravariant in param, both forbidden in properties, ctor, bounds, defaults).
- Subtype edges emitted between specializations
(
Producer<Banana>actually extendsProducer<Fruit>whenBanana extends Fruit). - Inner-template variance composition across nested generic args.
self<T>/static<T>/parent<T>in type-hint positions.new self::<T>(...),new parent::<T>(...),new static::<T>(...)at constructor sites.
instanceof T,T::class,T $argruntime-checked via monomorphization.- Marker interface per template so
$x instanceof App\Boxworks across everyBox<...>specialization.
- SHA-256-based generated FQCN; namespace mirrors the template.
- Build-time hash-collision detection with a copy-pasteable widen command in the error.
XPHP_HASH_LENGTHconfigurable (16..64).
- PSR-4 fixtures.
- RFC-aligned call-site syntax (
Name::<...>turbofish). - Empty turbofish (
Name::<>) for all-defaults templates.
- PhpStorm syntax highlighting.
- Language Server Protocol implementation (diagnostics, hover types, goto-definition).
- Composer plugin for autoload registration.
- Live transpilation via stream wrapper (no build step in dev).
- Compile-time migration hint for bare
Name<...>(...)call sites that point users at the::<...>turbofish. - PHPDoc substitution in generated bodies so generated
.phpreads naturally. - Source maps (stack traces back to
.xphplines).
- Generic type aliases (e.g.
type Pair<A, B> = ...). - Variance edges on trait-owned templates.
- Branching narrowing precision: today conservatively de-specializes when arms disagree; will track unions with runtime dispatch instead.
Items in this section are open design questions, not committed work. Each has a sketched answer to "would xphp support this?" but the implementation knobs are still being settled. Treat each Discovery entry as a starting point for community discussion, not a guarantee to ship.
internalvisibility modifier: replace PHPDoc@internalhints with a first-class keyword that the compiler enforces at the Composer-package boundary. Cross-package references to internal symbols become compile errors. The open question is whether to derive the boundary fromcomposer.jsonalone or via an explicitxphp.jsonfor sub-package granularity.- Friend declarations: per-symbol overrides to the package boundary
(the Rust
pub(super)/ Kotlin@PublishedApishape).
- Wildcards via marker methods: populate the empty marker
interface with upper-bound-typed read-only methods so
function f(Box $b)becomes a realBox<*>you can read through, matching Kotlin and TS wildcard ergonomics. The existential position already exists (the bare template-name form); only the method surface is missing. See runtime semantics → marker interfaces as wildcard-shaped positions. - Literal types (finite string / int sets).
- Mapped types over generics (
Partial,Readonly,Pick). - Conditional types (branching at the type level).
- Discriminated unions with exhaustiveness checks.
- Generic enums / sum types (Option of T, Result of T E).
- Variadic type parameters.
- Per-arg specialization (different body when T = int).
- phpstan / psalm bridge.
- REPL / playground.
- Migration tooling: lift PHPDoc
@templateannotations to xphp generic params.
- AST macros / metaprogramming.
- Decorators-as-attributes interop.
- Whatever the community wants to explore.