refactor(php): 重构属性访问解析逻辑并引入编译阶段管理

- 引入 PropertyAccessResolver 类统一处理属性访问逻辑
- 添加编译阶段管理 (idle/prepare/convert) 确保模块正确使用时机
- 将 isSameClassName、isSameOrSubclassOf、canAccessProtectedProperty 方法委托给 resolver
- 实现 PropertyAccessResult DTO 承载属性访问解析结果
- 为 Preprocessor 和 Translator 添加编译阶段进入/恢复逻辑
- 创建 CompilerPhaseTest 验证阶段管理功能
- 更新文档说明重构计划和模块拆分方向
- 修改函数返回值测试以适配重构后的测试需求
pull/5/head
韩天峰 2 months ago
parent 64cf047f07
commit 4b1b20c7ca
  1. 13
      docs/README.md
  2. 396
      docs/REFACTORING_PLAN.md
  3. 61
      phpunit/src/CompilerPhaseTest.php
  4. 99
      src/Php/CompilerBase.php
  5. 113
      src/Php/Preprocessor.php
  6. 109
      src/Php/Resolver/PropertyAccessResolver.php
  7. 24
      src/Php/Resolver/PropertyAccessResult.php
  8. 33
      src/Php/Translator.php
  9. 35
      tests/aot/closure/read-private-prop-in-closure.phpt
  10. 8
      tests/aot/functions/return-value.phpt

@ -119,6 +119,19 @@
---
### 9. [核心重构计划](REFACTORING_PLAN.md)
**必读指数**: ⭐⭐⭐⭐
内容概要:
- 核心类职责拆分方向
- TypeSystem、SymbolResolver、PropertyAccessResolver、CallResolver 等模块规划
- 渐进式重构阶段计划
- 测试门禁和风险控制要求
**适合人群**: 核心开发者、架构重构参与者
---
## 🎯 快速导航
### 按使用场景

