Merge pull request #28 from Giandonn/fix/intval-base-argument --skip-tests

fix(optimizer): intval() with a base must not drop the base
master
韩天峰 4 hours ago committed by GitHub
commit c74e088ff2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 15
      phpunit/code/intval-single-argument.php
  2. 21
      phpunit/code/intval-unpacked-argument.php
  3. 15
      phpunit/code/intval-with-base.php
  4. 67
      phpunit/src/ConversionArityTest.php
  5. 17
      src/Optimizer/FuncCallOptimizer.php
  6. 35
      tests/compiler/stdlib/type_conv.phpt

@ -0,0 +1,15 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
function main(): void
{
var_dump(intval('42'));
var_dump(strval(42));
var_dump(floatval('3.14'));
var_dump(boolval(1));
}

@ -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'));
}

@ -0,0 +1,15 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
function main(): void
{
$base = 16;
var_dump(intval('ff', 16));
var_dump(intval('ff', $base));
}

@ -0,0 +1,67 @@
<?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 ConversionArityTest extends TestCase
{
public function testIntvalWithABaseReachesTheRuntimeFunction(): void
{
$cpp = $this->compileToCpp('intval-with-base.php');
// The Native cast carries no base, so both calls must keep the second
// argument by going through the dynamic path.
self::assertStringNotContainsString('php::toInt(', $cpp);
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');
self::assertStringContainsString('php::toInt(', $cpp);
self::assertStringContainsString('php::toString(', $cpp);
self::assertStringContainsString('php::toFloat(', $cpp);
self::assertStringContainsString('php::toBool(', $cpp);
}
private function compileToCpp(string $file): string
{
global $translator;
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/' . $file;
$compiler->addFiles([$source]);
$compiler->prepareFile($source);
return file_get_contents($compiler->convertFile($source));
}
}

@ -527,8 +527,23 @@ trait FuncCallOptimizer
return $target . '(' . implode(', ', $args) . ')';
}
protected function dispatchConversion(Node\Expr\FuncCall $expr, string $convType): string
protected function dispatchConversion(Node\Expr\FuncCall $expr, string $convType): string|false
{
// 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.
//
// 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;
$type = $this->detectTypeOfExpr($arg);
$nativeClass = $this->detectClassOfExpr($arg);

@ -18,6 +18,29 @@ function main() {
var_dump(intval(true));
var_dump(intval(false));
// intval with an explicit base
var_dump(intval("ff", 16));
var_dump(intval("0x1A", 16));
var_dump(intval("101", 2));
var_dump(intval("777", 8));
$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"));
@ -44,6 +67,18 @@ int(42)
int(3)
int(1)
int(0)
int(255)
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