[PHP-DEV] [RFC] Type Aliases

Hello Internals,

I’d like to request your comments on type aliases (not to be confused with typedefs) at https://wiki.php.net/rfc/typed-aliases

TL;DR (it’s actually rather short):

Following the same syntax as other use’ing statements, you can alias a type:

use type int|float as Number;

function sum(Number $a, Number $b): Number { return $a + $b; }

You can also include types, with some restrictions:

include types 'math-types.php';

function sum(Number $a, Number $b): Number { return $a + $b; }

These are compile-time replacements that serve to make code more readable and reduce repetition with no runtime overhead. Type aliases follow the same scoping rules as other use imports.

— Rob

Can we follow the approach other languages used for their type alias implementation?

typedef UserType: string|null;

// or

typedef UserType = UserStatus|AlternativeType|null;

We could also introduce a way to group multiple type alias in a single file:

namespace App;

typedef TypeGroup {
typedef UserType: string|null;
typedef CustomType: \MyCustomType|\AnotherType|null;
}

Then, it can be imported by using:


// single
use type UserType;

// group
use type App\TypeGroup;

This feels more familiar, nice and structured.

NOTE: it’s just a suggestion from my own view.

On Tue, 2 Dec 2025, 11:24 pm Rob Landers, rob@bottled.codes wrote:

Hello Internals,

I’d like to request your comments on type aliases (not to be confused with typedefs) at https://wiki.php.net/rfc/typed-aliases

TL;DR (it’s actually rather short):

Following the same syntax as other use’ing statements, you can alias a type:

use type int|float as Number;

function sum(Number $a, Number $b): Number { return $a + $b; }

You can also include types, with some restrictions:

include types 'math-types.php';

function sum(Number $a, Number $b): Number { return $a + $b; }

These are compile-time replacements that serve to make code more readable and reduce repetition with no runtime overhead. Type aliases follow the same scoping rules as other use imports.

— Rob

On Tue, Dec 2, 2025, at 23:41, Oladoyinbo Vincent wrote:

Can we follow the approach other languages used for their type alias implementation?

typedef UserType: string|null;

// or

typedef UserType = UserStatus|AlternativeType|null;

We could also introduce a way to group multiple type alias in a single file:

namespace App;

typedef TypeGroup {
typedef UserType: string|null;
typedef CustomType: \MyCustomType|\AnotherType|null;
}

Then, it can be imported by using:


// single
use type UserType;

// group
use type App\TypeGroup;

This feels more familiar, nice and structured.

NOTE: it’s just a suggestion from my own view.

On Tue, 2 Dec 2025, 11:24 pm Rob Landers, rob@bottled.codes wrote:

Hello Internals,

I’d like to request your comments on type aliases (not to be confused with typedefs) at https://wiki.php.net/rfc/typed-aliases

TL;DR (it’s actually rather short):

Following the same syntax as other use’ing statements, you can alias a type:

use type int|float as Number;

function sum(Number $a, Number $b): Number { return $a + $b; }

You can also include types, with some restrictions:

include types 'math-types.php';

function sum(Number $a, Number $b): Number { return $a + $b; }

These are compile-time replacements that serve to make code more readable and reduce repetition with no runtime overhead. Type aliases follow the same scoping rules as other use imports.

— Rob

A typedef is a bit different. It’s a real type.

I’m working on combining my Records RFC and the pattern matching RFC (assuming it gets accepted). Thus you’ll be able to do something like:

typedef UserID: int is > 0;

Which would basically compile into the previous Record:

record UserID(int $value) { $value > 0 }

-ish.

That gets you value semantics and with a bit more work, inherited operators (numbers act like numbers, arrays act like arrays, strings like strings, etc).

It doesn’t need pattern matching, but it really helps. This is just aliases, which are complementary but separate.

PS: please remember to bottom post (write your reply under the person you are replying to).

— Rob

On Tue, Dec 2, 2025, at 4:23 PM, Rob Landers wrote:

Hello Internals,

I’d like to request your comments on type aliases (not to be confused
with typedefs) at PHP: rfc:typed-aliases

TL;DR (it’s actually rather short):

