[PHP-DEV] [RFC] [Discussion] Never parameters

Hi internals,

I’d like to start discussion on a new RFC about allowing never for parameter types when declaring a method.

-Daniel

Hi internals,

I’d like to start discussion on a new RFC about allowing never for parameter types when declaring a method.

At first signature seemed somewhat confusing to me, but now it makes perfect sense.

Type-wise, about LSP, I find it logical that more concrete implementations could define more generic parameter types without breaking the interface. In this respect any type is more generic than never.

Yet, do you think it’s reasonable that “never” type should be used rather than “void”? From what I know, never - is a special type used when function never returns, and always dies or throws an exception.

On the other hand, void is just an indication that function never returns (but doesn’t die).

In my opinion, it would make more sense to use void parameters.

Also, I remember that in C / C++ language there’s such a concept as “void pointer”, - a completely legitimate structure (likely the same concept as here) that could point to address of the data of any type.

Thank you

Hi Daniel

On Mon, Mar 10, 2025 at 8:06 PM Daniel Scherzer
<daniel.e.scherzer@gmail.com> wrote:

I'd like to start discussion on a new RFC about allowing `never` for parameter types when declaring a method.

* RFC: PHP: rfc:never-parameters-v2
* Implementation: [RFC] Never parameters by DanielEScherzer · Pull Request #18016 · php/php-src · GitHub

Thank you for your proposal!

I would have slightly preferred to investigate associated types first.
They mostly solve the same problem, except they can express
relationships between other parameters or return types. For example:

interface Enum {
    public type BackingType; // Potentially with `: int|float`?
    public function from(BackingType $value): static;
    public function tryFrom(BackingType $value): ?static;
}

This has a slightly more accurate signature, namely enforcing that
from() and tryFrom() take the same argument type. Furthermore, you can
use the same associated type in return types, whereas mixed would have
to be used without them. I'd also argue it's easier to understand
without deep knowledge of type systems. Associated types are similar
to what a generic interface would give you, except:

1. Every class implementing this interface can only implement it once,
restricting the associated type to one concrete type. This makes sense
for PHP, given that we don't have method overloading, which would be
required to make use of multiple generic interface implementations.
2. It's much simpler to implement than generics, given that this can
be fully enforced during inheritance rather than runtime.

I understand that associated types are still significantly more work
than a never type, so I don't mind going with this solution. Given
that BackedEnum cannot be implemented by users, migrating it to use
associated types could be done without BC breaks.

Ilija

Hi Daniel,

On Mar 11, 2025, at 08:05, Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:

Hi internals,

I'd like to start discussion on a new RFC about allowing `never` for parameter types when declaring a method.

* RFC: PHP: rfc:never-parameters-v2
* Implementation: [RFC] Never parameters by DanielEScherzer · Pull Request #18016 · php/php-src · GitHub

-Daniel

The RFC says that:

never can be used as a parameter type, subject to the following restrictions

* it cannot be used in the declaration of any method that has an implementation. Methods with never can never (pun intended) be called, because there are no values that satisfy the type constraint. Instead of delaying the error until runtime when the user attempts to call the method, just require that the method not have a body.

