Hi Larry,

> Consider, if I read the RFC correctly this will (correctly) error at link 
> time:
>
> interface Foo<T: DateTimeInterface> {}
> class Bar implements Foo<User> {}
>
> So that part of the type system is enforced. It will therefore be natural for 
> users to expect that
>
> new Baz<User>(new Product $p);
>
> will error on them.

I think there's a misreading here that's worth addressing directly.
Turbofish on `new` does check arity and bound. Given:

class Baz<T : DateTimeInterface> {
    public function __construct(public T $date) {}
}

new Baz::<string>(new DateTimeImmutable());  // TypeError: string does
not satisfy DateTimeInterface

That's enforced at runtime at the turbofish site. What is *not*
enforced is the relationship between the turbofish argument and the
constructor parameter. The runtime won't reject `new
Baz::<DateTimeImmutable>(new DateTime())` because `T` erases to
`DateTimeInterface` for parameter-checking purposes. The user-facing
distinction is "turbofish bounds are checked, parametric relationships
across multiple uses of T are not.", That's the bound-erasure trade.

> But count me out for opt-in enforcement. We either enforce it or we don't. 
> [...] If call-site enforcement can be made cheap enough to include, it's 
> cheap enough to *always* include.

The premise here is that runtime enforcement can be made cheap enough
in the foreseeable future. That's the universal-reification path, and
no implementation has yet demonstrated it works under PHP's
compilation model. Until we have a solution, "always enforce" isn't a
deferrable engineering choice, it's a constraint we currently can't
meet. If someone produces a viable design, the path forward is either
an opt-in mechanism (using the #[ReifiedGenerics] attribute, the reify
keyword, or the declare directive) or a complete switch to reified
generics, which would involve a breaking change (BC break). What I
won't commit to is "we'll just make it cheap enough later" when the
engineers who have tried haven't been able to.

> I previously suggested some sort of AOT first-party checker

The same constraint applies. Building a first-party static analysis
(SA) tool competitive with PHPStan, Psalm, or Mago is a year long
project requiring a dedicated team, which we do not have. I covered
this in detail earlier in the thread. Even if the engineering were
tractable, there's no one currently positioned to do it: I'm not, the
Foundation isn't proposing it, and the proposal would be evaluated
against the existing tools that have a decade of work behind them.

The alternative is what PHP already has: four mature SA tools (Phan,
Psalm, PHPStan, Mago, in order of seniority), each maintained, each
with significant production use. PHP's documentation could point users
to these. a recommendation toward the ecosystem standard rather than
an NIH reimplementation. That's a much smaller and more tractable
thing to do than building a fifth tool.

> Advanced users, who you say are the target audience, will still have to work 
> with both syntaxes; and will still find differences between tools which 
> aren't covered by the subset of validation that PHP has taken ownership of.

I responded to this in the Rowan reply: PHP's type system has grown
one feature per RFC for a decade, and the fact that this RFC doesn't
ship `class-string<T>`, `non-empty-string`, literal types, or negated
types isn't unique to generics; it's how every type-system RFC has
worked. Those are each their own RFC, and each can land later.

> Is there even a viable future way to include such more-complex checks 
> natively in the future?

Yes, and several of them are on the roadmap I'm planning to file once
this RFC settles: literal types (extending `null`/`true`/`false` to
string/int/float literals), negated types (`!""` and similar), tuples,
shapes, typed arrays, and a couple of others. The intent is to
incrementally move the parts of the PHPDoc type system that PHP can
express into PHP itself, so that docblocks return to being
documentation rather than the language's type-system fallback. None of
those RFCs make sense in isolation; they need this one to land first
because they all assume the type-parameter, and generic-instantiation
that this RFC introduces.

Cheers,
Seifeddine.

Reply via email to