[PHP-DEV][RFC] Deprecate type juggling to and from bool type within the function type juggling context

On Monday, 30 June 2025 at 10:26, Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:

Hi Gina

Thanks for experimenting with this on Fix implicit to and from bool type juggling by Girgias · Pull Request #60890 · symfony/symfony · GitHub

It's good to see that fitting this RFC requires a relatively small number of lines to change even on a codebase like Symfony. We're not at 100% but still nice.

Yet, it IS a big bang in terms of deprecations to fix that this will trigger. Their number is super high, because these boolean conversions happen on hotpath / in loops.

This reminds me of the mandatory "?" for arguments with null defaults: the change made sense from a language design PoV, but the impact was, and still is very high. It was and still is a very costly deprecation for the community, and it's not over yet. That made me say at conferences that this was maybe not a good idea...

Looking at the diff above, I think I can draw two sets of changes:
1. the ones that patch a cast-to-bool
2. the ones that patch a cast-from-bool

The latter, cast-from-bool, I think they're all useful improvements. While most likely harmless in all the specific cases of the PR, doing e.g. an strpos() on false feels hardly legit. I'm therefore sympathetic to making these changes.

For cast-to-bool, I'm WAY less convinced. From the PR above, explicit casts like "return (bool) preg_match(...)" on a method that returns a "bool" or "(bool) ($this->loggedErrors & $type)" are a clear downgrade: it makes the typing PHP just more verbose without any real benefit. That doesn't look worth asking the whole ecosystem to fix those deprecations. It is especially hard to see this as an improvement when comparing to using the same expressions with e.g. the "if ()" operator, which doesn't need the explicit cast (and shouldn't of course).

Every cast-to-bool example you are providing are int-to-bool coercions, not string nor float casts type.
Especially that even within SFs test suite, various cases of string-to-bool casts seems to be highlighting some sort of bug in the test suite.
Moreover, the fixes for the bitwise & operator are following the styles from surrounding functions that _already_ do that, and I was slightly confused about the inconsistencies.
The preg_match() API is just unfortunate that it returns a boolean value as integers rather than booleans, as it uses a false return to communicate it emitted a warning instead of null.
Improving this API, is a very orthogonal problem to this, that we probably should be doing anyway (e.g. see if we can promote the warnings to exceptions).
And I could be convinced to carve out an exception for int-to-bool, however that retains some complexity remembering PHP's type juggling rules, or have it as a secondary vote.

In the RFC, you write that its motivation is to allow making the bool type equivalent to the true|false union. To do so you propose to make the bool type always behave like in "strict" mode.

Personally, I'm moot about the motivation. I understand it, but it looks inappropriate to push deprecations from this somewhat (IMHO) theoretical angle. The added friction would need way stronger arguments to be justified IMHO.

Would it be possible to trigger the deprecation only in the cast-from-bool direction, and accept casts-to-bool as seamlessly as there are now? If yes, then would it also be possible to make the true and false types have the same behavior (aka non-strict in casts-to-bool direction - when not in strict mode of course)? If yes to both questions, then that'd look like a better outcome that'd still allow fulfilling your goal, while providing maximum value to the community.

As is, the cost/benefit ratio doesn't make this RFC look worth it to me.

As said above, it is even possible to only warn on string- and float-to-bool and not on int-to-bool coercions.
But I still think theoretical angles are important, because allowing edge cases always adds complexity that has causes us countless headaches in the past.

Last but not least, I'm not sure it's a good idea to rush this into 8.5. This is a high-impact change. We should give some time to the discussion, understand the impact and explore variants. Merging this in 8.6 would also give a few more months for open-source projects to adapt to the change (since many do run their CI with the master branch of PHP to spot changes as early as possible.)

I'm not exactly certain I buy the argument that delaying this deprecation is worthwhile, especially as I don't really think is as high impact as it is made out to be.
And I don't really see what "variants" there are to explore other than a possible allowance for int-to-bool.
As we are trading time for users to fix their code upcoming to PHP 9 for open source libraries where many use strict_types and as such are not affected by this change.

Best regards,

