[RFC] [DISCUSSION] Allow void return type for constructors/destructors

php.internals

Benas IML

6 years ago
Hey internals, I am proposing to allow void return type for constructors/destructors. Note, that this is an **optional** and cosmetic-only addition! All of the reasoning is in the RFC. RFC: https://wiki.php.net/rfc/constructor_return_type Best regards, Benas

Girgias

6 years ago
On Tue, 16 Jun 2020 at 02:35, Benas IML <benas.molis.iml@gmail.com> wrote:
> Hey internals, > > I am proposing to allow void return type for constructors/destructors. > Note, > that this is an **optional** and cosmetic-only addition! All of the > reasoning > is in the RFC. > > RFC: https://wiki.php.net/rfc/constructor_return_type > > Best regards, > Benas >
Seems like a no brainer and a good addition. :) Best regards George P. Banyard

Máté Kocsis

6 years ago
Hi Benas, Overall, I'm very much in favour of disallowing returning a value from constructors. However, I think the RFC should deal with the return value and the signature validation in the same time, so that we can vote about both, since introducing the latter check is not just a bug fix, it's a substantial change IMO. What I would like to avoid for sure is that the void return type is not actually enforced. (Offtopic: It seems that the magic method signature RFC exactly does this, and I'm not happy about it. I'd welcome an amendment RFC to fix the new inconsistencies). Furthermore, I personally also like the concept of an explicit return type declaration for constructors, but Nikita's earlier argument against (that most languages forbid the explicit return type) makes me think. Anyhow, this counterargument might be worth to add to the RFC. And regarding the last section of the RFC (Backward Incompatible Changes), I'm not exactly sure what you mean there, but since constructors are exempt to LSP validation, I think it's ok if a child class overrides the parent constructor with a widened return type. Regards, Máté

Benas IML

6 years ago
Hey Máté, On Tue, Jun 16, 2020, 11:31 AM Máté Kocsis <kocsismate90@gmail.com> wrote:
> Hi Benas,
> Overall, I'm very much in favour of disallowing returning a value from
constructors.
> However, I think the RFC should deal with the return value and the
signature validation
> in the same time, so that we can vote about both, since introducing the
latter check
> is not just a bug fix, it's a substantial change IMO. > > What I would like to avoid for sure is that the void return type is not
actually enforced.
> (Offtopic: It seems that the magic method signature RFC exactly does
this, and I'm not
> happy about it. I'd welcome an amendment RFC to fix the new
inconsistencies). Since there was a second PR already in the works on dealing with the return value, I wasn't sure whether it was a good idea to implement that. But, I'm happy to join forces and will contact the PR's author :)
> Furthermore, I personally also like the concept of an explicit return
type declaration for
>constructors, but Nikita's earlier argument against (that most languages
forbid the explicit
> return type) makes me think. Anyhow, this counterargument might be worth
to add to the RFC. I should have emphasized more on this but PHP constructors aren't like any other language constructors, since they are normal methods that can be called by simply doing `$object->__construct();`. I'm not sure if any other language allows you to do something similar.
> And regarding the last section of the RFC (Backward Incompatible
Changes), I'm not exactly
> sure what you mean there, but since constructors are exempt to LSP
validation, I think
> it's ok if a child class overrides the parent constructor with a widened
return type.
> > Regards, > Máté
What I meant, was that since no return type means `mixed|void`, you can't do the following: ``` public function __construct(): mixed {} ``` But yes, it is allowed to widen the type from `void` to no return type. Best regards, Benas

Dan Ackroyd

6 years ago
G.P.B. wrote:
> Seems like a no brainer and a good addition. :)
For situations where stuff is simple, 'no brainers' are okay. For situations where everything is horribly convoluted, 'no brainers' are often bad. Benas IML wrote:
> even though no return type means mixed|void
Not quite. No return type is _treated as_ 'mixed|void' in some circumstances particularly signature checks during inheritance. That's not quite the same as 'meaning' the same.
> What I meant, was that since no return type means `mixed|void`, > you can't do the following:
I'm actually not sure exactly what you're trying to say there, but I think pasting the text I reference whenever trying to think about LSP might be useful... "PHP allows contravariance (aka type widening) for parameter types to obey the LSP principle. A subclass may use a 'wider' aka less specific type in place of the inherited type for a parameter." "PHP allows covariance (aka type narrowing) for return types to obey the LSP principle. A subclass may use a 'narrower' aka more specific type in place of the inherited type for a function return."
> I am proposing to allow void return type for constructors/destructors.
Constructors in PHP have multiple issues that are 'surprising'. I'm not sure that fixing only a small part of how they are surprising, improves PHP drastically. Also...I really wish we already had null as a usable type, as most of the uses of void are slightly inaccurate. The meaning of void and null are: void - this function doesn't return a value null - this function returns a value that has no information in it. Presumably you want to be able to specify void as the return type to make it be easier to detect errors in code like the example you provided. It seems that making a rule for whichever static analyzer you're using, that errors on any assigning of a value from a __construct call, would achieve that, without touching a part of PHP that is full of edge cases. To be clear, I'm not sure if I am in favour or against the RFC. The real problem is that constructors just don't fit into how we think about normal functions is PHP, because of the magic that goes on around them and so both void and null as return types would be a little bit incorrect. cheers Dan Ack Some example functions using those two return types. function this_is_void_return(): void { exit(0); } function this_is_also_void_return(): void { throw new Exception("no usuable return value"); } function this_is_also_also_void_return(): void { while (1) { echo "Hello world\n"; } } function this_is_null_return(): null { // deliberately empty. } // This code is fine. var_dump(this_is_null_return()); // This code is never going to execute the var_dump, which // might indicate an error. var_dump(this_is_void_return()); var_dump(this_is_also_void_return()); var_dump(this_is_also_also_void_return());

