Table of Contents

PHP RFC: Allow void return type 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