Hi Benjamin,

Thanks for thinking through this.

I'd push back on the declare approach.

> I'd prefer to land with a version without declare's, but in this instance I 
> fear we need a temporary solution to get this off the ground until all the 
> pieces are in place.

The "temporary until all the pieces are in place" framing doesn't
quite hold. There's no committed path or timeline for reified
generics, no implementation work currently in flight, and no clear
answer to whether the runtime cost of reification can be brought to
acceptable levels. A declare added on the assumption that it
transitions to something else later would, realistically, remain in
PHP for the indefinite future. PHP doesn't remove declare directives;
`declare(ticks)` remains in the language decades after anyone uses it
(at least as far as I'm aware).

> declare(erased_types=generic_only);
>
> If this is not specified, then using generic syntax will throw an exception.

Requiring `declare(erased_types=...)` to use the syntax raises the
adoption barrier rather than lowering it. The point of native generics
over docblocks is that the syntax should be easier and more
discoverable, not require ceremony at the top of every file. A library
exposing generic APIs would need the `declare` in every file.
Consumers would copy-paste it as boilerplate, and new users would hit
"why isn't my generic syntax working" errors and have to learn about a
`declare` they didn't need to know about.

`declare(strict_types=1)` has already shown how "opt-in via declare"
plays out in practice. It shipped as a per-file choice but the
ecosystem turned it into mandatory boilerplate. Linter rules, style
guides, and codebase conventions all push toward "every file gets the
declare." I have a linter rule that adds it to every PHP file in my
projects, including config files that just `return [...];`, because
remembering when it does and doesn't matter is mental overhead I'd
rather not pay. Most serious PHP teams I know operate the same way.
Adding `declare(erased_types=...)` would create the same dynamic
within months, except now codebases have two declares to maintain
consistency on, and any file using generic syntax errors without it.
You'd ship a feature that's nominally opt-in but ecosystem-mandatory,
which is the worst of both worlds.

> And it could give the option in the future to add two modes:
>
> declare(erased_types=all);
> declare(erased_types=none);

The granularity is wrong for the future direction you're imagining. If
reified generics ever ship, the right opt-in is per-class or
per-function, not per-file. The strictness-vs-cost tradeoff is a
code-level decision; a single class wanting reified semantics
shouldn't require everything else in the file to share that choice.
Hack made this exact decision: their `reify` keyword applies
per-type-parameter (`function foo<reify T>(T $x)`), not per-file. The
granularity matches the design space.

`strict_types` is the PHP precedent worth comparing to, but in a
different way than your proposal uses it. It's per-file because scalar
coercion is a clear binary with a sensible default. Generics doesn't
have a clear binary; it has a spectrum (fully erased -> bound erased
-> reified -> fully dependent?), and the design space for "what gets
enforced at runtime" is still being explored. Locking in a syntactic
mechanism now for a feature whose semantics aren't settled would
constrain future RFCs unnecessarily.

The RFC as written doesn't preclude any of the directions you're
imagining. If reified generics ever ships, it'll ship with an opt-in
mechanism that fits the granularity of the actual feature, debated and
decided at the time it's proposed. That's the right place to make that
decision, not pre-loaded into this RFC.

Cheers,
Seifeddine.

Reply via email to