* fix(optimizer): round() must not lower a RoundingMode enum to an int
Since PHP 8.4 the third parameter of round() is a RoundingMode enum, but
php::fn::round() models the mode as an Int, which only covers the legacy
PHP_ROUND_* constants. genRound sent the argument through convertIntExpr
regardless, so the enum went through an object-to-int conversion that
yields 1 - PHP_ROUND_HALF_UP:
round(2.5, 0, RoundingMode::HalfEven);
// compiled: Warning: Object of class RoundingMode could not be
// converted to int
// compiled: 3
// PHP: 2
Banker's rounding silently became half away from zero. Code that spells
out HalfEven is usually money code, where that is the exact difference it
was avoiding.
A mode that is not statically an int now falls through to the dynamic
path, which passes the enum to the runtime function unchanged. The legacy
integer constants keep the native call - PHP_ROUND_HALF_DOWN still lowers
to a plain 2L - and the one and two argument forms are untouched.
Only the compile-time lowering is covered by a test here. A runtime PHPT
cannot pass yet: phpx resolves a class constant on an internal class by
reading the raw zval out of the constants table, so RoundingMode::HalfEven
does not materialise at all. That is reported separately; once it ships,
the runtime case can be added to type_conv-style coverage.
* fix(optimizer): send every explicit round() mode to the dynamic path
Checking only Type::INT was not enough. php::fn::round() calls
_php_math_round() directly and never runs Zend's validation of the mode,
so an integer outside 1-8 reaches php_round_helper and terminates the
process rather than raising ValueError:
round(2.5, 0, 99); // segmentation fault
A static int type does not prove the runtime value is a valid mode, so
an int variable reaches the same path. Since three-argument round() is
uncommon, take the conservative option and route every call with an
explicit mode to the dynamic Zend path, which validates the argument and
accepts both a RoundingMode enum and a legacy PHP_ROUND_* constant.
Reject unpacked and named arguments as well: they carry a single
Node\Arg whatever their runtime arity is, so genRound() was reading the
unpacked array as the number being rounded.
Add tests/compiler/stdlib/round-mode.phpt covering valid legacy modes,
out-of-range literal and variable modes, and full and partial unpacking.
The enum case still cannot produce PHP's result until the swoole/phpx
class-constant fix is part of the pinned dependency, so it stays out of
the runtime coverage for now.
---------
Co-authored-by: Giandonn <lucas_raineri@hotmail.com>