[Pre-RFC] Convert exit (and die) from language constructs to functions

php.internals

Gina P. Banyard

2 years ago
Hello internals, I've been having this mild annoyance with exit()/die() since I wrote a CLI script using a boolean $hasErrors variable to know if the script failed or not and to indicate if the script failed via a non-zero status code by doing: exit($hasErrors); However, it turns this doesn't work, because exit() will cast everything that is not an integer to a string, something that I find surprising but also weird as it will not type error on resources or array but rather print the warning to the CLI. Anyway, yesterday I decided to put on my mad scientist lab coat and make this a reality, I have a W.I.P. PR here: https://github.com/php/php-src/pull/13483 I hear you, but what about exit; ?! Do not worry exit; is still supported! It only requires a _tiny_ bit of dark magic to achieve this! exit; would just be interpreted as a fetch to a constant, so when attempting to access the undefined exit/die case-insensitive constant we just need to exit as if we were calling the function! Having exit and die be functions gives us access to some interesting functionality, such as defining exit() in a namespace, or removing it via the disable_functions INI setting (the latter allowing us to define a custom global exit() which could be useful for testing that a function actually calls exit). We can also pass it like any other callable and reflect on it with reflection. Finally, this removes the T_EXIT token and ZEND_EXIT opcode freeing one slot. The W.I.P. PR implement every possible restriction: - not being able to declare an exit()/die() function - not being able to disable them - not being able to define a constant named exit/die Maybe it would be wise to deprecate using exit as a statement to be able to get rid of the undefined constant magic handling. Anyhoot, before I spend more time on this and write a proper RFC, do people think this is a good idea or not? Best regards, Gina P. Banyard

Unnamed Person

2 years ago
On 24-2-2024 2:37, Gina P. Banyard wrote:
> Hello internals, > > I've been having this mild annoyance with exit()/die() since I wrote a CLI > script using a boolean $hasErrors variable to know if the script failed or not > and to indicate if the script failed via a non-zero status code by doing: > exit($hasErrors); > > However, it turns this doesn't work, because exit() will cast everything > that is not an integer to a string, something that I find surprising but > also weird as it will not type error on resources or array but rather print > the warning to the CLI. > > Anyway, yesterday I decided to put on my mad scientist lab coat and make this > a reality, I have a W.I.P. PR here: > https://github.com/php/php-src/pull/13483 > > I hear you, but what about exit; ?! > Do not worry exit; is still supported! > It only requires a _tiny_ bit of dark magic to achieve this! > exit; would just be interpreted as a fetch to a constant, > so when attempting to access the undefined exit/die case-insensitive constant > we just need to exit as if we were calling the function! > > Having exit and die be functions gives us access to some interesting > functionality, such as defining exit() in a namespace, or removing it via the > disable_functions INI setting (the latter allowing us to define a custom global > exit() which could be useful for testing that a function actually calls exit). > > We can also pass it like any other callable and reflect on it with reflection. > > Finally, this removes the T_EXIT token and ZEND_EXIT opcode freeing one slot. > > The W.I.P. PR implement every possible restriction: > - not being able to declare an exit()/die() function > - not being able to disable them > - not being able to define a constant named exit/die > > Maybe it would be wise to deprecate using exit as a statement to be able to get > rid of the undefined constant magic handling. > > Anyhoot, before I spend more time on this and write a proper RFC, do people > think this is a good idea or not? > > > Best regards, > > Gina P. Banyard >
Hi Gina, I'm not sure a pet-peeve is a good motivation for creating an (I expect large) breaking change. The upgrade path, I suppose, would be updating calls to `die`/`exit` to always have parentheses ? Or alternatively changing those calls to new throw expressions ? While that shouldn't be that huge a problem for real codebases (and would be auto-fixable for adding the parentheses), the bigger problem I see is the huge amount of teaching materials, tutorials and blog posts using the versions without parentheses which will now all be invalidated. I think the pain and confusion that will cause for a change like this, will linger for years and years. Smile, Juliette

