On Sun, Aug 15, 2021 at 1:25 AM Ondřej Mirtes <ond...@mirtes.cz> wrote:

> It’s true that having “never” in a parameter type (please use “parameter”
> name for the method declaration, “argument” is what you pass to a parameter
> when you call the method) would allow to use any type in an overriding
> method from contravariance/LSP point of view. But at the same time the
> interface with the “never” parameter type isn’t useful at all, because
> “never” cannot accept any type.
>

I fixed the parameter/argument discrepancy in the wiki version of the RFC
and the pull request already. :)


>
> Same as “never” in a return type means “this function never returns”,
> “never” in a parameter type means “you can never call this”.
>
> That’s because if you have interface “Foo" with method "doFoo(never $a):
> void”, you might be able to override it with “class Bar implements Foo” and
> method “doFoo(A $a)”, but at the same time it only allows you to call
> “Bar::doFoo(new A())”, it doesn’t allow you to call “Foo::doFoo(new A())”.
> Which is probably not what people expect from a polymorphic method. So
> having the method on the interface “Foo” is completely useless.
>
> Ondřej Mirtes
>

I understand the point you are making, but my answer would be two simple
points:

1. Mixed is equally useless (in a different way). It's so much so, that
it's literally the same as not providing a type at all. Omitting types is
assumed to be mixed. While never is unhelpful in terms of static analysis,
mixed is unhelpful in terms of semantic correctness of the code itself. I
would argue that the issues around mixed are worse, because they create
problems in actual programs... never only creates problems for IDEs and
static analysis tools. And that's not me arguing that mixed should be
removed (not that it could be). My point is that the top type and the
bottom type in any type system are always going to have some inherent
limitations in one direction, while being inherently limitless in the other
direction... that's what makes them the top type or bottom type.

2. Interfaces have more purposes than just standing in for classes in a
type hint. Interfaces also guarantee an implementation. The ArrayAccess
interface is really the shining example of this RFC. It's parameters are
all typed as mixed, which prohibits any implementing class from typing at
all. This is strictly incorrect. The key cannot be, for instance, null or a
resource. Well... okay, I can think of some really terrifying ways you
*could* do that, but I think my point stands. The types for all the
parameters in the ArrayAccess interface should be never, because it is the
bottom type. What the ArrayAccess interface indicates is that the engine
can treat the object like an array for the purposes of the `[]` operators,
but lots of code which implements the interface has to do all kinds of type
checks that should be unnecessary due to the requirement of typing the
parameter as mixed.

So I guess what I'm saying is that all the limitations and drawbacks of
using this type that you mentioned are 100% true. But I don't think that's
a good reason to reject the feature personally. It would not be correct for
every interface to type all parameters as never if this were passed, the
same way that it wouldn't be correct for all interfaces to type all
parameters int. The never type does not fit all use cases, but for the use
cases it does fit, it helps avoid a lot of unnecessary code, it helps make
code more correct and safe, and it improves run time consistency.

This RFC is not about formalizing the never type. I think that's a good
follow up to this one, personally. That would involve formalizing the set
theory of never as the bottom type (which would involve some changes to
unions and intersections, as well as probably some other changes). For
instance, T|never should reduce to T if never is the bottom type, while
T&never should reduce to never. Similarly, T|mixed should reduce to mixed
(which it does), and T&mixed should reduce to T.

I suppose, I just don't really understand this argument. It's not the right
choice for every interface, but I didn't think it would be, and I hope I
didn't give the impression that I thought it would be. But that doesn't, to
me, affect the utility that the type *does* serve.

Jordan

Reply via email to