[RFC] Transform void into an alias for null

php.internals

Gina P. Banyard

1 year ago
Hello internals, This is the second RFC out of a set of type system related RFCs I want to propose for PHP 8.5. The objective is to fix a weird quirk of PHP's type system, where void lives in its own type hierarchy. This is visible mainly in that a lack of return type is not isomorphic to a function that has a return type of mixed. Let me know what you think about it. RFC: https://wiki.php.net/rfc/void-as-null Best regards, Gina P. Banyard

Larry Garfield

1 year ago
On Mon, Jun 2, 2025, at 11:27 AM, Gina P. Banyard wrote:
> Hello internals, > > This is the second RFC out of a set of type system related RFCs I want > to propose for PHP 8.5. > > The objective is to fix a weird quirk of PHP's type system, where void > lives in its own type hierarchy. > This is visible mainly in that a lack of return type is not isomorphic > to a function that has a return type of mixed. > > Let me know what you think about it. > > RFC: https://wiki.php.net/rfc/void-as-null > > Best regards, > > Gina P. Banyard
The result of this RFC is that the following would no longer be an error, yes? function test(): void { print "test"; } // This currently gives an error, but you propose that it // would change to set $val to null? $val = test(); --Larry Garfield

Kamil Tekiela

1 year ago
The RFC mentions that this will now become valid: function foo(): void { return null; } But what about the opposite: function foo(): null { return; } or what Larry was trying to suggest: function foo(): null { print 'test'; } $val = foo();

Gina P. Banyard

1 year ago
On Monday, 2 June 2025 at 18:08, Kamil Tekiela <tekiela246@gmail.com> wrote:
> The RFC mentions that this will now become valid: > > function foo(): void { > return null; > } > > But what about the opposite: > > function foo(): null { > return; > }
This would also work indeed.
> or what Larry was trying to suggest: > > function foo(): null { > print 'test'; > } > > $val = foo();
This would also work, as null and void would be isomorphic. However, if you have a return type of T|null then you'd get errors in both cases. I will clarify this in the RFC. Best regards, Gina P. Banyard

Anton Smirnov

1 year ago
On 02/06/2025 20:01, Larry Garfield wrote:
> The result of this RFC is that the following would no longer be an error, yes? > > function test(): void { > print "test"; > } > > // This currently gives an error, but you propose that it > // would change to set $val to null? > $val = test();
There is no error: https://3v4l.org/UD4vn
-- Anton p.s. sorry Larry, first time I forgot that I'm answering to the mailing list :D

Christoph Becker

1 year ago
On 03.06.2025 at 03:36, Anton Smirnov wrote:
> On 02/06/2025 20:01, Larry Garfield wrote: > >> The result of this RFC is that the following would no longer be an >> error, yes? >> >> function test(): void { >>    print "test"; >> } >> >> // This currently gives an error, but you propose that it >> // would change to set $val to null? >> $val = test(); > > There is no error: https://3v4l.org/UD4vn
I guess that Larry meant `return` instead of `print`: <https://3v4l.org/7dtYH>.

Larry Garfield

1 year ago
On Tue, Jun 3, 2025, at 4:33 AM, Christoph M. Becker wrote:
> On 03.06.2025 at 03:36, Anton Smirnov wrote: > >> On 02/06/2025 20:01, Larry Garfield wrote: >> >>> The result of this RFC is that the following would no longer be an >>> error, yes? >>> >>> function test(): void { >>>    print "test"; >>> } >>> >>> // This currently gives an error, but you propose that it >>> // would change to set $val to null? >>> $val = test(); >> >> There is no error: https://3v4l.org/UD4vn > > I guess that Larry meant `return` instead of `print`: > <https://3v4l.org/7dtYH>.
No, I did not. I was sure I've run into places before where even trying to assign the return value of a void function to something gives me an error, but perhaps it was one of the many SA tools I use (IDE, PHPStan, etc.). --Larry Garfield

Rowan Tommins [IMSoP]

1 year ago
On 02/06/2025 17:27, Gina P. Banyard wrote:
> The objective is to fix a weird quirk of PHP's type system, where void lives in its own type hierarchy. > This is visible mainly in that a lack of return type is not isomorphic to a function that has a return type of mixed.
I think if "void" was added now, it would be an attribute, rather than a type. It is in effect the exact opposite of #[\NoDiscard], and distinguishes between these two cases: interface Foo {    public function getSomething(): ?Something; } class MyFoo implements Foo {    public function getSomething(): null {        // My result is always null, but still meaningful to consumers of the Foo interface        return null;    }    #[\DiscardReturn]    public function doSomething(): null {         // I have no meaningful information to return; any assignment of my implicit value is a mistake    } } I agree the type hierarchy you describe is weird, but rather than throwing away the functionality completely, I wonder if we can make it more consistent: - Make "no return type declared" and "mixed" equivalent - Make "void" a sub-type of "null", and therefore a sub-type of "mixed" If I've got that right, this would then be legal: class A{ public function foo() {} } class B extends A{ public function foo(): mixed{} } class C extends B{ public function foo(): null{} } class D extends C{ public function foo(): void{} } class E extends D{ public function foo(): never {} } That seems reasonable enough; I may have missed something important, though. Regards,
-- Rowan Tommins [IMSoP]