Following the same syntax as other use'ing statements, you can alias a type:

use type int|float as Number;

function sum(Number $a, Number $b): Number { return $a + $b; }

You can also include types, with some restrictions:

include types 'math-types.php';

function sum(Number $a, Number $b): Number { return $a + $b; }

These are compile-time replacements that serve to make code more
readable and reduce repetition with no runtime overhead. Type aliases
follow the same scoping rules as other use imports.

— Rob

Thanks, Rob! This does seem like a good low-hanging fruit piece to attack. Most importantly, I don't see anything here that would preclude more formal TypeDef structures in the future.

A few other notes:

- Should `mixed` be allowed as in an alias definition? Since `mixed` matches anything, it would render the entire alias redundant as it's then equivalent to `mixed`.

- What if any conflict resolution is there if you include two different type files that declare the same alias?

- Because it's all compile time, I assume two aliases with the same source but different names become compatible?
Eg:

use type string|Stringable as Stringy;
use type string|Stringable as Stringish;

Those are mutually compatible in practice, yes?

- Are aliases case sensitive? You're using Capitalized names in the RFC, but I don't know if that means anything... (Please state in the RFC.)

- The "include type" syntax implies that it would not be possible to pre-load aliases via Composer autoload or opcache preloading. (Well, maybe the latter.) That feels like an unnecessary limitation, and is my main issue with the current design. Some way to have project-wide or package-wide definitions would be very helpful, without needing a manual include statement in every file.

- Does it matter where the `include type` statement goes? Must it be at the top under the `use` statements, or could it be anywhere?

- Not allowing `use` statements in type include files: This feels like a very unfortunate limitation. Is there any way to obviate that? It's all compile time, as you said...

Overall, I am tentatively in favor.

--Larry Garfield

On Wed, Dec 3, 2025, at 16:51, Larry Garfield wrote:

Thanks, Rob! This does seem like a good low-hanging fruit piece to attack. Most importantly, I don’t see anything here that would preclude more formal TypeDef structures in the future.

A few other notes:

  • Should mixed be allowed as in an alias definition? Since mixed matches anything, it would render the entire alias redundant as it’s then equivalent to mixed.

This is already a compiler error: “Type mixed can only be used as a standalone type.” That being said, I guess this could be used to rename “mixed” to something else, but I don’t know why you’d want to do that. I’d be open to throwing it in with “never” and “void”, but “mixed” is an actual type that can be returned, which is why it wasn’t.

  • What if any conflict resolution is there if you include two different type files that declare the same alias?

It works the same as use in other ways:
“Cannot use type DateTimeImmutable as DT because the name is already in use”

  • Because it’s all compile time, I assume two aliases with the same source but different names become compatible?
    Eg:

use type string|Stringable as Stringy;
use type string|Stringable as Stringish;

Those are mutually compatible in practice, yes?

That’s correct. You’d need typedefs to be able to enforce a difference there.

  • Are aliases case sensitive? You’re using Capitalized names in the RFC, but I don’t know if that means anything… (Please state in the RFC.)

Good catch; will do. For the record, it’s as type-sensitive as any other type in PHP (as in not case-sensitive at all).

  • The “include type” syntax implies that it would not be possible to pre-load aliases via Composer autoload or opcache preloading. (Well, maybe the latter.) That feels like an unnecessary limitation, and is my main issue with the current design. Some way to have project-wide or package-wide definitions would be very helpful, without needing a manual include statement in every file.

Currently, we don’t have packages or even “real” namespaces, so as much as I’d want this, there’d need to be some settling on what authority a namespace really has. The amount of pushback that I got on the namespace-private RFC indicates to me that people don’t want namespaces to have any authority, but they want “packages” but nobody knows what a package is … but clearly, namespaces aren’t anything.

I digress. Sadly, no, this isn’t possible with the current design. That doesn’t mean it can’t be possible in the future, though, with some autoloading overhaul or changes (see: Gina’s Core Autoloading RFC under Drafts). But currently, it has to pull off some shenanigans to compile the file in the file (instead of during runtime like require and include). Hence why type-files may only contain types and no other code.

