fix(optimizer): preserve unpacked call semantics

master
韩天峰 4 hours ago
parent 223a10636b
commit 9ad213ea64
  1. 21
      phpunit/code/count-literal-fold-unpack.php
  2. 22
      phpunit/code/func-call-optimizer-unpack.php
  3. 8
      phpunit/src/CountLiteralFoldTest.php
  4. 36
      phpunit/src/FuncCallOptimizerUnpackTest.php
  5. 6
      src/Optimizer/FuncCallOptimizer.php
  6. 16
      tests/compiler/array/count-literal-fold.phpt
  7. 28
      tests/compiler/stdlib/optimized-call-unpack.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
{
$args = [[1, 2, 3]];
var_dump(count(...$args));
var_dump(count(...[[1, 2, 3]]));
try {
var_dump(count(...[]));
echo "argument-count-error-not-thrown\n";
} catch (ArgumentCountError $error) {
echo "caught=", $error->getMessage(), "\n";
}
}

@ -0,0 +1,22 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
function main(): void
{
$intvalArgs = ['ff', 16];
$roundArgs = [2.5, 0, PHP_ROUND_HALF_DOWN];
$nullArgs = [null];
$arrayKeysArgs = [['a' => 1]];
$functionExistsArgs = ['strlen'];
var_dump(intval(...$intvalArgs));
var_dump(round(...$roundArgs));
var_dump(is_null(...$nullArgs));
var_dump(array_keys(...$arrayKeysArgs));
var_dump(function_exists(...$functionExistsArgs));
}

@ -37,6 +37,14 @@ class CountLiteralFoldTest extends TestCase
self::assertStringNotContainsString('php::fn::count(', $cpp);
}
public function testArgumentUnpackingUsesTheRuntimeCallPath(): void
{
$cpp = $this->compileToCpp('count-literal-fold-unpack.php');
self::assertSame(3, substr_count($cpp, '.appendUnpacked('));
self::assertStringNotContainsString('php::fn::count(', $cpp);
}
private function compileToCpp(string $file): string
{
global $translator;

@ -0,0 +1,36 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
namespace TypePhp\Tests;
use PHPUnit\Framework\TestCase;
use TypePhp\CompilerTest;
/**
* @internal
* @coversNothing
*/
class FuncCallOptimizerUnpackTest extends TestCase
{
public function testOptimizedFunctionsLeaveArgumentUnpackingToTheRuntime(): void
{
global $translator;
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/func-call-optimizer-unpack.php';
$compiler->addFiles([$source]);
$compiler->prepareFile($source);
$cpp = file_get_contents($compiler->convertFile($source));
self::assertSame(5, substr_count($cpp, '.appendUnpacked('));
self::assertStringNotContainsString('php::toInt(', $cpp);
self::assertStringNotContainsString('php::fn::round(', $cpp);
self::assertStringNotContainsString('php::fn::array_keys(', $cpp);
}
}

@ -203,7 +203,11 @@ trait FuncCallOptimizer
if ($this->isPlaceholderExpr($arg)) {
return false;
}
if ($arg instanceof Node\Arg && $arg->name !== null) {
// Custom handlers, big-type dispatch and scalar conversions work
// with the syntactic argument list. Named arguments and unpacking
// require Zend's runtime binding/expansion semantics, so reject
// them before any optimizer-specific handler can consume them.
if ($arg instanceof Node\Arg && ($arg->name !== null || $arg->unpack)) {
return false;
}
}

@ -72,6 +72,19 @@ function main()
var_dump(count([1.5, 'text', true, false, null]));
var_dump(count([-2, +3, -1.5]));
var_dump(count([]));
// Argument unpacking must happen before count() receives its arguments.
$countArgs = [[1, 2, 3]];
var_dump(count(...$countArgs));
var_dump(count(...[[1, 2, 3]]));
// An empty unpack must retain Zend's argument-count validation.
try {
var_dump(count(...[]));
echo "argument-count-error-not-thrown\n";
} catch (ArgumentCountError $e) {
echo "caught=", $e->getMessage(), "\n";
}
}
?>
--EXPECT--
@ -93,3 +106,6 @@ int(2)
int(5)
int(3)
int(0)
int(3)
int(3)
caught=count() expects at least 1 argument, 0 given

@ -0,0 +1,28 @@
--TEST--
optimized builtin calls preserve argument unpacking
--FILE--
<?php
function main(): void
{
$intvalArgs = ['ff', 16];
$roundArgs = [2.5, 0, PHP_ROUND_HALF_DOWN];
$nullArgs = [null];
$arrayKeysArgs = [['a' => 1]];
$functionExistsArgs = ['strlen'];
var_dump(intval(...$intvalArgs));
var_dump(round(...$roundArgs));
var_dump(is_null(...$nullArgs));
var_dump(array_keys(...$arrayKeysArgs));
var_dump(function_exists(...$functionExistsArgs));
}
?>
--EXPECT--
int(255)
float(2)
bool(true)
array(1) {
[0]=>
string(1) "a"
}
bool(true)
Loading…
Cancel
Save