# [RFC] [Discussion] Never parameters

- Thread: https://externals.io/message/126698
- Original archive: https://news-web.php.net/php.internals/126698
- Started by Daniel Scherzer on 2025-03-10T19:05:47+00:00
- Messages: 33
- Page 1 of 2: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/126698.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #126698 — Daniel Scherzer — 2025-03-10T19:05:47+00:00

URL: https://externals.io/message/126698#126698

Hi internals,

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

* RFC: https://wiki.php.net/rfc/never-parameters-v2
* Implementation: https://github.com/php/php-src/pull/18016

-Daniel

---

## #126699 — Eugene Sidelnyk — 2025-03-10T19:37:58+00:00

URL: https://externals.io/message/126698#126699 · In reply to: #126698

> 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

---

## #126700 — Ilija Tovilo — 2025-03-10T21:44:32+00:00

URL: https://externals.io/message/126698#126700 · In reply to: #126698

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: https://wiki.php.net/rfc/never-parameters-v2
> * Implementation: https://github.com/php/php-src/pull/18016

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

---

## #126702 — John Bafford — 2025-03-10T22:11:25+00:00

URL: https://externals.io/message/126698#126702 · In reply to: #126698

Hi Daniel,

> 
> 
> Hi internals,
> 
> I'd like to start discussion on a new RFC about allowing `never` for parameter types when declaring a method.
> 
> * RFC: https://wiki.php.net/rfc/never-parameters-v2
> * Implementation: https://github.com/php/php-src/pull/18016
> 
> -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

---

## #126705 — Rob Landers — 2025-03-10T22:59:49+00:00

URL: https://externals.io/message/126698#126705 · In reply to: #126698

> Hi internals,
> 
> I'd like to start discussion on a new RFC about allowing `never` for parameter types when declaring a method.
> 
> * RFC: https://wiki.php.net/rfc/never-parameters-v2
> * Implementation: https://github.com/php/php-src/pull/18016
> 
> -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

---

## #126720 — Daniel Scherzer — 2025-03-11T20:00:59+00:00

URL: https://externals.io/message/126698#126720 · In reply to: #126702

