fix(compiler): convert runtime constants in default helpers

pull/35/head
韩天峰 1 month ago
parent 6d3fc61559
commit dfa10c8cbc
  1. 25
      src/Generator/DefaultArgumentGenerator.php
  2. 50
      src/Preprocessor.php
  3. 19
      tests/compiler/const/class-const-default-value-typed.phpt

@ -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) {

@ -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;
}

@ -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)

Loading…
Cancel
Save