[PHP-DEV] [RFC] Transform exit() from a language construct into a standard function

Hello Internals,

I would like to formally propose my idea for exit() as a function brought up to the list on 2024-02-24 [1] with the following RFC:
https://wiki.php.net/rfc/exit-as-function

There have been some slight tweaks to the implementation, namely that the transformation from a "constant" to a function is done at compile time and we do not hook into the behaviour of constants any longer.

Let me know what you think.

Best regards,

Gina P. Banyard

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

Hi

On 5/8/24 15:40, Gina P. Banyard wrote:

Let me know what you think.

The fewer not-actually-a-function functions, the better. I don't have any remarks and I'm in favor.

Best regards
Tim Düsterhus

On 08/05/2024 15:40, Gina P. Banyard wrote:

Hello Internals,

I would like to formally propose my idea for exit() as a function brought up to the list on 2024-02-24 [1] with the following RFC:
PHP: rfc:exit-as-function

There have been some slight tweaks to the implementation, namely that the transformation from a "constant" to a function is done at compile time and we do not hook into the behaviour of constants any longer.

Let me know what you think.

Best regards,

Gina P. Banyard

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

Hi Gina

Thanks for proposing this, I'm in favor of this change because this creates more consistency.

Kind regards
Niels

On Thu, 9 May 2024, Flávio Heleno wrote:

On Thu, May 9, 2024 at 7:51 AM Niels Dossche <dossche.niels@gmail.com>
wrote:

> On 08/05/2024 15:40, Gina P. Banyard wrote:
> > Hello Internals,
> >
> > I would like to formally propose my idea for exit() as a function
> > brought up to the list on 2024-02-24 [1] with the following RFC:
> > PHP: rfc:exit-as-function
> >
> > There have been some slight tweaks to the implementation, namely
> > that the transformation from a "constant" to a function is done at
> > compile time and we do not hook into the behaviour of constants
> > any longer.
> >
> > Let me know what you think.
>
> Thanks for proposing this, I'm in favor of this change because this
> creates more consistency.

Is there a list of other "functions" that are handled as "constants"?

I don't think there are any other "functions" like this.

That's why removing the special case for "exit" and "die" is a good
idea.

cheers,
Derick

Hi all,

Is there a list of other “functions” that are handled as “constants”?

···

Atenciosamente,

Flávio Heleno

I don’t think there are any other “functions” like this.

What about list(), isset(), print(), echo(), require(), include(), unset(), empty()? We use them the same way as functions, but those are not real functions.

Kind regards,

Jorg

On Wed, May 8, 2024, at 1:40 PM, Gina P. Banyard wrote:

Hello Internals,

I would like to formally propose my idea for exit() as a function
brought up to the list on 2024-02-24 [1] with the following RFC:
PHP: rfc:exit-as-function

There have been some slight tweaks to the implementation, namely that
the transformation from a "constant" to a function is done at compile
time and we do not hook into the behaviour of constants any longer.

Let me know what you think.

Best regards,

Gina P. Banyard

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

I support this. My only question (which applies to current exit() as well), is what the exit code is when you pass a string as the parameter. That's not clear from the exit() docs currently, but the RFC made me wonder. (This may be just a doc fix and we move on with life.)

--Larry Garfield

Hi Gina,

Hello Internals,

I would like to formally propose my idea for exit() as a function brought up to the list on 2024-02-24 [1] with the following RFC:
PHP: rfc:exit-as-function

There have been some slight tweaks to the implementation, namely that the transformation from a "constant" to a function is done at compile time and we do not hook into the behaviour of constants any longer.

Let me know what you think.

Best regards,

Gina P. Banyard

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

I generally agree :slight_smile:

My only concern is that if this function throws an error and I catch it, won't it proceed against the user's intentions about it. I am concerned that this may lead to data corruption and personal information leaks due to unintentional data being inserted into the DB.

try {
    exit([]);
} catch (Throwable) {
    //
}

Probably no one likes to write code like this, and even if there are, there are probably very few, but code that is written with the assumption that exit will always stop processing may be a little dangerous.

Is it nonsense to always stop processing even if an error occurs? Or, how about creating an uncatchable exception class specifically for exits? Or is this level of risk negligible?

