On Fri, Sep 6, 2024, at 1:41 PM, Rob Landers wrote:
> Hello Internals,
>
> I'm going to try something new. I've been working on another RFC called 
> "Typed Aliases" (https://wiki.php.net/rfc/typed-aliases). It is very 
> much a draft and in-flux, and I've already worked out the technical 
> mechanics of it ... however, I am very unhappy with the syntax, and 
> while I have a few ideas about that; I assume this list will be much 
> better at it than me. So, please bring your brushes and paint; I 
> brought a bikeshed.
>
> If you haven't read it already, here's the TL;DR:
>
> - This RFC expands the "use ... as ..." syntax to allow any type 
> expression on the left side. These aliases are PER FILE and expire once 
> the file is compiled.
>
> - This RFC also adds the ability for an alias to survive a file 
> (currently using the "as alias" syntax that I don't like) which 
> actually just creates a special kind of class. When this special class 
> is encountered during type-checking, the alias is expanded and checked. 
> It also allows this via a "type_alias()" function instead of the "use 
> ... as alias ..." syntax.
>
> How it works:
>
> use string as alias MyString
>
> gets virtually compiled into a special class that would look something 
> like this to ReflectionClass (as it is currently):
>
> class MyString extends PrimitiveAlias { 
>   const PrimitiveType aliasOf = PrimitiveType::string;
> }
>
> - Reflection is a bit weird here, and I'm not exactly happy with it; 
> but I'm curious what the list thinks. I'm open to virtually anything 
> that makes sense here; including not allowing ReflectionClass on the 
> type aliases at all.
>
> - Since these are "technically" classes, I went with just "use"-ing 
> them like normal classes. Originally, I had something different: "use 
> alias ..." (like "use function ...") to make it more clear. I will 
> probably go back to this, but I'm curious what others think.
>
> I'm going to take a step back and listen/answer questions. But please, 
> grab a brush and paint.
>
> — Rob

Hi Rob.

First of all, I'm very much in favor of type aliases generally, so thank you 
for taking a swing at this.

Second, it looks like you've run into the main design issue that has always 
prevented them in the past: Should aliases be file-local and thus not reusable, 
or global and thus we need to figure out autoloading for them?  It looks like 
your answer to that question at the moment is "yes". :-)  While I can see the 
appeal, I don't think that's the best approach.  Or rather, if we go that 
route, they shouldn't be quite so similar syntactically.

There seems to be two different implementations living in the same RFC, 
uncomfortably.  In one, it's a compiler-time replacement.  In the other, it's a 
special class-like.  But the RFC seems to go back and forth on what happens in 
which case, and I'm not sure which is which.

However, you have demonstrated a working class-like for it, which is frankly 
the biggest hurdle.  So I think the direction has promise, but should be 
adjusted to go all-in on that approach.

To wit:

typealias Stringy: string|Stringable;
typealias UserID: Int;
typealias TIme: Hour|Minute|Second;
typealias FilterCallback: callable(mixed $a): bool;  (eventually...)

(etc.)

Each of those produces a class-like, which can therefore be autoloaded like a 
class.  The syntax is also a bit closer to a class (or an Enum, I suppose), so 
it's much more self-evident that they are defining a reusable thing (whereas 
"use" does not do that currently).  And the syntax is not stringy, like the 
proposed type_alias(), so it's easier to write.  I wouldn't even include 
type_alias() at that point.  It exists at runtime, so reflection is meaningful.

Aliases can then be used only in parameter, return, property, and instanceof 
types.  Extends and implements are out of scope entirely.

(Whether the keyword is typealias or typedef, uses : or =, or whatever, is its 
own bikeshed I won't dive into at the moment.)

Then, as a separate, entirely optional, maybe even separate RFC (or second 
vote, or whatever), we have a `use string|Stringable as Stringy` syntax.  Like 
all other `use` declarations, these are compile-time only, single-file only, 
and do not exist at runtime, so no reflection.  They compile away just like all 
other use-statements now.

I'm not personally convinced the second is really necessary if we do a good 
enough job on the first, but I'd probably not stand in the way of having both.

Having typealias/typedef as a class-like also opens up some interesting 
potential in the future, because classes have all sorts of other things they 
do, but that is probably too complex scope creepy to get into here so I will 
not go further than that mention.

I suspect there's also other edge case bits to worry about, particularly if 
trying to combine a complex alias with a complex type, which could lead to 
violating the DNF rule.  For example:

typealias Foo: (Bar&Baz)|Beep;

use (Bar&Baz)|Beep as Foo;

function narf(Foo&Stringable $s) {}

With the compile time approach, that would expand to 
`(Bar&Baz)|Beep&Stringable`, which is not a valid type def.

With the runtime approach, I don't know if that could be handled gracefully or 
if it would still cause an error.

I'm not sure what the right solution is on this one, just pointing it out as a 
thing to resolve.

--Larry Garfield

Reply via email to