refactor(php): 重构属性访问解析逻辑以使用专门的解析器方法

- 将 findNativeProperty 方法替换为更具体的 resolveNativeInstanceProperty 和 resolveNativeStaticProperty
- 添加 resolveNullsafePropertyChain 方法处理空安全属性链检查
- 引入 PropertyAccessResult 类来统一属性访问结果的返回格式
- 使用 isNativePropertyAccess 方法替代直接的属性检查
- 移除旧的泛型 findNativeProperty 入口点以避免布尔参数扩散
- 更新 AssignOpTrait 中的属性访问检查逻辑
- 修改动态属性递增递减操作中的原生属性检查方式
- 调整编译器基类中的属性解析实现细节
pull/5/head
韩天峰 2 months ago
parent 4b1b20c7ca
commit 87c54236a0
  1. 5
      docs/REFACTORING_PLAN.md
  2. 99
      src/Php/CompilerBase.php
  3. 4
      src/Php/Parser/AssignOpTrait.php
  4. 51
      src/Php/Resolver/PropertyAccessResolver.php

@ -234,7 +234,10 @@
当前进展: 当前进展:
- 已建立 `PropertyAccessResolver``PropertyAccessResult` 作为属性访问解析的第一层抽象。 - 已建立 `PropertyAccessResolver``PropertyAccessResult` 作为属性访问解析的第一层抽象。
- `CompilerBase::findNativeProperty()` 保留为兼容入口,并委托 resolver 执行 native property 查找、static-vs-instance 检查和可见性检查。 - 实例属性读取已通过 resolver 的 `resolveNativeInstanceProperty()` 显式接口完成 native property 查找、static-vs-instance 检查和可见性检查。
- `findNativeStaticProperty()` 已通过 resolver 的 `resolveNativeStaticProperty()` 显式接口完成静态属性检查。
- nullsafe 属性链检查已通过 resolver 的 `resolveNullsafePropertyChain()` 显式接口完成类名推进和可见性检查。
- 旧的 `CompilerBase::findNativeProperty()` 泛型入口已移除,避免后续继续扩散带 `$static` 布尔参数的访问模式。
- `CompilerBase::isSameClassName()`、`isSameOrSubclassOf()`、`canAccessProtectedProperty()` 已委托 resolver,避免规则继续扩散。 - `CompilerBase::isSameClassName()`、`isSameOrSubclassOf()`、`canAccessProtectedProperty()` 已委托 resolver,避免规则继续扩散。
- 已添加 `prepare/convert/idle` 编译阶段状态;`PropertyAccessResolver` 只能在 convert 阶段创建和使用,避免预处理阶段误用不完整的类表状态。 - 已添加 `prepare/convert/idle` 编译阶段状态;`PropertyAccessResolver` 只能在 convert 阶段创建和使用,避免预处理阶段误用不完整的类表状态。
- 当前迁移保持生成代码不变,后续阶段再统一 read/write emitter。 - 当前迁移保持生成代码不变,后续阶段再统一 read/write emitter。

