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
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
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();
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
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
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.
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
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
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]
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
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
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
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
Hi Gina
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
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
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 thatvoid
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
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 thatvoid
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
Hi Gina
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 forset()
to return the previous value, or
null
if there was none. This change will go completely unnoticed by
Map::set()
, becausevoid
is now justnull
, 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 forvoid
/return;
is
also true. In this example, we now updateMap::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 somehowif (!$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 tonotify()
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 trailingreturn $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
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
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 treatvoid
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 tonull
.
I do not see how this invalidates my argument nor support yours.
Best regards,
Gina P. Banyard
[G1] https://wiki.php.net/rfc/void_return_type#why_call_it_void_and_not_null
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
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
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
> >> 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 usemixed
?-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
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 usemixed
?-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
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
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