Re: [RFC] [Discussion] Bound-Erased Generic Types
| From: | Rowan Tommins [IMSoP] | Date: | Fri, 15 May 2026 17:10:29 +0000 |
| Subject: | Re: [RFC] [Discussion] Bound-Erased Generic Types | ||
| References: | 1 2 3 4 5 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-130898@lists.php.net to get a copy of this message | ||
On 15 May 2026 17:11:22 BST, Larry Garfield <larry@garfieldtech.com> wrote:
>> Straw man example:
>>
>> class Foo ~~Foo<T> extends Bar ~~Bar<int,T> {
>> public function foo(string ~~non-empty-string $in): array ~~list<T> {
>> ...
>>
>>
>> Maybe PHP would process the syntax, but not the semantics, of the extra
>> type information; SA tools would then be free to invent new pseudotypes
>> within that framework, without needing to wait for a full PHP release
>> cycle every time.
>
>This feels like it would be isomorphic to attributes, no? Syntax validated, available via
>reflection, but nothing done with it.
Yeah, the basic concept is "attributes on types", but with a more concise syntax.
There are, I think, three basic cases such a syntax would need to handle:
- the "SA type" is the "runtime type" qualified by a generic parameter, either
placeholder or concrete: e.g. Collection vs Collection<T> or Collection<int>, array vs
array<Foo>
- more generally, the "SA type" is the "runtime type" with extra rules applied:
e.g. string vs non-empty-string
- the "SA type" is something that just can't be expressed at all, and the
"runtime type" has to be "mixed" or some other broad type: e.g. "function
foo(): T", where T is a type parameter on a generic class.
For the first two, you can just suffix the extra information rather than repeating yourself, so more
like this:
class Foo~<T> extends Bar~<int,T> {
public function foo(string~non-empty $in): array~<T> {
Other than properties, annotating with "mixed" is redundant anyway, so you could have this
public function bar(~T): ~T {
But if you wanted to enforce the lower bound, you'd have to manually include it:
public function bar(int~T): int~T {
It's obviously not as elegant as making every type string reserved natively, but it allows for
much faster iteration in SA tools, and could replace all their use of docblocks over night.
As and when the language added full runtime support for those types, users could just delete the ~
(or whatever delimiter we used) and opt in to enforcement. "class Foo~<T>" would be
an erased generic, "class Foo<T>" would be a reified or monomorphized one.
Regards,
Rowan Tommins
[IMSoP]