Allow null variables to be decremented

  108602
February 15, 2020 17:44 rowan.collins@gmail.com (Rowan Tommins)
Hi all,

There is currently an odd inconsistency when using the decrement 
operator on a null variable:

$a = null; $a=$a+1; // int(1)
$a = null; $a+=1; // int(1)
$a = null; ++$a; // int(1)

$a = null; $a=$a-1; // int(-1)
$a = null; $a-=1; // int(-1)
$a = null; --$a; // null

I would like to propose changing this behaviour for PHP 8, so that --$a 
would give int(-1), as I believe it is simply a long-standing bug.


This has been raised as a bug at least three times [1][2][3] but closed 
as documented behaviour / too much of a BC break. It is documented in 
the manual, but with no explanation of why it should work that way. [4]

I would be interested in any explanations of why it might be intended 
behaviour, or ways in which people might be relying on the current 
behaviour.

A proposal to change the behaviour was included in a wider RFC about 
standardising increment and decrement behaviour, but it never got beyond 
draft status. [5] I would prefer not to reopen that wider debate, but 
focus on this single issue.

As far as I can see, the change would be to add a "case IS_NULL" branch 
to decrement_function in zend_operators.c to match the one in 
increment_function. [6]


I will happily write up an RFC to formalise this, but wanted to gather 
people's immediate thoughts first.


Links:
[1] https://bugs.php.net/bug.php?id=20548
[2] https://bugs.php.net/bug.php?id=25674
[3] https://bugs.php.net/bug.php?id=41690
[4] https://www.php.net/manual/en/language.operators.increment.php
[5] https://wiki.php.net/rfc/normalize_inc_dec
[6] 
https://php-lxr.adamharvey.name/source/xref/master/Zend/zend_operators.c#2359


Regards,

-- 
Rowan Tommins (né Collins)
[IMSoP]
  108603
February 15, 2020 18:11 tysonandre775@hotmail.com (tyson andre)
My opinion is that it'd be more consistent for `--` to work like `-= 1` (e.g. become `-1`).
It might break some code, but that code was probably incorrect.

Out of scope of the proposed RFC, but this reminds me of a similar issue:
Currently, the `++` and `--` operators do nothing to arrays or objects,
not even emitting a notice or changing the value.
I see that `++` on typed properties/references can already throw a TypeError for integer overflow,
so it might make sense to also start throwing a TypeError for `++` on objects (without numeric operation handlers) and arrays.

I'd thought earlier that emitting a notice instead of throwing a TypeError for arrays/objects might negatively limit the optimizations opcache can do
because error handlers can have side effects.
But it looks like I'd just have to allow inferring that MAY_BE_OBJECT and MAY_BE_ARRAY
could have side effects for the INC/DEC opcodes in `may_have_side_effects()` in ext/opcache/Optimizer/dce.c.

(If your RFC ends up emitting warnings/notices, it would need to check for MAY_BE_NULL|MAY_BE_UNDEF in dce.c, I think.)

Would there be any interest in emitting a warning or deprecation warning for objects/arrays starting in php 8.0?

- Tyson
  108604