@ -43,6 +43,7 @@ use PhpAot\Php\Platform\Macos;
use PhpAot\Php\Platform\PlatformBase; use PhpAot\Php\Platform\PlatformBase;
use PhpAot\Php\Platform\PlatformFactory; use PhpAot\Php\Platform\PlatformFactory;
use PhpAot\Php\Platform\Windows; use PhpAot\Php\Platform\Windows;
use PhpAot\Php\Resolver\PropertyAccessResult;
use PhpAot\Php\Resolver\PropertyAccessResolver; use PhpAot\Php\Resolver\PropertyAccessResolver;
use PhpParser\Modifiers; use PhpParser\Modifiers;
use PhpParser\Node; use PhpParser\Node;
@ -2771,7 +2772,7 @@ class CompilerBase extends \PhpAot\Core\Translator
*/ */
protected function genDynamicPropIncDec($var, string $op, bool $isPre): ?string protected function genDynamicPropIncDec($var, string $op, bool $isPre): ?string
{ {
if (!$this->isPropertyFetch($var) || $var->getAttribute('nativeProperty')) { if (!$this->isPropertyFetch($var) || $this->isNativePropertyAccess($var)) {
return null; return null;
} }
@ -4893,13 +4894,14 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($this->classDef->trait) { if ($this->classDef->trait) {
goto _dynamic_attr; goto _dynamic_attr;
} }
$nativeProperty = $this->findNativeProperty($expr, $propertyName, $this->getFullClassName()); $result = $this->resolveNativeInstanceProperty($expr, $propertyName, $this->getFullClassName());
$nativeProperty = $result ? $this->applyNativePropertyAccessResult($expr, $result) : null;
} elseif ($this->isTypedObject($objectName)) { } elseif ($this->isTypedObject($objectName)) {
$className = $this->getObjectType($objectName); $className = $this->getObjectType($objectName);
$nativeProperty = $this->findNativeProperty($expr, $propertyName, $className); $result = $this->resolveNativeInstanceProperty($expr, $propertyName, $className);
$nativeProperty = $result ? $this->applyNativePropertyAccessResult($expr, $result) : null;
} }
if ($nativeProperty) { if ($nativeProperty) {
$expr->setAttribute('nativeProperty', $nativeProperty);
return $nativeProperty; return $nativeProperty;
} }
} }
@ -5427,7 +5429,7 @@ class CompilerBase extends \PhpAot\Core\Translator
// 单属性读取(非链式) // 单属性读取(非链式)
if ($this->isPropertyFetch($expr) and $this->isVarExpr($expr->var) and $this->isIdExpr($expr->name)) { if ($this->isPropertyFetch($expr) and $this->isVarExpr($expr->var) and $this->isIdExpr($expr->name)) {
$prop = $this->parsePropertyFetch($expr); $prop = $this->parsePropertyFetch($expr);
if ($expr->hasAttribute('nativeProperty')) { if ($this->isNativePropertyAccess($expr)) {
if ($op === self::OP_REFVAL) { if ($op === self::OP_REFVAL) {
return $prop . '.toReference()'; return $prop . '.toReference()';
} }
@ -5436,7 +5438,7 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
if ($this->isStaticPropertyFetch($expr) and $this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { if ($this->isStaticPropertyFetch($expr) and $this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) {
$prop = $this->parseStaticPropertyFetch($expr); $prop = $this->parseStaticPropertyFetch($expr);
if ($expr->hasAttribute('nativeProperty')) { if ($this->isNativePropertyAccess($expr)) {
if ($op === self::OP_REFVAL) { if ($op === self::OP_REFVAL) {
return $prop . '.toReference()'; return $prop . '.toReference()';
} }
@ -5825,10 +5827,9 @@ class CompilerBase extends \PhpAot\Core\Translator
} else { } else {
$class = $this->getNamespacedClassName($class); $class = $this->getNamespacedClassName($class);
} }
$nativeProperty = $this->findNativeProperty($expr, $propertyName, $class, true); $result = $this->resolveNativeStaticProperty($expr, $propertyName, $class);
if ($nativeProperty) { if ($result !== null) {
$expr->setAttribute('nativeProperty', $nativeProperty); return $this->applyNativePropertyAccessResult($expr, $result);
return $nativeProperty;
} }
} }
return null; return null;
@ -5859,20 +5860,30 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->createPropertyAccessResolver()->canAccessProtectedProperty($scope, $declaringClass); return $this->createPropertyAccessResolver()->canAccessProtectedProperty($scope, $declaringClass);
} }
/** private function resolveNativeInstanceProperty(NodeAbstract $expr, string $property, string $class): ?PropertyAccessResult
* @param NodeAbstract $expr 仅用于输出错误日志
* @param string $class 必须传入带有完整命名空间的类名
*/
protected function findNativeProperty(NodeAbstract $expr, string $property, string $class, bool $static = false): ?string
{ {
$scope = $this->class ? $this->getFullClassName() : ''; $scope = $this->class ? $this->getFullClassName() : '';
$result = $this->createPropertyAccessResolver()->resolveNativeProperty($expr, $property, $class, $scope, $static); return $this->createPropertyAccessResolver()->resolveNativeInstanceProperty($expr, $property, $class, $scope);
if ($result !== null) { }
$expr->setAttribute('nativePropertyDef', $result->propertyDef);
$expr->setAttribute('nativeClassDef', $result->classDef); private function resolveNativeStaticProperty(NodeAbstract $expr, string $property, string $class): ?PropertyAccessResult
return $this->getPropertyOffset($result->classDef->getNamespacedName(false), $result->property); {
} $scope = $this->class ? $this->getFullClassName() : '';
return null; return $this->createPropertyAccessResolver()->resolveNativeStaticProperty($expr, $property, $class, $scope);
}
private function applyNativePropertyAccessResult(NodeAbstract $expr, PropertyAccessResult $result): string
{
$offset = $this->getPropertyOffset($result->classDef->getNamespacedName(false), $result->property);
$expr->setAttribute('nativePropertyDef', $result->propertyDef);
$expr->setAttribute('nativeClassDef', $result->classDef);
$expr->setAttribute('nativeProperty', $offset);
return $offset;
}
protected function isNativePropertyAccess(NodeAbstract $expr): bool
{
return $expr->hasAttribute('nativeProperty');
} }
protected function parseNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): string|bool protected function parseNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): string|bool
@ -5913,7 +5924,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return $refVar; return $refVar;
} }
if ($expr->hasAttribute('nativeProperty')) { if ($this->isNativePropertyAccess($expr)) {
$classPtr = $this->getClassEntryPtr($class); $classPtr = $this->getClassEntryPtr($class);
return Symbol::getStaticProperty() . '(' . $classPtr . ', ' . $nativeProp . ')'; return Symbol::getStaticProperty() . '(' . $classPtr . ', ' . $nativeProp . ')';
} else { } else {
@ -6895,37 +6906,37 @@ class CompilerBase extends \PhpAot\Core\Translator
private function checkNullsafePropertyAccesses(NodeAbstract $baseExpr, array $list): void private function checkNullsafePropertyAccesses(NodeAbstract $baseExpr, array $list): void
{ {
$className = $this->detectClassOfExpr($baseExpr); $properties = [];
if ($className === '') {
return;
}
foreach ($list as $item) { foreach ($list as $item) {
if ($item[0] !== 'property') { if ($item[0] !== 'property') {
$className = ''; break;
continue;
} }
/** @var Expr\NullsafePropertyFetch $node */ /** @var Expr\NullsafePropertyFetch $node */
$node = $item[2]; $node = $item[2];
if (!$this->isIdExpr($node->name)) { if (!$this->isIdExpr($node->name)) {
$className = ''; break;
continue;
} }
$property = $this->parseIdentifier($node->name); $properties[] = [
$this->findNativeProperty($node, $property, $className); 'node' => $node,
if (!$node->hasAttribute('nativePropertyDef')) { 'property' => $this->parseIdentifier($node->name),
$className = ''; ];
continue; }
}
/** @var PropertyDef $def */ if (!$properties) {
$def = $node->getAttribute('nativePropertyDef'); return;
$className = $def->type === self::TYPE_OBJECT ? $def->class : ''; }
if ($className === '') {
return; $scope = $this->class ? $this->getFullClassName() : '';
} $results = $this->createPropertyAccessResolver()->resolveNullsafePropertyChain(
$this->detectClassOfExpr($baseExpr),
$properties,
$scope,
self::TYPE_OBJECT,
);
foreach ($results as $index => $result) {
$this->applyNativePropertyAccessResult($properties[$index]['node'], $result);
} }
} }

@ -260,7 +260,7 @@ trait AssignOpTrait
$this->checkVarAssignExpr($left, $finalVarType, $type); $this->checkVarAssignExpr($left, $finalVarType, $type);
} }
} }
} elseif ($this->isPropertyFetch($left) and !$left->getAttribute('nativeProperty')) { } elseif ($this->isPropertyFetch($left) and !$this->isNativePropertyAccess($left)) {
return $this->parseAssignPropertyFetch($left, $right); return $this->parseAssignPropertyFetch($left, $right);
} elseif ($this->isArrayDimFetch($left) and $this->isVarExpr($left->var)) { } elseif ($this->isArrayDimFetch($left) and $this->isVarExpr($left->var)) {
$tmp = $this->parseIdentifier($left->var); $tmp = $this->parseIdentifier($left->var);
@ -397,7 +397,7 @@ trait AssignOpTrait
return $this->parseArrayDimStore($node->var->var, $dim, $tmpVar); return $this->parseArrayDimStore($node->var->var, $dim, $tmpVar);
} }
if ($this->isPropertyFetch($node->var) and !$node->var->getAttribute('nativeProperty')) { if ($this->isPropertyFetch($node->var) and !$this->isNativePropertyAccess($node->var)) {
$obj = $this->parseIdentifier($node->var->var); $obj = $this->parseIdentifier($node->var->var);
$propName = $this->identifierToStr($node->var->name, literal: true); $propName = $this->identifierToStr($node->var->name, literal: true);
$binaryOp = $this->removeAssignOp($op); $binaryOp = $this->removeAssignOp($op);

@ -102,6 +102,57 @@ final class PropertyAccessResolver
return null; return null;
} }
public function resolveNativeInstanceProperty(
NodeAbstract $expr,
string $property,
string $class,
string $scope,
): ?PropertyAccessResult {
return $this->resolveNativeProperty($expr, $property, $class, $scope);
}
public function resolveNativeStaticProperty(
NodeAbstract $expr,
string $property,
string $class,
string $scope,
): ?PropertyAccessResult {
return $this->resolveNativeProperty($expr, $property, $class, $scope, true);
}
/**
* @param array<int, array{node: NodeAbstract, property: string}> $properties
* @return array<int, PropertyAccessResult>
*/
public function resolveNullsafePropertyChain(
string $baseClass,
array $properties,
string $scope,
string $objectType,
): array {
if ($baseClass === '') {
return [];
}
$className = $baseClass;
$resolved = [];
foreach ($properties as $index => $property) {
$result = $this->resolveNativeProperty($property['node'], $property['property'], $className, $scope);
if ($result === null) {
break;
}
$resolved[$index] = $result;
$def = $result->propertyDef;
if ($def->type !== $objectType || $def->class === '') {
break;
}
$className = $def->class;
}
return $resolved;
}
private function fatal(NodeAbstract $expr, string $message): never private function fatal(NodeAbstract $expr, string $message): never
{ {
($this->fatalError)($expr, $message); ($this->fatalError)($expr, $message);

Loading…
Cancel
Save