I would point out that `never` can be conceptually represented as a caseless enum. Right now, code like this is valid (as in, does not produce errors, though in practice, it's also not callable):

enum Never {}
function foo(Never $never) { echo "wat"; }
function bar(Never $never) { foo($never); }

If we're going to deny the use of `never` as a parameter to a function with a body (even an empty body), perhaps we should also consider the caseless enum case. For the sake of consistency, we might want to deny use of caseless enums as function parameters. Alternatively, we might want to explicitly allow that use case to allow for a "I know I'm going to have cases here, but I don't know what they will be, but I'm still going to write some functional code around this first and would very much like my code to compile even though I haven't actually added any cases yet." scenario. Which I have definitely done before. Either way, it's an inconsistency worth clarifying.

This is a bit future-gazing, but, when considered in the context of generics, it's normal and useful to have class members and bodied functions with (generically) never types. You might see this, for example, with a Result<Success, Failure> type, where Failure == Never. We would want to allow those use-cases, and disallowing it for non-generic functions would be an inconsistency.

-John

On Mon, Mar 10, 2025, at 20:05, Daniel Scherzer wrote:

Hi internals,

I’d like to start discussion on a new RFC about allowing never for parameter types when declaring a method.

-Daniel

Hey Daniel,

This looks interesting. I’m not sure that I like “never” as a parameter type and while it “technically” doesn’t violate LSP, it seems like a backdoor to doing just that:

abstract class Point {

function add(never $other);

function subtract(never $other);
}

class Vector2 extends Point {

public function add(Banana $other) {}

public function subtract(Football $other) {}
}

There’s basically only a gentleman’s agreement that a subclass will implement things in a way that makes sense. I would also personally prefer associated types:

abstract class Point {

public type OtherPoint;

public function add(OtherPoint $other);

pubic function subtract(OtherPoint $other);

}

This at least lets you ensure the “other point” is the same type in both functions, though personally, I’d rather just have generics.

— Rob

[responding to multiple people instead of spamming multiple emails, I hope that is okay]

On Mon, Mar 10, 2025 at 12:38 PM Eugene Sidelnyk <zsidelnik@gmail.com> wrote:

Yet, do you think it’s reasonable that “never” type should be used rather than “void”? From what I know, never - is a special type used when function never returns, and always dies or throws an exception.

Never is a bottom type that means “absolutely no value is possible” - for function returns, since return; implicitly means return null; in terms of what the caller receives (https://3v4l.org/TNBFE), the only way that this type is usable is when the function never returns (dies or throws).

On the other hand, void is just an indication that function never returns (but doesn’t die).

From my understanding, void is an indication that a function returns no value, but from a

In my opinion, it would make more sense to use void parameters.

Also, I remember that in C / C++ language there’s such a concept as “void pointer”, - a completely legitimate structure (likely the same concept as here) that could point to address of the data of any type.

A void pointer that can point to the address of any type is essentially the opposite of what I am trying to accomplish - coming from C/C++, I would assume a void parameter would therefore be the top type, allowing anything, while never is meant to be the bottom type, allowing nothing.


On Mon, Mar 10, 2025 at 2:44 PM Ilija Tovilo <tovilo.ilija@gmail.com> wrote:

I would have slightly preferred to investigate associated types first.
They mostly solve the same problem, except they can express
relationships between other parameters or return types. For example:

I had never heard of associated types before this email thread - google suggests that they are a feature of rust and swift, neither of which I have used.
While it certainly sounds interesting, it looks like a lot more work, and since if we introduce associated types later we can change BackedEnum without a BC break, I don’t think a future possibility of associated types should stand in the way of never types now.


On Mon, Mar 10, 2025 at 3:11 PM John Bafford <jbafford@zort.net> wrote:

I would point out that never can be conceptually represented as a caseless enum. Right now, code like this is valid (as in, does not produce errors, though in practice, it’s also not callable):

While a caseless enum might represent the concept of a parameter than can never be valid, subclasses cannot just widen a caseless-enum-never to just (e.g.) an int, it would need to be Never|int. I’ll leave further discussion of caseless enums for another thread since they are not equivalent from a type-theory perspective.


On Mon, Mar 10, 2025 at 3:59 PM Rob Landers rob@bottled.codes wrote:

This looks interesting. I’m not sure that I like “never” as a parameter type and while it “technically” doesn’t violate LSP, it seems like a backdoor to doing just that:

So the initial inspiration for this was the BackedEnum class, where it isn’t a technicality - LSP is violated when the interface allows both strings and ints, but no actual enum can use both. I think odd code like subclasses of Point not accepting reasonable things is something that should be caught by other developers doing code review, not enforced on a language level.

For example (https://3v4l.org/pTdMg)

abstract class Point {
abstract public function add(Point $other);
abstract public function subtract(Point $other);
}

class Vector2 extends Point {
public function add(Point|Banana $other) {
if (!$other instanceof Banana) {
throw new TypeError("Point only allowed to prevent compiler errors");
}
// ...rest of the function
}
public function subtract(Point|Football $other) {
if (!$other instanceof Football) {
throw new TypeError("Point only allowed to prevent compiler errors");
}
// ...rest of the function
}
}

There’s basically only a gentleman’s agreement that a subclass will implement things in a way that makes sense.

I think that this agreement is present for any method that isn’t final, and that doesn’t change now - the only difference is that now the PHP compiler won’t get in the way

I would also personally prefer associated types: … This at least lets you ensure the “other point” is the same type in both functions, though personally, I’d rather just have generics.

I’d also like to have generics, but that isn’t something I can implement myself. Associated types would be interesting, but I don’t think that associated types would remove the entire use-case for never parameters, just perhaps the specific example of BackedEnum.


  • Daniel

On Mon, Mar 10, 2025, at 2:05 PM, Daniel Scherzer wrote:

Hi internals,

I'd like to start discussion on a new RFC about allowing `never` for
parameter types when declaring a method.

* RFC: PHP: rfc:never-parameters-v2
* Implementation: [RFC] Never parameters by DanielEScherzer · Pull Request #18016 · php/php-src · GitHub

-Daniel

I have a use case for this in Serde, so would be in favor.

We should not block this kind of improvement on the hope of generics. Worst case, we have this plus generics so you have options, how terrible.

Rust-style associated types would probably work as well. I'd be fine with that approach, too. One could argue they're more valuable as a sort of "junior generics," but absent anyone able and willing to implement them, again, worst-case we end up with options in the future.

--Larry Garfield

I would also personally prefer associated types: … This at least lets you ensure the “other point” is the same type in both functions, though personally, I’d rather just have generics.

I’d also like to have generics, but that isn’t something I can implement myself. Associated types would be interesting, but I don’t think that associated types would remove the entire use-case for never parameters, just perhaps the specific example of BackedEnum.


  • Daniel

Heh, this is the long game I am playing with inner classes: https://externals.io/message/125049#125057

If inner classes can be implemented, then we are just a short hop from being able to implement generics with a similar approach to what I outlined in that thread. Granted, I’m making some major changes to the RFC at the moment and the implementation – based on feedback, so it’ll be a few days before that is finished.

An inner class view of generics (as opposed to the type aliasing view in that thread) looks something like this:

// Box

class Box {

public class T {}

// store(T $item)

public function store(static:>T $item) {}

}

// new Box()

$box = new class() extends Box {

public class ItemType as T {}

};

-Ish. Note that the example won’t actually work; but it illustrates how the engine could implement it if there were inner/nested types.

So, assuming this new implementation I’ve been working on is waaay better than the old one (I think it is), and the RFC is accepted; we may be closer to generics than you thought. Or maybe there is someone out there with a whole different way of doing it that is even better… This is the approach I’m chasing though.

— Rob

On 10 Mar 2025, at 20:07, Daniel Scherzer daniel.e.scherzer@gmail.com wrote:

Hi internals,

I’d like to start discussion on a new RFC about allowing never for parameter types when declaring a method.

-Daniel

Hi Daniel,

To begin, I’m all for a never bottom type, and I use it regularly when I need to write TypeScript.

However, I think the example offered in this RFC – while a valid one, for now – isn’t the most appropriate use case for a never type. A bottom type is more often used when defining conditional types, or to assert that the default for a switch/match expression should never be reached without raising an error that the switch/match cases are not exhaustive.

As such, a never type provides a lot of value for static analysis tools and IDEs, and as a matter of fact both PHPStan[1] and PhpStorm[2] provide ways to declare a bottom type. Furthermore, when conditional return types and/or generics are finally implemented in PHP, never will become even more valuable if not essential.

But, when generics eventually do make their way to PHP, it also means the provided example no longer applies, because you would just be able to use generic types:


interface BackedEnum<T> extends UnitEnum {
public T $value;

public static function from(T $value): static;
public static function tryFrom(T $value): ?static;
}

So I think it may help sell your RFC if you provide some additional context illustrating the use of the never type in generics and static analysis. Otherwise I fear that some people may interpret never as a stopgap solution until we get generics.

Alwin

[1] https://phpstan.org/writing-php-code/phpdoc-types#bottom-type
[2] https://github.com/JetBrains/phpstorm-attributes/blob/master/README.md#noreturn

Hi

Am 2025-03-11 22:45, schrieb Larry Garfield:

We should not block this kind of improvement on the hope of generics. Worst case, we have this plus generics so you have options, how terrible.

In this case, I agree. This is an obvious addition to the type system that uses the existing infrastructure of the type system.

But generally speaking, having too many options is not a good thing. It makes the language larger and more complex to learn, requires documentation effort, support by IDEs and static analyzers and similar things.

Rust-style associated types would probably work as well. I'd be fine with that approach, too. One could argue they're more valuable as a sort of "junior generics," but absent anyone able and willing to implement them, again, worst-case we end up with options in the future.

… so for associated types, I do not necessarily agree. They seem to be a strict subset of the functionality enabled by generics, or the functionality enabled by type aliases combined with “inner classes / inner types”. Since introducing associated types would bring entirely new syntax and semantics to the language, it is less obvious to me that they are a useful (intermediate) addition to the language.

Best regards
Tim Düsterhus

On Thu, Mar 13, 2025, at 10:35 AM, Tim Düsterhus wrote:

Hi

Am 2025-03-11 22:45, schrieb Larry Garfield:

We should not block this kind of improvement on the hope of generics.
Worst case, we have this plus generics so you have options, how
terrible.

In this case, I agree. This is an obvious addition to the type system
that uses the existing infrastructure of the type system.

But generally speaking, having too many options is not a good thing. It
makes the language larger and more complex to learn, requires
documentation effort, support by IDEs and static analyzers and similar
things.

It's definitely a balancing act, yes. TIMTOWTDI definitely has downsides, but at the same time, having multiple ways to accomplish the same goal isn't inherently wrong, unless they conflict with each other in some way.

Rust-style associated types would probably work as well. I'd be fine
with that approach, too. One could argue they're more valuable as a
sort of "junior generics," but absent anyone able and willing to
implement them, again, worst-case we end up with options in the future.

… so for associated types, I do not necessarily agree. They seem to be a
strict subset of the functionality enabled by generics, or the
functionality enabled by type aliases combined with “inner classes /
inner types”. Since introducing associated types would bring entirely
new syntax and semantics to the language, it is less obvious to me that
they are a useful (intermediate) addition to the language.

The counter argument here is that a number of languages (like Rust) have both generics and associated types, so they can coexist and serve slightly different use cases. I have not used Rust enough to have an opinion on how and when to use one or the other, but having one doesn't seem to impede the other.

From my (admittedly limited) understanding, associated types sounds rather like the "inheritance only" pseudo-generics that Derick had proposed for collections last year, in a more general form. (cf: State of Generics and Collections — The PHP Foundation — Supporting, Advancing, and Developing the PHP Language)

--Larry Garfield

On Mon, Mar 10, 2025 at 12:07 PM Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:

Hi internals,

I’d like to start discussion on a new RFC about allowing never for parameter types when declaring a method.

-Daniel

Hi Daniel,

I believe this feature essentially amounts to “add methods which can never be called”, which in my mind makes no sense. If a method types against an interface, and that interface uses a method with a never parameter type, then we cannot actually call that method. We’d need to know the specific concrete type, which defeats the purpose of using an interface in the first place.

See note from Nikita [1] from previous discussion which expands on this idea more, and shows that generics is really what we need here.

[1] https://externals.io/message/115712#115719

Best regards,
–Matthew

On Tuesday, 11 March 2025 at 21:45, Larry Garfield <larry@garfieldtech.com> wrote:

On Mon, Mar 10, 2025, at 2:05 PM, Daniel Scherzer wrote:

> Hi internals,
>
> I'd like to start discussion on a new RFC about allowing `never` for
> parameter types when declaring a method.
>
> * RFC: PHP: rfc:never-parameters-v2
> * Implementation: [RFC] Never parameters by DanielEScherzer · Pull Request #18016 · php/php-src · GitHub
>
> -Daniel

I have a use case for this in Serde, so would be in favor.

We should not block this kind of improvement on the hope of generics. Worst case, we have this plus generics so you have options, how terrible.

Rust-style associated types would probably work as well. I'd be fine with that approach, too. One could argue they're more valuable as a sort of "junior generics," but absent anyone able and willing to implement them, again, worst-case we end up with options in the future.

--Larry Garfield

As the person that had the initial discussion in R11 with Jordan [1] never as a parameter type for an interface actually is not the solution for "poor man generics".
Matthew Fonda [2] already replied to the thread pointing out the remark Nikita made in the discussion of the previous RFC.
But importantly, going from mixed parameter type to a generic parameter type is *allowed* and not a BC change,
however, going from a never parameter type to a generic parameter type is a BC break.

Thus, I am not sure this really a good idea.
The argument from Alwin is more compelling but considering we don't have conditional types, not sure if this makes sense either.

Best regards,

Gina P. Banyard

[1] PHP - 2021-08-12
[2] [RFC] [Discussion] Never parameters - Externals

On Sun, Mar 16, 2025 at 12:31 PM Matt Fonda <matthewfonda@gmail.com> wrote:

Hi Daniel,

I believe this feature essentially amounts to “add methods which can never be called”, which in my mind makes no sense. If a method types against an interface, and that interface uses a method with a never parameter type, then we cannot actually call that method. We’d need to know the specific concrete type, which defeats the purpose of using an interface in the first place.

First, I would point out that I tried to make this limited to methods that already cannot be called - only to interfaces and abstract methods. You already can’t call those directly, you need to have a subclass. And you can always type against the implementation with the actual methods that are not never-typed.

So I would ask - if not never parameters, then what should users do? It seems like, for the use case of “the interface adds a method but there are no promises about what parameters it accepts”, the options would otherwise be

  • documenting that the method should exist, and checking for it, but not adding it to the interface directly so that PHP doesn’t complain
  • having implementations “accept” parameters that they then manually throw errors for, c.f. https://3v4l.org/pTdMg

The original inspiration which I discussed in the RFC is fixing the signature of the BackedEnum methods, which currently use the second option.

See note from Nikita [1] from previous discussion which expands on this idea more, and shows that generics is really what we need here.

[1] https://externals.io/message/115712#115719

Best regards,
–Matthew

I agree that generics would be great and really useful, but should that stop never parameters? There has also been some discussion about generics on the inner classes RFC thread - should the dream of generics eventually stand in the way of independently-useful and smaller-scoped features now?

-Daniel

On Thu, Mar 20, 2025, at 11:24 AM, Gina P. Banyard wrote:

As the person that had the initial discussion in R11 with Jordan [1]
never as a parameter type for an interface actually is not the solution
for "poor man generics".
Matthew Fonda [2] already replied to the thread pointing out the remark
Nikita made in the discussion of the previous RFC.
But importantly, going from mixed parameter type to a generic parameter
type is *allowed* and not a BC change,
however, going from a never parameter type to a generic parameter type
is a BC break.

To clarify, you're saying this:

interface I {
  pubic function foo(mixed $a);
}
class C implements I {
  public function foo(mixed $b) { ... }
}

Can turn into this:

interface I<A> {
  pubic function foo(A $a);
}
class C implements I<Foo> {
  public function foo(Foo $b) { ... }
}

But this could not turn into that:

interface I {
  pubic function foo(never $a);
}
class C implements I {
  public function foo(Foo $b) { ... }
}

Am I following that? Because just from writing that I am not sure I agree, which means I may be misunderstanding. :slight_smile:

--Larry Garfield

On 2025-03-20 18:50, Daniel Scherzer wrote:

The original inspiration which I discussed in the RFC is fixing the signature of the BackedEnum methods, which currently use the second option.

Hey,

I’m not opposed to having a bottom type, but the try/tryFrom issue feels caused by another shortcoming.

In my viewfrom and tryFrom are factory methods that construct an instance by being called on the class itself. As such they should not be subject to LSP at all, just like __construct is not.

BR,
Juris

On Thu, Mar 20, 2025 at 9:51 AM Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:

On Sun, Mar 16, 2025 at 12:31 PM Matt Fonda <matthewfonda@gmail.com> wrote:

Hi Daniel,

I believe this feature essentially amounts to “add methods which can never be called”, which in my mind makes no sense. If a method types against an interface, and that interface uses a method with a never parameter type, then we cannot actually call that method. We’d need to know the specific concrete type, which defeats the purpose of using an interface in the first place.

First, I would point out that I tried to make this limited to methods that already cannot be called - only to interfaces and abstract methods. You already can’t call those directly, you need to have a subclass. And you can always type against the implementation with the actual methods that are not never-typed.

To clarify, what I mean by calling methods on an interface is typing against the interface (for example, as a parameter to a method/function) and then calling methods defined by the interface. As users of the interface, we can call methods on it without knowing or caring which specific subclass we’re actually working with. In my mind, this is the entire point of interfaces. We can write code like the following:

function foo(SomeInterface $i) {
// … call $i->someMethod();
}

Within foo, all we know is that $i is an instance of SomeInterface. We can call any method on $i that SomeInterface defines. If one of those methods had a never argument, then we could never call it.

If we did want to call the method with a never argument, then we’d need to know a specific subclass and what type it overrides never with. At that point, we’re no longer using the interface; we’re instead using the specific subclass, entirely eliminating the need for the interface in the first place.

So I would ask - if not never parameters, then what should users do? It seems like, for the use case of “the interface adds a method but there are no promises about what parameters it accepts”

If an interface adds a method but makes no promises about what parameters it accepts, then why is it part of the interface in the first place–why add a method that can’t be used?

Best regards,
–Matthew

On Thu, Mar 20, 2025 at 4:00 PM Larry Garfield <larry@garfieldtech.com> wrote:

I have a use case for this in Serde, so would be in favor.

We should not block this kind of improvement on the hope of generics. Worst case, we have this plus generics so you have options, how terrible.

Would you mind sharing details of your Serde use case? It seems that the BackedEnum example might not have been the best (since it is for static methods) and so perhaps a userland case where this would be used would help.

–Daniel

Hi

Am 2025-03-20 21:04, schrieb Juris Evertovskis:

I'm not opposed to having a bottom type, but the `try`/`tryFrom` issue feels caused by another shortcoming.

In my view`from` and `tryFrom` are factory methods that construct an instance by being called on the class itself. As such they should not be subject to LSP at all, just like __construct is not.

Indeed. I said the same in the PR on GitHub: [RFC] Never parameters by DanielEScherzer · Pull Request #18016 · php/php-src · GitHub. Static methods within an interface are not useful.

The `never` type would however still be useful for non-static methods in interfaces and to round off the type hierarchy.

Best regards
Tim Düsterhus

Hi

Am 2025-03-20 21:27, schrieb Matt Fonda:

If an interface adds a method but makes no promises about what parameters
it accepts, then why is it part of the interface in the first place--why
add a method that can't be used?

It would more cleanly allow for userland / PHPDoc-based generics, while still providing some engine-enforced type safety. Consider this example (not sure if I got the syntax completely right):

     /** @template T */
     interface Comparable {
         /** @param T $other */
         public function compareTo(never $other): int;
     }

     /** @implements Comparable<Number> */
     final class Number implements Comparable {
         public function compareTo(Number $other): int { return $this <=> $other; }
     }

Without `never`, the `$other` parameter in the interface would need to be `mixed` or untyped, preventing the method in the `Number` class from adding the type to the engine-enforced signature, requiring it to check manually inside the method body.

To me this is another good example of how a small engine change can improve the safety of the language for all users, even when third party static analysis tools are required to make full use of it.

Best regards
Tim Düsterhus