I am writing down the ideas I came up with, so please excuse me if there are any mistakes in my understanding.

Regard,

Saki

Hi

On 5/9/24 16:27, Larry Garfield wrote:

I support this. My only question (which applies to current exit() as well), is what the exit code is when you pass a string as the parameter. That's not clear from the exit() docs currently, but the RFC made me wonder. (This may be just a doc fix and we move on with life.)

If a string is passed, the exit_status is left untouched [1]. In practice this should always result in an exit status of 0, because I can't see how the exit_status would be non-zero before `exit("foo");` successfully executes.

Best regards
Tim Düsterhus

[1] php-src/Zend/zend_vm_def.h at f88fc9c6e8262fe48eb9cf078e32e63af19047f2 · php/php-src · GitHub

Hi

On 5/9/24 16:17, Jorg Sowa wrote:

I don't think there are any other "functions" like this.

What about list(), isset(), print(), echo(), require(), include(), unset(),
empty()? We use them the same way as functions, but those are not real
functions.

All of them require to be followed by an expression (or some other non-empty token), thus they do not act like constants the same way 'exit' and 'die' do:

- unset, isset, empty, list must be followed by '('.
- echo is a statement and needs to be terminated with ';'.
- print is an expression and requires a single non-empty expression.
- The include family is an expression and requires a single non-empty expression.

Best regards
Tim Düsterhus

PS: Today I learned that echo and print are not aliases.

Hi

On 5/9/24 17:18, Saki Takamachi wrote:

Is it nonsense to always stop processing even if an error occurs? Or, how about creating an uncatchable exception class specifically for exits? Or is this level of risk negligible?

This is already the case. `exit` throws an internal uncatchable Exception. Quoting from the RFC:

Finally, the need for exit() to be a language construct with its own dedicated opcode is not a requirement any more since PHP 8.0 as the opcode throws a special kind of exception which cannot be caught, 2) nor executes finally blocks, to unwind the stack normally.

Best regards
Tim Düsterhus

Hi Tim,

This is already the case. `exit` throws an internal uncatchable Exception. Quoting from the RFC:

Thanks, I had overlooked that. I have no other concerns.

Regards,

Saki

No objections from my side, though, yes, PHPCS will need to be updated/adjusted to work around this, but that’s no biggie. When reading the RFC, there are two things about which I still have questions. 1. As things are, exit and die cannot be used as a label for a goto statement. php goto exit; exit: echo 'exited'; and Will that change now exit would no longer be a reserved keyword ? 2. The RFC mentions the “old” semantics regarding type juggling for exit/die - always cast to string - and it mentions that passing resources or arrays in the new situation will become a TypeError, but that still leaves some room for interpretation for the other types, in particular the handling of booleans. How I read the RFC, the type juggling would change as follows (but I may well be wrong!): Might it be an idea to make all the type juggling changes explicit in the RFC ? (and correct whatever I interpreted incorrectly) Smile, Juliette

···

On 8-5-2024 15:40, Gina P. Banyard wrote:

