Hi Tim and Larry,
Thanks for sharing examples. I'm not sure I follow how never parameters
help in either of these cases. As far as I can tell, the problem remains
that these methods can't actually be called.
On Fri, Mar 21, 2025 at 4:50 AM Tim Düsterhus <[email protected]> wrote:
> 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; }
> }
I don't follow why Number would implement Comparable in the first place,
since we won't actually be able to call Comparable::compareTo. i.e. we
cannot write the following method:
function greaterThan(Comparable $a, Comparable $b): bool {
return $a->compareTo($b) > 0;
}
If we wanted to actually call compareTo, we need to use a specific
implementation:
function greaterThan(Number $a, Number $b): bool {
return $a->compareTo($b) > 0;
}
At this point, we're no longer using the interface, so there's no point in
Number implementing it. Number is then free to define compareTo(Number
$other).
I share Nikita's sentiment in the previous RFC discussion [1], and have yet
to see an answer to it:
I don't think this really addresses my concern, so let me repeat it: You
> cannot actually call a method using a never-type argument while typing
> against the interface. What's the point of the interface then?
>
> I don't think "you must use this in conjunction with a 3rd-party phpdoc
> generics implementation for it to make any sense at all" is a suitable way
> to resolve that.
On Fri, Mar 21, 2025 at 8:53 AM Larry Garfield <[email protected]>
wrote:
> 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.
>
Suppose you made this change from mixed to never. As long as you're typing
against the Formatter interface and not a specific implementation, then you
cannot actually call $formatter->serializeInt() etc. because the interface
defines the parameter type as never, and you cannot call a method with a
parameter type of never.
This is the case in your real world usage [1]. Here, $serializer->formatter
is typed against Formatter [2], not a specific implementation. As such, all
we know is that we have an instance of Formatter--we don't know anything
about the concrete type--and thus we can't actually call e.g.
serializeInt() because never is the only type we know here.
Best regards,
--Matthew
[1] https://externals.io/message/115712#115752
[2]
https://github.com/Crell/Serde/blob/777fc16e932d4dcf1d600335961685885cd815c4/src/PropertyHandler/ScalarExporter.php#L17
[3]
https://github.com/Crell/Serde/blob/777fc16e932d4dcf1d600335961685885cd815c4/src/Serializer.php#L31