Basically, we just have to have some way to tell the compiler “also include these type aliases when compiling this file” – that could be “every file from here on out gets the aliases” (ie, a use global type float|int as Number – or something like that), or “include this type file” into this source file.

The former would work best with today’s current autoloader, but it would be “trapped” in that paradigm and would make it harder to eventually create a “type autoloader”. I could be wrong, though, and I’m open to other people’s ideas and opinions. I don’t like the current implementation very much, and while the former is much simpler to implement, I’m reasonably confident the current implementation can be improved.

  • Does it matter where the include type statement goes? Must it be at the top under the use statements, or could it be anywhere?

It’s basically a “copy this file right here”, so it doesn’t need to necessarily be at the top, but trying to include it in a function would create a syntax issue. I should clarify this in the RFC.

  • Not allowing use statements in type include files: This feels like a very unfortunate limitation. Is there any way to obviate that? It’s all compile time, as you said…

I had to go make sure I didn’t have a typo in there. I assume you mean namespaces? Namespaces in type files have a very weird problem once they get included. For instance, namespaces are also compile-time prefixes, and using statements are per namespace, not per file. By eliminating the namespace, it becomes unambiguous that it’s per-file. That prevents shenanigans like:

// my-types.php
namespace Hello {
  use int|float as Number; // at the end of the namespace, the "use" is lost
}

namespace World {
  use Number as Foo; // Number isn't a thing, neither is Hello\Number
}

Even just declaring a namespace for resolving types would create interesting effects:

// my-types.php
namespace Foo;

use int|float as Number; 
// alias actually becomes Foo\Number and gets cleared at the end of the file

In all reality, I generally expect tooling to make this easier. Like right-click the alias in your IDE and select: “move to type file” or something like that and have it automatically appended to your project’s type file. Same with typing a type in a type file and having it automatically include it, just like it will automatically “use” a type outside of the current namespace for you.

Overall, I am tentatively in favor.

–Larry Garfield

Thanks!

— Rob

On Wed, Dec 3, 2025, at 10:41 AM, Rob Landers wrote:

On Wed, Dec 3, 2025, at 16:51, Larry Garfield wrote:

Thanks, Rob! This does seem like a good low-hanging fruit piece to attack. Most importantly, I don't see anything here that would preclude more formal TypeDef structures in the future.

A few other notes:

- Should `mixed` be allowed as in an alias definition? Since `mixed` matches anything, it would render the entire alias redundant as it's then equivalent to `mixed`.

This is already a compiler error: "Type mixed can only be used as a
standalone type." That being said, I guess this could be used to rename
"mixed" to something else, but I don't know why you'd want to do that.
I'd be open to throwing it in with "never" and "void", but "mixed" is
an actual type that can be returned, which is why it wasn't.

Since it would never be meaningful, I would agree with tossing it into the no pile with never and void.

- The "include type" syntax implies that it would not be possible to pre-load aliases via Composer autoload or opcache preloading. (Well, maybe the latter.) That feels like an unnecessary limitation, and is my main issue with the current design. Some way to have project-wide or package-wide definitions would be very helpful, without needing a manual include statement in every file.

Currently, we don't have packages or even "real" namespaces, so as much
as I'd want this, there'd need to be some settling on what authority a
namespace really has. The amount of pushback that I got on the
namespace-private RFC indicates to me that people don't want namespaces
to have any authority, but they want "packages" but nobody knows what a
package is ... but clearly, namespaces aren't anything.

I digress. Sadly, no, this isn't possible with the current design. That
doesn't mean it can't be possible in the future, though, with some
autoloading overhaul or changes (see: Gina's Core Autoloading RFC under
Drafts). But currently, it has to pull off some shenanigans to compile
the file in the file (instead of during runtime like require and
include). Hence why type-files may only contain types and no other code.

Basically, we just have to have some way to tell the compiler "also
include these type aliases when compiling this file" -- that could be
"every file from here on out gets the aliases" (ie, a `use global type
float|int as Number` -- or something like that), or "include this type
file" into this source file.

The former would work best with today's current autoloader, but it
would be "trapped" in that paradigm and would make it harder to
eventually create a "type autoloader". I could be wrong, though, and
I'm open to other people's ideas and opinions. I don't like the current
implementation very much, and while the former is much simpler to
implement, I'm reasonably confident the current implementation can be
improved.

