Skip to content

count() on an array literal folds away spreads, duplicate keys and element side effects #23

Description

@Giandonn

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions