PHP RFC: Allow void return type variance
- Version: 1.0
- Date: 2019-02-04
- Author: Wes (@WesNetmo)
- Status: Under Discussion
- First Published at: http://wiki.php.net/rfc/allow-void-variance
Introduction
This RFC proposes to change in part void
's behavior in class methods; the current behavior is causing more annoyance than benefit and is probably too restrictive regardless. What is proposed here is backward-compatible and in accordance with the goals of the previous void
RFC.
Rationale
The intent of void
should be simply making clear to the reader that the function does not return anything useful, and that any attempt to use the returned value should be considered an invalid operation.
It shouldn't do more than that, but currently it also denies methods to be overridden with different types in subclasses.
class Foo{ function method (): void {} } class Bar extends Foo{ function method (): array { return []; } // fails } class Baz extends Foo{ function method () { return 42; } // also fails }
This should be allowed because it is causing discontent among users, and, most importantly, because it achieves nothing at all apart getting in the way of the developers.
function myFooConsumer (Foo $foo) { // method() is void, therefore we won't be using its return value $foo->method(); }
If a Bar
object is passed to the function (i.e. myFooConsumer(new Bar())
), even if Bar::method()
now returns an array
,
it is not breaking the code in myFooConsumer
, because the function myFooConsumer
has no interest in the return value of
method()
at all.
Even if it's receiving a Bar
object, the function was originally written to be compatible with Foo
objects.
The method()
can return anything, or nothing at all, but myFooConsumer
just does not care about it.
For this reason adding a type to void
in subclasses is not an invalid operation, and denying it is a pointless restriction. Changing from void
to something else is probably a bad idea, but PHP should not enforce void
in subclasses just on that basis.
Proposal
This RFC proposes to allow void
to be changed to any type (including mixed
) in sub classes.
The rest of the current behavior of void
is preserved.
A void
return's inheritance would work exactly like mixed
; that is, the following methods follow the very same covariance rules:
class Foo{ function method1 (): void {} function method2 () {} } class Bar extends Foo{ function method1 (): array { return []; } function method2 (): array { return []; } } class Baz extends Foo{ function method1 () { return 42; } function method2 () { return 42; } }
Except that method1
, unlike method2
, makes clear that it's not returning anything (but it might do so in subclasses).
void
can be overridden to any type including mixed
and void
itself, but mixed
can't be overridden with void
like so:
class Foo{ function method (): void {} } class Bar extends Foo{ function method () {} // ok } class Baz extends Bar{ function method (): void {} // error: can't go back to void }
This is technically not invalid since mixed
includes null
, but it certainly feels “not LSP-valid” and it's therefore disallowed by this RFC.
And, more obviously, also the following is disallowed:
class Foo{ function method (): void {} } class Bar extends Foo{ function method (): string {} // ok } class Baz extends Bar{ function method (): void {} // error: can't go back to void }
Precedents in other languages
TypeScript does this (are there other languages?).
Backward Incompatible Changes
None.
Proposed PHP Versions
7.2.x, 7.3.x, 7.4.x
Voting
2/3 majority will be required.
References
https://externals.io/message/104091 discussion