Jesse Rushlow

6 years ago
From a PHP developer's POV - when writing a class or refactoring one. I have to catch myself all the time trying to declare ": void" on the constructor. It's mostly a habit from doing so with the other methods in the class. I lean in favor of being able to declare a void return type on a constructor, but only for the aforementioned reason. I have never ran into a use case where I've questioned what the return type is for a constructor. Although I could see newcomers to PHP / Development in general ponder that thought. Thanks, Jesse Rushlow On Tue, Jun 16, 2020 at 8:59 AM Dan Ackroyd <Danack@basereality.com> wrote:

Benas IML

6 years ago
Exactly :) Personally, I have numerous times caught myself declaring constructors `: void` too. I would love to have everything consistent and all methods to have a return type. Although, I do understand that not everybody agrees with me and that's why this RFC isn't planning to ever force people to use `void` return type. As I have said before: some might use it, some might not, but both cases are valid and allowed :) Best regards, Benas On Tue, Jun 16, 2020, 5:11 PM Jesse Rushlow <jr@rushlow.dev> wrote:

Benas IML

6 years ago
Hey Dan, Thanks for your reply.
> No return type is _treated as_ 'mixed|void' in some circumstances
Could you elaborate? No return type DOES mean `mixed|void`, could you give any pointers as to where that is different?
> I'm actually not sure exactly what you're trying to say there
I meant something like this: ``` <?php class Test { public function __construct() {} } class Test2 extends Test { /* this is legal */ public function __construct(): void {} } class Test3 extends Test { /* * this is illegal, even though no * return type means mixed|void */ public function __construct(): mixed {} } class Test4 extends Test2 { /* * this is legal, even though we are * widening void type to mixed|void * but LSP checks don't apply to * constructors and destructors */ public function __construct() {} } ``` This RFC's goal isn't to fix constructor issues. My goal is to allow to (for those that want to) add an optional void return type to constructors/destructors (for example to have complete consistency with other methods). On Tue, Jun 16, 2020, 3:59 PM Dan Ackroyd <Danack@basereality.com> wrote:

Christoph Becker

6 years ago
On 16.06.2020 at 16:19, Benas IML wrote:
> I meant something like this: > ``` > <?php > class Test { > public function __construct() {} > } > > class Test2 extends Test { > /* this is legal */ > public function __construct(): void {} > } > > class Test3 extends Test { > /* > * this is illegal, even though no > * return type means mixed|void > */ > public function __construct(): mixed {} > } > > class Test4 extends Test2 { > /* > * this is legal, even though we are > * widening void type to mixed|void > * but LSP checks don't apply to > * constructors and destructors > */ > public function __construct() {} > } > ```
Currently, constructors are exempt from LSP checks[1]; are you planning to change that? [1] <https://3v4l.org/PeDCY>
-- Christoph M. Becker

Benas IML

6 years ago
Nope, that isn't changing. I simply wanted to point out that this RFC is proposing to allow to declare constructor return type as `: void`, but not as `: mixed`. The entire RFC is just a matter of cosmetic addition allowing to add `: void` to constructors/destructors (for those that want to). It seems that I will have to rewrite the BC section to be more clear on that. Best regards, Benas On Tue, Jun 16, 2020, 5:36 PM Christoph M. Becker <cmbecker69@gmx.de> wrote:

Rowan Collins

