Apply strict_types to internal functions

php.internals

Saki Takamachi

3 years ago
Hello. I’m Saki Takamachi. Inspired by the issue below, I'm thinking of proposing this RFC. https://github.com/php/php-src/issues/12055 As the documentation below states, PHP's internal functions currently ignore strict_types. https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict I think the current spec is not intuitive, so I propose changing the behavior of the macro ZEND_ARG_USES_STRICT_TYPES() to allow internal functions to accept strict_types. I plan to make changes in zend_compile.h and zend_compile.c and do the changes myself, I haven't written any tests yet, but I'm thinking about making changes like this. https://github.com/SakiTakamachi/php-src/pull/1 As per How To Create an RFC, I will first try to gauge response to this proposal on the mailing list. Thank you Saki Takamachi

Lynn

3 years ago
On Tue, Aug 29, 2023 at 2:46 AM Saki Takamachi <saki@sakiot.com> wrote:
> Hello. > > I’m Saki Takamachi. > > Inspired by the issue below, I'm thinking of proposing this RFC. > https://github.com/php/php-src/issues/12055 > > As the documentation below states, PHP's internal functions currently > ignore strict_types. > > https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict > > I think the current spec is not intuitive, > so I propose changing the behavior of the macro > ZEND_ARG_USES_STRICT_TYPES() to allow internal functions to accept > strict_types. > > I plan to make changes in zend_compile.h and zend_compile.c and do the > changes myself, I haven't written any tests yet, but I'm thinking about > making changes like this. > https://github.com/SakiTakamachi/php-src/pull/1 > > As per How To Create an RFC, I will first try to gauge response to this > proposal on the mailing list. > > Thank you > > Saki Takamachi >
Heya! I was not aware that strict types didn't work here. While I'm 100% behind the idea, I am afraid that changing this will break code in currently strict files where the assumption was made that it already worked like that. It would probably have to give deprecation notices first, and then possibly a warning or removal in the next major to be feasible.

Saki Takamachi

3 years ago
> Heya! > > I was not aware that strict types didn't work here. While I'm 100% behind > the idea, I am afraid that changing this will break code in currently > strict files where the assumption was made that it already worked like > that. It would probably have to give deprecation notices first, and then > possibly a warning or removal in the next major to be feasible.
Hi, Thank you for confirming! Admittedly, it might be better to have some stages rather than a sudden change in 8.4. saki

Claude Pache

3 years ago
> Le 29 août 2023 à 02:46, Saki Takamachi <saki@sakiot.com> a écrit : > > Hello. > > I’m Saki Takamachi. > > Inspired by the issue below, I'm thinking of proposing this RFC. > https://github.com/php/php-src/issues/12055 > > As the documentation below states, PHP's internal functions currently ignore strict_types. > https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict > > I think the current spec is not intuitive, > so I propose changing the behavior of the macro ZEND_ARG_USES_STRICT_TYPES() to allow internal functions to accept strict_types. > > I plan to make changes in zend_compile.h and zend_compile.c and do the changes myself, I haven't written any tests yet, but I'm thinking about making changes like this. > https://github.com/SakiTakamachi/php-src/pull/1 > > As per How To Create an RFC, I will first try to gauge response to this proposal on the mailing list. > > Thank you > > Saki Takamachi
Hi, The larger issue is that higher-order functions do not inherit the `strict_types` mode from its calling code, and this is not specific to internal functions. If you intend to change that for internal functions (with proper deprecation period, of course), you might consider making such a behaviour also possible for userland functions (probably using some opt-in); so that future higher-order internal functions do not become impossible to polyfill. —Claude

Saki Takamachi

3 years ago
> Hi, > > The larger issue is that higher-order functions do not inherit the `strict_types` mode from its calling code, and this is not specific to internal functions. > > If you intend to change that for internal functions (with proper deprecation period, of course), you might consider making such a behaviour also possible for userland functions (probably using some opt-in); so that future higher-order internal functions do not become impossible to polyfill. > > —Claude
Hi, thank you for confirming. If you simply put the problem you pointed out in the code, would it be something like this? https://gist.github.com/SakiTakamachi/8292dbfe92a2029a6c7b506b12296b7d Admittedly, I don't think this is intuitive either. However, if you always inherit the strict_types of the PHP file that is the starting point of processing, in most frameworks the settings in “index.php" will affect everything. When considering the inheritance of strict_types, I think that it should be an attribute that can be set in “php.ini" instead of being specified for each file. What do you think? saki

Claude Pache

