doFoldCountLiteral replaced `count([...])` with the number of AST items
and dropped the array literal entirely. That is only correct when the
item count equals the runtime element count and no element carries an
observable effect. Three common shapes break both assumptions:
count([bump(), bump()]); // folded to 2, bump() never ran
count(['a' => 1, 'a' => 2]); // folded to 2, PHP counts 1
count([...$rest, 9]); // folded to 2, PHP counts 6
The spread case is the most damaging: it silently yields a wrong number
in ordinary code that compiles without any diagnostic.
The fold now applies only when every item is unkeyed, is not a spread,
and holds an expression whose evaluation cannot be observed - a scalar,
a constant fetch, a unary sign over either, a nested literal that is
itself foldable, or a variable already known to be defined. An undefined
variable still reaches the dynamic path so it reports the same
diagnostic as PHP. Everything else keeps the runtime php::fn::count()
call, so `count([1, 2, 3])` and friends still fold as before.
Covered by tests/compiler/array/count-literal-fold.phpt for the runtime
semantics and by CountLiteralFoldTest for the fold/no-fold decision in
the generated C++.
master
parent
b493ac79c5
commit
044ba91094
5 changed files with 159 additions and 0 deletions
@ -0,0 +1,10 @@ |
||||
<?php |
||||
function main(): void |
||||
{ |
||||
$a = 1; |
||||
|
||||
echo count([1, 2, 3]), "\n"; |
||||
echo count([[1, 2], [3]]), "\n"; |
||||
echo count([$a, -2, true, null]), "\n"; |
||||
echo count([]), "\n"; |
||||
} |
||||
@ -0,0 +1,17 @@ |
||||
<?php |
||||
function bump(): int |
||||
{ |
||||
echo "bump\n"; |
||||
return 1; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$rest = [1, 2, 3, 4, 5]; |
||||
$i = 0; |
||||
|
||||
echo count([bump(), bump()]), "\n"; |
||||
echo count(['a' => 1, 'a' => 2]), "\n"; |
||||
echo count([...$rest, 9]), "\n"; |
||||
echo count([$i++, $i++]), "\n"; |
||||
} |
||||
@ -0,0 +1,40 @@ |
||||
<?php |
||||
|
||||
namespace TypePhp\Tests; |
||||
|
||||
use PHPUnit\Framework\TestCase; |
||||
use TypePhp\CompilerTest; |
||||
|
||||
class CountLiteralFoldTest extends TestCase |
||||
{ |
||||
public function testUnfoldableArrayLiteralsKeepTheRuntimeCall(): void |
||||
{ |
||||
$cpp = $this->compileToCpp('count-literal-fold-unsafe.php'); |
||||
|
||||
// Element side effects, a repeated key and a spread each make the |
||||
// number of AST items differ from the runtime element count. |
||||
self::assertSame(4, 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,47 @@ |
||||
--TEST-- |
||||
count() on an array literal keeps spreads, duplicate keys and element side effects |
||||
--FILE-- |
||||
<?php |
||||
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); |
||||
|
||||
// Plain literals stay eligible for the compile-time fold. |
||||
$a = 1; |
||||
var_dump(count([1, 2, 3])); |
||||
var_dump(count([[1, 2], [3]])); |
||||
var_dump(count([$a, -2, true, null])); |
||||
var_dump(count([])); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bump |
||||
bump |
||||
int(2) |
||||
int(1) |
||||
int(6) |
||||
int(2) |
||||
int(2) |
||||
int(3) |
||||
int(2) |
||||
int(4) |
||||
int(0) |
||||
Loading…
Reference in new issue