fix(optimizer): keep unpacked and named conversion arguments dynamic

An unpacked call carries a single Node\Arg regardless of its runtime
arity, so the arity check alone still accepted intval(...$args) as a
single-argument Native conversion and lowered the array itself:

    intval(...['ff', 16])   // php::toInt(withBase) -> int(1)
    strval(...['42'])       // php::toString(single) -> "Array" + warning

A named argument has the same shape and need not be the value being
converted: intval(bogus: 1) must raise "Unknown named parameter", not
fold to a cast of 1.

Reject both in dispatchConversion() so the runtime determines the
expanded arity and the parameter names, matching what
dispatchFuncCall() already does for every other builtin.
master
Giandonn 5 days ago
parent 93909274e2
commit c291d79703
  1. 21
      phpunit/code/intval-unpacked-argument.php
  2. 15
      phpunit/src/ConversionArityTest.php
  3. 11
      src/Optimizer/FuncCallOptimizer.php
  4. 22
      tests/compiler/stdlib/type_conv.phpt

@ -0,0 +1,21 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
function main(): void
{
$withBase = ['ff', 16];
$single = ['42'];
var_dump(intval(...$withBase));
var_dump(intval(...$single));
var_dump(strval(...$single));
var_dump(floatval(...$single));
var_dump(boolval(...$single));
var_dump(intval('ff', ...[16]));
var_dump(intval(value: '42'));
}

@ -27,6 +27,21 @@ class ConversionArityTest extends TestCase
self::assertSame(2, substr_count($cpp, '16L')); self::assertSame(2, substr_count($cpp, '16L'));
} }
public function testUnpackedAndNamedArgumentsStayOnTheDynamicPath(): void
{
$cpp = $this->compileToCpp('intval-unpacked-argument.php');
// An unpacked argument is one Node\Arg whatever its runtime arity is,
// so the array itself must never be handed to a Native cast.
self::assertStringNotContainsString('php::toInt(', $cpp);
self::assertStringNotContainsString('php::toString(', $cpp);
self::assertStringNotContainsString('php::toFloat(', $cpp);
self::assertStringNotContainsString('php::toBool(', $cpp);
// Five full unpacks plus the partial intval('ff', ...[16]).
self::assertSame(6, substr_count($cpp, 'appendUnpacked('));
}
public function testSingleArgumentConversionsStillLowerToNativeCasts(): void public function testSingleArgumentConversionsStillLowerToNativeCasts(): void
{ {
$cpp = $this->compileToCpp('intval-single-argument.php'); $cpp = $this->compileToCpp('intval-single-argument.php');

@ -532,7 +532,16 @@ trait FuncCallOptimizer
// These four are lowered as single-argument Native casts, which cannot // These four are lowered as single-argument Native casts, which cannot
// carry intval()'s $base. Any other arity must reach the runtime // carry intval()'s $base. Any other arity must reach the runtime
// function instead of silently dropping the extra argument. // function instead of silently dropping the extra argument.
if (count($expr->args) !== 1) { //
// An unpacked or named argument is a single Node\Arg whatever its
// runtime arity turns out to be, so neither may be read as the value
// being converted; both stay on the dynamic path like dispatchFuncCall()
// already does for every other builtin.
if (count($expr->args) !== 1
|| !($expr->args[0] instanceof Node\Arg)
|| $expr->args[0]->unpack
|| $expr->args[0]->name !== null
) {
return false; return false;
} }
$arg = $expr->args[0]->value; $arg = $expr->args[0]->value;

@ -26,6 +26,21 @@ function main() {
$base = 16; $base = 16;
var_dump(intval("ff", $base)); var_dump(intval("ff", $base));
// An unpacked argument carries its own arity, which only the runtime
// knows, so these must not be lowered as single-argument casts.
$withBase = ["ff", 16];
$single = ["42"];
var_dump(intval(...$withBase));
var_dump(intval(...$single));
var_dump(strval(...$single));
var_dump(floatval(...$single));
var_dump(boolval(...$single));
var_dump(intval("ff", ...[16]));
// A named argument is likewise a single Arg node that does not have to
// be the value being converted.
var_dump(intval(value: "42"));
// floatval // floatval
var_dump(floatval(42)); var_dump(floatval(42));
var_dump(floatval("3.14")); var_dump(floatval("3.14"));
@ -57,6 +72,13 @@ int(26)
int(5) int(5)
int(511) int(511)
int(255) int(255)
int(255)
int(42)
string(2) "42"
float(42)
bool(true)
int(255)
int(42)
float(42) float(42)
float(3.14) float(3.14)
float(42) float(42)

Loading…
Cancel
Save