Anton Smirnov

1 year ago
On 02/06/2025 23:50, Rowan Tommins [IMSoP] wrote:
> I agree the type hierarchy you describe is weird, but rather than > throwing away the functionality completely, I wonder if we can make it > more consistent: > > - Make "no return type declared" and "mixed" equivalent > - Make "void" a sub-type of "null", and therefore a sub-type of "mixed"
I think that null and void are semantically very different so I'd like to suggest just making void subtype of mixed. This will both keep the semantic meaning of void and make mixed and undeclared mean the same thing.
-- Anton

Gina P. Banyard

1 year ago
On Tuesday, 3 June 2025 at 02:36, Anton Smirnov <sandfox@sandfox.me> wrote:
> On 02/06/2025 23:50, Rowan Tommins [IMSoP] wrote: > > > I agree the type hierarchy you describe is weird, but rather than > > throwing away the functionality completely, I wonder if we can make it > > more consistent: > > > > - Make "no return type declared" and "mixed" equivalent > > - Make "void" a sub-type of "null", and therefore a sub-type of "mixed" > > > I think that null and void are semantically very different so I'd like > to suggest just making void subtype of mixed. This will both keep the > semantic meaning of void and make mixed and undeclared mean the same thing.
You are going to need to expand on why you think those two are semantically very different. PHP does not have the concept of "execution control is returned to the calling scope, but no concrete value is returned". And this is a good thing IMHO, as it means you can always take the result of a function. Moreover, "just making void subtype of mixed" can mean everything and nothing. Do you mean for void to live on its own island like the int or string types? Make it a subtype of null like Roman suggested? Have void be a super type of some weird union of types representing scalars int|string|float|bool? Something else altogether? Best regards, Gina P. Banyard

Anton Smirnov

1 year ago
On 03/06/2025 18:20, Gina P. Banyard wrote:
> On Tuesday, 3 June 2025 at 02:36, Anton Smirnov <sandfox@sandfox.me> wrote: > >> On 02/06/2025 23:50, Rowan Tommins [IMSoP] wrote: >> >>> I agree the type hierarchy you describe is weird, but rather than >>> throwing away the functionality completely, I wonder if we can make it >>> more consistent: >>> >>> - Make "no return type declared" and "mixed" equivalent >>> - Make "void" a sub-type of "null", and therefore a sub-type of "mixed" >> >> >> I think that null and void are semantically very different so I'd like >> to suggest just making void subtype of mixed. This will both keep the >> semantic meaning of void and make mixed and undeclared mean the same thing. > > You are going to need to expand on why you think those two are semantically very different. > PHP does not have the concept of "execution control is returned to the calling scope, but no concrete value is returned". > And this is a good thing IMHO, as it means you can always take the result of a function.
Basically I agree with Ilija and Tim that functions that "return null" and functions that "return nothing but it's shown as null for historical and compatibility reasons" are different, but I see some value in aligning mixed and undeclared return type behavior.
> Moreover, "just making void subtype of mixed" can mean everything and nothing. > Do you mean for void to live on its own island like the int or string types? > Make it a subtype of null like Roman suggested? > Have void be a super type of some weird union of types representing scalars int|string|float|bool? > Something else altogether?
this:
> Do you mean for void to live on its own island like the int or string
types? never <- void <- mixed
-- Anton

Gina P. Banyard

1 year ago
On Monday, 2 June 2025 at 21:53, Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote: > On 02/06/2025 17:27, Gina P. Banyard wrote: > >> The objective is to fix a weird quirk of PHP's type system, where void lives in its own type hierarchy. >> This is visible mainly in that a lack of return type is not isomorphic to a function that has a return type of mixed. > > I think if "void" was added now, it would be an attribute, rather than a type. It is in effect the exact opposite of #[\NoDiscard], and distinguishes between these two cases: > > interface Foo { > public function getSomething(): ?Something; > } > > class MyFoo implements Foo { > public function getSomething(): null { > // My result is always null, but still meaningful to consumers of the Foo interface > return null; > } > > #[\DiscardReturn] > public function doSomething(): null { > // I have no meaningful information to return; any assignment of my implicit value is a mistake > } > > } A function that always returns the same value is not meaningful to a consumer, the only exception is in a class hierarchy with an overloaded method. But in that case the consumer expects potential other concrete values, so this point is a bit moot IMHO. In the same way as saving the result of print() is pointless because it always returns 1, or a function that always returns true. > I agree the type hierarchy you describe is weird, but rather than throwing away the functionality completely, I wonder if we can make it more consistent: > > - Make "no return type declared" and "mixed" equivalent > - Make "void" a sub-type of "null", and therefore a sub-type of "mixed" > > If I've got that right, this would then be legal: > > class > > A > > { > > public > > function > > foo > > ( > > ) > > { > > } > > } > > class > > B extends A > > { > > public > > function > > foo > > ( > > ) > > : mixed > > { > > } > > } > > class > > C extends B > > { > > public > > function > > foo > > ( > > ) > > : null > > { > > } > > } > > class > > D extends C > > { > > public > > function > > foo > > ( > > ) > > : void > > { > > } > > } > > class > > E extends D > > { > > public > > function > > foo > > ( > > ): never > > { > > } > > } > > That seems reasonable enough; I may have missed something important, though Not sure if it is important, but you are missing the case where null takes part in a union type. Your proposed change would also allow the following class hierarchy: class A { public function foo ( ) { } } class B extends A { public function foo ( ) : mixed { } } class C extends B { public function foo ( ) : string|int|null { } } class D extends C { public function foo ( ) : void { } } class E extends D { public function foo ( ): never { } } But if people think this type hierarchy makes more sense, then sure. I am not convinced, as I think it is a *good* thing PHP always returns a value from a function. Lying about this just seems pointless and leading to misunderstandings about how the language behaves. Best regards, Gina P. Banyard

Ilija Tovilo

1 year ago
Hi Gina On Mon, Jun 2, 2025 at 6:28 PM Gina P. Banyard <internals@gpb.moe> wrote:
> > RFC: https://wiki.php.net/rfc/void-as-null
After a read, I think I fundamentally disagree with the proposal. It says (regarding the status-quo):
> * void is not a subtype of a function with a mixed return type
This is laid out as a downside, but I don't think it is. Consider this example under the specified behavior: interface MapInterface { public function set(string $key, mixed $value): void; } class Map implements MapInterface { public function set(string $key, mixed $value): void { // Store the key/value pair _somehow_ } } Let's assume the return type `MapInterface::set()` `mixed` instead, where the intention is for `set()` to return the previous value, or `null` if there was none. This change will go completely unnoticed by `Map::set()`, because `void` is now just `null`, which is a subtype of `mixed`. This is a bug that would previously have been caught, notifying you that you're supposed to return _something_. Similarly, the code `function foo(): void { return null; }` is now proposed to be valid, and I assume the inverse for `void`/`return;` is also true. In this example, we now update `Map::set()` to the new return type. class Map implements MapInterface { public function set(string $key, mixed $value): mixed { $oldValue = /* Fetch old value _somehow_ */; // Store the key/value pair _somehow_ if (!$this->observers) { return; // This line was here before. } $this->observers->notify($key, $oldValue, $value); return $oldValue; } } Good examples are hard, but imagine `Map::set()` would allow notifying a list of observers about changes to the map. Previously, the `return;` would have prevented an erroneous call to `notify()` on `null`. However, now it is missing the returning of `$oldValue`. This is another bug that would previously have been caught. In fact, even a missing trailing `return $something;` would not be caught anymore. IMO, these checks are useful enough not to be removed. Please let me know if I'm missing anything. Ilija

Tim Düsterhus

1 year ago
Hi Am 2025-06-03 01:46, schrieb Ilija Tovilo:
> IMO, these checks are useful enough not to be removed.
I agree with Ilija (and also Rowan). To me there is an important semantic difference between “not returning anything” and “always returning null”. I believe that `void` being in a distinct type hierarchy is the right choice and when considering “untyped returns” to be soft-deprecated / discouraged, there are no inconsistencies either. Best regards Tim Düsterhus

Gina P. Banyard

1 year ago
On Tuesday, 3 June 2025 at 16:18, Tim Düsterhus <tim@bastelstu.be> wrote:
> Am 2025-06-03 01:46, schrieb Ilija Tovilo: > > > IMO, these checks are useful enough not to be removed. > > I agree with Ilija (and also Rowan).
Well, I'm not sure Ilija and Rowan agree between each other. AFAIU Ilija thinks void living on its weird island is good. Meanwhile, Rowan thinks it is somewhat confusing and that void should be a subtype of null.
> To me there is an important semantic difference between “not returning anything” > and “always returning null”. > I believe that `void` being in a distinct type > hierarchy is the right choice and when considering “untyped returns” to > be soft-deprecated / discouraged, there are no inconsistencies either.
Then our type system is not logical as we have a top type that is not actually a top type. This is something that will cause problems for function types, especially if they have generic arguments. (if not causing weird issues on its own just for generics) Best regards, Gina P. Banyard

Tim Düsterhus

1 year ago
Hi Am 2025-06-03 17:46, schrieb Gina P. Banyard:
>> > IMO, these checks are useful enough not to be removed. >> >> I agree with Ilija (and also Rowan). > > Well, I'm not sure Ilija and Rowan agree between each other.
I believe they agree in that the distinction between `void` and `null` is a useful one.
> AFAIU Ilija thinks void living on its weird island is good. > Meanwhile, Rowan thinks it is somewhat confusing and that void should > be a subtype of null.
But indeed I'm more aligned with Ilija than Rowan (that’s why Rowan’s name is in parentheses).
>> To me there is an important semantic difference between “not returning >> anything” >> and “always returning null”. >> I believe that `void` being in a distinct type >> hierarchy is the right choice and when considering “untyped returns” >> to >> be soft-deprecated / discouraged, there are no inconsistencies either. > > Then our type system is not logical as we have a top type that is not > actually a top type.
See my reply to your reply to Ilija. Best regards Tim Düsterhus

