fix: 修复类常量使用self作为方法参数默认值

pull/16/head
Yurun 2 months ago
parent 9f6a13d5f7
commit 99d76ad5dc
  1. 15
      phpunit/code/class-const-default-value.php
  2. 7
      phpunit/src/ClassTest.php
  3. 11
      src/CompilerBase.php
  4. 55
      src/Translator.php
  5. 23
      tests/aot/const/class-const-default-value.phpt

@ -0,0 +1,15 @@
<?php
class LazyArrayObject extends \ArrayObject
{
public function test($value1 = self::ARRAY_AS_PROPS, $value2 = LazyArrayObject::ARRAY_AS_PROPS, $value3 = \ArrayObject::ARRAY_AS_PROPS)
{
var_dump($value1, $value2, $value3);
}
}
function main()
{
$test = new LazyArrayObject;
$test->test();
}

@ -155,4 +155,11 @@ class ClassTest extends \BaseTest
{
$this->exec('Method MagicGetProtectedInvalid::__get() must have public visibility', 'magic-get-protected.php');
}
public function testClassConstDefaultValue()
{
// 类常量(self:: / 类名:: / 完全限定名::,含继承自内部父类的常量)
// 作为函数/方法默认参数值应当能够在编译期正确解析。
$this->compile('class-const-default-value.php');
}
}

@ -1471,13 +1471,18 @@ class CompilerBase implements PropertyAccessContext
return null;
}
/*
* 函数参数默认值只能为字面量,无法使用表达式获取值
* 函数参数默认值只能为字面量,无法使用表达式获取值。
* 但 PHP 自 5.6 起支持在默认参数值中使用常量表达式,包括
* 类常量(self::FOO、ClassName::BAR、\Full\Class::BAZ),
* 编译器需要在编译期将其折叠为对应的字面量。
*/
if ($default instanceof Expr\ConstFetch) {
return $this->parseConstFetch($default, true);
} else {
return $this->parseIdentifier($default);
}
if ($default instanceof Expr\ClassConstFetch) {
return $this->parseClassConstFetch($default);
}
return $this->parseIdentifier($default);
}
protected function getComment(Node\Stmt $v, string $class): string

@ -2062,14 +2062,31 @@ CODE;
$nativeConst = $this->findNativeClassConst($expr, $class, $name);
if ($nativeConst and $expr->hasAttribute('nativeConst')) {
$constDef = $expr->getAttribute('nativeConst');
if ($constDef->valueExpr !== null) {
return $constDef->valueExpr->value;
}
// 内部类的常量没有 valueExpr(值来自 PHP 反射),用“定义该常量的类”
// 通过反射取回标量值。继承自有内部父类(如 ArrayObject)的常量也走这里。
if ($constDef->class !== '') {
$refConst = $constDef->class . '::' . $name;
if (defined($refConst)) {
return constant($refConst);
}
}
}
if ($this->isInternalClass($class)) {
$constName = $class . '::' . $name;
if (defined($constName)) {
return constant($constName);
}
}
// findNativeClassConst 不会遍历到内部父类(如 \ArrayObject),
// 因此继承自有内部父类的常量(例如 self::ARRAY_AS_PROPS,常量定义于
// 内部父类)在此沿继承链解析(内部类在运行时已加载,可用反射取值)。
$inherited = $this->resolveInheritedClassConst($class, $name);
if ($inherited !== null) {
return $inherited;
}
// Resolve enum case references. Enum cases are runtime objects; their
// actual values in class constant arrays are set at runtime by
// genClassArrayConstants() via php::getEnumCase(). Return the backing
@ -2084,6 +2101,44 @@ CODE;
$this->fatalError($expr, "Class constant `{$class}::{$name}` not found");
}
/**
* 沿类继承链解析类常量,支持继承自自定义父类或内部父类
* (如 LazyArrayObject 继承自内部类 \ArrayObject,其常量 ARRAY_AS_PROPS
* 定义于内部父类中;内部类在运行时已加载,可用 PHP 反射取值)。
*
* @return mixed|null 解析到的值;未找到返回 null
*/
protected function resolveInheritedClassConst(string $class, string $name): mixed
{
$current = ltrim($class, '\\');
$visited = [];
while ($current !== '' && $current !== '\\' && !isset($visited[strtolower($current)])) {
$visited[strtolower($current)] = true;
if ($this->hasClass($current)) {
$classDef = $this->getClass($current);
if ($classDef->hasConstant($name)) {
$constDef = $classDef->getConstant($name);
if ($constDef->valueExpr !== null) {
return $constDef->valueExpr->value;
}
if ($constDef->class !== '' && defined($constDef->class . '::' . $name)) {
return constant($constDef->class . '::' . $name);
}
}
$current = $classDef->extends;
} elseif (Reflection::isInternalClass($current)) {
$constName = $current . '::' . $name;
if (defined($constName)) {
return constant($constName);
}
break;
} else {
break;
}
}
return null;
}
public function getConstValue(string $name): mixed
{
if ($this->isInternalConstant($name)) {

@ -0,0 +1,23 @@
--TEST--
Class constant as default parameter value (self / class name / FQCN, including constant inherited from an internal parent class)
--FILE--
<?php
class LazyArrayObject extends \ArrayObject
{
public function test($value1 = self::ARRAY_AS_PROPS, $value2 = LazyArrayObject::ARRAY_AS_PROPS, $value3 = \ArrayObject::ARRAY_AS_PROPS)
{
var_dump($value1, $value2, $value3);
}
}
function main()
{
$test = new LazyArrayObject;
$test->test();
}
?>
--EXPECT--
int(2)
int(2)
int(2)
Loading…
Cancel
Save