6 years ago
On Tue, 16 Jun 2020 at 13:59, Dan Ackroyd <Danack@basereality.com> wrote:
> To be clear, I'm not sure if I am in favour or against the RFC. The > real problem is that constructors just don't fit into how we think > about normal functions is PHP, because of the magic that goes on > around them and so both void and null as return types would be a > little bit incorrect. >
I'm not clear what you mean by this. The definition of ": void" is currently that the function must either use "return;" without a value, or conclude without an explicit "return"; a correctly written constructor would meet that constraint. The language doesn't currently prevent void functions from being used in expression context, but even if it did, that would be consistent with how constructors are actually called internally, which is roughly equivalent to this: $newInstance = initialise_object($classDefinition); $newInstance->__construct(...$args); return $newInstance; If a constructor returns a value, it is simply discarded, so a signature of void would be completely appropriate. Regards,
-- Rowan Tommins [IMSoP]

Benas IML

6 years ago
Hey internals, I put the original RFC on hold and created a new PR [0] for implicitly enforcing `void` rules on both constructors and destructors. Note, that this results in a BC break since it is no longer legal to return non-void value from constructors/destructors. In other words, it is now illegal to return something from ctor. As a side bonus, it is also allowed to explicitly declare both `__construct()` and `__destruct()` as `void` (but this is by no means mandatory, it's optional). I'm not sure whether this needs a proper RFC since this is more of a patch (fix: #79679) than a new feature, so let me know! This PR should also address all of Máté's concerns since it makes constructors and destructors always return `void` (even when no explicit `void` return type is specified). Best regards, Benas Seliuginas On Tue, Jun 16, 2020, 3:34 AM Benas IML <benas.molis.iml@gmail.com> wrote:

Christoph Becker

6 years ago
On 16.06.2020 at 21:30, Benas IML wrote:
> I put the original RFC on hold and created a new PR [0] for implicitly > enforcing `void` rules on both constructors and destructors. Note, that > this results in a BC break since it is no longer legal to return non-void > value from constructors/destructors. In other words, it is now illegal to > return something from ctor. > > As a side bonus, it is also allowed to explicitly declare both > `__construct()` and `__destruct()` as `void` (but this is by no means > mandatory, it's optional). > > I'm not sure whether this needs a proper RFC since this is more of a patch > (fix: #79679) than a new feature, so let me know! > > This PR should also address all of Máté's concerns since it makes > constructors and destructors always return `void` (even when no explicit > `void` return type is specified).
Thanks, I tend to prefer this solution. Given the potential BC break (constructors may be called like normal methods, so returning something can make sense), I think an RFC is justified.
-- Christoph M. Becker

Christian Schneider

6 years ago
Am 16.06.2020 um 21:30 schrieb Benas IML <benas.molis.iml@gmail.com>:
> I put the original RFC on hold and created a new PR [0] for implicitly > enforcing `void` rules on both constructors and destructors. Note, that > this results in a BC break since it is no longer legal to return non-void > value from constructors/destructors. In other words, it is now illegal to > return something from ctor.
This is a huge BC break as up to now returning something in a constructor was silently ignored. Therefore I'd strongly advise to go through a deprecation phase (E_DEPRECATED or E_WARNING) before making it a fatal error so old software can be fixed first. - Chris

Nikita Popov

6 years ago
On Wed, Jun 17, 2020 at 9:18 AM Christian Schneider <cschneid@cschneid.com> wrote:
> Am 16.06.2020 um 21:30 schrieb Benas IML <benas.molis.iml@gmail.com>: > > I put the original RFC on hold and created a new PR [0] for implicitly > > enforcing `void` rules on both constructors and destructors. Note, that > > this results in a BC break since it is no longer legal to return non-void > > value from constructors/destructors. In other words, it is now illegal to > > return something from ctor. > > This is a huge BC break as up to now returning something in a constructor > was silently ignored. > Therefore I'd strongly advise to go through a deprecation phase > (E_DEPRECATED or E_WARNING) before making it a fatal error so old software > can be fixed first. >
I analyzed the top 2k composer packages, and found 95 places that would be affected by making __construct implicitly void: https://gist.github.com/nikic/e0d7c9c810e15b843ffd7d6779a2e49a Common cases seem to be "return parent::__construct()", "return $this" and "return false". There's not a lot of them, but I do tend to agree that starting out with a warning would be better. Regards, Nikita

Máté Kocsis

6 years ago
> > This PR should also address all of Máté's concerns since it makes > constructors and destructors always return `void` (even when no explicit > `void` return type is specified).
To be clear, my main concern was that declaring a void return type shouldn't be allowed, unless it's actually enforced. Now, it's all fine because you stated your intentions :) However, I am also fine if the void return value is only enforced when the void return type is declared. Additionally, we could emit a deprecation notice/warning when there is no return type declaration, but a value is returned. I think this would be a nice way to immediately give people the possibility to disallow a return value, while being respectful for older code bases. Regards, Máté

Benas IML

6 years ago
Thanks everybody for the replies.
> However, I am also fine if the void return value is only enforced when the void return > type is declared. Additionally, we could emit a deprecation notice/warning when > there is no return type declaration, but a value is returned. I think this would be > a nice way to immediately give people the possibility to disallow a return value, > while being respectful for older code bases.
Completely agreed. I'll update the RFC to reflect this as well. We should allow newer codebases to enforce `void` rules on constructors/destructors by allowing to explicitly declare return type as `void`. Meanwhile, we should throw a deprecation warning, if the constructor/destructor is returning a non-void value and doesn't have an explicit `: void` declaration. Then, presumably in PHP 9, in addition to explicit `: void`, also enforce `void` rules implicitly altogether. Best regards, Benas On Wed, 17 Jun 2020 at 11:31, Máté Kocsis <kocsismate90@gmail.com> wrote:

Christian Schneider

6 years ago
Am 17.06.2020 um 13:59 schrieb Benas IML <benas.molis.iml@gmail.com>:
> We should allow newer codebases to enforce `void` rules on > constructors/destructors by allowing to explicitly declare return type as `void`.
I don't see a big benefit in this explicit return type void as newer codebases hopefully will try to get rid of deprecation warnings and thus fixing it but if you think this is helpful then I'll shut up :-)
> Meanwhile, we should throw a deprecation warning, if the constructor/destructor is > returning a non-void value and doesn't have an explicit `: void` declaration.
I'm not sure I understand the second part "and doesn't have an explicit `: void` declaration". If there is a ' : void' declaration then returning any value will be an error. As far as I can see this is already the case: php -r 'class A { function __construct() : void { return 42; } }' with PHP 7.4 gives PHP Fatal error: A void function must not return a value in Command line code on line 1 But I definitely wouldn't want a deprecation warning for constructors without explicit ': void' as I see no reason to force people to use it.
> Then, presumably in PHP 9, in addition to explicit `: void`, also enforce `void` rules implicitly > altogether.
Yes, that's how I would do it. - Chris

Benas IML

6 years ago
Hey Christian, Wrong wording, sorry! :)
> But I definitely wouldn't want a deprecation warning for constructors without explicit ': void' as I see no reason to force people to use it.
This RFC proposes 2 things: 1) If a non-void value is returned from a constructor/destructor, a deprecation warning is thrown. Meanwhile, in PHP 9.0 a fatal error is thrown instead. 2) It will be allowed to declare constructors/destructors explicitly as `: void` (previously this was illegal). Best regards, Benas On Wed, 17 Jun 2020 at 16:36, Christian Schneider <cschneid@cschneid.com> wrote:

