The four scalar conversions are lowered to a single-argument Native cast
by dispatchConversion, which reads args[0] and ignores the rest. intval()
takes a $base as its second argument, so it was silently discarded:
intval("ff", 16); // php::toInt("ff") -> 0, PHP gives 255
intval("0x1A", 16); // php::toInt("0x1A") -> 0, PHP gives 26
intval("101", 2); // php::toInt("101") -> 101, PHP gives 5
Nothing reports the loss: the program compiles clean and the number is
simply wrong, which is easy to miss in the code that most often uses a
base - parsing hex colors, permission masks and binary flags.
A conversion call with any arity other than one now falls through to the
dynamic path, where both arguments are passed to the runtime function.
Single-argument intval(), strval(), floatval() and boolval() keep their
Native cast, so the common case is unchanged.
type_conv.phpt gains the base cases, with a literal and a variable base;
none of them were covered anywhere in the test suite. ConversionArityTest
pins the lowering decision in the generated C++.
master
parent
b493ac79c5
commit
93909274e2
5 changed files with 102 additions and 1 deletions
@ -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,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,52 @@ |
||||
<?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 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)); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue