Hi Rowan,
I think the core premise is empirically testable, and the test has
already run: PHP has had generics in docblocks for a decade, used by
every major framework. If "someone publishes a library with broken
type information and downstream users get surprised by runtime
behavior" were a real failure mode, we would see it today, as Laravel,
Doctrine, Symfony, PSL, and PHPUnit all run on `@template`-annotated
code and are consumed by millions of downstream applications. The
failure mode you describe is the one that would occur most under the
current system, yet it doesn't.
The reason it doesn't is that the people who care about generic type
information are the same people who run static analysis. The
intersection of "uses generics" and "doesn't run a static analyzer" is
approximately empty in practice. Native syntax doesn't change that. It
just gives current users a better way to express what they mean.
Regarding the second point, the proposed implementation is enforced
more at compile time than docblocks are. The engine validates:
- Generic parameter declarations: arity cap, bound conformance,
default-vs-bound, no top-level self-reference, no shadowing.
- Variance soundness: Covariant parameters cannot appear in input
positions, contravariant parameters cannot appear in output positions,
at the declaration site.
- Inheritance: arity at extends/implements/use, bound conformance with
bound-on-bound for forwarded parameters, diamond detection, parametric
LSP into properties/hooks/trait methods/inherited methods.
- Turbofish at runtime: arity and bounds at every call site that
supplies type arguments.
Docblocks validate none of this; the engine accepts a docblock that
says anything. Native syntax is a stricter contract than what the
ecosystem has today.
Finally, on the "broken code published to Packagist" concern: someone
can already publish:
```
/** Map the given array using the provided callable. */
function map(array $array, callable $fn): array {
return [];
}
```
The signature claims to take an array and a callable, returns an
array. The body returns an empty array regardless. Today, this passes
every PHP type check, parses without error, runs in production, and is
technically conformant. Nobody is calling for PHP to ship a [precious
type system](https://www.youtube.com/watch?v=Ay-gnCqDw9o) with support
for
[lifetimes](https://doc.rust-lang.org/rust-by-example/scope/lifetime/explicit.html),
and [const generic
parameters](https://doc.rust-lang.org/reference/items/generics.html#r-items.generics.const.usage)
to prevent it (e.g. `function map<'a, I, O, const SIZE: int>(vec<&'a
I, SIZE> & ![] $array, fn(&'a I): &'a O $fn): vec<&'a O, SIZE> &
![]`). The same logic applies to generics: the contract a user writes
is the contract a user is responsible for honoring, and PHP's role is
to provide tools for expressing and checking that contract, not to
make every conceivable mistake impossible.
The options you listed both close off paths the PHP ecosystem has
already shown it doesn't want.
Cheers,
Seifeddine.