I would like to formally propose my idea for exit() as a function brought up to the list on 2024-02-24 [1] with the following RFC:
[https://wiki.php.net/rfc/exit-as-function](https://wiki.php.net/rfc/exit-as-function)

There have been some slight tweaks to the implementation, namely that the transformation from a "constant" to a function is done at compile time and we do not hook into the behaviour of constants any longer.

https://3v4l.org/fluukhttps://3v4l.org/cNMEW

| Param passed | Old | New | Consequences |
|-----------------------|---------|-----------|---------------------------------------------------------------------------------------------------------------|
| integer | integer | integer | No change, interpreted as exit code |
| string | string | string | No change, interpreted as status message |
| bool | string | integer | Was status message, now exit code |
| float | string | integer | Was status message, now exit code, "Implicit conversion from float to int loses precision" deprecation notice |
| null | string | integer | Was status message, now exit code, "Passing null to parameter #1 ($status) of type string\|int is deprecated" |
| stringable object | string | string | No change, interpreted as status message |
| non-stringable object | string | TypeError | |
| array | string | TypeError | |
| resource | string | TypeError | |

On Saturday, 11 May 2024 at 09:05, Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl> wrote:

On 8-5-2024 15:40, Gina P. Banyard wrote:

I would like to formally propose my idea for exit() as a function brought up to the list on 2024-02-24 [1] with the following RFC:
PHP: rfc:exit-as-function
There have been some slight tweaks to the implementation, namely that the transformation from a "constant" to a function is done at compile time and we do not hook into the behaviour of constants any longer.

No objections from my side, though, yes, PHPCS will need to be updated/adjusted to work around this, but that's no biggie.

When reading the RFC, there are two things about which I still have questions.

1. As things are, `exit` and `die` cannot be used as a label for a goto statement.

goto exit;

exit:
echo 'exited';

Online PHP editor | output for fluuk and Online PHP editor | output for cNMEW

Will that change now `exit` would no longer be a reserved keyword ?

I had not thought about goto labels, but indeed it would be possible to use exit and die as goto labels with this RFC.
I have clarrified this change in the RFC text and added tests for this in the PR.

2. The RFC mentions the "old" semantics regarding type juggling for exit/die - always cast to string - and it mentions that passing resources or arrays in the new situation will become a TypeError, but that still leaves some room for interpretation for the other types, in particular the handling of booleans.

How I read the RFC, the type juggling would change as follows (but I may well be wrong!):

| Param passed | Old | New | Consequences |
|-----------------------|---------|-----------|---------------------------------------------------------------------------------------------------------------|
| integer | integer | integer | No change, interpreted as exit code |
| string | string | string | No change, interpreted as status message |
| bool | string | integer | Was status message, now exit code |
| float | string | integer | Was status message, now exit code, "Implicit conversion from float to int loses precision" deprecation notice |
| null | string | integer | Was status message, now exit code, "Passing null to parameter #1 ($status) of type string\|int is deprecated" |
| stringable object | string | string | No change, interpreted as status message |
| non-stringable object | string | TypeError | |
| array | string | TypeError | |
| resource | string | TypeError | |

Might it be an idea to make all the type juggling changes explicit in the RFC ? (and correct whatever I interpreted incorrectly)

I have done so, and fixed the one case you got wrong.

Best regards,
Gina P. Banyard

On Thursday, 9 May 2024 at 15:17, Jorg Sowa <jorg.sowa@gmail.com> wrote:

I don't think there are any other "functions" like this.

What about list(), isset(), print(), echo(), require(), include(), unset(), empty()? We use them the same way as functions, but those are not real functions.
Kind regards,
Jorg

list() (and array()) may look like functions but do not behave like one as they affect the current scope by setting various variables.

isset()/empty()/unset() require to work with undefined variables and have deeply ingrained behaviour within the engine, so making them simple functions is not as much of a "trivial" change.

print, echo, include(_once) and require(_once) do not mandate their "argument" to be passed within parenthethis, so making them functions does not simplify the lexer/parser nor removes them as keywords.
Also I don't know the last time I've used those language constructs "like a function".

Hope this clarifies things.

Best regards,
Gina P. Banyard

On Thursday, 9 May 2024 at 16:18, Saki Takamachi <saki@sakiot.com> wrote:

Hi Gina,

> Hello Internals,
>
> I would like to formally propose my idea for exit() as a function brought up to the list on 2024-02-24 [1] with the following RFC:
> PHP: rfc:exit-as-function
>
> There have been some slight tweaks to the implementation, namely that the transformation from a "constant" to a function is done at compile time and we do not hook into the behaviour of constants any longer.
>
> Let me know what you think.
>
> Best regards,
>
> Gina P. Banyard
>
> [1] [Pre-RFC] Convert exit (and die) from language constructs to functions - Externals

I generally agree :slight_smile:

My only concern is that if this function throws an error and I catch it, won't it proceed against the user's intentions about it. I am concerned that this may lead to data corruption and personal information leaks due to unintentional data being inserted into the DB.

`try { exit(); } catch (Throwable) { // }`

Probably no one likes to write code like this, and even if there are, there are probably very few, but code that is written with the assumption that exit will always stop processing may be a little dangerous.

Is it nonsense to always stop processing even if an error occurs? Or, how about creating an uncatchable exception class specifically for exits? Or is this level of risk negligible?

I am writing down the ideas I came up with, so please excuse me if there are any mistakes in my understanding.

Regard,

Saki

You can already escape an exit() call, either by setting an error handler that throws, or by passing a non-stringable object.
See: Online PHP editor | output for vR3up

Best regards,

Gina P. Banyard

On 11 May 2024 15:43:19 BST, "Gina P. Banyard" <internals@gpb.moe> wrote:

print, echo, include(_once) and require(_once) do not mandate their "argument" to be passed within parenthethis, so making them functions does not simplify the lexer/parser nor removes them as keywords.

It's actually a much stronger difference than that: parentheses are not parsed as surrounding the argument lists for those keywords at all.

A while ago, I added notes to the manual pages of each showing how this can lead to misleading code, e.g. one of the examples on PHP: print - Manual is this:

print(1 + 2) * 3;
// outputs "9"; the parentheses cause 1+2 to be evaluated first, then 3*3
// the print statement sees the whole expression as one argument

echo has further peculiarities, because it takes an unparenthesised list of arguments, and can't be used in an expression.

While it would probably have been better if those had been parsed like functions to begin with, changing them now would not just be pointless, it would be actively dangerous, changing the behaviour of existing code.

Regards,
Rowan Tommins
[IMSoP]

Seeing this list, makes me wonder: what about eval() ?

···

On 11-5-2024 16:43, Gina P. Banyard wrote:

On Thursday, 9 May 2024 at 15:17, Jorg Sowa jorg.sowa@gmail.com wrote:

I don’t think there are any other “functions” like this.

What about list(), isset(), print(), echo(), require(), include(), unset(), empty()? We use them the same way as functions, but those are not real functions.

list() (and array()) may look like functions but do not behave like one as they affect the current scope by setting various variables.

isset()/empty()/unset() require to work with undefined variables and have deeply ingrained behaviour within the engine, so making them simple functions is not as much of a “trivial” change.

print, echo, include(_once) and require(_once) do not mandate their “argument” to be passed within parenthethis, so making them functions does not simplify the lexer/parser nor removes them as keywords.
Also I don’t know the last time I’ve used those language constructs “like a function”.

On Tuesday, 14 May 2024 at 17:24, Juliette Reinders Folmer <php-internals_nospam@adviesenzo.nl> wrote:

On 11-5-2024 16:43, Gina P. Banyard wrote:

On Thursday, 9 May 2024 at 15:17, Jorg Sowa [<jorg.sowa@gmail.com>](mailto:jorg.sowa@gmail.com) wrote:

I don't think there are any other "functions" like this.

What about list(), isset(), print(), echo(), require(), include(), unset(), empty()? We use them the same way as functions, but those are not real functions.

list() (and array()) may look like functions but do not behave like one as they affect the current scope by setting various variables.

isset()/empty()/unset() require to work with undefined variables and have deeply ingrained behaviour within the engine, so making them simple functions is not as much of a "trivial" change.

print, echo, include(_once) and require(_once) do not mandate their "argument" to be passed within parenthethis, so making them functions does not simplify the lexer/parser nor removes them as keywords.
Also I don't know the last time I've used those language constructs "like a function".

Seeing this list, makes me wonder: what about eval() ?

eval() might make sense to try and convert to a function, this would probably also cleany solve the complaint about not being able to disable eval() [1][2]

Best regards,
Gina P. Banyard

[1] PHP :: Request #62397 :: disable_functions = eval does not work

[2] Fix bug #62397 - disable_functions does not work with eval. by beberlei · Pull Request #4084 · php/php-src · GitHub

On Wednesday, 8 May 2024 at 14:40, Gina P. Banyard <internals@gpb.moe> wrote:

Hello Internals,

I would like to formally propose my idea for exit() as a function brought up to the list on 2024-02-24 [1] with the following RFC:
PHP: rfc:exit-as-function

There have been some slight tweaks to the implementation, namely that the transformation from a "constant" to a function is done at compile time and we do not hook into the behaviour of constants any longer.

Let me know what you think.

Best regards,

Gina P. Banyard

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

As there haven't been any comments for nearly two weeks, I'm planning on opening the vote for the RFC on Tuesday.

Best regards,

Gina P. Banyard