diff --git a/src/Generator/DefaultArgumentGenerator.php b/src/Generator/DefaultArgumentGenerator.php index 891c4b77..ef41305a 100644 --- a/src/Generator/DefaultArgumentGenerator.php +++ b/src/Generator/DefaultArgumentGenerator.php @@ -88,7 +88,8 @@ trait DefaultArgumentGenerator $code .= 'return ' . $plan->expr . ';' . PHP_EOL; } } else { - $code .= 'return ' . $argInfo->default . ';' . PHP_EOL; + $default = $this->convertRuntimeConstantDefault($type, $argInfo->default); + $code .= 'return ' . $default . ';' . PHP_EOL; } $code .= '}' . PHP_EOL . PHP_EOL; @@ -98,6 +99,28 @@ trait DefaultArgumentGenerator return $code; } + /** + * Runtime constant lookup returns Variant, but a typed default helper must + * return its native C++ type explicitly. Convert the complete expression so + * constants nested in expressions are covered as well. + */ + private function convertRuntimeConstantDefault(string $type, string $default): string + { + if (!str_contains($default, 'php::constant(')) { + return $default; + } + + return match ($type) { + Type::INT => 'php::toInt(' . $default . ')', + Type::FLOAT => 'php::toFloat(' . $default . ')', + Type::BOOL => 'php::toBool(' . $default . ')', + Type::STR => 'php::toString(' . $default . ')', + Type::ARRAY => 'php::toArray(' . $default . ')', + Type::OBJECT => 'php::toObject(' . $default . ')', + default => $default, + }; + } + private function shouldGenerateDefaultArgumentHelper(ArgInfo $argInfo): bool { if ($argInfo->variadic) { diff --git a/src/Preprocessor.php b/src/Preprocessor.php index 3e46799d..877cc4f2 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -83,53 +83,6 @@ class Preprocessor extends CompilerBase return $type . ' ' . $argInfo->name; } - /** - * A default value that can only be resolved at runtime (e.g. a class/global - * constant coming from a class that is not compiled into the binary) is emitted - * as a `php::constant(...)` call, which returns a `php::Variant`. - * - * Copy-initializing a typed (non-Variant) parameter such as `php::Int`, - * `php::Float`, `php::Bool`, `php::Str`, `php::Array` or `php::Object` from a - * `php::Variant` is rejected by C++ because the conversion is explicit: - * - * php::Int type = php::constant(...); // error C2440 - * - * The function body already converts such values with `php::toInt(...)` / - * `php::toFloat(...)` / ... (see convertExprFromType), so we wrap the default - * with the very same conversion here. This keeps the declaration consistent with - * the body and produces compilable code: - * - * php::Int type = php::toInt(php::constant(...)); // OK - * - * Parameters whose effective type is `php::Var` (including Stream/Box, which are - * mapped to `php::Var`) accept a `php::Variant` directly, so they are left alone. - */ - protected function wrapScalarDefaultValue(string $type, string $defaultExpr): string - { - if (!str_starts_with($defaultExpr, 'php::constant(')) { - return $defaultExpr; - } - $target = $type; - if ($target === Type::STREAM || $target === Type::BOX) { - $target = Type::VAR; - } - static $converters = [ - Type::INT => 'php::toInt', - Type::FLOAT => 'php::toFloat', - Type::BOOL => 'php::toBool', - Type::STR => 'php::toString', - Type::ARRAY => 'php::toArray', - Type::OBJECT => 'php::toObject', - Type::BIGINT => 'php::toBigInt', - Type::DECIMAL => 'php::toDecimal', - Type::BIGFLOAT => 'php::toBigFloat', - ]; - if (isset($converters[$target])) { - return $converters[$target] . '(' . $defaultExpr . ')'; - } - return $defaultExpr; - } - public function getCppFile(string $file): string { $info = pathinfo($file); @@ -515,8 +468,7 @@ class Preprocessor extends CompilerBase $argInfo->default = 'php::newReference(' . $this->parseParamDefaultValue($param->default) . ')'; } } else { - $defaultExpr = $arrayInitPlan ? $arrayInitPlan->expr : $this->parseParamDefaultValue($param->default); - $argInfo->default = $this->wrapScalarDefaultValue($argInfo->type, $defaultExpr); + $argInfo->default = $arrayInitPlan ? $arrayInitPlan->expr : $this->parseParamDefaultValue($param->default); $argInfo->arrayInitPlan = $arrayInitPlan; $argInfo->defaultValue = $param->default; } diff --git a/tests/compiler/const/class-const-default-value-typed.phpt b/tests/compiler/const/class-const-default-value-typed.phpt index 7ee526ed..d9b5d26d 100644 --- a/tests/compiler/const/class-const-default-value-typed.phpt +++ b/tests/compiler/const/class-const-default-value-typed.phpt @@ -5,14 +5,15 @@ Typed parameter default value from an unresolvable (external) class constant class TypedDefault { - // \ArrayObject is an internal (external) class whose constants cannot be folded - // at compile time, so the default is emitted as php::constant(...). The parameter - // type `int` maps to php::Int, which cannot be copy-initialized from the - // php::Variant returned by php::constant(...). The compiler must wrap the default - // in php::Int(...) so the generated C++ compiles. - public function run(int $value = \ArrayObject::ARRAY_AS_PROPS) + public function run( + int $value = \ArrayObject::ARRAY_AS_PROPS, + float $floatValue = \ArrayObject::ARRAY_AS_PROPS, + string $format = \DateTime::ATOM, + int $composite = 1 | \ArrayObject::ARRAY_AS_PROPS, + mixed $variant = \ArrayObject::STD_PROP_LIST, + ) { - var_dump($value); + var_dump($value, $floatValue, $format, $composite, $variant); } } @@ -23,3 +24,7 @@ function main() ?> --EXPECT-- int(2) +float(2) +string(13) "Y-m-d\TH:i:sP" +int(3) +int(1)