diff --git a/src/Php/Generator/TypeCheckGenerator.php b/src/Php/Generator/TypeCheckGenerator.php index 1c8e4843..ce7ac739 100644 --- a/src/Php/Generator/TypeCheckGenerator.php +++ b/src/Php/Generator/TypeCheckGenerator.php @@ -126,6 +126,10 @@ trait TypeCheckGenerator } $varName = $argInfo->name; + if ($argInfo->variadic) { + return $this->genUnionVariadicParamCheck($argInfo, $argIndex); + } + $conditions = []; foreach ($argInfo->typeCheck as $entry) { $cond = $this->genSingleTypeCondition($varName, $entry); @@ -138,10 +142,7 @@ trait TypeCheckGenerator } $orExpr = implode(' || ', $conditions); - $fnName = $this->functionDef->getNamespacedName(); - $msgExpr = 'php::concat(php::concat(php::Str(' . $this->genCharPtr($fnName, true) . ' "(): Argument #' . ($argIndex + 1) - . ' ($' . $varName . ') must be of type " ' . $this->genCharPtr($argInfo->typeStr, true) . ' ", "), ' - . $varName . '.typeStr()), php::Str(" given"))'; + $msgExpr = $this->genUnionParamTypeErrorExpr($argInfo, $varName, (string) ($argIndex + 1)); $code = $this->getIndent() . 'if (UNEXPECTED(!(' . $orExpr . '))) {' . PHP_EOL; $this->indentLevel++; @@ -152,6 +153,55 @@ trait TypeCheckGenerator return $code; } + protected function genUnionVariadicParamCheck(ArgInfo $argInfo, int $argIndex): string + { + $valueVar = $this->genTmpVarName(); + $iterVar = $this->genTmpVarName(); + $argNoVar = $this->genTmpVarName(); + + $conditions = []; + foreach ($argInfo->typeCheck as $entry) { + $cond = $this->genSingleTypeCondition($valueVar, $entry); + if ($cond !== '') { + $conditions[] = $cond; + } + } + if (empty($conditions)) { + return ''; + } + + $orExpr = implode(' || ', $conditions); + $msgExpr = $this->genUnionParamTypeErrorExpr($argInfo, $valueVar, $argNoVar); + + $code = $this->getIndent() . 'for (auto ' . $iterVar . ' = ' . $argInfo->name . '.begin(); ' . $iterVar . ' != ' . $argInfo->name . '.end(); ++' . $iterVar . ') {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->getIndent() . self::TYPE_VAR . ' ' . $valueVar . ' = ' . $iterVar . '.value();' . PHP_EOL; + $code .= $this->getIndent() . self::TYPE_INT . ' ' . $argNoVar . ' = ' . ($argIndex + 1) . ' + ' . $iterVar . '.index();' . PHP_EOL; + $code .= $this->getIndent() . 'if (UNEXPECTED(!(' . $orExpr . '))) {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->getIndent() . 'php::throwException(zend_ce_type_error, (' . $msgExpr . ').toCString());' . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}' . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}' . PHP_EOL; + + return $code; + } + + protected function genUnionParamTypeErrorExpr(ArgInfo $argInfo, string $valueExpr, string $argNoExpr): string + { + $fnName = $this->functionDef->getNamespacedName(); + return 'php::concat({' + . 'php::Str(' . $this->genCharPtr($fnName . '(): Argument #', true) . '), ' + . 'php::toString(' . $argNoExpr . '), ' + . 'php::Str(' . $this->genCharPtr(' ($' . $argInfo->name . ') must be of type ', true) . '), ' + . 'php::Str(' . $this->genCharPtr($argInfo->typeStr, true) . '), ' + . 'php::Str(", "), ' + . $valueExpr . '.typeStr(), ' + . 'php::Str(" given")' + . '})'; + } + protected function genUnionReturnCheck(string $varName): string { $typeCheck = $this->functionDef->returnTypeCheck; diff --git a/src/gen_stub.php b/src/gen_stub.php index 5aaaa0cd..3565b900 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -868,9 +868,15 @@ class ArgInfo { !$this->isVariadic ? ", " . $this->getDefaultValueAsArginfoString() : "" ); } + if ($this->isVariadic) { + return sprintf( + "\tZEND_ARG_VARIADIC_TYPE_INFO(%s, %s, IS_MIXED, 0)\n", + $this->sendBy, $this->name + ); + } return sprintf( - "\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n", - $argKind, $this->sendBy, $this->name, + "\tZEND_ARG_TYPE_MASK(%s, %s, %s, %s)\n", + $this->sendBy, $this->name, $arginfoType->toTypeMask(), $this->getDefaultValueAsArginfoString() ); diff --git a/tests/aot/type_decl/variadic-union-param-check.phpt b/tests/aot/type_decl/variadic-union-param-check.phpt new file mode 100644 index 00000000..0ad7a0d7 --- /dev/null +++ b/tests/aot/type_decl/variadic-union-param-check.phpt @@ -0,0 +1,55 @@ +--TEST-- +Variadic union and nullable parameter runtime type checking +--FILE-- +getMessage(); + } + try { + collect_nullable(ok: 1, bad: "x"); + } catch (\TypeError $e) { + $errors[] = $e->getMessage(); + } + + foreach ($errors as $error) { + var_dump($error); + } +} +?> +--EXPECT-- +array(3) { + [0]=> + int(1) + [1]=> + string(3) "two" + ["named"]=> + int(3) +} +array(3) { + [0]=> + int(1) + [1]=> + NULL + [2]=> + int(3) +} +string(80) "collect_scalars(): Argument #3 ($values) must be of type int|string, array given" +string(76) "collect_nullable(): Argument #2 ($values) must be of type ?int, string given"