3 years ago
> Le 29 août 2023 à 15:19, Saki Takamachi <saki@sakiot.com> a écrit : > >> Hi, >> >> The larger issue is that higher-order functions do not inherit the `strict_types` mode from its calling code, and this is not specific to internal functions. >> >> If you intend to change that for internal functions (with proper deprecation period, of course), you might consider making such a behaviour also possible for userland functions (probably using some opt-in); so that future higher-order internal functions do not become impossible to polyfill. >> >> —Claude > > Hi, thank you for confirming. > > If you simply put the problem you pointed out in the code, would it be something like this? > https://gist.github.com/SakiTakamachi/8292dbfe92a2029a6c7b506b12296b7d > > Admittedly, I don't think this is intuitive either.
That might not be intuitive to you, but it follows directly from the specified semantics: whether a function is called in `strict_types` mode or not, depends only on where the function is called. I personally doubt that there is an easy way to adjust the rule in order to make it both “intuitive” and simple.
> > [...] > > When considering the inheritance of strict_types, I think that it should be an attribute that can be set in “php.ini" instead of being specified for each file. > > What do you think?
Beware that there are many third-party packages that have not opted for `strict_types=1`; if you enable that mode globally, you might make those packages malfunction. As I have just checked, among the packages I’ve installed via composer, there are more packages that do not use `strict_types=1`, than packages that use it. Among those that don’t use `strict_types`, the following ones are fairly common: google/apiclient league/oauth2-client michelf/php-markdown microsoft/microsoft-graph phpmailer/phpmailer phpoffice/phpspreadsheet phpseclib/phpseclib —Claude

Hans Henrik Bergan

3 years ago
maybe we could do a declare(internal_strict_types=1); on a per-file basis just like the existing userland strict_types? (name is up for bikeshedding ofc) On Tue, 29 Aug 2023 at 17:49, Claude Pache <claude.pache@gmail.com> wrote:

Hans Henrik Bergan

3 years ago
* I think most code actually using strict_types=1 is built with the assumption that internal functions use strict too, so it's entirely possible that the backwards-compatibility issue is so small that we can get away with internal functions just inheriting the userland strict_types On Tue, 29 Aug 2023 at 18:36, Hans Henrik Bergan <divinity76@gmail.com> wrote:

Claude Pache

3 years ago
> Le 29 août 2023 à 18:40, Hans Henrik Bergan <divinity76@gmail.com> a écrit : > > * I think most code actually using strict_types=1 is built with the > assumption that internal functions use strict too, so it's entirely > possible that the backwards-compatibility issue is so small that we > can get away with internal functions just inheriting the userland > strict_types > > On Tue, 29 Aug 2023 at 18:36, Hans Henrik Bergan <divinity76@gmail.com> wrote: >> >> maybe we could do a >> declare(internal_strict_types=1); >> on a per-file basis just like the existing userland strict_types? >> (name is up for bikeshedding ofc) >> >> On Tue, 29 Aug 2023 at 17:49, Claude Pache <claude.pache@gmail.com> wrote: >> [...] >
Hi Hans Henrik, The rule of the mailing list is to post your answer below the text you are answering to, and to strip the parts of the original message that are not relevant to your answer. The aim is to make readers not to guess what you are responding to, and not to read backwards, thanks. Furthermore, I can’t connect your answer to any of the points I made in the specific email you replied to, so that you may have replied to the wrong email. —Claude

Saki Takamachi

3 years ago
> That might not be intuitive to you, but it follows directly from the specified semantics: whether a function is called in `strict_types` mode or not, depends only on where the function is called. I personally doubt that there is an easy way to adjust the rule in order to make it both “intuitive” and simple.
On second thought based on your point, it seems confusing to change the behavior of strict_types only for calls from internal functions. A minor problem is that there is no way to control strict_types for calls from internal functions. Saki

Saki Takamachi

3 years ago
I also skimmed over past discussions. I've found that having full control over `strict_types` globally is not a good idea. This is because it can break the behavior of many libraries, and developers don't always respond appropriately to this fix. So I came up with the following idea. Allows a new value of 2 for strict_types. However, since magic numbers make the code difficult to read, I thought it would be a good idea to also provide strings that can serve as constants and aliases. This could provide new options to users while safely maintaining backwards compatibility. example: ``` // weak declare(strict_types=0); declare(strict_types='weak'); declare(strict_types=STRICT_TYPE_MODE_WEAK); // strict declare(strict_types=1); declare(strict_types='strict'); declare(strict_types=STRICT_TYPE_MODE_STRICT); // strict with internal func declare(strict_types=1); declare(strict_types='strict_with_internal_func'); declare(strict_types=STRICT_TYPE_MODE_WITH_INTERNAL_FUNC); ``` I need more time to come up with a better name. Saki

Saki Takamachi

3 years ago
> // strict with internal func > declare(strict_types=1); > declare(strict_types='strict_with_internal_func'); > declare(strict_types=STRICT_TYPE_MODE_WITH_INTERNAL_FUNC);
I made a mistake. Here is exactly: ``` // strict with internal func declare(strict_types=2); declare(strict_types='strict_with_internal_func'); declare(strict_types=STRICT_TYPE_MODE_WITH_INTERNAL_FUNC); ``` Saki

Saki Takamachi