Gina P. Banyard

On Fri, July 11, 2025 at 05:00 G. P. Banyard wrote:

On Monday, 30 June 2025 at 10:26, Nicolas Grekas wrote:
...
The latter, cast-from-bool, I think they're all useful improvements. While most likely harmless in all the specific
cases of the PR, doing e.g. an strpos() on false feels hardly legit. I'm therefore sympathetic to making these changes.

For cast-to-bool, I'm WAY less convinced. From the PR above, explicit casts like "return (bool) preg_match(...)"
on a method that returns a "bool" or "(bool) ($this->loggedErrors & $type)" are a clear downgrade: it makes the
typing PHP just more verbose without any real benefit. That doesn't look worth asking the whole ecosystem to fix
those deprecations. It is especially hard to see this as an improvement when comparing to using the same
expressions with e.g. the "if ()" operator, which doesn't need the explicit cast (and shouldn't of course).

From my experience there is a very real benefit to deprecating both cast-from-bool and cast-to-bool.
Let me give an example of both.

Recently had to fix some legacy code where the result of `filemtime` was being passed to `gmdate` to format
the modification timestamp. `filemtime` returns false on failure, but this is silently coerced to zero
(a valid timestamp) when passed to the int parameter, which would result in the script continuing with bad
data instead of halting with a type error.

Cast-to-bool can cause the same kinds of issues, which are sometimes even harder to notice. Consider the following:

    function processArray(array $items, bool $hasCertainKey) { ... }

    $keyOrFalse = array_search('my value', $items);

    processArray($items, $keyOrFalse);

It's very easy to miss the bug in this code, especially since it seems to function correctly as long as 'my value'
is not in the array, or is not the first item in the array. However, when it's at index 0 the key will be silently
coerced to false and the array will be incorrectly processed as though the value is not in the array.

I believe deprecating type juggling to/from bool will help avoid creating bugs like this (and likely
surface existing ones that should be fixed).

Kind regards,
Theodore

On Sat, Jul 12, 2025, at 01:29, Theodore Brown wrote:

On Fri, July 11, 2025 at 05:00 G. P. Banyard wrote:

On Monday, 30 June 2025 at 10:26, Nicolas Grekas wrote:

The latter, cast-from-bool, I think they’re all useful improvements. While most likely harmless in all the specific
cases of the PR, doing e.g. an strpos() on false feels hardly legit. I’m therefore sympathetic to making these changes.

For cast-to-bool, I’m WAY less convinced. From the PR above, explicit casts like “return (bool) preg_match(…)”
on a method that returns a “bool” or “(bool) ($this->loggedErrors & $type)” are a clear downgrade: it makes the
typing PHP just more verbose without any real benefit. That doesn’t look worth asking the whole ecosystem to fix
those deprecations. It is especially hard to see this as an improvement when comparing to using the same
expressions with e.g. the “if ()” operator, which doesn’t need the explicit cast (and shouldn’t of course).

From my experience there is a very real benefit to deprecating both cast-from-bool and cast-to-bool.
Let me give an example of both.

Recently had to fix some legacy code where the result of filemtime was being passed to gmdate to format
the modification timestamp. filemtime returns false on failure, but this is silently coerced to zero
(a valid timestamp) when passed to the int parameter, which would result in the script continuing with bad
data instead of halting with a type error.

Cast-to-bool can cause the same kinds of issues, which are sometimes even harder to notice. Consider the following:

function processArray(array $items, bool $hasCertainKey) { … }

$keyOrFalse = array_search(‘my value’, $items);

processArray($items, $keyOrFalse);

It’s very easy to miss the bug in this code, especially since it seems to function correctly as long as ‘my value’
is not in the array, or is not the first item in the array. However, when it’s at index 0 the key will be silently
coerced to false and the array will be incorrectly processed as though the value is not in the array.

I believe deprecating type juggling to/from bool will help avoid creating bugs like this (and likely
surface existing ones that should be fixed).

Kind regards,
Theodore