@ -0,0 +1,396 @@
# AOT 编译器核心重构计划
## 背景
当前 AOT 编译器核心类承担了过多职责,尤其是 `CompilerBase`、`Translator` 等类同时包含 AST 分发、类型推导、属性访问解析、调用解析、代码生成、诊断信息、上下文状态维护等逻辑。随着功能持续增加,这种结构会带来以下问题:
- 封装性不足,修改一个语义点时容易影响多个代码路径。
- 代码复用不足,同类逻辑在普通属性、静态属性、nullsafe、assignment、isset/empty/refval 等路径中重复实现。
- 编译期检查容易出现绕过路径,例如某些动态 fallback 没有复用静态 resolver。
- 单个类代码量过大,review、测试定位和长期维护成本持续升高。
- 设计边界不清晰,类型系统、符号解析、属性访问、调用生成之间耦合过深。
本计划用于指导后续逐步重构,目标是改善架构质量,同时避免一次性大规模重写带来的行为回归风险。
## 核心原则
1. 渐进式重构,禁止一次性重写核心编译流程。
2. 优先抽离无状态或低状态依赖的纯逻辑,再抽离依赖编译上下文的逻辑。
3. 每一步重构应尽量保持行为不变,行为变更必须单独说明并补测试。
4. 新模块应围绕稳定领域建模,而不是简单按 AST 节点机械拆分。
5. 优先使用小型 service/helper、明确 DTO、resolver、emitter;仅在边界稳定后再引入 Visitor、Strategy 等模式。
6. 所有错误信息保持 PHP 风格,不暴露实现细节,不使用 “AOT 禁止” 这类表述。
7. 每个阶段必须有 phpunit 或 phpt 回归验证,尤其是属性、类型、调用、继承、异常等高风险路径。
## 目标架构方向
### CompilerBase
最终应收敛为编译上下文、公共工具、AST 调度入口和跨模块协作层,不再直接承载大量领域逻辑。
保留职责:
- 当前文件、函数、类、方法上下文管理。
- 临时变量、局部变量、作用域状态维护。
- AST 顶层分发入口。
- fatal/warning 的统一入口。
- 与下层 resolver/emitter 的协作。
逐步移出职责:
- 类型声明解析和类型兼容判断。
- 属性可见性、属性 offset、typed property 检查。
- 函数和方法调用解析。
- 复杂表达式代码生成。
- union/intersection/nullable runtime typecheck 生成。
### Translator
保留文件级、类级、函数级编译流程控制,逐步减少具体语义检查和表达式生成逻辑。
保留职责:
- 文件扫描和转换入口。
- 类、函数、方法代码块生成流程。
- class/function/constant/property metadata 注册。
- 编译产物组织。
逐步移出职责:
- 继承兼容检查的细节逻辑。
- trait 属性冲突检查细节。
- 参数、返回值、属性类型兼容判断。
## 模块拆分计划
### 1. TypeSystem
职责:
- 类型声明解析。
- PHP 类型到 AOT 内部类型映射。
- nullable、union、intersection 类型展开。
- 参数、返回值、属性、常量的类型兼容判断。
- 静态类型与 runtime typecheck 的边界定义。
- 类型字符串格式化。
建议子模块:
- `TypeResolver`
- `TypeCompatibility`
- `TypeCheckEmitter`
- `TypeStringFormatter`
设计要求:
- union、intersection、nullable 在静态阶段仍可按 mixed/any 处理,但必须保留 runtime typecheck 信息。
- `self`、`parent`、`static` 等特殊类型名的解析规则必须集中,避免多个路径实现不一致。
- 类型错误信息必须包含函数、方法、参数、属性等必要上下文。
### 2. SymbolResolver
职责:
- 命名空间解析。
- use alias 解析。
- `self`、`parent`、`static` 解析。
- 类、接口、trait、函数、常量名称规范化。
- 动态符号和静态可解析符号的边界判断。
建议接口:
- `resolveClassName(NodeAbstract $node): SymbolResolution`
- `resolveFunctionName(NodeAbstract $node): SymbolResolution`
- `resolveMethodScope(NodeAbstract $node): SymbolResolution`
- `resolveClassConstScope(NodeAbstract $node): SymbolResolution`
设计要求:
- 不同调用路径不能重复实现 `self/parent/static` 规则。
- 对无法静态确定的符号,应明确返回 dynamic 状态,而不是默默退化为字符串拼接。
### 3. PropertyAccessResolver
职责:
- 对象属性访问解析。
- 静态属性访问解析。
- nullsafe 属性访问静态检查。
- private/protected/public 可见性检查。
- native property offset 查找。
- typed property 写入 typecheck 信息生成入口。
- static-vs-instance 属性误用检查。
建议接口:
- `resolveInstancePropertyAccess(PropertyAccessRequest $request): PropertyAccessResult`
- `resolveStaticPropertyAccess(StaticPropertyAccessRequest $request): PropertyAccessResult`
- `assertReadable(PropertyAccessResult $result): void`
- `assertWritable(PropertyAccessResult $result): void`
- `emitRead(PropertyAccessResult $result): string`
- `emitWrite(PropertyAccessResult $result, string $value): string`
需要统一覆盖的路径:
- `$obj->prop`
- `$obj?->prop`
- `Class::$prop`
- `self::$prop`
- `parent::$prop`
- `static::$prop`
- `isset($obj->prop)`
- `empty($obj->prop)`
- `refval($obj->prop)`
- 普通赋值、复合赋值、自增自减、unset。
第一优先级建议从本模块开始,因为近期问题集中在属性访问和可见性绕过,测试边界相对清晰。
### 4. CallResolver
职责:
- 函数调用解析。
- 对象方法调用解析。
- 静态方法调用解析。
- native call 与 dynamic call 选择。
- named args、unpack、by-ref 参数处理。
- closure 和动态 callable 退化规则。
建议接口:
- `resolveFunctionCall(CallRequest $request): CallResolution`
- `resolveMethodCall(MethodCallRequest $request): CallResolution`
- `resolveStaticCall(StaticCallRequest $request): CallResolution`
- `emitCall(CallResolution $resolution): string`
设计要求:
- 静态 function 和内置 function 参数信息明确时,可以自动转引用。
- 动态调用、closure、编译期无法获取参数 by-ref 信息时,必须要求显式 `refval()`
- 使用 unpack 并追加尾部 named args 时,应退化为 dynamic call,不能走 native call。
### 5. ExpressionEmitter
职责:
- 表达式级代码生成。
- 将大型 `parseExpr()` 分发逻辑逐步拆分。
- 复用 resolver 结果生成 C++ 代码。
建议按领域拆分,而不是一开始拆成大量 AST visitor:
- `AssignmentEmitter`
- `PropertyEmitter`
- `CallEmitter`
- `ArrayEmitter`
- `ControlExprEmitter`
- `ObjectEmitter`
设计要求:
- 先保持现有 `parseExpr()` 作为调度入口。
- 每次只迁移一组表达式,迁移后跑对应测试组。
- 对表达式副作用、求值顺序、临时变量生成必须保守处理。
### 6. Diagnostic
职责:
- 统一 fatal/warning 构造。
- 提供上下文增强能力。
- 保证错误消息风格接近 PHP。
建议能力:
- 当前函数/方法名。
- 参数名。
- 属性名和类名。
- 源码位置。
- 声明位置与使用位置。
设计要求:
- 错误信息不出现 “AOT” 作为行为主体。
- 对用户可修复的问题,应包含准确符号名。
- 编译期能发现的问题优先编译期 fatal,不应依赖运行时 typecheck 异常兜底。
## 阶段计划
### 阶段 1:属性访问解析模块化
目标:
- 抽离 `findNativeProperty()`、`findNativeStaticProperty()`、`canAccessProtectedProperty()` 等属性访问解析逻辑。
- 保持生成代码基本不变。
- 建立统一的 `PropertyAccessResult`,承载属性声明、声明类、访问类、是否 native、offset、是否 dynamic 等信息。
范围:
- 普通对象属性读取。
- 静态属性读取。
- nullsafe 属性静态检查。
- 属性可见性检查。
当前进展:
- 已建立 `PropertyAccessResolver``PropertyAccessResult` 作为属性访问解析的第一层抽象。
- `CompilerBase::findNativeProperty()` 保留为兼容入口,并委托 resolver 执行 native property 查找、static-vs-instance 检查和可见性检查。
- `CompilerBase::isSameClassName()`、`isSameOrSubclassOf()`、`canAccessProtectedProperty()` 已委托 resolver,避免规则继续扩散。
- 已添加 `prepare/convert/idle` 编译阶段状态;`PropertyAccessResolver` 只能在 convert 阶段创建和使用,避免预处理阶段误用不完整的类表状态。
- 当前迁移保持生成代码不变,后续阶段再统一 read/write emitter。
验证:
- `phpunit/src/NativePropertyTest.php`
- `phpunit/src/InheritanceErrorTest.php`
- object property 相关 phpt。
- static property 相关 phpt。
- nullsafe 相关 phpt。
### 阶段 2:属性写入路径统一
目标:
- 所有属性写入路径先 resolve,再 emit。
- typed property runtime typecheck 从散落逻辑收敛到属性写入模块。
- 消除 assignment、compound assignment、inc/dec、unset 各自实现属性访问规则的问题。
范围:
- `$obj->prop = $value`
- `$obj->prop += $value`
- `$obj->prop++`
- `unset($obj->prop)`
- `Class::$prop = $value`
- `??=` 相关属性路径。
验证:
- typed property 相关 phpunit/phpt。
- object property optimization 相关 phpt。
- nullsafe 写上下文错误测试。
- private/protected/static 属性错误测试。
### 阶段 3:类型系统模块化
目标:
- 抽离类型声明解析、类型兼容、runtime typecheck 生成。
- 明确静态类型推导和运行时 typecheck 的职责边界。
- 减少参数、返回值、属性、常量各自重复处理类型规则。
范围:
- `parseTypeDecl()`
- `buildTypeCheckFromNode()`
- 参数 typecheck。
- 返回值 typecheck。
- 属性 typecheck。
- 常量类型处理。
验证:
- union、intersection、nullable 类型测试。
- 参数、返回值、属性 typecheck 测试。
- namespace constant 测试。
- constructor、void expression 相关测试。
### 阶段 4:调用解析模块化
目标:
- 统一函数、方法、静态方法调用解析。
- 明确 native call、dynamic call、closure call 的退化规则。
- 集中处理 named args、unpack、by-ref 参数。
范围:
- `parseFuncCall()`
- `parseMethodCall()`
- `parseStaticCall()`
- call args 解析。
- native/internal/user function 调用路径。
验证:
- named args、unpack 相关 phpt。
- by-ref 参数相关 phpt。
- closure 相关 phpt。
- parent/self/static call 相关 phpt。
### 阶段 5:表达式生成器拆分
目标:
- 将 `parseExpr()` 背后的大量表达式生成逻辑迁移到领域 emitter。
- `CompilerBase` 保持调度和共享上下文能力。
- 降低单文件和单类代码量。
范围:
- AssignmentEmitter。
- PropertyEmitter。
- CallEmitter。
- ArrayEmitter。
- ControlExprEmitter。
验证:
- 每迁移一个 emitter,跑对应测试组。
- 最后跑核心 phpunit 和选定 phpt 回归集。
### 阶段 6:Translator 收敛
目标:
- 将继承兼容、trait 合并、类成员校验等逻辑抽离成专门 checker。
- `Translator` 聚焦编译流程组织。
建议子模块:
- `InheritanceChecker`
- `TraitCompositionChecker`
- `ClassMemberValidator`
- `FunctionSignatureChecker`
验证:
- 继承错误 phpunit。
- trait 相关 phpt。
- interface/abstract/final/readonly 相关 phpt。
## 测试门禁
每个重构 PR 或阶段至少满足:
- 相关 phpunit 必须通过。
- 相关 phpt 必须通过。
- 如果修改 C++/phpx,需要补 gtest 并通过对应测试。
- 如果引入新编译期错误,需要新增固定 fixture 到 `phpunit/code` 或新增 phpt。
- 不使用 `file_put_contents()` 临时生成源码作为新测试方式。
建议按模块维护最小回归集:
- 属性模块:`NativePropertyTest`、`InheritanceErrorTest`、object property、static property、nullsafe。
- 类型模块:type_decl、type_hits、typed property、union/intersection/nullable。
- 调用模块:function call、method call、parent_call、closure、named args、unpack、by-ref。
- 控制流和表达式:ternary、match、goto、loop、array、coalesce、void expression。
## 风险控制
- 不在同一个变更中同时做大规模文件移动和行为变更。
- 每次迁移前先补当前行为的测试,尤其是历史 bug 对应路径。
- 保留旧入口一段时间,通过 adapter 调用新模块,降低切换风险。
- 对动态 PHP 语义保持保守,无法静态确定时不要过度优化。
- 对 AOT 明确不兼容 PHP 历史包袱的地方,应在文档和错误信息中表达为语言规则,而不是实现限制。
## 推荐下一步
`PropertyAccessResolver` 开始。
理由:
- 最近 bug 多集中在属性访问、可见性、typed property、nullsafe、static-vs-instance 路径。
- 现有测试较容易扩展。
- 属性访问是类型系统、优化器、调用生成之外相对独立的领域,适合先建立 resolver/result 模式。
- 完成后可直接减少 `CompilerBase` 中的复杂分支,并为后续 ExpressionEmitter 拆分打基础。

@ -0,0 +1,61 @@
<?php
use PhpAot\Php\CompilerTest;
use PhpAot\Php\Exception\TestError;
class CompilerPhaseProbe extends CompilerTest
{
public static function createProbe(string $rootPath): self
{
$compiler = new self($rootPath);
$compiler->forTest = true;
return $compiler;
}
public function enterPreparePhase(): void
{
$this->enterCompilerPhase(self::PHASE_PREPARE);
}
public function enterConvertPhase(): void
{
$this->enterCompilerPhase(self::PHASE_CONVERT);
}
public function probePropertyAccessResolver(): bool
{
return $this->canAccessProtectedProperty('ProbeClass', 'ProbeClass');
}
}
class CompilerPhaseTest extends \PHPUnit\Framework\TestCase
{
public function testPropertyAccessResolverCannotBeUsedOutsideConvertPhase(): void
{
$compiler = CompilerPhaseProbe::createProbe(ROOT_PATH);
$this->expectException(TestError::class);
$this->expectExceptionMessage('PropertyAccessResolver can only be used during convert phase');
$compiler->probePropertyAccessResolver();
}
public function testPropertyAccessResolverCannotBeUsedDuringPreparePhase(): void
{
$compiler = CompilerPhaseProbe::createProbe(ROOT_PATH);
$compiler->enterPreparePhase();
$this->expectException(TestError::class);
$this->expectExceptionMessage('PropertyAccessResolver can only be used during convert phase');
$compiler->probePropertyAccessResolver();
}
public function testPropertyAccessResolverCanBeUsedDuringConvertPhase(): void
{
$compiler = CompilerPhaseProbe::createProbe(ROOT_PATH);
$compiler->enterConvertPhase();
$this->assertTrue($compiler->probePropertyAccessResolver());
}
}

@ -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\PropertyAccessResolver;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\Node\ArrayItem;
@ -163,8 +164,12 @@ class CompilerBase extends \PhpAot\Core\Translator
public const string BUILD_MODE_EXT = 'ext';
public const string ENTRY_FUNCTION = 'main';
public const string PHPX_VENDOR_DIR = '/vendor/swoole/phpx';
protected const string PHASE_IDLE = 'idle';
protected const string PHASE_PREPARE = 'prepare';
protected const string PHASE_CONVERT = 'convert';
protected string $lang = 'PHP';
protected string $compilerPhase = self::PHASE_IDLE;
protected string $cppCompiler = '';
protected array $literalStrings = [];
protected int $literalStringIndex = 0;
@ -845,6 +850,25 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->methodDef = null;
}
protected function enterCompilerPhase(string $phase): string
{
$previous = $this->compilerPhase;
$this->compilerPhase = $phase;
return $previous;
}
protected function restoreCompilerPhase(string $phase): void
{
$this->compilerPhase = $phase;
}
protected function assertCompilerPhase(string $expected, string $feature): void
{
if ($this->compilerPhase !== $expected) {
$this->error("Internal compiler error: {$feature} can only be used during {$expected} phase, current phase is {$this->compilerPhase}");
}
}
protected function resetClass(): void
{
$this->class = '';
@ -5810,31 +5834,29 @@ class CompilerBase extends \PhpAot\Core\Translator
return null;
}
private function createPropertyAccessResolver(): PropertyAccessResolver
{
$this->assertCompilerPhase(self::PHASE_CONVERT, 'PropertyAccessResolver');
return new PropertyAccessResolver(
fn(string $class): ?ClassDef => $this->classes[$this->escapeClass($class)] ?? null,
fn(string $class): string => $this->classExtends[strtolower(ltrim($class, '\\'))] ?? '',
fn(NodeAbstract $expr, string $message) => $this->fatalError($expr, $message),
);
}
protected function isSameClassName(string $classA, string $classB): bool
{
return strcasecmp(ltrim($classA, '\\'), ltrim($classB, '\\')) === 0;
return $this->createPropertyAccessResolver()->isSameClassName($classA, $classB);
}
protected function isSameOrSubclassOf(string $class, string $parent): bool
{
$class = strtolower(ltrim($class, '\\'));
$parent = strtolower(ltrim($parent, '\\'));
while ($class !== '') {
if ($class === $parent) {
return true;
}
$class = $this->classExtends[$class] ?? '';
}
return false;
return $this->createPropertyAccessResolver()->isSameOrSubclassOf($class, $parent);
}
protected function canAccessProtectedProperty(string $scope, string $declaringClass): bool
{
if ($scope === '') {
return false;
}
return $this->isSameOrSubclassOf($scope, $declaringClass)
|| $this->isSameOrSubclassOf($declaringClass, $scope);
return $this->createPropertyAccessResolver()->canAccessProtectedProperty($scope, $declaringClass);
}
/**
@ -5843,49 +5865,12 @@ class CompilerBase extends \PhpAot\Core\Translator
*/
protected function findNativeProperty(NodeAbstract $expr, string $property, string $class, bool $static = false): ?string
{
$class = ltrim($class, '\\');
$findClass = $class;
$scope = $this->class ? $this->getFullClassName() : '';
$propertyDef = null;
$classDef = null;
while (true) {
if ($this->hasClass($findClass)) {
$classDef = $this->getClass($findClass);
if ($classDef->hasProperty($property)) {
$propertyDef = $classDef->getProperty($property);
if (!$static and $propertyDef->isStatic()) {
$this->fatalError($expr, "Cannot access static property `{$class}::\${$property}` as non-static instance property.");
}
if ($static and !$propertyDef->isStatic()) {
$this->fatalError($expr, "Cannot access non-static property `{$class}::\${$property}` as static property.");
}
if ($propertyDef->isPublic()) {
break;
}
if ($propertyDef->isProtected()) {
if ($this->canAccessProtectedProperty($scope, $findClass)) {
break;
}
$displayClass = ltrim($class, '\\');
$this->fatalError($expr, "Cannot access protected property `{$property}` of class `{$displayClass}`");
} else {
if ($this->isSameClassName($scope, $findClass)) {
break;
}
$displayClass = ltrim($class, '\\');
$this->fatalError($expr, "Cannot access private property `{$property}` of class `{$displayClass}`");
}
} elseif ($classDef->extends) {
$findClass = $classDef->extends;
continue;
}
}
break;
}
if ($propertyDef and $classDef) {
$expr->setAttribute('nativePropertyDef', $propertyDef);
$expr->setAttribute('nativeClassDef', $classDef);
return $this->getPropertyOffset($classDef->getNamespacedName(false), $property);
$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;
}

@ -93,62 +93,67 @@ class Preprocessor extends CompilerBase
public function prepareFile(string $file): void
{
$phpCode = $this->loadFile($file);
$this->symbolCallInFile[$this->file] = [];
$this->resetFile();
$this->resetFunction();
$this->resetMethod();
$this->resetClass();
$this->resetNamespace();
$this->climate->info('prepare: ' . $this->getRelativePath($this->file));
$previousPhase = $this->enterCompilerPhase(self::PHASE_PREPARE);
try {
$ast = $this->parser->parse($phpCode);
} catch (\PhpParser\Error $e) {
$this->climate->red("Fatal error: {$e->getMessage()} in {$this->file}");
throw new SyntaxError($e->getMessage(), $e->getCode());
}
$traverser = new NodeTraverser();
$traverser->addVisitor(new Visitor());
$stmts = $traverser->traverse($ast);
foreach ($stmts as $v) {
$type = $v->getType();
switch ($type) {
case 'Stmt_Namespace':
$this->prepareNamespace($v);
break;
case 'Stmt_Enum':
case 'Stmt_Class':
case 'Stmt_Trait':
$this->prepareClass($v);
break;
case 'Stmt_Interface':
$this->parseInterface($v);
break;
case 'Stmt_Function':
$this->prepareFunction($v);
break;
case 'Stmt_Use':
$this->parseUse($v);
break;
case 'Stmt_GroupUse':
$this->parseGroupUse($v);
break;
case 'Stmt_Declare':
case 'Stmt_Nop':
break;
case 'Stmt_Const':
$this->parseConstDef($v);
break;
case 'Stmt_Expression':
$this->foundStrayCode($v);
break;
default:
$this->fatalError($v, 'Unsupported statement: ' . $type);
break;
$phpCode = $this->loadFile($file);
$this->symbolCallInFile[$this->file] = [];
$this->resetFile();
$this->resetFunction();
$this->resetMethod();
$this->resetClass();
$this->resetNamespace();
$this->climate->info('prepare: ' . $this->getRelativePath($this->file));
try {
$ast = $this->parser->parse($phpCode);
} catch (\PhpParser\Error $e) {
$this->climate->red("Fatal error: {$e->getMessage()} in {$this->file}");
throw new SyntaxError($e->getMessage(), $e->getCode());
}
$traverser = new NodeTraverser();
$traverser->addVisitor(new Visitor());
$stmts = $traverser->traverse($ast);
foreach ($stmts as $v) {
$type = $v->getType();
switch ($type) {
case 'Stmt_Namespace':
$this->prepareNamespace($v);
break;
case 'Stmt_Enum':
case 'Stmt_Class':
case 'Stmt_Trait':
$this->prepareClass($v);
break;
case 'Stmt_Interface':
$this->parseInterface($v);
break;
case 'Stmt_Function':
$this->prepareFunction($v);
break;
case 'Stmt_Use':
$this->parseUse($v);
break;
case 'Stmt_GroupUse':
$this->parseGroupUse($v);
break;
case 'Stmt_Declare':
case 'Stmt_Nop':
break;
case 'Stmt_Const':
$this->parseConstDef($v);
break;
case 'Stmt_Expression':
$this->foundStrayCode($v);
break;
default:
$this->fatalError($v, 'Unsupported statement: ' . $type);
break;
}
}
} finally {
$this->restoreCompilerPhase($previousPhase);
}
}

@ -0,0 +1,109 @@
<?php
/**
* This file is part of Swoole-Compiler(AOT).
*
* @link https://www.swoole.com/
* @contact service@swoole.com
*/
namespace PhpAot\Php\Resolver;
use Closure;
use PhpAot\Php\Entity\ClassDef;
use PhpParser\NodeAbstract;
final class PropertyAccessResolver
{
/**
* @param Closure(string): ?ClassDef $getClassDef
* @param Closure(string): string $getParentClass
*/
public function __construct(
private readonly Closure $getClassDef,
private readonly Closure $getParentClass,
private readonly Closure $fatalError,
) {
}
public function isSameClassName(string $classA, string $classB): bool
{
return strcasecmp(ltrim($classA, '\\'), ltrim($classB, '\\')) === 0;
}
public function isSameOrSubclassOf(string $class, string $parent): bool
{
$class = strtolower(ltrim($class, '\\'));
$parent = strtolower(ltrim($parent, '\\'));
while ($class !== '') {
if ($class === $parent) {
return true;
}
$class = ($this->getParentClass)($class);
}
return false;
}
public function canAccessProtectedProperty(string $scope, string $declaringClass): bool
{
if ($scope === '') {
return false;
}
return $this->isSameOrSubclassOf($scope, $declaringClass)
|| $this->isSameOrSubclassOf($declaringClass, $scope);
}
public function resolveNativeProperty(
NodeAbstract $expr,
string $property,
string $class,
string $scope,
bool $static = false,
): ?PropertyAccessResult {
$class = ltrim($class, '\\');
$findClass = $class;
while (true) {
$classDef = ($this->getClassDef)($findClass);
if ($classDef === null) {
break;
}
if ($classDef->hasProperty($property)) {
$propertyDef = $classDef->getProperty($property);
if (!$static && $propertyDef->isStatic()) {
$this->fatal($expr, "Cannot access static property `{$class}::\${$property}` as non-static instance property.");
}
if ($static && !$propertyDef->isStatic()) {
$this->fatal($expr, "Cannot access non-static property `{$class}::\${$property}` as static property.");
}
if ($propertyDef->isPublic()) {
return new PropertyAccessResult($class, $findClass, $property, $classDef, $propertyDef);
}
if ($propertyDef->isProtected()) {
if ($this->canAccessProtectedProperty($scope, $findClass)) {
return new PropertyAccessResult($class, $findClass, $property, $classDef, $propertyDef);
}
$displayClass = ltrim($class, '\\');
$this->fatal($expr, "Cannot access protected property `{$property}` of class `{$displayClass}`");
}
if ($this->isSameClassName($scope, $findClass)) {
return new PropertyAccessResult($class, $findClass, $property, $classDef, $propertyDef);
}
$displayClass = ltrim($class, '\\');
$this->fatal($expr, "Cannot access private property `{$property}` of class `{$displayClass}`");
}
if (!$classDef->extends) {
break;
}
$findClass = $classDef->extends;
}
return null;
}
private function fatal(NodeAbstract $expr, string $message): never
{
($this->fatalError)($expr, $message);
}
}

@ -0,0 +1,24 @@
<?php
/**
* This file is part of Swoole-Compiler(AOT).
*
* @link https://www.swoole.com/
* @contact service@swoole.com
*/
namespace PhpAot\Php\Resolver;
use PhpAot\Php\Entity\ClassDef;
use PhpAot\Php\Entity\PropertyDef;
final readonly class PropertyAccessResult
{
public function __construct(
public string $requestedClass,
public string $declaringClass,
public string $property,
public ClassDef $classDef,
public PropertyDef $propertyDef,
) {
}
}

@ -481,21 +481,26 @@ class Translator extends Preprocessor
public function convertFile(string $file): string
{
$file = realpath($file);
$phpCode = $this->loadFile($file);
$this->localHeaders = [];
while (true) {
try {
$cppCode = $this->doConvert($phpCode);
$cppFile = $this->getCppFile($file);
$this->save($cppCode, $cppFile);
$this->phpSrcFiles[] = $file;
// 生成 stub 文件,依赖 convert 阶段的 use 等信息
$this->genStubFile($this->file);
return $cppFile;
} catch (Redo $e) {
continue;
$previousPhase = $this->enterCompilerPhase(self::PHASE_CONVERT);
try {
$file = realpath($file);
$phpCode = $this->loadFile($file);
$this->localHeaders = [];
while (true) {
try {
$cppCode = $this->doConvert($phpCode);
$cppFile = $this->getCppFile($file);
$this->save($cppCode, $cppFile);
$this->phpSrcFiles[] = $file;
// 生成 stub 文件,依赖 convert 阶段的 use 等信息
$this->genStubFile($this->file);
return $cppFile;
} catch (Redo $e) {
continue;
}
}
} finally {
$this->restoreCompilerPhase($previousPhase);
}
}

@ -0,0 +1,35 @@
--TEST--
closure 001
--FILE--
<?php
class Bar {
public function __construct(callable $fn) {
$data = $fn();
var_dump($data);
}
}
class Foo {
private $arr = [1, 2, 3];
function run() {
$bar = new Bar(function () {
return $this->arr;
});
}
}
function main()
{
$foo = new Foo();
$foo->run();
}
?>
--EXPECT--
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}

@ -2,9 +2,15 @@
object link operator
--FILE--
<?php
function var_dump_test()
{
var_dump('foo');
return;
}
function main()
{
$a = var_dump('foo');
$a = var_dump_test();
var_dump($a);
}
?>

Loading…
Cancel
Save