3 years ago
I accidentally wrote the name in Japanese and it looks weird, so I'll resend it, sorry. I also skimmed over past discussions. I've found that having full control over `strict_types` globally is not a good idea. This is because it can break the behavior of many libraries, and developers don't always respond appropriately to this fix. So I came up with the following idea. Allows a new value of 2 for strict_types. However, since magic numbers make the code difficult to read, I thought it would be a good idea to also provide strings that can serve as constants and aliases. This could provide new options to users while safely maintaining backwards compatibility. example: ``` // weak declare(strict_types=0); declare(strict_types='weak'); declare(strict_types=STRICT_TYPE_MODE_WEAK); // strict declare(strict_types=1); declare(strict_types='strict'); declare(strict_types=STRICT_TYPE_MODE_STRICT); // strict with internal func declare(strict_types=2); declare(strict_types='strict_with_internal_func'); declare(strict_types=STRICT_TYPE_MODE_WITH_INTERNAL_FUNC); ``` I need more time to come up with a better name. Saki

Flávio Heleno

3 years ago
On Wed, Aug 30, 2023 at 7:14 AM Saki Takamachi <saki@sakiot.com> wrote:
> I accidentally wrote the name in Japanese and it looks weird, so I'll > resend it, sorry. > > I also skimmed over past discussions. > > I've found that having full control over `strict_types` globally is not a > good idea. This is because it can break the behavior of many libraries, and > developers don't always respond appropriately to this fix. > > So I came up with the following idea. > Allows a new value of 2 for strict_types. > > However, since magic numbers make the code difficult to read, I thought it > would be a good idea to also provide strings that can serve as constants > and aliases. > > This could provide new options to users while safely maintaining backwards > compatibility. > > example: > ``` > // weak > declare(strict_types=0); > declare(strict_types='weak'); > declare(strict_types=STRICT_TYPE_MODE_WEAK); > > // strict > declare(strict_types=1); > declare(strict_types='strict'); > declare(strict_types=STRICT_TYPE_MODE_STRICT); > > // strict with internal func > declare(strict_types=2); > declare(strict_types='strict_with_internal_func'); > declare(strict_types=STRICT_TYPE_MODE_WITH_INTERNAL_FUNC); > ``` > > I need more time to come up with a better name. > > Saki > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > >
I'm not entirely sure if this is 100% related, but it seems to be. If you don't declare "strict_types" and pass a Stringable instance as the first argument to json_decode, it works as expected [1]. On the other hand, when you declare "strict_types" and pass a Stringable instance as the first argument to json_decode, it fails [2]. Fatal error: Uncaught TypeError: json_decode(): Argument #1 ($json) must be
> of type string, strcls given >
[1] https://3v4l.org/TXAUi [2] https://3v4l.org/uEhbM
-- Atenciosamente, Flávio Heleno

Rowan Collins

3 years ago
On 29/08/2023 01:46, Saki Takamachi wrote:
> As the documentation below states, PHP's internal functions currently ignore strict_types. > https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict
Just to be really clear, it is not true that internal functions "ignore" the setting; they behave exactly the same as any userland function which was *declared* in strict_types=0 mode. The warning in the manual is actually a consequence of the following Note (perhaps the order should be reversed?): 1. Strict typing applies to function calls made from within the file with strict typing enabled 2. Function calls from within internal functions will not be affected by the |strict_types| declaration To clarify the scenario, imagine three files: /* a.php */ declare(strict_types=0); function call_me_back(callable $f, mixed $input) {     $f($input); } /* b.php */ declare(strict_types=1); function output_me(string $output) {     echo "$output\n"; } /* c.php */ declare(strict_types=1); call_me_back( output_me(...), 42 ); The only calls that happen are: 1. A call from c.php to call_me_back; c.php is in strict_types=1 mode, so the parameters $f and $input will not be coerced 2. A call from a.php to output_me; a.php is in strict_types=0 mode, so the parameter $output *will* be coerced to a string The behaviour of internal functions like array_filter is exactly the same - array_filter is  the caller, and it is defined in strict_types=0 mode, just like call_me_back above; the mode set in any other code is irrelevant, because it is not the caller. As Claude Pache, it's actually very difficult to design a version of the feature that would be fully intuitive in this case - if I write the below, does the caller of wrapped_array_filter still get to control the mode that call_me_back is run under? function call_me_back(int $value) {     return $value <= 42; } function backwards_array_filter(array $array, ?callable $callback = null, int $mode = 0): array {     return array_reverse(   array_filter($array, $callback, $mode)   ); } // In another file: var_dump( backwards_array_filter($some_array, call_me_back(...)) ); Regards,
-- Rowan Tommins [IMSoP]

Saki Takamachi

2 years ago
> Just to be really clear, it is not true that internal functions "ignore" the setting; they behave exactly the same as any userland function which was *declared* in strict_types=0 mode.
I see, I thought I understood, but it seems I was missing this point of view. If there is a change, it should be to allow strict_types to be specified for internal functions. I would like to withdraw this proposal. Thank you to everyone who commented on this proposal. Saki