fix(preprocessor): 包装运行时常量作为标量参数默认值

pull/35/head
Yurun 1 month ago
parent 2287695b44
commit 8fff430fcc
  1. 50
      src/Preprocessor.php
  2. 25
      tests/compiler/const/class-const-default-value-typed.phpt

@ -83,6 +83,53 @@ 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);
@ -466,7 +513,8 @@ class Preprocessor extends CompilerBase
$argInfo->default = 'php::newReference(' . $this->parseParamDefaultValue($param->default) . ')';
}
} else {
$argInfo->default = $arrayInitPlan ? $arrayInitPlan->expr : $this->parseParamDefaultValue($param->default);
$defaultExpr = $arrayInitPlan ? $arrayInitPlan->expr : $this->parseParamDefaultValue($param->default);
$argInfo->default = $this->wrapScalarDefaultValue($argInfo->type, $defaultExpr);
$argInfo->arrayInitPlan = $arrayInitPlan;
$argInfo->defaultValue = $param->default;
}

@ -0,0 +1,25 @@
--TEST--
Typed parameter default value from an unresolvable (external) class constant
--FILE--
<?php
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)
{
var_dump($value);
}
}
function main()
{
(new TypedDefault)->run();
}
?>
--EXPECT--
int(2)
Loading…
Cancel
Save