Gina P. Banyard

1 year ago
On Tuesday, 3 June 2025 at 00:48, Ilija Tovilo <tovilo.ilija@gmail.com> wrote:
> Hi Gina > > On Mon, Jun 2, 2025 at 6:28 PM Gina P. Banyard internals@gpb.moe wrote: > > > RFC: https://wiki.php.net/rfc/void-as-null > > > After a read, I think I fundamentally disagree with the proposal. It > says (regarding the status-quo): > > > * void is not a subtype of a function with a mixed return type > > > This is laid out as a downside, but I don't think it is. Consider this > example under the specified behavior: > > interface MapInterface { > public function set(string $key, mixed $value): void; > } > > class Map implements MapInterface { > public function set(string $key, mixed $value): void { > // Store the key/value pair somehow > } > } > > Let's assume the return type `MapInterface::set()` `mixed` instead, > where the intention is for `set()` to return the previous value, or > `null` if there was none. This change will go completely unnoticed by > `Map::set()`, because `void` is now just `null`, which is a subtype of > `mixed`. This is a bug that would previously have been caught, > notifying you that you're supposed to return something. > > Similarly, the code `function foo(): void { return null; }` is now > proposed to be valid, and I assume the inverse for `void`/`return;` is > also true. In this example, we now update `Map::set()` to the new > return type. > > class Map implements MapInterface { > public function set(string $key, mixed $value): mixed { > $oldValue = /* Fetch old value somehow */; > // Store the key/value pair somehow > > if (!$this->observers) { > > return; // This line was here before. > } > > $this->observers->notify($key, $oldValue, $value); > > > return $oldValue; > } > } > > Good examples are hard, but imagine `Map::set()` would allow notifying > a list of observers about changes to the map. Previously, the > `return;` would have prevented an erroneous call to `notify()` on > `null`. However, now it is missing the returning of `$oldValue`. This > is another bug that would previously have been caught. In fact, even a > missing trailing `return $something;` would not be caught anymore. > > IMO, these checks are useful enough not to be removed. > > Please let me know if I'm missing anything.
I must say the examples are not really convincing to me. :) The compile time warning would *only* be suppressed for case where the return type of the function/method is *exactly* null or void, the moment you have a union type or mixed you would still get the compile time error. I realise that I didn't properly describe the behaviour of my PoC implementation in the RFC, apologies for that. And a type system on its own is never going to be able to catch all implementation bugs, even with dependant and effect types, you can write code that passes a type checker yet not do what is expected. A type system should be logical, and the fact that the top type (mixed) is not a super-type of all types doesn't make any sense. We *specifically* included null within mixed as having a proper top type is required, even if many people would have preferred it to exclude null and just use ?mixed if you wanted "everything". As said to Anton, PHP does not have the concept/capability of "not returning a value" (unless you throw/exit) Final small cheeky note, we don't warn/error in other cases where we detect dead code, we let the optimizer get rid of them, is this something we should warn about? Best regards, Gina P. Banyard

Tim Düsterhus

1 year ago
Hi Am 2025-06-03 17:42, schrieb Gina P. Banyard:
> A type system should be logical, and the fact that the top type (mixed) > is not a super-type of all types doesn't make any sense.
I do not consider `void` to be a type per se, but rather as an indicator for the absence of a value. Basically the difference between a “procedure” and a “function” [1]. That's why it makes sense to me to treat `void` differently from the other types. See also: The `(void)` cast which we decided to make a statement rather than an expression that always evaluates to `null`. Best regards Tim Düsterhus [1] https://stackoverflow.com/a/721132

Gina P. Banyard