February 15, 2020 18:21 marandall@php.net (Mark Randall)
On 15/02/2020 17:44, Rowan Tommins wrote:
> There is currently an odd inconsistency when using the decrement > operator on a null variable:
I'm not so sure... That incrementing a null works at all is a painful part of the language spec that I would argue needs flushing down the toilet, rather than further reinforcing. I recon the required precursor to doing so is erroring on undefined variables, which I suspect accounts for about 99% of increment-on-nulls. The majority are in favour (56%) but not the supermajority necessary - Athough there's an argument to be made that a supermajority may exist in a straight up or down vote rather than a 3-way (https://wiki.php.net/rfc/engine_warnings). So on one hand, consistency is good. On the other hand, being consistently bad is still being bad. -- Mark Randall marandall@php.net
  108658
February 18, 2020 14:00 nikita.ppv@gmail.com (Nikita Popov)
On Sat, Feb 15, 2020 at 6:44 PM Rowan Tommins collins@gmail.com>
wrote:

> Hi all, > > There is currently an odd inconsistency when using the decrement > operator on a null variable: > > $a = null; $a=$a+1; // int(1) > $a = null; $a+=1; // int(1) > $a = null; ++$a; // int(1) > > $a = null; $a=$a-1; // int(-1) > $a = null; $a-=1; // int(-1) > $a = null; --$a; // null > > I would like to propose changing this behaviour for PHP 8, so that --$a > would give int(-1), as I believe it is simply a long-standing bug. > > > This has been raised as a bug at least three times [1][2][3] but closed > as documented behaviour / too much of a BC break. It is documented in > the manual, but with no explanation of why it should work that way. [4] > > I would be interested in any explanations of why it might be intended > behaviour, or ways in which people might be relying on the current > behaviour. > > A proposal to change the behaviour was included in a wider RFC about > standardising increment and decrement behaviour, but it never got beyond > draft status. [5] I would prefer not to reopen that wider debate, but > focus on this single issue. > > As far as I can see, the change would be to add a "case IS_NULL" branch > to decrement_function in zend_operators.c to match the one in > increment_function. [6] > > > I will happily write up an RFC to formalise this, but wanted to gather > people's immediate thoughts first. >
Principally in favor of this change, I do think that ++ and -- should behave consistently if nothing else. We might want to consider giving the same treatment to false/true as well, which should be interpreted as 0/1. That is $foo++ / $foo-- should behave the same ways as $foo+=1, $foo-=1 for null, true, false. It seems odd to single out only "null" here. Additionally I would suggest a notice when trying to increment arrays, resources and objects, rather than just silently doing nothing. As long as it's just a notice, this should have minimal BC implications. Nikita
  108662
February 18, 2020 20:27 rowan.collins@gmail.com (Rowan Tommins)
On 18/02/2020 14:00, Nikita Popov wrote:
> Principally in favor of this change, I do think that ++ and -- should > behave consistently if nothing else. We might want to consider giving > the same treatment to false/true as well, which should be interpreted > as 0/1. That is $foo++ / $foo-- should behave the same ways as > $foo+=1, $foo-=1 for null, true, false. It seems odd to single out > only "null" here. > > Additionally I would suggest a notice when trying to increment arrays, > resources and objects, rather than just silently doing nothing. As > long as it's just a notice, this should have minimal BC implications.
Thanks. That seems a reasonably modest expansion, without getting into the deeper questions of string increments or fatal errors. For the record, the reason I singled out null is that it's the only type where ++ and -- behave differently from each other. With booleans, there is at least a consistency between those two operators, even though it's consistently weird. There's definitely a strong case for making them match +=1 and -=1 though. Regards, -- Rowan Tommins (né Collins) [IMSoP]
  108666
February 19, 2020 14:52 come.chilliet@fusiondirectory.org (=?ISO-8859-1?Q?C=F4me?= Chilliet)
Le mardi 18 février 2020, 20:27:37 CET Rowan Tommins a écrit :
> With booleans, there is at least a consistency between those two > operators, even though it's consistently weird. There's definitely a > strong case for making them match +=1 and -=1 though.
Is there any reason the engine is not running the same code or even compiling to the same opcodes $a++ and $a+=1? If it should never differ, why is it not the same operation? -- Côme Chilliet FusionDirectory - https://www.fusiondirectory.org
  108667
February 19, 2020 14:59 cschneid@cschneid.com (Christian Schneider)
Am 19.02.2020 um 15:52 schrieb Côme Chilliet chilliet@fusiondirectory.org>:
> Le mardi 18 février 2020, 20:27:37 CET Rowan Tommins a écrit : >> With booleans, there is at least a consistency between those two >> operators, even though it's consistently weird. There's definitely a >> strong case for making them match +=1 and -=1 though. > > Is there any reason the engine is not running the same code or even compiling to the same opcodes $a++ and $a+=1? > If it should never differ, why is it not the same operation?
$a++ is magic, see example #1 at https://www.php.net/manual/en/language.operators.increment.php And no, this should not be changed as it would be a major BC. - Chris
  108668
February 19, 2020 15:05 come.chilliet@fusiondirectory.org (=?ISO-8859-1?Q?C=F4me?= Chilliet)
Le mercredi 19 février 2020, 15:59:24 CET Christian Schneider a écrit :
> Am 19.02.2020 um 15:52 schrieb Côme Chilliet chilliet@fusiondirectory.org>: > > Is there any reason the engine is not running the same code or even compiling to the same opcodes $a++ and $a+=1? > > If it should never differ, why is it not the same operation? > > $a++ is magic, see example #1 at https://www.php.net/manual/en/language.operators.increment.php > > And no, this should not be changed as it would be a major BC.
Right, $a+=1 is the same as ++$a, not $a++, my bad, I forgot about this. Is ++$a behaving differently than $a++ for NULL/FALSE/TRUE? -- Côme Chilliet FusionDirectory - https://www.fusiondirectory.org
  108676
February 19, 2020 17:05 rowan.collins@gmail.com (Rowan Tommins)
On Wed, 19 Feb 2020 at 15:05, Côme Chilliet <
come.chilliet@fusiondirectory.org> wrote:

> > Is ++$a behaving differently than $a++ for NULL/FALSE/TRUE? >
The actual implementation of incrementing and decrementing is the same whichever operator you use, it's just wrapped in code to return the old/new value as appropriate. So right now, for false and true (and for null with --), you can't tell the difference, because the variable is the same before and after. For null (with ++), they evaluate to the old or new value, as expected: $a = null; var_dump( $a++, $a ); // NULL, int(1) $a = null; var_dump( ++$a, $a ); // int(1), int(1) As far as I can see, null and bool are simply missing from the switch statement that special cases each type of variable: - increment_function (has null but no bool): https://github.com/php/php-src/blob/26327bcd3b6375a4883f00a289ba129e5b23717d/Zend/zend_operators.c#L2299 - decrement_function (has neither): https://github.com/php/php-src/blob/26327bcd3b6375a4883f00a289ba129e5b23717d/Zend/zend_operators.c#L2359 Regards, -- Rowan Tommins [IMSoP]