From 99d76ad5dcf9c939435650eb2d9193cb3a78ab09 Mon Sep 17 00:00:00 2001 From: Yurun Date: Fri, 10 Jul 2026 19:28:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=B1=BB=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E4=BD=BF=E7=94=A8self=E4=BD=9C=E4=B8=BA=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=8F=82=E6=95=B0=E9=BB=98=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpunit/code/class-const-default-value.php | 15 +++++ phpunit/src/ClassTest.php | 7 +++ src/CompilerBase.php | 11 +++- src/Translator.php | 57 ++++++++++++++++++- .../aot/const/class-const-default-value.phpt | 23 ++++++++ 5 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 phpunit/code/class-const-default-value.php create mode 100644 tests/aot/const/class-const-default-value.phpt diff --git a/phpunit/code/class-const-default-value.php b/phpunit/code/class-const-default-value.php new file mode 100644 index 00000000..4989600d --- /dev/null +++ b/phpunit/code/class-const-default-value.php @@ -0,0 +1,15 @@ +test(); +} diff --git a/phpunit/src/ClassTest.php b/phpunit/src/ClassTest.php index 2f86f9ef..32f47743 100644 --- a/phpunit/src/ClassTest.php +++ b/phpunit/src/ClassTest.php @@ -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'); + } } diff --git a/src/CompilerBase.php b/src/CompilerBase.php index dd133582..9441dede 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.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 diff --git a/src/Translator.php b/src/Translator.php index 456d1fca..1f22fc84 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -2062,7 +2062,17 @@ CODE; $nativeConst = $this->findNativeClassConst($expr, $class, $name); if ($nativeConst and $expr->hasAttribute('nativeConst')) { $constDef = $expr->getAttribute('nativeConst'); - return $constDef->valueExpr->value; + 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; @@ -2070,6 +2080,13 @@ CODE; 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)) { diff --git a/tests/aot/const/class-const-default-value.phpt b/tests/aot/const/class-const-default-value.phpt new file mode 100644 index 00000000..346c2f31 --- /dev/null +++ b/tests/aot/const/class-const-default-value.phpt @@ -0,0 +1,23 @@ +--TEST-- +Class constant as default parameter value (self / class name / FQCN, including constant inherited from an internal parent class) +--FILE-- +test(); +} +?> +--EXPECT-- +int(2) +int(2) +int(2)