- Does it matter where the `include type` statement goes? Must it be at the top under the `use` statements, or could it be anywhere?

It's basically a "copy this file right here", so it doesn't need to
necessarily be at the top, but trying to include it in a function would
create a syntax issue. I should clarify this in the RFC.

Since `use` statements have to be before any executable code, doesn't that therefore imply that the `include type` directive must go there as well?

Which then leads me to asking if we shouldn't just use `use` for that as well. Something like:

use type string|Stringable as Stringy;
use types /some/file/here.php; // Pull in all the types from this file.

Might that help with the autoloading issues? (I dunno, just thinking aloud here.)

- Not allowing `use` statements in type include files: This feels like a very unfortunate limitation. Is there any way to obviate that? It's all compile time, as you said...

I had to go make sure I didn't have a typo in there. I assume you mean
namespaces? Namespaces in type files have a very weird problem once
they get included. For instance, namespaces are also compile-time
prefixes, and using statements are per namespace, not per file. By
eliminating the namespace, it becomes unambiguous that it’s per-file.
That prevents shenanigans like:

No no, I'm talking about use statements. One of the examples you show is:

// types.php - correct usage
use type \App\Models\User|\App\Models\Admin as AuthUser;

Which... eew. I totally understand not allowing a namespace declaration in the type file, but it would be much nicer to be able to type:

<?php

use App\Models\User;
use App\Models\Admin;

use type User|Admin as AuthUser;

That's what I'm talking about.

--Larry Garfield

Hi Rob,