1 year ago
On Wednesday, 4 June 2025 at 09:50, Tim Düsterhus <tim@bastelstu.be> wrote:
> Am 2025-06-03 17:42, schrieb Gina P. Banyard: > > > A type system should be logical, and the fact that the top type (mixed) > > is not a super-type of all types doesn't make any sense. > > > I do not consider `void` to be a type per se, but rather as an indicator > for the absence of a value.
Well we fundamentally disagree on this topic then, the RFC that introduced the void type [G1] used as justification for the name void:
> The main reason to choose void over null is that it is the customary name to use for such a return type. > [...] > others [PLs] (TypeScript, ActionScript, Swift) do allow void functions in expressions, just as PHP does, by making them *implicitly return some unit type*.
*Emphasis mine* This reads to me that the authors know that `void` would mean "return unit type", rather than "lack of return value". Moreover, another justification is:
> There's no precedent for it and the name doesn't seem to have been an *issue until now*.
*Emphasis mine* Which, IMHO, is not the case any more in that there is an issue now with a split and nonsensical type hierarchy.
> Basically the difference between a “procedure” and a “function” [1]. > That's why it makes sense to me to treat `void` differently from the other types.
PHP does not, like many modern PLs, make a distinction between a "procedure" and a "function", as they are the same thing. In many PLs a function that returns "nothing" means returning the unit type. And PHP's unit type is null. Making void and null isomorphic does not prevent people from using either type name to communicate intent in their source code that this function is "procedure" or a "true function". And if the point of void is for a function to say that it causes side effects, then adding effect type declarations to PHP would be a better solution, e.g. ``` function foo(): null!my_side_effect1|my_side_effect2 { bar(); /* causes my_side_effect1 */ foobar(); /* causes my_side_effect2 */ } ```
> See also: The `(void)` > cast which we decided to make a statement rather than an expression that > always evaluates to `null`.
I do not see how this invalidates my argument nor support yours. Best regards, Gina P. Banyard
> [1] https://stackoverflow.com/a/721132
[G1] https://wiki.php.net/rfc/void_return_type#why_call_it_void_and_not_null

Bob Weinand

1 year ago
On 2.6.2025 18:27:51, Gina P. Banyard wrote:
> Hello internals, > > This is the second RFC out of a set of type system related RFCs I want to propose for PHP 8.5. > > The objective is to fix a weird quirk of PHP's type system, where void lives in its own type hierarchy. > This is visible mainly in that a lack of return type is not isomorphic to a function that has a return type of mixed. > > Let me know what you think about it. > > RFC: https://wiki.php.net/rfc/void-as-null > > Best regards, > > Gina P. Banyard
I have to agree with other posters here that the distinction between null and void is an useful one. In particular I'd consider the null returned by void to be incidental rather than intentional. I consider the return value of void functions "some arbitrary value". It just happens to be null. Like every function has to return something. But returning null is not an intrinsic property of a void function. It's an extrinsic one. You observe void functions to generally return null. But that null in itself is meaningless. So, my counter-proposal would be allowing covariance with void and allowing everything, including non-nullable types as child type of void functions. I.e. effectively giving void and never the same semantics, except that never also indicates that it never returns. Additionally I'd be in favour of disallowing (e.g. E_WARNING) consuming the return value of _direct_ calls to void functions (with the exception of standalone direct calls in short closures, because consuming that value is intrinsic rather than necessarily intentional). (Disallowing indirect calls would be detrimental for usage as callback.) Bob

Bob Weinand

1 year ago
On 4.6.2025 16:54:05, Bob Weinand wrote:
> On 2.6.2025 18:27:51, Gina P. Banyard wrote: >> Hello internals, >> >> This is the second RFC out of a set of type system related RFCs I >> want to propose for PHP 8.5. >> >> The objective is to fix a weird quirk of PHP's type system, where >> void lives in its own type hierarchy. >> This is visible mainly in that a lack of return type is not >> isomorphic to a function that has a return type of mixed. >> >> Let me know what you think about it. >> >> RFC: https://wiki.php.net/rfc/void-as-null >> >> Best regards, >> >> Gina P. Banyard > > I have to agree with other posters here that the distinction between > null and void is an useful one. > > In particular I'd consider the null returned by void to be incidental > rather than intentional. I consider the return value of void functions > "some arbitrary value". It just happens to be null. > Like every function has to return something. But returning null is not > an intrinsic property of a void function. It's an extrinsic one. You > observe void functions to generally return null. But that null in > itself is meaningless. > > So, my counter-proposal would be allowing covariance with void and > allowing everything, including non-nullable types as child type of > void functions. > I.e. effectively giving void and never the same semantics, except that > never also indicates that it never returns. > > Additionally I'd be in favour of disallowing (e.g. E_WARNING) > consuming the return value of _direct_ calls to void functions (with > the exception of standalone direct calls in short closures, because > consuming that value is intrinsic rather than necessarily > intentional). (Disallowing indirect calls would be detrimental for > usage as callback.) > > Bob
Clarification: *opposite* semantics to never (which is the bottom type). Void would be effectively the top type (only inferior to untyped). So, it allows child classes to then return a meaningful value when the interface was just "void" (= no significant return type). As an example, when the interface says "set($val): void", the child class can specify "set($val): mixed" and return the old stored value. Basically, an interface can now say without further clarification "I have no real return value" = "void", rather than having to say "mixed" and then explaining "this is not really mixed, but whatever you want". (I have seen interface method return values being "upgraded" from void to mixed (or just untyped) in the past, just so that a specific child class can now return a meaningful value.) Bob

Daniel Scherzer

1 year ago
On Wed, Jun 4, 2025 at 1:35 PM Bob Weinand <bobwei9@hotmail.com> wrote:
> > On 4.6.2025 16:54:05, Bob Weinand wrote: > > On 2.6.2025 18:27:51, Gina P. Banyard wrote: > >> Hello internals, > >> > >> This is the second RFC out of a set of type system related RFCs I > >> want to propose for PHP 8.5. > >> > >> The objective is to fix a weird quirk of PHP's type system, where > >> void lives in its own type hierarchy. > >> This is visible mainly in that a lack of return type is not > >> isomorphic to a function that has a return type of mixed. > >> > >> Let me know what you think about it. > >> > >> RFC: https://wiki.php.net/rfc/void-as-null > >> > >> Best regards, > >> > >> Gina P. Banyard > > > > I have to agree with other posters here that the distinction between > > null and void is an useful one. > > > > In particular I'd consider the null returned by void to be incidental > > rather than intentional. I consider the return value of void functions > > "some arbitrary value". It just happens to be null. > > Like every function has to return something. But returning null is not > > an intrinsic property of a void function. It's an extrinsic one. You > > observe void functions to generally return null. But that null in > > itself is meaningless. > > > > So, my counter-proposal would be allowing covariance with void and > > allowing everything, including non-nullable types as child type of > > void functions. > > I.e. effectively giving void and never the same semantics, except that > > never also indicates that it never returns. > > > > Additionally I'd be in favour of disallowing (e.g. E_WARNING) > > consuming the return value of _direct_ calls to void functions (with > > the exception of standalone direct calls in short closures, because > > consuming that value is intrinsic rather than necessarily > > intentional). (Disallowing indirect calls would be detrimental for > > usage as callback.) > > > > Bob > > > Clarification: *opposite* semantics to never (which is the bottom type). > Void would be effectively the top type (only inferior to untyped). > > So, it allows child classes to then return a meaningful value when the > interface was just "void" (= no significant return type). As an example, > when the interface says "set($val): void", the child class can specify > "set($val): mixed" and return the old stored value. > > Basically, an interface can now say without further clarification "I > have no real return value" = "void", rather than having to say "mixed" > and then explaining "this is not really mixed, but whatever you want". > > (I have seen interface method return values being "upgraded" from void > to mixed (or just untyped) in the past, just so that a specific child > class can now return a meaningful value.) > > > Bob >
MediaWiki's hook system (https://www.mediawiki.org/wiki/Manual:Hooks) has two different kinds of hooks - those that can be aborted, for one hook handler to say that no other hook handlers should run - those that cannot be aborted MediaWiki uses `void` return types to help enforce this system, where hooks that cannot be aborted must have void returns. See https://www.mediawiki.org/wiki/Manual:Hooks#Hook_handler_return_values. Making it so that any interface function with a void return can be implemented by a function returning anything would seem to be a huge B/C break. If you want to use the top type, why not just use `mixed`? -Daniel

Bob Weinand

