Skip to content

fix(compiler): PHP semantics for bool operands of comparisons, arithmetic and unary minus - #146

Open
Giandonn wants to merge 1 commit into
swoole:masterfrom
Giandonn:fix/bool-numeric-context
Open

Giandonn wants to merge 1 commit into
swoole:masterfrom
Giandonn:fix/bool-numeric-context

Conversation

@Giandonn

@Giandonn Giandonn commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #145

Native bool values reached raw C++ operators that treat them as the integers 0 and 1.

The change

  • Relational comparison (BinaryOpTrait::parseBinaryOp()): when exactly one operand of <, <=, > or >= is a bool, the other int/float/string operand is converted to bool, as PHP compares them. == and <=> already go through php::equals() / php::compare() and are unchanged.
  • Arithmetic: a bool operand of +, -, * or / is converted to int first, so the existing int paths apply — in particular the checked varint division, which turns 7 / false from a SIGFPE into DivisionByZeroError. % is left alone on purpose: a non-int operand already takes php::fn::mod (converting it would move native-mode % onto the raw C++ operator). Native-mode / keeps the documented raw int division (see Native mode: a runtime zero divisor or PHP_INT_MIN % -1 kills the process (SIGFPE) #142).
  • Unary minus (UnaryExpressionTrait::parseUnaryMinus()): -$bool is an int, and a non-numeric operand is lowered like Zend does, as multiplication by -1 — the same approach unary plus already uses (fix(compiler): apply unary plus numeric conversion #132).
  • Inferred types: arithmetic/bitwise/shift operators treat a bool operand as int, -bool is int, and minus of a non-numeric value is dynamic.

Verified on a binary

PHP 8.4.26 ZTS with embed, GCC 11, Linux x64. 25 cases (bool vs int/float/string for each relational operator in both orders, bool + int, bool * bool, float - bool, int / bool, int % bool, -true, -false, -"5", -"1.5", -"abc", $x = -true, division/modulo by false):

master this PR
use varint_types 9 differ from PHP + SIGFPE on 7 / false identical to PHP (25/25)
native mode 9 differ + SIGFPE identical except 7 / false, the documented native int division (unchanged, #142)
new operator/bool-operands-native.phpt and bool-operands-varint.phpt FAIL PASS
tests/compiler (full) — 1249 passed, 0 failed, 27 skipped
PHPUnit (full) — 2408 tests OK, 4 skipped

Found with a differential fuzzer.

…etic and unary minus

Native bool values reached raw C++ operators that treat them as the integers
0 and 1, which differs from PHP in three places:

- Relational comparison. PHP compares a bool with any other scalar as two
  bools (false < true); the C++ operator compared numerically:

      function le(bool $a, int $b): bool { return $a <= $b; }
      le(false, -7);   // PHP: true   compiled: false

  <, <=, > and >= now convert the non-bool scalar operand to bool. == and
  <=> already used php::equals()/php::compare().

- Arithmetic. A bool operand of +, -, * or / was not treated as an int, so
  the checked varint paths did not apply and `7 / false` divided by a raw
  bool: a SIGFPE even with `use varint_types`, where PHP raises
  DivisionByZeroError. Bool operands are now converted to int first. % is
  unchanged: a non-int operand already takes php::fn::mod.

- Unary minus. -$bool stayed a bool (-true printed `true`, PHP gives -1). It
  is now an int, and a non-numeric operand (e.g. a string) is lowered like
  Zend does, as multiplication by -1, matching what unary plus already does.

The inferred types follow (bitwise/shift/arithmetic operators treat a bool
operand as int; -bool is int; minus of a non-numeric value is dynamic).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bool operands: relational comparison, unary minus and division behave numerically

1 participant