diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 3ef2a3a8..562cadeb 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -3142,8 +3142,13 @@ protected function detectTypeOfExpr($expr): string if ($this->isNameExpr($expr->name)) { $name = $this->parseIdentifier($expr->name); $globalName = ltrim($name, '\\'); - // Math function optimization: propagate Big* return types - if (in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true) && !empty($expr->args)) { + // Math function optimization: propagate Big* return types. + // Skip first-class callables like `round(...)`: their single + // VariadicPlaceholder arg has no ->value, and they resolve to + // a Closure (Type::OBJECT) further below. + if (in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true) + && !empty($expr->args) + && !$expr->isFirstClassCallable()) { $argType = $this->detectTypeOfExpr($expr->args[0]->value); if ( $argType === Type::BIGINT @@ -3458,6 +3463,12 @@ protected function checkInternalFunctionArgCount(string $funcName, Node\Expr\Fun if ($this->hasUnpackCallArg($expr->args)) { return; } + // `foo(...)` is PHP 8.1 first-class callable syntax: it creates a + // Closure instead of calling foo, so its single VariadicPlaceholder + // must not be counted/validated against foo's real signature. + if ($expr->isFirstClassCallable()) { + return; + } $actualArgCount = count($expr->args); $config = $this->getFuncCallConfig()[ltrim($funcName, '\\')] ?? null; $allowedArgCounts = is_array($config) ? ($config['argCounts'] ?? null) : null; diff --git a/tests/compiler/place-holder/builtin-multi-arg-callable.phpt b/tests/compiler/place-holder/builtin-multi-arg-callable.phpt new file mode 100644 index 00000000..6d8e0156 --- /dev/null +++ b/tests/compiler/place-holder/builtin-multi-arg-callable.phpt @@ -0,0 +1,19 @@ +--TEST-- +First-class callable of builtins requiring multiple arguments +--FILE-- + +--EXPECT-- +ababab +2,3 diff --git a/tests/compiler/place-holder/math-function-callable.phpt b/tests/compiler/place-holder/math-function-callable.phpt new file mode 100644 index 00000000..8e2a0830 --- /dev/null +++ b/tests/compiler/place-holder/math-function-callable.phpt @@ -0,0 +1,16 @@ +--TEST-- +First-class callable of a math function (round) +--FILE-- +value, so the optimization must be skipped and the expression must + // resolve to a Closure instead of crashing. + $values = [1.4, 2.6, 3.5]; + $rounded = array_map(round(...), $values); + echo implode(',', $rounded), "\n"; +} +?> +--EXPECT-- +1,3,4