Summary
count() on an array literal is folded at compile time by counting AST items
(FuncCallOptimizer::doFoldCountLiteral). The array literal is then discarded.
That is only valid when the number of AST items equals the runtime element
count and no element has an observable effect. Three ordinary shapes violate
both, and the compiled binary silently disagrees with PHP.
Reproducer
<?php
function bump(): int
{
echo "bump\n";
return 1;
}
function main()
{
var_dump(count([bump(), bump()])); // PHP: prints bump twice, int(2)
var_dump(count(['a' => 1, 'a' => 2])); // PHP: int(1)
$rest = [1, 2, 3, 4, 5];
var_dump(count([...$rest, 9])); // PHP: int(6)
$i = 0;
var_dump(count([$i++, $i++])); // PHP: int(2)
var_dump($i); // PHP: int(2)
}
Expected (PHP 8.5.4)
bump
bump
int(2)
int(1)
int(6)
int(2)
int(2)
Actual (generated C++)
Every call folds to a constant and the literal disappears:
void php_main() {
php::Var i = 0L;
php::echo(php::toString(2L)); // count([bump(), bump()]) - bump() never called
php::echo(php::toString(2L)); // count(['a' => 1, 'a' => 2]) - PHP says 1
php::echo(php::toString(2L)); // count([...$rest, 9]) - PHP says 6
php::echo(php::toString(2L)); // count([$i++, $i++])
php::echo(php::toString(i)); // i is still 0
}
The spread case is the most damaging one: count([...$parts, $extra]) is
ordinary modern PHP, it compiles without any diagnostic, and the number is
simply wrong.
Notes
This is a different handler from the FOLD_SSA_TYPE issue in #21
(is_int / is_float / is_bool), though it belongs to the same family of
"the fold discards its operand". genIsNull in the same file already shows the
intended discipline by keeping the operand evaluated.
Happy to send a PR: restrict the fold to literals whose element count is
provably static and whose elements are effect-free, and keep the runtime
php::fn::count() call otherwise. count([1, 2, 3]) and nested literals stay
folded.
Environment: TypePHP master (b493ac79), PHP 8.5.4, Linux x64.
Summary
count()on an array literal is folded at compile time by counting AST items(
FuncCallOptimizer::doFoldCountLiteral). The array literal is then discarded.That is only valid when the number of AST items equals the runtime element
count and no element has an observable effect. Three ordinary shapes violate
both, and the compiled binary silently disagrees with PHP.
Reproducer
Expected (PHP 8.5.4)
Actual (generated C++)
Every call folds to a constant and the literal disappears:
The spread case is the most damaging one:
count([...$parts, $extra])isordinary modern PHP, it compiles without any diagnostic, and the number is
simply wrong.
Notes
This is a different handler from the
FOLD_SSA_TYPEissue in #21(
is_int/is_float/is_bool), though it belongs to the same family of"the fold discards its operand".
genIsNullin the same file already shows theintended discipline by keeping the operand evaluated.
Happy to send a PR: restrict the fold to literals whose element count is
provably static and whose elements are effect-free, and keep the runtime
php::fn::count()call otherwise.count([1, 2, 3])and nested literals stayfolded.
Environment: TypePHP master (
b493ac79), PHP 8.5.4, Linux x64.