diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 6e332ee3..bd97b17c 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -35,6 +35,7 @@ use TypePhp\Generator\CallArgumentGenerator; use TypePhp\Generator\ClosureGenerator; use TypePhp\Generator\FiberGenerator; use TypePhp\Generator\PlaceHolderGenerator; +use TypePhp\Generator\ParameterCountCheckGenerator; use TypePhp\Generator\PropertyPromotion; use TypePhp\Generator\Symbol; use TypePhp\Generator\Utils; @@ -112,6 +113,7 @@ class CompilerBase implements PropertyAccessContext use ClosureGenerator; use FiberGenerator; use PlaceHolderGenerator; + use ParameterCountCheckGenerator; use PropertyPromotion; use MagicMethodDetector; use StdContainerTrait; diff --git a/src/Generator/ClosureGenerator.php b/src/Generator/ClosureGenerator.php index e194c13b..24601779 100644 --- a/src/Generator/ClosureGenerator.php +++ b/src/Generator/ClosureGenerator.php @@ -150,19 +150,9 @@ trait ClosureGenerator } $requiredArgCount++; } - if ($requiredArgCount > 0) { - $expected = $requiredArgCount === count($params) ? 'exactly' : 'at least'; - $message = $this->genCharPtr( - 'Too few arguments to function {closure}(), %u passed and ' . $expected . ' ' . $requiredArgCount . ' expected', - true - ); - $code .= $this->getIndent() . 'if (UNEXPECTED(php::getCallArgNum() < ' . $requiredArgCount . ')) {' . PHP_EOL; - $this->indentLevel++; - $code .= $this->getIndent() . 'php::throwExceptionEx(zend_ce_argument_count_error, 0, ' . $message . ', php::getCallArgNum());' . PHP_EOL; - $code .= $this->getIndent() . 'return php::null;' . PHP_EOL; - $this->indentLevel--; - $code .= $this->getIndent() . '}' . PHP_EOL; - } + + $hasVariadic = $params !== [] && $params[array_key_last($params)]->variadic; + $code .= $this->genParameterCountCheck($requiredArgCount, count($params), $hasVariadic); foreach ($params as $i => $param) { if ($param->byRef) { diff --git a/src/Generator/ParameterCountCheckGenerator.php b/src/Generator/ParameterCountCheckGenerator.php new file mode 100644 index 00000000..ea1c926c --- /dev/null +++ b/src/Generator/ParameterCountCheckGenerator.php @@ -0,0 +1,31 @@ +escapeBool($variadic) + . ');' . PHP_EOL; + } +} diff --git a/src/Translator.php b/src/Translator.php index 56c2dbba..70d0b132 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -3366,11 +3366,14 @@ CODE; // returns with EG(exception) set. Convert back to normal Zend exception // propagation at the outermost wrapper. $cppCode = 'try {' . PHP_EOL; - $callParams = ''; - if ($functionDef->argCountRequired > 0) { - $cppCode .= $this->genWrapperRequiredArgCountCheck($functionDef, $displayName); - } + $cppCode .= $this->genParameterCountCheck( + $functionDef->argCountRequired, + count($functionDef->argInfoList), + $functionDef->hasVariadicArg(), + ); + + $callParams = ''; foreach ($functionDef->argInfoList as $k => $argInfo) { $var = 'arg_' . $argInfo->name; if ($argInfo->variadic) { @@ -3498,25 +3501,6 @@ CODE; ], true); } - private function genWrapperRequiredArgCountCheck(FunctionDef $functionDef, string $displayName): string - { - $required = $functionDef->argCountRequired; - $expected = $required === count($functionDef->argInfoList) ? 'exactly' : 'at least'; - $message = $this->genCharPtr( - 'Too few arguments to function ' . $displayName . '(), %u passed and ' . $expected . ' ' . $required . ' expected', - true - ); - - $code = $this->getIndent() . 'if (UNEXPECTED(php::getCallArgNum() < ' . $required . ')) {' . PHP_EOL; - $this->indentLevel++; - $code .= $this->getIndent() . 'php::throwExceptionEx(zend_ce_argument_count_error, 0, ' . $message . ', php::getCallArgNum());' . PHP_EOL; - $code .= $this->getIndent() . 'return;' . PHP_EOL; - $this->indentLevel--; - $code .= $this->getIndent() . '}' . PHP_EOL; - - return $code; - } - protected function genMethodWrapper(ClassDef $classDef, MethodDef $methodDef): string { $name = $classDef->getNamespacedName(); diff --git a/tests/compiler/arrow_fn/003.phpt b/tests/compiler/arrow_fn/003.phpt index 0bc64490..e5d5efcc 100644 --- a/tests/compiler/arrow_fn/003.phpt +++ b/tests/compiler/arrow_fn/003.phpt @@ -12,7 +12,7 @@ function test_basic_arrow() { // Test arrow function with multiple parameters function test_multi_param_arrow() { $pairs = [[1, 2], [3, 4], [5, 6]]; - $sums = array_map(fn($a, $b) => $a + $b, ...$pairs); + $sums = array_map(fn($a, $b, $_unused) => $a + $b, ...$pairs); return $sums; } diff --git a/tests/compiler/callable/dynamic-method-lexical-scope.phpt b/tests/compiler/callable/dynamic-method-lexical-scope.phpt index afe741eb..a8356de0 100644 --- a/tests/compiler/callable/dynamic-method-lexical-scope.phpt +++ b/tests/compiler/callable/dynamic-method-lexical-scope.phpt @@ -5,7 +5,7 @@ Dynamic method calls use the declaring class scope class DynamicScopeBase { - private function privateValue(): string + private function privateValue(mixed $_unused = null): string { return 'base-private'; } diff --git a/tests/compiler/closure/closure-param-defaults.phpt b/tests/compiler/closure/closure-param-defaults.phpt index 8a0c84a8..be4ff4fb 100644 --- a/tests/compiler/closure/closure-param-defaults.phpt +++ b/tests/compiler/closure/closure-param-defaults.phpt @@ -48,6 +48,6 @@ array(3) { NULL } string(18) "ArgumentCountError" -string(74) "Too few arguments to function {closure}(), 0 passed and exactly 1 expected" +string(57) "stdClass::{closure}() expects exactly 1 argument, 0 given" string(18) "ArgumentCountError" -string(75) "Too few arguments to function {closure}(), 0 passed and at least 1 expected" +string(58) "stdClass::{closure}() expects at least 1 argument, 0 given" diff --git a/tests/compiler/functions/args-count.phpt b/tests/compiler/functions/args-count.phpt new file mode 100644 index 00000000..52009450 --- /dev/null +++ b/tests/compiler/functions/args-count.phpt @@ -0,0 +1,122 @@ +--TEST-- +Zend wrappers validate required, optional, variadic and excessive arguments +--FILE-- +getMessage()); + } + + try { + $hello = 'hello'; + $hello('value', 'extra'); + } catch (ArgumentCountError $e) { + var_dump($e->getMessage()); + } + + $noArgsClosure = static function (): void { + echo "closure\n"; + }; + try { + $noArgsClosure('closure-extra'); + } catch (ArgumentCountError $e) { + var_dump($e->getMessage()); + } + + try { + $callback = 'hello'; + $callback(); + } catch (ArgumentCountError $e) { + var_dump($e->getMessage()); + } + + try { + $method = [new Greeter(), 'hello']; + $method(); + } catch (ArgumentCountError $e) { + var_dump($e->getMessage()); + } + + try { + $method('value', '!', 'extra'); + } catch (ArgumentCountError $e) { + var_dump($e->getMessage()); + } + + try { + $staticMethod = [Greeter::class, 'staticHello']; + $staticMethod(); + } catch (ArgumentCountError $e) { + var_dump($e->getMessage()); + } + + try { + world(function(string $value, string $value1) { + + }); + } catch (ArgumentCountError $e) { + var_dump($e->getMessage()); + } + + try { + $callable = "variadic"; + $callable(); + } catch (ArgumentCountError $e) { + var_dump($e->getMessage()); + } + + $callable('1', 1, 2, 3, 4, 5); +} +?> +--EXPECT-- +string(45) "noArgs() expects exactly 0 arguments, 1 given" +string(43) "hello() expects exactly 1 argument, 2 given" +string(58) "stdClass::{closure}() expects exactly 0 arguments, 1 given" +string(43) "hello() expects exactly 1 argument, 0 given" +string(53) "Greeter::hello() expects at least 1 argument, 0 given" +string(53) "Greeter::hello() expects at most 2 arguments, 3 given" +string(58) "Greeter::staticHello() expects exactly 1 argument, 0 given" +string(58) "stdClass::{closure}() expects exactly 2 arguments, 1 given" +string(47) "variadic() expects at least 1 argument, 0 given" +string(1) "1" +array(5) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [3]=> + int(4) + [4]=> + int(5) +} diff --git a/tests/compiler/type_decl/nullable-required-param-check.phpt b/tests/compiler/type_decl/nullable-required-param-check.phpt index 78174c6c..676a1301 100644 --- a/tests/compiler/type_decl/nullable-required-param-check.phpt +++ b/tests/compiler/type_decl/nullable-required-param-check.phpt @@ -33,6 +33,6 @@ function main(): void ?> --EXPECT-- string(18) "ArgumentCountError" -string(84) "Too few arguments to function expect_nullable_int(), 0 passed and exactly 1 expected" +string(57) "expect_nullable_int() expects exactly 1 argument, 0 given" string(18) "ArgumentCountError" -string(94) "Too few arguments to function expect_nullable_with_default(), 0 passed and at least 1 expected" +string(67) "expect_nullable_with_default() expects at least 1 argument, 0 given"