diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index b44d9231..cc3c77c6 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -999,8 +999,12 @@ class CompilerBase extends \PhpAot\Core\Translator $propertyDef = new PropertyDef($name, $param->flags, $type, $default, $nullable); $this->classDef->properties[$name] = $propertyDef; } - if ($param->variadic and $i !== $last) { - $this->fatalError($param, 'Variadic parameters must be the last parameter'); + if ($param->variadic) { + if ($i !== $last) { + $this->fatalError($param, 'Variadic parameters must be the last parameter'); + } elseif ($param->byRef) { + $this->fatalError($param, 'Variadic parameters cannot be passed by reference'); + } } $name = $this->parseIdentifier($param->var); $type = $this->parseParameterType($param, $name); @@ -1022,6 +1026,11 @@ class CompilerBase extends \PhpAot\Core\Translator $defaultValueCount++; $argInfo->default = $this->parseParamDefaultValue($param->default); $argInfo->defaultValue = $param->default; + } elseif ($param->variadic) { + // 变长参数可以视为空数组默认值 + $defaultValueCount++; + $argInfo->default = '{}'; + $argInfo->defaultValue = new Node\Expr\Array_(); } $functionDef->argInfoList[] = $argInfo; } @@ -1595,6 +1604,17 @@ class CompilerBase extends \PhpAot\Core\Translator } } + protected function checkNativeCallArgs(CallLike $expr, FunctionDef $funcDef, array $args, string $name): void + { + $argc = count($args); + $type = str_contains($name, '::') ? 'Method' : 'Function'; + if ($argc < $funcDef->argCountRequired) { + $this->fatalError($expr, $type . ' `' . $name . '()` requires ' . $funcDef->argCountRequired . ' arguments, ' . $argc . ' given'); + } elseif (!$funcDef->hasVariadicArg() and count($expr->args) > count($funcDef->argInfoList)) { + $this->fatalError($expr, $type . ' `' . $name . '()` accepts ' . count($funcDef->argInfoList) . ' arguments, ' . $argc . ' given'); + } + } + protected function getNativeMethod(CallLike $expr, string $class, string $method): string|false { if (!$this->hasNativeClass($class)) { @@ -1635,11 +1655,7 @@ class CompilerBase extends \PhpAot\Core\Translator if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) { return false; } - if (count($expr->args) < $methodDef->functionDef->argCountRequired) { - $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` requires ' . $methodDef->functionDef->argCountRequired . ' arguments, ' . count($expr->args) . ' given'); - } elseif (count($expr->args) > count($methodDef->functionDef->argInfoList)) { - $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` accepts ' . count($methodDef->functionDef->argInfoList) . ' arguments, ' . count($expr->args) . ' given'); - } + $this->checkNativeCallArgs($expr, $methodDef->functionDef, $expr->args, $classDef->getNamespacedName() . '::' . $method); return $this->getNativeName($method, $classDef->namespace, $classDef->name); } @@ -1697,6 +1713,14 @@ class CompilerBase extends \PhpAot\Core\Translator return self::TYPE_FLOAT; } if ($leftType === self::TYPE_INT || $rightType === self::TYPE_INT) { + // 除法存在特殊性,若未能整除,会返回浮点数,其他则一律视为整数 + if ($exprType === 'Expr_BinaryOp_Div') { + if ($leftType === self::TYPE_INT && $rightType === self::TYPE_INT) { + return self::TYPE_INT; + } else { + return self::TYPE_VAR; + } + } return self::TYPE_INT; } break; @@ -2205,6 +2229,7 @@ class CompilerBase extends \PhpAot\Core\Translator $nativeFn = $this->findNativeFunction($name); if ($nativeFn) { $expr->setAttribute('nativeCall', $nativeFn); + $this->checkNativeCallArgs($expr, $this->getNativeFunction($nativeFn), $expr->args, $name); try { return self::PREFIX . $nativeFn . '(' . $this->parseNativeCallArgs($expr->args, $nativeFn) . ')'; } catch (PlaceHolder) { @@ -2272,6 +2297,11 @@ class CompilerBase extends \PhpAot\Core\Translator ksort($args); } + // 函数只接受一个变长参数,且调用参数为空,直接传入空数组 + if (count($args) === 0 and count($functionDef->argInfoList) === 1 and $functionDef->argInfoList[0]->variadic) { + return '{}'; + } + foreach ($args as $i => $arg) { $argInfo = $this->getArgInfo($arg, $nativeFunc, $i); if ($argInfo->variadic) { @@ -2290,7 +2320,6 @@ class CompilerBase extends \PhpAot\Core\Translator foreach ($argsSlice as $item) { if ($item->unpack) { $this->context->beforeStmtLines[] = $tmpVar . '.merge(' . $this->parseArg($item) . ');'; - break; } else { $this->context->beforeStmtLines[] = $tmpVar . '.append(' . $this->parseArg($item) . ');'; } diff --git a/src/Php/Entity/FunctionDef.php b/src/Php/Entity/FunctionDef.php index 3e896517..a0d7edb2 100644 --- a/src/Php/Entity/FunctionDef.php +++ b/src/Php/Entity/FunctionDef.php @@ -35,4 +35,9 @@ class FunctionDef { return $this->namespace ? $this->namespace . '\\' . $this->name : $this->name; } + + public function hasVariadicArg(): bool + { + return $this->argInfoList && $this->argInfoList[count($this->argInfoList) - 1]->variadic; + } } diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 534b440a..93fa7c11 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -420,7 +420,7 @@ class Translator extends Preprocessor if ($argInfoList) { foreach ($argInfoList as $argInfo) { if ($argInfo->variadic) { - $arg = self::TYPE_ARRAY . ' ' . $argInfo->name; + $arg = self::TYPE_ARRAY . ' ' . $argInfo->name . ' = {}'; } else { $arg = $argInfo->type . ' ' . $argInfo->name; if ($argInfo->default) { diff --git a/tests/aot/variadic/calculator.phpt b/tests/aot/variadic/calculator.phpt new file mode 100644 index 00000000..9739c5b2 --- /dev/null +++ b/tests/aot/variadic/calculator.phpt @@ -0,0 +1,40 @@ +--TEST-- +Variadic Functions - Variable number of arguments with ... +--SKIPIF-- +--FILE-- +add(1, 2, 3, 4)); + var_dump($calc->average(13, 14, 17, 26)); + var_dump(Calculator::max(5, 15, 10, 20)); +} +?> +--EXPECT-- +int(10) +float(17.5) +int(20) diff --git a/tests/aot/variadic/misc.phpt b/tests/aot/variadic/misc.phpt new file mode 100644 index 00000000..0d8dc397 --- /dev/null +++ b/tests/aot/variadic/misc.phpt @@ -0,0 +1,56 @@ +--TEST-- +Variadic Functions - Variable number of arguments with ... +--SKIPIF-- +--FILE-- + $n > 0); +} + +// Test variadic with reference (should fail gracefully) +function test_by_reference(...$params) { + foreach ($params as &$param) { + $param *= 2; + } + return $params; +} + +function main() { + // Test combining variadic with type hints + var_dump(concat_strings("Hello", " ", "World", "!")); + var_dump(concat_strings("PHP", "AOT")); + + // Test variadic returning array + var_dump(filter_positive(1, -2, 3, -4, 5)); + + // Test variadic with reference + $values = [1, 2, 3]; + var_dump(test_by_reference(...$values)); +} +?> +--EXPECT-- +string(12) "Hello World!" +string(6) "PHPAOT" +array(3) { + [0]=> + int(1) + [2]=> + int(3) + [4]=> + int(5) +} +array(3) { + [0]=> + int(2) + [1]=> + int(4) + [2]=> + int(6) +} diff --git a/tests/aot/variadic/multiply.phpt b/tests/aot/variadic/multiply.phpt new file mode 100644 index 00000000..7e5a5774 --- /dev/null +++ b/tests/aot/variadic/multiply.phpt @@ -0,0 +1,26 @@ +--TEST-- +sum +--FILE-- + +--EXPECT-- +int(24) +int(50) +int(7) \ No newline at end of file diff --git a/tests/aot/variadic/sum.phpt b/tests/aot/variadic/sum.phpt new file mode 100644 index 00000000..5ed2ea9d --- /dev/null +++ b/tests/aot/variadic/sum.phpt @@ -0,0 +1,39 @@ +--TEST-- +sum +--FILE-- + +--EXPECT-- +int(15) +int(60) +int(0) +int(10) +int(150) +int(10)