diff --git a/docs/REFACTORING_PLAN.md b/docs/REFACTORING_PLAN.md index 18d5095c..b97deaf9 100644 --- a/docs/REFACTORING_PLAN.md +++ b/docs/REFACTORING_PLAN.md @@ -234,7 +234,10 @@ 当前进展: - 已建立 `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,避免规则继续扩散。 - 已添加 `prepare/convert/idle` 编译阶段状态;`PropertyAccessResolver` 只能在 convert 阶段创建和使用,避免预处理阶段误用不完整的类表状态。 - 当前迁移保持生成代码不变,后续阶段再统一 read/write emitter。 diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 8ef0bf04..edf43899 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -43,6 +43,7 @@ use PhpAot\Php\Platform\Macos; use PhpAot\Php\Platform\PlatformBase; use PhpAot\Php\Platform\PlatformFactory; use PhpAot\Php\Platform\Windows; +use PhpAot\Php\Resolver\PropertyAccessResult; use PhpAot\Php\Resolver\PropertyAccessResolver; use PhpParser\Modifiers; use PhpParser\Node; @@ -2771,7 +2772,7 @@ class CompilerBase extends \PhpAot\Core\Translator */ 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; } @@ -4893,13 +4894,14 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->classDef->trait) { 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)) { $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) { - $expr->setAttribute('nativeProperty', $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)) { $prop = $this->parsePropertyFetch($expr); - if ($expr->hasAttribute('nativeProperty')) { + if ($this->isNativePropertyAccess($expr)) { if ($op === self::OP_REFVAL) { 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)) { $prop = $this->parseStaticPropertyFetch($expr); - if ($expr->hasAttribute('nativeProperty')) { + if ($this->isNativePropertyAccess($expr)) { if ($op === self::OP_REFVAL) { return $prop . '.toReference()'; } @@ -5825,10 +5827,9 @@ class CompilerBase extends \PhpAot\Core\Translator } else { $class = $this->getNamespacedClassName($class); } - $nativeProperty = $this->findNativeProperty($expr, $propertyName, $class, true); - if ($nativeProperty) { - $expr->setAttribute('nativeProperty', $nativeProperty); - return $nativeProperty; + $result = $this->resolveNativeStaticProperty($expr, $propertyName, $class); + if ($result !== null) { + return $this->applyNativePropertyAccessResult($expr, $result); } } return null; @@ -5859,20 +5860,30 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->createPropertyAccessResolver()->canAccessProtectedProperty($scope, $declaringClass); } - /** - * @param NodeAbstract $expr 仅用于输出错误日志 - * @param string $class 必须传入带有完整命名空间的类名 - */ - protected function findNativeProperty(NodeAbstract $expr, string $property, string $class, bool $static = false): ?string + private function resolveNativeInstanceProperty(NodeAbstract $expr, string $property, string $class): ?PropertyAccessResult { $scope = $this->class ? $this->getFullClassName() : ''; - $result = $this->createPropertyAccessResolver()->resolveNativeProperty($expr, $property, $class, $scope, $static); - if ($result !== null) { - $expr->setAttribute('nativePropertyDef', $result->propertyDef); - $expr->setAttribute('nativeClassDef', $result->classDef); - return $this->getPropertyOffset($result->classDef->getNamespacedName(false), $result->property); - } - return null; + return $this->createPropertyAccessResolver()->resolveNativeInstanceProperty($expr, $property, $class, $scope); + } + + private function resolveNativeStaticProperty(NodeAbstract $expr, string $property, string $class): ?PropertyAccessResult + { + $scope = $this->class ? $this->getFullClassName() : ''; + 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 @@ -5913,7 +5924,7 @@ class CompilerBase extends \PhpAot\Core\Translator return $refVar; } - if ($expr->hasAttribute('nativeProperty')) { + if ($this->isNativePropertyAccess($expr)) { $classPtr = $this->getClassEntryPtr($class); return Symbol::getStaticProperty() . '(' . $classPtr . ', ' . $nativeProp . ')'; } else { @@ -6895,37 +6906,37 @@ class CompilerBase extends \PhpAot\Core\Translator private function checkNullsafePropertyAccesses(NodeAbstract $baseExpr, array $list): void { - $className = $this->detectClassOfExpr($baseExpr); - if ($className === '') { - return; - } - + $properties = []; foreach ($list as $item) { if ($item[0] !== 'property') { - $className = ''; - continue; + break; } /** @var Expr\NullsafePropertyFetch $node */ $node = $item[2]; if (!$this->isIdExpr($node->name)) { - $className = ''; - continue; + break; } - $property = $this->parseIdentifier($node->name); - $this->findNativeProperty($node, $property, $className); - if (!$node->hasAttribute('nativePropertyDef')) { - $className = ''; - continue; - } + $properties[] = [ + 'node' => $node, + 'property' => $this->parseIdentifier($node->name), + ]; + } - /** @var PropertyDef $def */ - $def = $node->getAttribute('nativePropertyDef'); - $className = $def->type === self::TYPE_OBJECT ? $def->class : ''; - if ($className === '') { - return; - } + if (!$properties) { + 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); } } diff --git a/src/Php/Parser/AssignOpTrait.php b/src/Php/Parser/AssignOpTrait.php index eab77ac8..abdaedf3 100644 --- a/src/Php/Parser/AssignOpTrait.php +++ b/src/Php/Parser/AssignOpTrait.php @@ -260,7 +260,7 @@ trait AssignOpTrait $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); } elseif ($this->isArrayDimFetch($left) and $this->isVarExpr($left->var)) { $tmp = $this->parseIdentifier($left->var); @@ -397,7 +397,7 @@ trait AssignOpTrait 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); $propName = $this->identifierToStr($node->var->name, literal: true); $binaryOp = $this->removeAssignOp($op); diff --git a/src/Php/Resolver/PropertyAccessResolver.php b/src/Php/Resolver/PropertyAccessResolver.php index dd092c27..9fe2a1ca 100644 --- a/src/Php/Resolver/PropertyAccessResolver.php +++ b/src/Php/Resolver/PropertyAccessResolver.php @@ -102,6 +102,57 @@ final class PropertyAccessResolver 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 $properties + * @return array + */ + 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 { ($this->fatalError)($expr, $message);