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 17 hours 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'));
}
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
{
$cpp = $this->compileToCpp('intval-single-argument.php');

@ -532,7 +532,16 @@ trait FuncCallOptimizer
// These four are lowered as single-argument Native casts, which cannot
// carry intval()'s $base. Any other arity must reach the runtime
// 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;
}
$arg = $expr->args[0]->value;

@ -26,6 +26,21 @@ function main() {
$base = 16;
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
var_dump(floatval(42));
var_dump(floatval("3.14"));
@ -57,6 +72,13 @@ int(26)
int(5)
int(511)
int(255)
int(255)
int(42)
string(2) "42"
float(42)
bool(true)
int(255)
int(42)
float(42)
float(3.14)
float(42)

Loading…
Cancel
Save