Christoph Becker

6 years ago
On 17.06.2020 at 15:36, Christian Schneider wrote:
> I'm not sure I understand the second part "and doesn't have an explicit `: void` declaration". > If there is a ' : void' declaration then returning any value will be an error. > As far as I can see this is already the case: > php -r 'class A { function __construct() : void { return 42; } }' > with PHP 7.4 gives > PHP Fatal error: A void function must not return a value in Command line code on line 1
But see <https://3v4l.org/uEOnQ>.
-- Christoph M. Becker

Dan Ackroyd

6 years ago
On Tue, 16 Jun 2020 at 01:35, Benas IML <benas.molis.iml@gmail.com> wrote:
> > Hey internals, > > RFC: https://wiki.php.net/rfc/constructor_return_type >
Hi Benas, Instead of deleting RFC docs, please can you update their status to 'inactive' or 'replaced by xyz'. The wiki is part of the history of the discussion, and without having the document easily available, it's not possible for people to fully understand the discussion that we had on the list. cheers Dan Ack

Christoph Becker

6 years ago
On 17.06.2020 at 11:35, Dan Ackroyd wrote:
> On Tue, 16 Jun 2020 at 01:35, Benas IML <benas.molis.iml@gmail.com> wrote: >> >> Hey internals, >> >> RFC: https://wiki.php.net/rfc/constructor_return_type >> > > Hi Benas, > > Instead of deleting RFC docs, please can you update their status to > 'inactive' or 'replaced by xyz'. > > The wiki is part of the history of the discussion, and without having > the document easily available, it's not possible for people to fully > understand the discussion that we had on the list.
I have restored the last revision, and set the status to superseded.
-- Christoph M. Becker

Benas IML

6 years ago
Just a heads up :) Both PR and the RFC were updated to reflect the changes and to minimize the amount of BC breaks. TL;DR the RFC proposes to deprecate returning non-void values from constructors/destructors in PHP 8.0 and always enforce void rules on constructors/destructors in PHP 9.0. Additionally, to allow declaring an explicit `: void` return type on constructors/destructors. Best regards, Benas On Tue, 16 Jun 2020 at 03:34, Benas IML <benas.molis.iml@gmail.com> wrote: