Hi Larry,
On Fri, Mar 28, 2025 at 7:48 PM Larry Garfield <[email protected]>
wrote:
> I have to think people are misunderstanding Nikita's earlier comment, or
> perhaps that he phrased it poorly.
>
> The determination of whether a method call is type-compatible with the
> parameters passed to it is made *at runtime*, on the class. You can widen
> a parameter type in an implementing class, and it will work. That's been
> the case since PHP 7.4.
>
> For example: https://3v4l.org/5YPdg
>
> Even though the function is typed against I, if it's passed a C, you can
> call it with a string. That's because C::a()'s param type is wider than
> the interface.
>
> The idea of a never typed parameter is exactly the same: It starts off
> super-narrow (accepts nothing), so implementations can accept anything
> wider than "nothing" and still be type compatible. You *can* call it.
>
> What you cannot do is determine *statically* that it is callable, because
> at static-analysis time, all you know is the interface. So SA tools won't
> be able to verify that anything is valid for that interface. That's a
> valid criticism of never params, I agree. Is it enough to vote against it
> on those grounds? That's up to each voter to decide.
>
> But "you cannot ever even call it" is simply not true, unless there's some
> weird engine limitation that I don't know about.
>
Correct, in saying you can't call it, we're referring to a static analysis
perspective--or phrased differently, a "this is why interfaces exist and
how you correctly use them" perspective. If we're writing code against an
interface, the only thing we're "allowed" to do with a method is exactly
what the interface specifies. If the interface specifies we can never call
a method, then we can... never call it. In other words, we can't write code
against the interface, so what's the point of the interface? It doesn't
provide any extra safety. In fact, quite the opposite. Any code written
against a method with a never
parameter is inherently unsafe--it only
works if we happen to pass the correct types at runtime.
Widening from something (as opposed to widening from nothing, i.e. never
)
to something wider (e.g. int
to int|string
in your example) makes sense
in a way that widening never
does not. In this case, you have an actual
type to begin with, so you can still write code against the interface.
Continuing your example, if we're writing code against I::a(), the only
thing we're "allowed" to do with it is call it with an int (otherwise the
code may fail at runtime, e.g. https://3v4l.org/WUTv4).
However, being able
to widen here is still useful, because we can _also_ write code against
C::a(), and in that context we're allowed to call with an int or a string.
See https://3v4l.org/qMZOH.
Contrast this to never parameters, where we were never able to write code
against the interface.
I'd argue that this is certainly grounds to vote against it--there's no
point in using an interface if we can't write code against it.
Best regards,
--Matthew