Merge pull request #24 from Giandonn/fix/count-literal-fold-side-effects --skip-tests
fix(optimizer): stop folding count() on unfoldable array literalsmaster
commit
a182a2cde0
5 changed files with 266 additions and 0 deletions
@ -0,0 +1,16 @@ |
||||
<?php |
||||
/** |
||||
* This file is part of TypePHP(AOT). |
||||
* |
||||
* @link https://www.swoole.com/aot/ |
||||
* @contact service@swoole.com |
||||
*/ |
||||
|
||||
function main(): void |
||||
{ |
||||
echo count([1, 2, 3]), "\n"; |
||||
echo count([[1, 2], [3]]), "\n"; |
||||
echo count([1.5, 'text', true, false, null]), "\n"; |
||||
echo count([-2, +3, -1.5]), "\n"; |
||||
echo count([]), "\n"; |
||||
} |
||||
@ -0,0 +1,47 @@ |
||||
<?php |
||||
/** |
||||
* This file is part of TypePHP(AOT). |
||||
* |
||||
* @link https://www.swoole.com/aot/ |
||||
* @contact service@swoole.com |
||||
*/ |
||||
|
||||
class KnownClass |
||||
{ |
||||
public const KNOWN = 1; |
||||
} |
||||
|
||||
class MagicHolder |
||||
{ |
||||
public function __get(string $name): int |
||||
{ |
||||
echo "get-{$name}\n"; |
||||
return 1; |
||||
} |
||||
} |
||||
|
||||
function bump(): int |
||||
{ |
||||
echo "bump\n"; |
||||
return 1; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$rest = [1, 2, 3, 4, 5]; |
||||
$i = 0; |
||||
$plain = 1; |
||||
$ref = 1; |
||||
$object = new MagicHolder(); |
||||
|
||||
echo count([bump(), bump()]), "\n"; |
||||
echo count(['a' => 1, 'a' => 2]), "\n"; |
||||
echo count([...$rest, 9]), "\n"; |
||||
echo count([$i++, $i++]), "\n"; |
||||
echo count([$plain]), "\n"; |
||||
echo count([&$ref]), "\n"; |
||||
echo count([UNDEFINED_COUNT_LITERAL]), "\n"; |
||||
echo count([KnownClass::MISSING]), "\n"; |
||||
echo count(["{$object->property}"]), "\n"; |
||||
echo count([KnownClass::KNOWN]), "\n"; |
||||
} |
||||
@ -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 CountLiteralFoldTest extends TestCase |
||||
{ |
||||
public function testUnfoldableArrayLiteralsKeepTheRuntimeCall(): void |
||||
{ |
||||
$cpp = $this->compileToCpp('count-literal-fold-unsafe.php'); |
||||
|
||||
// Every call in the fixture must stay on the runtime path: element |
||||
// side effects, a repeated key, a spread, a by-reference item, a |
||||
// plain variable read, a constant or class constant fetch that may |
||||
// be undefined, and an interpolated string that may call __get(). |
||||
self::assertSame(10, substr_count($cpp, 'php::fn::count(')); |
||||
self::assertStringContainsString('php_bump()', $cpp); |
||||
self::assertStringContainsString('i++', $cpp); |
||||
} |
||||
|
||||
public function testPlainArrayLiteralsStillFoldAtCompileTime(): void |
||||
{ |
||||
$cpp = $this->compileToCpp('count-literal-fold-safe.php'); |
||||
|
||||
self::assertStringNotContainsString('php::fn::count(', $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)); |
||||
} |
||||
} |
||||
@ -0,0 +1,95 @@ |
||||
--TEST-- |
||||
count() on an array literal keeps spreads, duplicate keys and element side effects |
||||
--FILE-- |
||||
<?php |
||||
class KnownClass |
||||
{ |
||||
public const KNOWN = 1; |
||||
} |
||||
|
||||
class MagicHolder |
||||
{ |
||||
public function __get(string $name): int |
||||
{ |
||||
echo "get-{$name}\n"; |
||||
return 1; |
||||
} |
||||
} |
||||
|
||||
function bump(): int |
||||
{ |
||||
echo "bump\n"; |
||||
return 1; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
// Element expressions must still run. |
||||
var_dump(count([bump(), bump()])); |
||||
|
||||
// A repeated key collapses onto the first one. |
||||
var_dump(count(['a' => 1, 'a' => 2])); |
||||
|
||||
// A spread contributes a count only known at runtime. |
||||
$rest = [1, 2, 3, 4, 5]; |
||||
var_dump(count([...$rest, 9])); |
||||
|
||||
// Side effects of the elements must be observable afterwards. |
||||
$i = 0; |
||||
var_dump(count([$i++, $i++])); |
||||
var_dump($i); |
||||
|
||||
// An undefined constant must still raise the same Error PHP raises. |
||||
try { |
||||
var_dump(count([UNDEFINED_COUNT_LITERAL])); |
||||
echo "constant-error-not-thrown\n"; |
||||
} catch (Error $e) { |
||||
echo "caught=", $e->getMessage(), "\n"; |
||||
} |
||||
|
||||
// A missing class constant on a known class must also still throw. |
||||
try { |
||||
var_dump(count([KnownClass::MISSING])); |
||||
echo "class-constant-error-not-thrown\n"; |
||||
} catch (Error $e) { |
||||
echo "caught=", $e->getMessage(), "\n"; |
||||
} |
||||
|
||||
// An interpolated string may invoke __get(), which must still happen. |
||||
$object = new MagicHolder(); |
||||
var_dump(count(["{$object->property}"])); |
||||
|
||||
// A by-reference item binds the source variable instead of reading it. |
||||
$ref = 1; |
||||
var_dump(count([&$ref])); |
||||
|
||||
// A defined class constant is still evaluated, not discarded. |
||||
var_dump(count([KnownClass::KNOWN])); |
||||
|
||||
// Plain literals stay eligible for the compile-time fold. |
||||
var_dump(count([1, 2, 3])); |
||||
var_dump(count([[1, 2], [3]])); |
||||
var_dump(count([1.5, 'text', true, false, null])); |
||||
var_dump(count([-2, +3, -1.5])); |
||||
var_dump(count([])); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bump |
||||
bump |
||||
int(2) |
||||
int(1) |
||||
int(6) |
||||
int(2) |
||||
int(2) |
||||
caught=Undefined constant "UNDEFINED_COUNT_LITERAL" |
||||
caught=Undefined constant KnownClass::MISSING |
||||
get-property |
||||
int(1) |
||||
int(1) |
||||
int(1) |
||||
int(3) |
||||
int(2) |
||||
int(5) |
||||
int(3) |
||||
int(0) |
||||
Loading…
Reference in new issue