[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.

----



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.

----



>
> 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.

----

 

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

---

## #126722 — Larry Garfield — 2025-03-11T21:45:46+00:00

URL: https://externals.io/message/126698#126722 · In reply to: #126698

> Hi internals,
>
> I'd like to start discussion on a new RFC about allowing `never` for 
> parameter types when declaring a method.
>
> * RFC: https://wiki.php.net/rfc/never-parameters-v2
> * Implementation: https://github.com/php/php-src/pull/18016
>
> -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

---

## #126723 — Rob Landers — 2025-03-11T22:16:47+00:00

URL: https://externals.io/message/126698#126723 · In reply to: #126720

> 
> > […]
> 
> 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<T>
class Box {
  public class T {}

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

// new Box<ItemType>()
$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

---

## #126725 — Yogarine — 2025-03-12T09:17:45+00:00

URL: https://externals.io/message/126698#126725 · In reply to: #126698

> Hi internals,
> 
> I'd like to start discussion on a new RFC about allowing `never` for parameter types when declaring a method.
> 
> * RFC: https://wiki.php.net/rfc/never-parameters-v2
> * Implementation: https://github.com/php/php-src/pull/18016
> 
> -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:

```php
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

---

## #126736 — tim@bastelstu.be — 2025-03-13T15:35:24+00:00

URL: https://externals.io/message/126698#126736 · In reply to: #126722

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

---

## #126737 — Larry Garfield — 2025-03-13T16:07:28+00:00

URL: https://externals.io/message/126698#126737 · In reply to: #126736

> Hi
>
> Am 2025-03-11 22:45, schrieb Larry Garfield:
> > […]
>
> 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.

> > […]
>
> … 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: https://thephp.foundation/blog/2024/08/19/state-of-generics-and-collections/#collections)

--Larry Garfield

---

## #126791 — Matt Fonda — 2025-03-16T19:30:34+00:00

URL: https://externals.io/message/126698#126791 · In reply to: #126698

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.
>
> * RFC: https://wiki.php.net/rfc/never-parameters-v2
> * Implementation: https://github.com/php/php-src/pull/18016
>
> -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

---

## #126869 — Gina P. Banyard — 2025-03-20T16:24:19+00:00

URL: https://externals.io/message/126698#126869 · In reply to: #126722

> 
> 
> > […]
> 
> 
> 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] https://chat.stackoverflow.com/transcript/11?m=52810456#52810456
[2] https://externals.io/message/126698#126791

---

## #126871 — Daniel Scherzer — 2025-03-20T16:50:37+00:00

URL: https://externals.io/message/126698#126871 · In reply to: #126791

> 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

---

## #126872 — Larry Garfield — 2025-03-20T16:57:09+00:00

URL: https://externals.io/message/126698#126872 · In reply to: #126869

> 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. :-)

--Larry Garfield

---

## #126878 — Juris Evertovskis — 2025-03-20T20:04:15+00:00

URL: https://externals.io/message/126698#126878 · In reply to: #126871

> 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 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.

BR,
Juris

---

## #126880 — Matt Fonda — 2025-03-20T20:27:54+00:00

URL: https://externals.io/message/126698#126880 · In reply to: #126871

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:
>
> > […]
>
> 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

---

## #126882 — Daniel Scherzer — 2025-03-20T23:02:49+00:00

URL: https://externals.io/message/126698#126882 · In reply to: #126722

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

---

## #126893 — tim@bastelstu.be — 2025-03-21T11:36:01+00:00

URL: https://externals.io/message/126698#126893 · In reply to: #126878

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: 
https://github.com/php/php-src/pull/18016#issuecomment-2715887293. 
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

---

## #126894 — tim@bastelstu.be — 2025-03-21T11:50:39+00:00

URL: https://externals.io/message/126698#126894 · In reply to: #126880

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

---

## #126896 — Rowan Tommins [IMSoP] — 2025-03-21T14:32:58+00:00

URL: https://externals.io/message/126698#126896 · In reply to: #126894

> Am 2025-03-20 21:27, schrieb Matt Fonda:
> > […]
>
> 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; }
>     } 


I think I agree with Matt on this: the interface isn't making any usable 
promises about that method.

In this example, Comparable is a kind of "abstract interface" - in order 
to actually make use of it, you need to specialise it.

Declaring that a class implements a template interface is like 
inheriting an abstract method: either you fill in the type parameter 
("class Foo implements Comparable<Foo> { ... }"), or the class is also a 
template ("class Foo<A> implements Comparable<A> { ... }")


I don't think the language should pretend to support something that it 
doesn't - if the contract is actually enforced by a third-party tool 
reading docblocks, put the contract in a docblock:

     /**
        * @template T
        * @method compareTo(T $other): int;
        */
     interface Comparable {
     }

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


-- 
Rowan Tommins
[IMSoP]

---

## #126898 — Larry Garfield — 2025-03-21T15:51:20+00:00

URL: https://externals.io/message/126698#126898 · In reply to: #126882

> 
> > […]
>
> 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

Simplified example to show the thing we care about:

I have an interface Formatter, like:

interface Formatter
{
    public function serializeInitialize(ClassSettings $classDef, Field $rootField): mixed;

    public function serializeInt(mixed $runningValue, Field $field, ?int $next): mixed;

    public function serializeFloat(mixed $runningValue, Field $field, ?float $next): mixed;

   // And other methods for other types
}

The $runningValue is of a type known concretely to a given implementation, but not at the interface level.  It's returned from serializeIntialize(), and then passed along to every method, recursively, as it writes out an object.

So for instance, the JSON formatter looks like this:

class JsonSerializer implements Formatter
{
     public function serializeInitialize(ClassSettings $classDef, Field $rootField): array
    {
        return ['root' => []];
    }

    /**
     * @param array<string, mixed> $runningValue
     * @return array<string, mixed>
     */
    public function serializeInt(mixed $runningValue, Field $field, ?int $next): array
    {
        $runningValue[$field->serializedName] = $next;
        return $runningValue;
    }
}

Because JsonFormatter works by building up an array and passing it to `json_encode()`, eventually.  So $runningValue is guaranteed to always be an array.  I can narrow the return value to an array, but not the parameter.

The JsonStreamFormatter, however, has a stream object that it passes around (which wraps a file handle internally):

class JsonStreamFormatter implements Formatter
{
    public function serializeInitialize(ClassSettings $classDef, Field $rootField): FormatterStream
    {
        return FormatterStream::new(fopen('php://temp/', 'wb'));
    }

   /**
     * @param FormatterStream $runningValue
     */
    public function serializeInt(mixed $runningValue, Field $field, ?int $next): FormatterStream
    {
        $runningValue->write((string)$next);
        return $runningValue;
    }
}

Again, I can narrow the return value but not the param.

To be clear, generics would absolutely be better in this case.  I'm just not holding my breath.

Associated Types would probably work in this case, too, since it's always relevant when creating a new concrete object, not when parameterizing a common object.  If we got that, I'd probably use that instead.

Changing the interface to use `never` instead of `mixed` would have the weakest guarantees of the three, since it doesn't force me to use the *same* widened type on serializeInt(), serializeFloat(), serializeString(), etc., even though it would always be the same.  But it would allow me to communicate more type information than I can now.

How compelling this use case is, I leave as an exercise for the reader.

--Larry Garfield

---

## #126902 — Gina P. Banyard — 2025-03-21T20:41:36+00:00

URL: https://externals.io/message/126698#126902 · In reply to: #126872

> 
> 
> > […]
> 
> 
> To clarify, you're saying this:
> 
> [...]
>
> Am I following that? Because just from writing that I am not sure I agree, which means I may be misunderstanding. :-)

I am saying:
interface I {
  pubic function foo(never $a);
}

can ***not*** be "upgraded" to

interface I<A> {
    pubic function foo(A $a);
}

whereas it is possible to go from

interface I {
  pubic function foo(mixed $a);
}

to

interface I<A> {
    pubic function foo(A $a);
}

The implementing classes are completely irrelevant in this context.

Best regards,

Gina P. Banyard

---

## #126935 — Daniel Scherzer — 2025-03-25T18:14:21+00:00

URL: https://externals.io/message/126698#126935 · In reply to: #126896

On Tue, Mar 25, 2025 at 11:01 AM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk>
wrote:

>
> I don't think the language should pretend to support something that it
> doesn't
>

I don't see what the pretending is here - the engine supports declaring
that a method must accept a parameter but makes no promises about the type
of that parameter, which is what we claim it supports. If people want to
use this for generics, then great, but I don't think this should be blocked
on the expectation that it is only useful for userland generics (e.g.
the Serde use case isn't really generics).


> - if the contract is actually enforced by a third-party tool reading
> docblocks, put the contract in a docblock:
>
>     /**
>        * @template T
>        * @method compareTo(T $other): int;
>        */
>     interface Comparable {
>     }
>
>     /** @implements Comparable<Number> */
>     final class Number implements Comparable {
>         public function compareTo(Number $other): int { return $this <=>
> $other; }
>     }
>
>
> What happens if there is a bigger contract than just the never parameter,
and we want the engine to enforce the rest? For example, the Serde use case
given previously. Or, if you had an object store that might occasionally
clean up expired items:

     /** @template T */
     interface ObjectStore {
         /** @param T $object */
         public function storeObject(never $object, bool $alsoDoCleanup):
void;
     }

If we move the entire contract to the docblock, then the engine cannot be
used to enforce the non-never-related parts.

--Daniel

---

## #126941 — Rowan Tommins [IMSoP] — 2025-03-25T20:42:39+00:00

URL: https://externals.io/message/126698#126941 · In reply to: #126935

>On Tue, Mar 25, 2025 at 11:01 AM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk>
>wrote:
>
> > […]
>
>I don't see what the pretending is here - the engine supports declaring
>that a method must accept a parameter but makes no promises about the type
>of that parameter


I guess I just struggle to make such a declaration have any meaning; it feels like a philosophical statement rather than a practical one.

Perhaps "pretend" was too strong in this case; I was mentally comparing it to Python's unenforced type hints, which are only as reliable as comments; and the occasional suggestion of doing the same in PHP with generic declarations (built-in syntax with no built-in behaviour, which would be a hard No from me).

If I assert($foo instanceof InterfaceUsingNever), I can't actually do anything useful with the promised method; I can only call it if I either read the unenforced rules (e.g. in docblocks and comments), or assert($foo instanceof SomeConcreteImplementation) instead.


In the context of this thread, I can see that the examples are logically consistent, but if I just saw one in the wild, I'd just be scratching my head what it means to require that no values are acceptable. 

And I've seen plenty of junior devs struggle with simple things like the difference between "nullable" and "optional", so I worry that making the type system too rich will lose PHP its reputation as an approachable language.


It's possible I'd react less strongly if a keyword was chosen that made more sense in context, although I'm not sure what that would be.

public function foo(int $a, must_override $b);

That's not quite right, I think.


Rowan Tommins
[IMSoP]

---

Next page: https://externals.io/message/126698.md?page=2