I’m not so confident that will be the case. In lots of strict-mode code I’ve worked with over the years, people tend to blindly cast things without realizing they’re in a worse situation than using non-strict mode. Like ((int)"123password") === 123, however attempting to coerce that string to an int results in a proper type error in non-strict mode.

That is to say, I highly suspect that gmdate(filemtime($file)) would just be rewritten as gmdate((int)filemtime($file)) without a second thought.

In your other example showing the difference in array_search, I also highly suspect people would just cast it to bool without noticing the 0-key edge case.

Both cases, I believe, can simply be solved by tooling highlighting when you’re passing a union type to a function that doesn’t overlap with the function’s expectations. (“you are passing int|string to a function expecting bool” and/or “you are casting int|string to bool, check that (bool)“0” won’t be an edge-case; consider using empty() instead”).

— Rob

Le ven. 11 juil. 2025 à 13:00, Gina P. Banyard internals@gpb.moe a écrit :

On Monday, 30 June 2025 at 10:26, Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:

Hi Gina

Thanks for experimenting with this on https://github.com/symfony/symfony/pull/60890/files

It’s good to see that fitting this RFC requires a relatively small number of lines to change even on a codebase like Symfony. We’re not at 100% but still nice.

Yet, it IS a big bang in terms of deprecations to fix that this will trigger. Their number is super high, because these boolean conversions happen on hotpath / in loops.

This reminds me of the mandatory “?” for arguments with null defaults: the change made sense from a language design PoV, but the impact was, and still is very high. It was and still is a very costly deprecation for the community, and it’s not over yet. That made me say at conferences that this was maybe not a good idea…

Looking at the diff above, I think I can draw two sets of changes:

  1. the ones that patch a cast-to-bool

  2. the ones that patch a cast-from-bool

The latter, cast-from-bool, I think they’re all useful improvements. While most likely harmless in all the specific cases of the PR, doing e.g. an strpos() on false feels hardly legit. I’m therefore sympathetic to making these changes.

For cast-to-bool, I’m WAY less convinced. From the PR above, explicit casts like “return (bool) preg_match(…)” on a method that returns a “bool” or “(bool) ($this->loggedErrors & $type)” are a clear downgrade: it makes the typing PHP just more verbose without any real benefit. That doesn’t look worth asking the whole ecosystem to fix those deprecations. It is especially hard to see this as an improvement when comparing to using the same expressions with e.g. the “if ()” operator, which doesn’t need the explicit cast (and shouldn’t of course).

Every cast-to-bool example you are providing are int-to-bool coercions, not string nor float casts type.

That’s correct. I feel quite strongly about this: int-to-bool casts should be handled gracefully by the engine seamlessly. Deprecating them will (plan to) break many apps that are just fine, and as Rob wrote (and you also in a gist), casting to (bool) is what many will do, wrongly. This won’t improve the language, quite the contrary.

About string-to-bool PHP is still that language that allows quickly consuming $_GET[‘foo’] as whatever it looks like as a string. I suspect tons of codebases rely on this behavior. To me, that’s where the costs might outweigh the benefits…

Especially that even within SFs test suite, various cases of string-to-bool casts seems to be highlighting some sort of bug in the test suite.
Moreover, the fixes for the bitwise & operator are following the styles from surrounding functions that already do that, and I was slightly confused about the inconsistencies.

That’s an inconsistency favored by the strict-mode hype: people blindly cast (and introduce bugs)… That inconsistency should be fixed the other way around: removing these needless (and dangerous) casts.

The preg_match() API is just unfortunate that it returns a boolean value as integers rather than booleans, as it uses a false return to communicate it emitted a warning instead of null.

Unfortunate but still to be acknowledged as a likely contribution to making this RFC a high-cost one for userland.

Improving this API, is a very orthogonal problem to this, that we probably should be doing anyway (e.g. see if we can promote the warnings to exceptions).
And I could be convinced to carve out an exception for int-to-bool, however that retains some complexity remembering PHP’s type juggling rules, or have it as a secondary vote.

In the RFC, you write that its motivation is to allow making the bool type equivalent to the true|false union. To do so you propose to make the bool type always behave like in “strict” mode.

