diff --git a/docs/INCOMPATIBLE_PHP_FEATURES.md b/docs/INCOMPATIBLE_PHP_FEATURES.md index fd15e5ec..0021246c 100644 --- a/docs/INCOMPATIBLE_PHP_FEATURES.md +++ b/docs/INCOMPATIBLE_PHP_FEATURES.md @@ -59,7 +59,7 @@ - `static::class` 在需要编译期常量类名的位置不支持。 - `__CLASS__` 只允许在 `class` 定义的代码段中使用(`PHP`允许,返回空字符串)。 - `__TRAIT__` 只允许在 `trait` 定义的代码段中使用(`PHP`允许,返回空字符串)。 -- 动态属性链、动态类名、动态函数名、动态回调在部分 native 优化路径上会退化或被拒绝。 +- 动态属性链、动态类名、动态函数名和动态回调会统一走 Zend runtime fallback,不保证 native 优化;动态调用的引用参数仍需显式使用 `refval()` 或 `toRef()`。 - `Closure::bind()` 绑定静态闭包访问私有成员时,当前行为与标准 PHP 不完全一致。 - first-class callable 存入 typed nullable `Closure` 属性后,当前存在运行时稳定性限制。 - 所有源文件必须是 `UTF-8` 编码。 diff --git a/docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md b/docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md index d10db428..52ed18dc 100644 --- a/docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md +++ b/docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md @@ -95,7 +95,7 @@ These items should be documented with the exact boundary. | `foreach` by-reference with list destructuring | Pending | Requires by-reference foreach value lowering followed by destructuring assignment. | | Dynamic `ClassName::class` | Pending | Runtime class-name resolution can be used when the class expression is dynamic. | | `static::class` in runtime contexts | Pending / Partial | Runtime contexts can use called-class lookup. True compile-time constant contexts should remain unsupported. | -| Dynamic property chains, class names, function names and callbacks in native-optimized paths | Pending | A unified dynamic runtime path should handle these cases; native paths should be optimization only. | +| Dynamic property chains, class names, function names and callbacks in native-optimized paths | Partial | Supported through a Zend runtime fallback. Native dispatch is only an optimization; automatic by-reference argument conversion remains unsupported. | | First-class callable stored in nullable `Closure` typed property | Pending / Partial | Requires stable runtime lifetime, refcount and typed-property write handling. | | Attribute arguments containing arrays or `new` expressions | Pending | Requires full constant-expression and attribute metadata generation support. | | Static analysis of union, intersection and nullable types | Pending optimization | Requires a real union/intersection type lattice instead of treating these as `mixed/any` during static analysis. | @@ -105,8 +105,8 @@ These items should be documented with the exact boundary. | Feature | Classification | Boundary | |---|---|---| | `eval()` | Partial / Hard Limit | `eval()` can execute PHP code through Zend VM, but it cannot access compiled local variables. Use return values or `$GLOBALS` for data exchange. | -| Dynamic calls and callbacks | Partial | Many cases can fall back to Zend dynamic calls, but by-reference argument conversion and native-call optimization are limited. | -| Dynamic properties and dynamic property chains | Partial | Simple dynamic property paths may work; complex chains may be rejected or fall back to slower runtime paths. | +| Dynamic calls and callbacks | Partial | Zend runtime fallback handles dynamic calls and callbacks. By-reference arguments still need explicit `refval()` / `toRef()`, and native-call optimization is not guaranteed. | +| Dynamic properties and dynamic property chains | Partial | Dynamic property reads and writes use the runtime property API; native property optimization is not guaranteed. | | Native typed properties | Partial / Intentional Rule | Fast native paths may not preserve every PHP dynamic state transition. Unknown or incompatible values can fall back to `setProperty()`. | | Reflection metadata | Partial | Runtime declarations exist, but some AOT-specific metadata such as promoted-property flags may be incomplete. | diff --git a/src/CompilerBase.php b/src/CompilerBase.php index edc95bfa..241b8951 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -7150,7 +7150,55 @@ class CompilerBase implements PropertyAccessContext if ($native !== null) { return $native; } - return Symbol::getStaticProperty() . '(' . $this->identifierToStr($expr->class) . ', ' . $this->identifierToStr($expr->name) . ')'; + + return $this->parseDynamicStaticPropertyFetch($expr); + } + + /** + * Resolve a static-property target through the runtime path. + * + * PHP permits the class operand to be either a class-name string or an + * object. Materialising both operands preserves PHP's left-to-right + * evaluation order and avoids ambiguous C++ overload resolution for Var. + */ + private function parseDynamicStaticPropertyFetch(Expr\StaticPropertyFetch $expr): string + { + $classValue = $this->getDynamicStaticClassValue($expr->class); + $propertyValue = $this->identifierToStr($expr->name, literal: true); + + $classVar = $this->addTmpVar(self::TYPE_VAR); + $propertyVar = $this->addTmpVar(self::TYPE_VAR); + $this->context->beforeStmtLines[] = $classVar . ' = ' . $classValue . ';'; + $this->context->beforeStmtLines[] = $propertyVar . ' = ' . $propertyValue . ';'; + + $className = '(' . $classVar . '.isObject() ? php::fn::get_class(' . $classVar . ') : php::toString(' . $classVar . '))'; + return Symbol::getStaticProperty() . '(' . $className . ', php::toString(' . $propertyVar . '))'; + } + + private function getDynamicStaticClassValue(NodeAbstract $class): string + { + if (!$this->isNameExpr($class)) { + return $this->parseExprAsValue($class); + } + + $name = $this->parseIdentifier($class); + if ($name === 'self') { + return $this->getLiteralString($this->getFullClassName()); + } + if ($name === 'parent') { + if (!$this->classDef || !$this->classDef->extends) { + $this->fatalError($class, 'Cannot access parent:: when current class does not extend any class'); + } + return $this->getLiteralString($this->classDef->extends); + } + if ($name === 'static') { + if (!$this->methodDef) { + $this->fatalError($class, "The 'static' keyword can only be used as the class name in class methods"); + } + return Symbol::getCalledClass(); + } + + return $this->getLiteralString($this->getNamespacedClassName($name)); } protected function parseClassConstFetch(Expr\ClassConstFetch $expr): string