feat(compiler): implement dynamic static property fetch with runtime fallback

- Replace direct static property access with parseDynamicStaticPropertyFetch method
- Add proper runtime evaluation order for class and property operands
- Implement dynamic class value resolution supporting self, parent, static keywords
- Add temporary variable assignment for class and property values
- Create proper class name resolution using get_class or toString methods
- Update documentation to reflect dynamic property chains now use Zend runtime fallback
- Modify incompatibility classification from pending to partial support
- Clarify that native optimization is not guaranteed for dynamic operations
pull/17/head
韩天峰 2 months ago
parent 60aaced040
commit 6059e07827
  1. 2
      docs/INCOMPATIBLE_PHP_FEATURES.md
  2. 6
      docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md
  3. 50
      src/CompilerBase.php

@ -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` 编码。

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

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

Loading…
Cancel
Save