Personally, I’m moot about the motivation. I understand it, but it looks inappropriate to push deprecations from this somewhat (IMHO) theoretical angle. The added friction would need way stronger arguments to be justified IMHO.

Would it be possible to trigger the deprecation only in the cast-from-bool direction, and accept casts-to-bool as seamlessly as there are now? If yes, then would it also be possible to make the true and false types have the same behavior (aka non-strict in casts-to-bool direction - when not in strict mode of course)? If yes to both questions, then that’d look like a better outcome that’d still allow fulfilling your goal, while providing maximum value to the community.

As is, the cost/benefit ratio doesn’t make this RFC look worth it to me.

As said above, it is even possible to only warn on string- and float-to-bool and not on int-to-bool coercions.
But I still think theoretical angles are important, because allowing edge cases always adds complexity that has causes us countless headaches in the past.

Last but not least, I’m not sure it’s a good idea to rush this into 8.5. This is a high-impact change. We should give some time to the discussion, understand the impact and explore variants. Merging this in 8.6 would also give a few more months for open-source projects to adapt to the change (since many do run their CI with the master branch of PHP to spot changes as early as possible.)

I’m not exactly certain I buy the argument that delaying this deprecation is worthwhile, especially as I don’t really think is as high impact as it is made out to be.

Deprecating implicit null-ness was (still is) a high impact deprecation, and this one has a higher potential impact IMHO.

And I don’t really see what “variants” there are to explore other than a possible allowance for int-to-bool.

The variant I suggested is to relax the true and false types and make them not-strict when not in strict-mode. Your motivation for the RFC is to make the bool type equivalent to true|false. This can be achieved by relaxing true and false types.

Nicolas
PS: what about null-forwarding cast operators? (?bool) to let null get through the cast? Just throwing in case it reaches anyone :slight_smile:

On Thu, 26 Jun 2025, Gina P. Banyard wrote:

On Monday, 2 June 2025 at 17:11, Gina P. Banyard <internals@gpb.moe> wrote:

> This is the first RFC out of a set of type system related RFCs I
> want to propose for PHP 8.5. It also used the recently enabled
> Markdown support on the wiki, so there might be a few oddities.
>
> The RFC proposes to deprecate implicit type coercions to and from
> the bool type for other scalar types. This a "weak" mode change
> only, as when strict_types are enabled none of these coercions can
> happen.
>
> Let me know what you think about it.
>
> RFC: PHP: rfc:deprecate-function-bool-type-juggling

I have updated the RFC to version 0.2 that expands on it and addresses
some of the counterarguments which were said during the discussion.
RFC: PHP: rfc:deprecate-function-bool-type-juggling

If there is no-follow up feedback, I will open the vote for it
sometime next week.

I am sorry to say, but this RFC seems to be mostly to have a 'purity'
argument behind it.

The RFC also does not seems to include an analysis
of the impact.

I can almost guarantee you that this is going to 'break'
(by a deprecation warning?) a lot of code. Even the changes made for
symfony seem non-trivial, and easy to get wrong
(Fix implicit to and from bool type juggling by Girgias · Pull Request #60890 · symfony/symfony · GitHub).

But we don't know, as there is no data on this — that's why I feel that
this is not a mature enough RFC.

cheers,
Derick

On Monday, 2 June 2025 at 17:11, Gina P. Banyard <internals@gpb.moe> wrote:

Hello internals,

This is the first RFC out of a set of type system related RFCs I want to propose for PHP 8.5.
It also used the recently enabled Markdown support on the wiki, so there might be a few oddities.

The RFC proposes to deprecate implicit type coercions to and from the bool type for other scalar types.
This a "weak" mode change only, as when strict_types are enabled none of these coercions can happen.

Let me know what you think about it.

RFC: PHP: rfc:deprecate-function-bool-type-juggling

As the discussion doesn't feel productive any longer as this is now just about clashing opinions I will open the vote in the coming days.
I have slightly changed the wording and expanded on some points, but the RFC is effectively unchanged.

RFC: PHP: rfc:deprecate-function-bool-type-juggling

Best regards,

Gina P. Banyard