(We've been using a lot of PHPStan type aliasing lately in the Star-Interop work, so this topic is timely.)

Is there some possibility of making the aliases more classlike so they can be autoloaded? A la:

  namespace Foo;
  type number { as int|float; }

Then elsewhere:

  use Foo\number;
  function sum(number $a, number $b): number { return $a + $b; }

Let me know if I've missed something fundamental.

-- pmj

On Wed, Dec 3, 2025, at 22:04, Paul M. Jones wrote:

Hi Rob,

(We’ve been using a lot of PHPStan type aliasing lately in the Star-Interop work, so this topic is timely.)

Is there some possibility of making the aliases more classlike so they can be autoloaded? A la:

namespace Foo;
type number { as int|float; }

Then elsewhere:

use Foo\number;
function sum(number $a, number $b): number { return $a + $b; }

Let me know if I’ve missed something fundamental.

– pmj

They fundamentally cannot be autoloaded because autoloading happens at runtime, not compile time. At least, with the current autoloader. Autoloading type aliases would need to have a fundamentally different type of autoloading that doesn’t exist today.

— Rob

On Dec 3, 2025, at 15:09, Rob Landers <rob@bottled.codes> wrote:

On Wed, Dec 3, 2025, at 22:04, Paul M. Jones wrote:

Hi Rob,

(We've been using a lot of PHPStan type aliasing lately in the Star-Interop work, so this topic is timely.)

Is there some possibility of making the aliases more classlike so they can be autoloaded? A la:

namespace Foo;
type number { as int|float; }

Then elsewhere:

use Foo\number;
function sum(number $a, number $b): number { return $a + $b; }

Let me know if I've missed something fundamental.

-- pmj

They fundamentally cannot be autoloaded because autoloading happens at runtime, not compile time. At least, with the current autoloader. Autoloading type aliases would need to have a fundamentally different type of autoloading that doesn’t exist today.

Got it; thanks for the clarification.

-- pmj

Hey Rob & all,

One objection, one question.

The objection is to `include types 'types.php'`. You've clarified why types cannot be autoloaded (thanks again!) and so this is a way to let the compiler know where the types are. But I have to say I've spent a huge part of my career removing include/require when refactoring legacy code, and introducing it here causes me great heartache.

The question is a little more complicated, and also involves autoloading. Given a class Number that can be autoloaded ...

  namespace Foo;

  class Number { /* ... */ }

... and *another* class in the same namespace, that creates a "number" type alias...

  namespace Foo;

  use type int|float as Number;

  class Bar { public function add(Number $a, Number $b) : Number { /* ... */ }

... which one "wins" -- the runtime autoloaded class, or the compile-time type alias?

(Apologies if this is covered in the RFC and I missed it.)

-- pmj

On Wed, Dec 3, 2025, at 22:27, Paul M. Jones wrote:

Hey Rob & all,

One objection, one question.

The objection is to include types '[types.php](http://types.php)'. You’ve clarified why types cannot be autoloaded (thanks again!) and so this is a way to let the compiler know where the types are. But I have to say I’ve spent a huge part of my career removing include/require when refactoring legacy code, and introducing it here causes me great heartache.

A new way of autoloading is out of scope… :smiley: It doesn’t preclude it from being autoloaded in the future, but currently, the only other reason to refactor autoloading would be to get function autoloading. That in itself would be nice, having another reason wouldn’t necessarily be a bad thing either.

Further up-thread, I mentioned there were two ways to do this: this way, and making aliases “global”. For example, a “use global type … as …” or something like that. I’m not opposed to this, per se, and if that’s the consensus, I’m fine with it.

The question is a little more complicated, and also involves autoloading. Given a class Number that can be autoloaded …

namespace Foo;

class Number { /* … */ }

… and another class in the same namespace, that creates a “number” type alias…

namespace Foo;

use type int|float as Number;

class Bar { public function add(Number $a, Number $b) : Number { /* … */ }

… which one “wins” – the runtime autoloaded class, or the compile-time type alias?

(Apologies if this is covered in the RFC and I missed it.)

It works just like it works today if you were to do this:

namespace Foo {
 class Number {}
}

namespace Foo {
  use GMP as Number;

  class Bar { public function add Number $a, \Foo\Number $b): Number {}
}

In other words, the alias shadows the original and you have to use the FQN to resolve the real one.

— Rob

On 2025-12-04 05:41, Rob Landers wrote:

Good catch; will do. For the record, it's as type-sensitive as any other type in PHP (as in not case-sensitive at all).

Well, provided you stick to ASCII-only identifiers...


class Thïng {}

class THÏNG {}

class thïng {}

On Wed, Dec 3, 2025, at 22:56, Morgan wrote:

On 2025-12-04 05:41, Rob Landers wrote:

Good catch; will do. For the record, it’s as type-sensitive as any other
type in PHP (as in not case-sensitive at all).

Well, provided you stick to ASCII-only identifiers…


class Thïng {}

class THÏNG {}

class thïng {}

Classes are not case-sensitive: https://3v4l.org/PLb4J#v8.5.0

If you are using PSR-4 + a case-sensitive filesystem, then they appear to be, but that’s due to the filesystem, not PHP.

— Rob

On 3 December 2025 22:01:10 GMT, Rob Landers <rob@bottled.codes> wrote:

On Wed, Dec 3, 2025, at 22:56, Morgan wrote:

Well, provided you stick to ASCII-only identifiers...


class Thïng {}

class THÏNG {}

class thïng {}

Classes are not case-sensitive: Online PHP editor | output for PLb4J

Morgan is right that the case folding is only applied to ASCII letters. In the example, "Thïng" and "thïng" are the same, because they differ only by "T" vs "t", but "THÏNG" is different, because "ï" and "Ï" are not case folded. <Online PHP editor | output for PLb4J;

In fact, they're not treated as letters at all. If I remember correctly, they're not even analysed as code points, each individual byte above 0x7F is treated as completely opaque.

Rowan Tommins
[IMSoP]

On Wed, Dec 3, 2025, at 23:23, Rowan Tommins [IMSoP] wrote:

On 3 December 2025 22:01:10 GMT, Rob Landers <rob@bottled.codes> wrote:

On Wed, Dec 3, 2025, at 22:56, Morgan wrote:

Well, provided you stick to ASCII-only identifiers…


class Thïng {}

class THÏNG {}

class thïng {}

Classes are not case-sensitive: https://3v4l.org/PLb4J#v8.5.0

Morgan is right that the case folding is only applied to ASCII letters. In the example, “Thïng” and “thïng” are the same, because they differ only by “T” vs “t”, but “THÏNG” is different, because “ï” and “Ï” are not case folded. <https://3v4l.org/PLb4J#v8.5.0>

In fact, they’re not treated as letters at all. If I remember correctly, they’re not even analysed as code points, each individual byte above 0x7F is treated as completely opaque.

Rowan Tommins
[IMSoP]

Ah, thanks! I missed the little dots on the i. It’s worth pointing out that the behaviour here isn’t changed, just my understanding of what the current behaviour actually is. :slight_smile: It’s using the same stuff everywhere else is using, so there isn’t any new behaviour here, just the same behaviour in new places.

— Rob

HI Rob,

Thanks for the RFC. I’m really happy to see a proposal for typed aliases, but to be honest, I have some major concerns about the implementation strategy, specifically the use types mechanism.

The proposed syntax (use types 'file.php') feels like a step backward. We are essentially re-introducing the same autoloading pain points we currently have with functions. I strongly believe type aliases should just be first-class citizens like classes, interfaces, or enums, they should live in the same symbol table (so you can’t have a class User and a type User) and follow standard namespacing and autoloading rules.

It should look like this:

namespace App\Aliases;

type UserIdentifier = string|int|Uuid;

And be used just like any other symbol:

use App\Aliases\UserIdentifier;

function greet(UserIdentifier $uid): void { ... }

I also want to highlight a specific issue from a tooling perspective. I maintain a static analyzer (Mago - https://github.com/carthage-software/mago), which uses a multi-threaded, and has an architecture similar to Psalm.

The use types <file> directive would be a massive headache. If an alias is being imported from a file that was not configured in the source paths, our analyzer would have to pause all analysis threads to fetch, parse, and index that one file and make it available globally. This would severely impact performance and concurrency. This is not a total blocker, and we would probably find a workaround, but it would add significant complexity and slow down analysis for everyone.

I recommend looking at how Hack handles this. They successfully implement type (aliasing) and newtype (opaque types) in a way that feels natural and integrates with the autoloader. Adopting a similar approach type Foo = ... would future-proof PHP for potential newtype additions later without requiring a clunky import syntax now.

Regards,

Seifeddine Gmati.

On Tue, 2 Dec 2025 at 23:24, Rob Landers rob@bottled.codes wrote:

Hello Internals,

I’d like to request your comments on type aliases (not to be confused with typedefs) at https://wiki.php.net/rfc/typed-aliases

TL;DR (it’s actually rather short):

Following the same syntax as other use’ing statements, you can alias a type:

use type int|float as Number;

function sum(Number $a, Number $b): Number { return $a + $b; }

You can also include types, with some restrictions:

include types 'math-types.php';

function sum(Number $a, Number $b): Number { return $a + $b; }

These are compile-time replacements that serve to make code more readable and reduce repetition with no runtime overhead. Type aliases follow the same scoping rules as other use imports.

— Rob

On Thu, 4 Dec 2025 at 00:38, Seifeddine Gmati <azjezz@carthage.software> wrote:

HI Rob,

Thanks for the RFC. I’m really happy to see a proposal for typed aliases, but to be honest, I have some major concerns about the implementation strategy, specifically the `use types` mechanism.

The proposed syntax (`use types 'file.php'`) feels like a step backward. We are essentially re-introducing the same autoloading pain points we currently have with functions. I strongly believe type aliases should just be first-class citizens like classes, interfaces, or enums, they should live in the same symbol table (so you can't have a class `User` and a type `User`) and follow standard namespacing and autoloading rules.

It should look like this:

namespace App\Aliases;

type UserIdentifier = string|int|Uuid;

And be used just like any other symbol:

use App\Aliases\UserIdentifier;

function greet(UserIdentifier $uid): void { ... }

I also want to highlight a specific issue from a tooling perspective. I maintain a static analyzer (Mago - GitHub - carthage-software/mago: Mago is a toolchain for PHP that aims to provide a set of tools to help developers write better code.), which uses a multi-threaded, and has an architecture similar to Psalm.

The `use types <file>` directive would be a massive headache. If an alias is being imported from a file that was not configured in the source paths, our analyzer would have to pause all analysis threads to fetch, parse, and index that one file and make it available globally. This would severely impact performance and concurrency. This is not a total blocker, and we would probably find a workaround, but it would add significant complexity and slow down analysis for everyone.

I recommend looking at how Hack handles this. They successfully implement `type` (aliasing) and `newtype` (opaque types) in a way that feels natural and integrates with the autoloader. Adopting a similar approach `type Foo = ...` would future-proof PHP for potential `newtype` additions later without requiring a clunky import syntax now.

Regards,
Seifeddine Gmati.

On Tue, 2 Dec 2025 at 23:24, Rob Landers <rob@bottled.codes> wrote:

Hello Internals,

I’d like to request your comments on type aliases (not to be confused with typedefs) at PHP: rfc:typed-aliases

TL;DR (it’s actually rather short):

Following the same syntax as other use'ing statements, you can alias a type:

use type int|float as Number;

function sum(Number $a, Number $b): Number { return $a + $b; }

You can also include types, with some restrictions:

include types 'math-types.php';

function sum(Number $a, Number $b): Number { return $a + $b; }

These are compile-time replacements that serve to make code more readable and reduce repetition with no runtime overhead. Type aliases follow the same scoping rules as other use imports.

— Rob

Hi again!

I saw that you mentioned in previous emails that implementing
autoloading for type aliases isn't possible, so I wanted to verify
that, I spent some time digging to see if I could get the syntax I
proposed earlier working and I managed to get a working
implementation.

ref: GitHub - carthage-software/php-src at type-aliases

This approach would resolve the static analysis issues I mentioned
earlier, give us a cleaner mechanism for autoloading, and keep PHP's
behavior consistent.

Note that my implementation above is not complete, and would probably
need to be double and triple checked.

Regards,
Seifeddine Gmati.

Hi

Am 2025-12-02 23:23, schrieb Rob Landers:

I’d like to request your comments on type aliases (not to be confused with typedefs) at PHP: rfc:typed-aliases

I'm generally in favor of having type aliases available, provided that the design is sound. I believe there have been some discussions and efforts before and my understanding is that getting them to work with a sound design is complicated - or at least raises many edge case questions.

As for your RFC, I have the following notes:

1.

I also was wondering why the special `include types` syntax exists. You answered that in a sibling thread, but it should also be in the RFC.

2.

I feel that the `include types` syntax doesn't really fit PHP with two consecutive keywords without any clear delimiters to indicate that the second keyword effectively acts as a parameter to the first keyword.

3.

I'm seeing in the PR that `include types` must use a string literal. That should be mentioned in the RFC. I believe it's a significant limitation in the developer experience.

4.

In combination with (3), you are effectively reliant on relative paths to include a types file. This raises questions about what the “base path” is. Is it __FILE__? Is it the include_path INI? Is it the cwd?

5.

The “copy and paste” semantics of `include types` also results in backwards compatibility issues for libraries, because any time I add a new type alias to my type file, it could conflict with another type that the user of my library also needs.

6.

The RFC doesn't explain where type aliases work. In the RFC I'm seeing parameter and return type declarations. What about:

- Property Type Declarations
- The right side of `instanceof`
- `new TypeAlias()`

The latter very likely doesn't work, but what will the error message be? Will it be a “class not found”? Will it be “Cannot instantiate type alias TypeAlias”?

7.

How will type aliases appear in error messages more generally? Say I have:

     use type Foo|Bar as FooOrBar;

     function foo(FooOrBar $x) { }

     foo(1); // Will the error message reference FooOrBar or will it reference Foo|Bar

8.

What about Reflection?

9.

For nested aliases, how will that work with “DNF types”? Consider:

     use type Foo|Bar as FooOrBar;
     use type Baz&FooOrBar as BazAndFooOrBar;

This would result in `Baz&(Foo|Bar)` which is not in DNF.

----------

That's all I can think of for now, but I believe it already shows that there are quite a number of questions that need to be answered and I feel that the answers to those questions are likely not going to be satisfactory by either resulting in a questionable developer experience or by preventing a clean implementation of the future scope (e.g. autoloading semantics).

Best regards
Tim Düsterhus