1 year ago
On 4.6.2025 22:39:28, Daniel Scherzer wrote:
> On Wed, Jun 4, 2025 at 1:35 PM Bob Weinand <bobwei9@hotmail.com> wrote: > > > On 4.6.2025 16:54:05, Bob Weinand wrote: > > On 2.6.2025 18:27:51, Gina P. Banyard wrote: > >> Hello internals, > >> > >> This is the second RFC out of a set of type system related RFCs I > >> want to propose for PHP 8.5. > >> > >> The objective is to fix a weird quirk of PHP's type system, where > >> void lives in its own type hierarchy. > >> This is visible mainly in that a lack of return type is not > >> isomorphic to a function that has a return type of mixed. > >> > >> Let me know what you think about it. > >> > >> RFC: https://wiki.php.net/rfc/void-as-null > >> > >> Best regards, > >> > >> Gina P. Banyard > > > > I have to agree with other posters here that the distinction > between > > null and void is an useful one. > > > > In particular I'd consider the null returned by void to be > incidental > > rather than intentional. I consider the return value of void > functions > > "some arbitrary value". It just happens to be null. > > Like every function has to return something. But returning null > is not > > an intrinsic property of a void function. It's an extrinsic one. > You > > observe void functions to generally return null. But that null in > > itself is meaningless. > > > > So, my counter-proposal would be allowing covariance with void and > > allowing everything, including non-nullable types as child type of > > void functions. > > I.e. effectively giving void and never the same semantics, > except that > > never also indicates that it never returns. > > > > Additionally I'd be in favour of disallowing (e.g. E_WARNING) > > consuming the return value of _direct_ calls to void functions > (with > > the exception of standalone direct calls in short closures, because > > consuming that value is intrinsic rather than necessarily > > intentional). (Disallowing indirect calls would be detrimental for > > usage as callback.) > > > > Bob > > > Clarification: *opposite* semantics to never (which is the bottom > type). > Void would be effectively the top type (only inferior to untyped). > > So, it allows child classes to then return a meaningful value when > the > interface was just "void" (= no significant return type). As an > example, > when the interface says "set($val): void", the child class can > specify > "set($val): mixed" and return the old stored value. > > Basically, an interface can now say without further clarification "I > have no real return value" = "void", rather than having to say > "mixed" > and then explaining "this is not really mixed, but whatever you want". > > (I have seen interface method return values being "upgraded" from > void > to mixed (or just untyped) in the past, just so that a specific child > class can now return a meaningful value.) > > > Bob > > > > MediaWiki's hook system (https://www.mediawiki.org/wiki/Manual:Hooks) > has two different kinds of hooks > - those that can be aborted, for one hook handler to say that no other > hook handlers should run > - those that cannot be aborted > > MediaWiki uses `void` return types to help enforce this system, where > hooks that cannot be aborted must have void returns. See > https://www.mediawiki.org/wiki/Manual:Hooks#Hook_handler_return_values. > Making it so that any interface function with a void return can be > implemented by a function returning anything would seem to be a huge > B/C break. If you want to use the top type, why not just use `mixed`? > > -Daniel
Hey Daniel, where's the BC break? Nothing which worked today will stop working (except you won't get exceptions in some cases). That's not a BC break. The only thing which stops working is if it's intentionally used as a guard. However, in the case of MediaWiki they do actually _care_ about the return type (and the caller of these hooks will actually check for null/true/false). So it should be annotated ": null". And not ": void". Explicit intentions are important. They probably still use ": void" as to be compatible with PHP 8.1 and older. ": null" is only supported starting PHP 8.2. I'd assume as they upgrade their required PHP version (8.1 currently) they'll shift to ": null". So, yeah, the guard will lose its guarding functionality (but we don't consider that a BC break). Regarding why not mixed? Because the intention with mixed is that the value is something meaningful. With void it's meaningless. There's a semantic distinction (and it forbids returning). And, as proposed, you could forbid direct calls of void functions giving runtime / static analysis hints. With void being covariant with respect to child functions now. Bob

Daniel Scherzer

1 year ago
On Wed, Jun 4, 2025 at 4:49 PM Bob Weinand <bobwei9@hotmail.com> wrote:
> On 4.6.2025 22:39:28, Daniel Scherzer wrote: > > On Wed, Jun 4, 2025 at 1:35 PM Bob Weinand <bobwei9@hotmail.com> wrote: > >> >> On 4.6.2025 16:54:05, Bob Weinand wrote: >> > On 2.6.2025 18:27:51, Gina P. Banyard wrote: >> >> Hello internals, >> >> >> >> This is the second RFC out of a set of type system related RFCs I >> >> want to propose for PHP 8.5. >> >> >> >> The objective is to fix a weird quirk of PHP's type system, where >> >> void lives in its own type hierarchy. >> >> This is visible mainly in that a lack of return type is not >> >> isomorphic to a function that has a return type of mixed. >> >> >> >> Let me know what you think about it. >> >> >> >> RFC: https://wiki.php.net/rfc/void-as-null >> >> >> >> Best regards, >> >> >> >> Gina P. Banyard >> > >> > I have to agree with other posters here that the distinction between >> > null and void is an useful one. >> > >> > In particular I'd consider the null returned by void to be incidental >> > rather than intentional. I consider the return value of void functions >> > "some arbitrary value". It just happens to be null. >> > Like every function has to return something. But returning null is not >> > an intrinsic property of a void function. It's an extrinsic one. You >> > observe void functions to generally return null. But that null in >> > itself is meaningless. >> > >> > So, my counter-proposal would be allowing covariance with void and >> > allowing everything, including non-nullable types as child type of >> > void functions. >> > I.e. effectively giving void and never the same semantics, except that >> > never also indicates that it never returns. >> > >> > Additionally I'd be in favour of disallowing (e.g. E_WARNING) >> > consuming the return value of _direct_ calls to void functions (with >> > the exception of standalone direct calls in short closures, because >> > consuming that value is intrinsic rather than necessarily >> > intentional). (Disallowing indirect calls would be detrimental for >> > usage as callback.) >> > >> > Bob >> >> >> Clarification: *opposite* semantics to never (which is the bottom type). >> Void would be effectively the top type (only inferior to untyped). >> >> So, it allows child classes to then return a meaningful value when the >> interface was just "void" (= no significant return type). As an example, >> when the interface says "set($val): void", the child class can specify >> "set($val): mixed" and return the old stored value. >> >> Basically, an interface can now say without further clarification "I >> have no real return value" = "void", rather than having to say "mixed" >> and then explaining "this is not really mixed, but whatever you want". >> >> (I have seen interface method return values being "upgraded" from void >> to mixed (or just untyped) in the past, just so that a specific child >> class can now return a meaningful value.) >> >> >> Bob >> > > > MediaWiki's hook system (https://www.mediawiki.org/wiki/Manual:Hooks) has > two different kinds of hooks > - those that can be aborted, for one hook handler to say that no other > hook handlers should run > - those that cannot be aborted > > MediaWiki uses `void` return types to help enforce this system, where > hooks that cannot be aborted must have void returns. See > https://www.mediawiki.org/wiki/Manual:Hooks#Hook_handler_return_values. > Making it so that any interface function with a void return can be > implemented by a function returning anything would seem to be a huge B/C > break. If you want to use the top type, why not just use `mixed`? > > -Daniel > > > Hey Daniel, > > where's the BC break? Nothing which worked today will stop working (except > you won't get exceptions in some cases). That's not a BC break. The only > thing which stops working is if it's intentionally used as a guard. >
That (intentionally using `void` returns as a guard) is exactly what MediaWiki does. MediaWiki has an interface for each hook, that requires that hooks that cannot abort (return false) must return void; https://www.mediawiki.org/wiki/Manual:Hooks#Handling_hooks_in_MediaWiki_1.35_and_later. PHP is used to help enforce this.
> However, in the case of MediaWiki they do actually _care_ about the return > type (and the caller of these hooks will actually check for > null/true/false). So it should be annotated ": null". And not ": void". > Explicit intentions are important. > They probably still use ": void" as to be compatible with PHP 8.1 and > older. ": null" is only supported starting PHP 8.2. I'd assume as they > upgrade their required PHP version (8.1 currently) they'll shift to ": > null". > > So, yeah, the guard will lose its guarding functionality (but we don't > consider that a BC break). >
Why is this not considered a BC break? You can consider it a small break, but I think it should be noted in the BC section of the RFC.
> > Regarding why not mixed? Because the intention with mixed is that the > value is something meaningful. With void it's meaningless. There's a > semantic distinction (and it forbids returning). And, as proposed, you > could forbid direct calls of void functions giving runtime / static > analysis hints. With void being covariant with respect to child functions > now. > > > Bob >
If `void` is a top type indicating a return is meaningless, then callers would have no reason to examine the returned value, and then when subclasses do try to add meaning it might be missed. Am I missing something? How would void be different from "the base implementation happens to always return null, but subclasses can return other things, and the result can be meaningful"? -Daniel

Bob Weinand

1 year ago
On 5.6.2025 02:59:56, Daniel Scherzer wrote:
> On Wed, Jun 4, 2025 at 4:49 PM Bob Weinand <bobwei9@hotmail.com> wrote: > > Hey Daniel, > > where's the BC break? Nothing which worked today will stop working > (except you won't get exceptions in some cases). That's not a BC > break. The only thing which stops working is if it's intentionally > used as a guard. > > > That (intentionally using `void` returns as a guard) is exactly what > MediaWiki does. MediaWiki has an interface for each hook, that > requires that hooks that cannot abort (return false) must return void; > https://www.mediawiki.org/wiki/Manual:Hooks#Handling_hooks_in_MediaWiki_1.35_and_later. > PHP is used to help enforce this. > > However, in the case of MediaWiki they do actually _care_ about > the return type (and the caller of these hooks will actually check > for null/true/false). So it should be annotated ": null". And not > ": void". Explicit intentions are important. > They probably still use ": void" as to be compatible with PHP 8.1 > and older. ": null" is only supported starting PHP 8.2. I'd assume > as they upgrade their required PHP version (8.1 currently) they'll > shift to ": null". > > So, yeah, the guard will lose its guarding functionality (but we > don't consider that a BC break). > > > > Why is this not considered a BC break? You can consider it a small > break, but I think it should be noted in the BC section of the RFC.
It's a small break in the sense of workflow, but *compatibility* breaks are concerned with the behaviours of non-erroneous functionality. (Code with worked yesterday works today. And not code which did not work yesterday works today). But sure, the RFC may indicate it.
> Regarding why not mixed? Because the intention with mixed is that > the value is something meaningful. With void it's meaningless. > There's a semantic distinction (and it forbids returning). And, as > proposed, you could forbid direct calls of void functions giving > runtime / static analysis hints. With void being covariant with > respect to child functions now. > > > Bob > > > If `void` is a top type indicating a return is meaningless, then > callers would have no reason to examine the returned value, and then > when subclasses do try to add meaning it might be missed. Am I missing > something? How would void be different from "the base implementation > happens to always return null, but subclasses can return other things, > and the result can be meaningful"? > > -Daniel
What LSP builds upon is "if I code against this interface, my expectations will always be fulfilled". If I code against the interface returning void, I do not care about the actual returned value and such my expectations are always fulfilled. If I code against some subclass (or any of its children) which has a meaningful return type specified, and I'm aware of that return type, I may for sure use the return value. Nothing requires me to actually use the return value. It's similar to optional parameters "When subclasses do try to add optional parameters, they might be missed". That line of argumentation is a bit absurd :-) Bob

Rob Landers

1 year ago
On Mon, Jun 2, 2025, at 18:27, Gina P. Banyard wrote:
> Hello internals, > > This is the second RFC out of a set of type system related RFCs I want to propose for PHP 8.5. > > The objective is to fix a weird quirk of PHP's type system, where void lives in its own type hierarchy. > This is visible mainly in that a lack of return type is not isomorphic to a function that has a return type of mixed. > > Let me know what you think about it. > > RFC: https://wiki.php.net/rfc/void-as-null > > Best regards, > > Gina P. Banyard >
Hi Gina, This RFC feels inverted to me. Instead of making null equivalent to void, why not make using a void return an error (eventually) instead of it returning null? — Rob