Gina P. Banyard

2 years ago
On Saturday, 24 February 2024 at 01:57, Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl> wrote: > Hi Gina, > > I'm not sure a pet-peeve is a good motivation for creating an (I expect large) breaking change. > > The upgrade path, I suppose, would be updating calls to `die`/`exit` to always have parentheses ? Or alternatively changing those calls to new throw expressions ? > > While that shouldn't be that huge a problem for real codebases (and would be auto-fixable for adding the parentheses), the bigger problem I see is the huge amount of teaching materials, tutorials and blog posts using the versions without parentheses which will now all be invalidated. I think the pain and confusion that will cause for a change like this, will linger for years and years. > > Smile, > Juliette I didn't actually know one could do exit; But like I said, it is extremely easy to support, and the current PR does support it by hooking into the undefined constant code in the engine. I don't have strong opinions about removing support for this. However, I do have strong opinions about changing the type juggling semantics of exit() to be the usual ones, because the current one is just confusing. I am also not sure what would make this a large breaking change, as changing this from a language construct to a function provides us with more capabilities. Best regards, Gina P. Banyard

Unnamed Person

2 years ago
On 24-2-2024 3:47, Gina P. Banyard wrote:
> > > On Saturday, 24 February 2024 at 01:57, Juliette Reinders Folmer > <php-internals_nospam@adviesenzo.nl> wrote: > >> Hi Gina, >> >> I'm not sure a pet-peeve is a good motivation for creating an (I >> expect large) breaking change. >> >> The upgrade path, I suppose, would be updating calls to `die`/`exit` >> to always have parentheses ? Or alternatively changing those calls to >> new throw expressions ? >> >> While that shouldn't be that huge a problem for real codebases (and >> would be auto-fixable for adding the parentheses), the bigger problem >> I see is the huge amount of teaching materials, tutorials and blog >> posts using the versions without parentheses which will now all be >> invalidated. I think the pain and confusion that will cause for a >> change like this, will linger for years and years. >> >> Smile, >> Juliette >> > > I didn't actually know one could do exit; > But like I said, it is extremely easy to support, and the current PR > does support it by hooking into the undefined constant code in the engine. > I don't have strong opinions about removing support for this. > However, I do have strong opinions about changing the type juggling > semantics of exit() to be the usual ones, because the current one is > just confusing. > I am also not sure what would make this a large breaking change, as > changing this from a language construct to a function provides us with > more capabilities. >
Ah, I think I missed the part about the syntax both with and without parentheses still being supported, with the "with parentheses" mapping to a function call and the "without parentheses" mapping to a case-insensitive constant. In that case, I don't see a BC-break and I would regard this as an "under the hood" change with only a very subtle, minimal impact (the type checking part if a param is passed). I do wonder what the documentation would look like as it would leave it as a function, but one with a special status, in that there is a native constant of the same name which will enforce the same behaviour. Smile, Juliette

Claude Pache

2 years ago
> > I hear you, but what about exit; ?! > Do not worry exit; is still supported! > It only requires a _tiny_ bit of dark magic to achieve this! > exit; would just be interpreted as a fetch to a constant, > so when attempting to access the undefined exit/die case-insensitive constant > we just need to exit as if we were calling the function! >
Hi Gina, If it is mostly an implementation detail, this is probably ok. However, please make sure that `$foo = 'exit'; constant($foo);` (or any other future way to interpolate the constant) does not stop the program, but only fetch the value of a “real” constant. Alternatively, it is probably possible to keep the current parsing with T_EXIT, but to make it equivalent to invoke a “regular” function named `exit()` — somehow the same way that using the backtick operator is equivalent to calling `shell_exec()`, up to the point that disabling `shell_exec()` will also disable the the backtick operator? (BTW, it *is* currently possible to define and use a constant named “exit”: https://3v4l.org/aBq2R — of course, no need to keep such a “feature”.) —Claude