refactor(php): 重构属性写入目标处理逻辑以支持动态对象属性

- 引入 PropertyWriteTarget 类来统一管理属性写入目标
- 修改 parseAssignPropertyFetch 方法以接受 PropertyWriteTarget 参数
- 移除已废弃的 parseAssignStaticProperty 方法
- 添加动态对象属性读取和写入的目标级辅助方法
- 更新复合赋值操作以使用统一的属性写入目标处理
- 改进属性引用获取逻辑以复用目标级引用辅助方法
- 在 .gitignore 中添加 swoole_compiler 和 tpc 目录排除规则
- 更新重构计划文档以反映动态属性写入目标的实现进展
pull/4/head
韩天峰 2 months ago
parent 9c5bf37b6f
commit df5dd2a5f6
  1. 4
      .gitignore
  2. 6
      docs/REFACTORING_PLAN.md
  3. 107
      src/Php/CompilerBase.php
  4. 73
      src/Php/Parser/AssignOpTrait.php
  5. 7
      src/Php/Resolver/PropertyWriteTarget.php

4
.gitignore vendored

@ -16,4 +16,6 @@
*.exe
*.obj
*.pdb
*.class
*.class
/swoole_compiler
/tpc

@ -294,7 +294,11 @@
- 普通赋值和 `??=` 已接入 `preparePropertyWriteTarget()`,在写入前统一完成属性 target 准备,并通过 `assertCanAssignPropertyWrite()``wrapPropertyWriteTypeCheck()` 执行静态检查和 runtime typecheck 包装。
- dynamic object property 的 `getProperty()` / `setProperty()` 生成已收敛到 `emitDynamicPropertyRead()` / `emitDynamicPropertyWrite()` helper;普通动态属性赋值、复合赋值、自增自减已复用该入口。
- 复合赋值的动态属性路径已接入 `preparePropertyWriteTarget()`,先统一完成属性写入 target 准备和静态检查。
- 当前步骤保持生成代码不变;后续继续收敛 dynamic/native property write emitter、compound assignment、inc/dec、unset 和 refval 路径。
- `PropertyWriteTarget` 已开始携带安全动态属性写入目标的 object/property 表达式;普通动态属性赋值、复合赋值、自增自减已优先通过 target 级 read/write helper 发射代码。
- 动态属性 `unset`、属性数组维度写入、引用参数/refval/引用赋值中的安全对象属性引用路径已开始复用 target 级 unset/ref helper。
- 对象属性引用表达式的 target/ref 生成已收敛到 `emitDynamicPropertyFetchRef()`;未使用的旧静态属性赋值入口已删除,静态属性赋值继续走统一 assignment target 路径。
- 为避免改变复杂表达式求值顺序,当前仅对对象部分为变量的动态属性写入填充 target object/property 字段,复杂对象表达式仍保留旧路径。
- 当前步骤对有效代码保持生成逻辑兼容,但会让更多属性写入路径进入统一静态检查;后续继续收敛 dynamic/native property write emitter、compound assignment、inc/dec、unset 和 refval 路径。
### 阶段 3:类型系统模块化

@ -2893,11 +2893,22 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
return null;
}
$obj = $this->parseIdentifier($var->var);
$propName = $this->identifierToStr($var->name, literal: true);
$target = $this->preparePropertyWriteTarget($var);
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
if ($target !== null && $target->isDynamicObjectProperty()) {
if ($isPre) {
$this->context->beforeStmtLines[] = "{$tmpVar} = " . $this->emitDynamicPropertyTargetRead($target) . " {$op} 1; " . $this->emitDynamicPropertyTargetWrite($target, $tmpVar) . ';';
} else {
$this->context->beforeStmtLines[] = "{$tmpVar} = " . $this->emitDynamicPropertyTargetRead($target) . ';';
$this->context->afterStmtLines[] = $this->emitDynamicPropertyTargetWrite($target, "{$tmpVar} {$op} 1") . ';';
}
return $tmpVar;
}
$obj = $this->parseIdentifier($var->var);
$propName = $this->identifierToStr($var->name, literal: true);
if ($isPre) {
$this->context->beforeStmtLines[] = "{$tmpVar} = " . $this->emitDynamicPropertyRead($obj, $propName) . " {$op} 1; " . $this->emitDynamicPropertyWrite($obj, $propName, $tmpVar) . ';';
} else {
@ -3674,14 +3685,14 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$this->fatalError($arg, 'Undefined variable `$' . $name . '`');
}
} elseif ($this->isPropertyFetch($arg->value) and $this->isVarExpr($arg->value->var)) {
$obj = $this->parseIdentifier($arg->value->var);
if (!$this->hasVar($obj)) {
$this->fatalError($arg, 'Undefined variable `$' . $obj . '`');
}
if ($byRef) {
$this->addPositionalCallArg($obj . '.attrRef(' . $this->identifierToStr($arg->value->name) . ')', $arrayArgsVar, $list_args);
$this->addPositionalCallArg($this->emitDynamicPropertyFetchRef($arg->value, $arg), $arrayArgsVar, $list_args);
continue;
}
$objectExpr = $this->parseIdentifier($arg->value->var);
if (!$this->hasVar($objectExpr)) {
$this->fatalError($arg, 'Undefined variable `$' . $objectExpr . '`');
}
} elseif ($this->isArrayDimFetch($arg->value) and $this->isVarExpr($arg->value->var)) {
$array = $this->parseIdentifier($arg->value->var);
if ($array === 'GLOBALS') {
@ -3815,11 +3826,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
}
if ($this->isPropertyFetch($arg->value) and $this->isVarExpr($arg->value->var)) {
$obj = $this->parseIdentifier($arg->value->var);
if (!$this->hasVar($obj)) {
$this->fatalError($arg, 'Undefined variable `$' . $obj . '`');
}
return $obj . '.attrRef(' . $this->identifierToStr($arg->value->name) . ')';
return $this->emitDynamicPropertyFetchRef($arg->value, $arg);
}
if ($this->isArrayDimFetch($arg->value) and $this->isVarExpr($arg->value->var)) {
@ -3856,11 +3863,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
protected function expandRefvalExpr(NodeAbstract $inner, Node\Arg $arg): ?string
{
if ($this->isPropertyFetch($inner) and $this->isVarExpr($inner->var)) {
$obj = $this->parseIdentifier($inner->var);
if (!$this->hasVar($obj)) {
$this->fatalError($arg, 'Undefined variable `$' . $obj . '`');
}
return $obj . '.attrRef(' . $this->identifierToStr($inner->name) . ')';
return $this->emitDynamicPropertyFetchRef($inner, $arg);
}
if ($this->isArrayDimFetch($inner) and $this->isVarExpr($inner->var)) {
$array = $this->parseIdentifier($inner->var);
@ -4762,10 +4765,16 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
protected function preparePropertyWriteTarget(NodeAbstract $left): ?PropertyWriteTarget
{
if ($left instanceof Expr\PropertyFetch) {
$objectExpr = null;
$propertyExpr = null;
if (!$this->isNativePropertyAccess($left) && $this->isVarExpr($left->var)) {
$objectExpr = $this->parseIdentifier($left->var);
$propertyExpr = $this->identifierToStr($left->name, literal: true);
}
if ($this->isIdExpr($left->name)) {
$this->getPropertyIdentifier($left, $left->var, $left->name);
}
return new PropertyWriteTarget($left, 'object property');
return new PropertyWriteTarget($left, 'object property', $objectExpr, $propertyExpr);
}
if ($left instanceof Expr\StaticPropertyFetch) {
@ -4927,7 +4936,10 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$lines[] = $array . '.offsetUnset(' . $dim . ');';
}
} elseif ($this->isPropertyFetch($var)) {
$object = $this->parseIdentifier($var->var);
$propertyWriteTarget = $this->preparePropertyWriteTarget($var);
$object = $propertyWriteTarget !== null && $propertyWriteTarget->isDynamicObjectProperty()
? $propertyWriteTarget->objectExpr
: $this->parseIdentifier($var->var);
$restoreDefault = null;
if ($this->isIdExpr($var->name)) {
$propertyId = $this->getPropertyIdentifier($var, $var->var, $var->name);
@ -4950,7 +4962,11 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
}
}
if ($restoreDefault === null) {
$lines[] = $object . '.unsetProperty(' . $this->identifierToStr($var->name, literal: true) . ');';
if ($propertyWriteTarget !== null && $propertyWriteTarget->isDynamicObjectProperty()) {
$lines[] = $this->emitDynamicPropertyTargetUnset($propertyWriteTarget) . ';';
} else {
$lines[] = $object . '.unsetProperty(' . $this->identifierToStr($var->name, literal: true) . ');';
}
}
} elseif ($this->isStaticPropertyFetch($var)) {
$this->fatalError($var, 'Attempt to unset static property ' . $this->parseIdentifier($var->class) . '::$' . $this->parseIdentifier($var->name));
@ -5557,6 +5573,57 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
return "{$object}.setProperty({$property}, {$value})";
}
protected function emitDynamicPropertyTargetRead(PropertyWriteTarget $target): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyRead($target->objectExpr, $target->propertyExpr);
}
protected function emitDynamicPropertyTargetWrite(PropertyWriteTarget $target, string $value): string
{
$this->assertDynamicPropertyTarget($target);
return $this->emitDynamicPropertyWrite($target->objectExpr, $target->propertyExpr, $value);
}
protected function emitDynamicPropertyTargetUnset(PropertyWriteTarget $target): string
{
$this->assertDynamicPropertyTarget($target);
return $target->objectExpr . '.unsetProperty(' . $target->propertyExpr . ')';
}
protected function emitDynamicPropertyTargetRef(PropertyWriteTarget $target): string
{
$this->assertDynamicPropertyTarget($target);
return $target->objectExpr . '.attrRef(' . $target->propertyExpr . ')';
}
protected function assertDynamicPropertyTarget(PropertyWriteTarget $target): void
{
if (!$target->isDynamicObjectProperty()) {
$this->fatalError($target->node, 'Internal error: property write target is not a dynamic object property');
}
}
protected function emitDynamicPropertyFetchRef(Expr\PropertyFetch $expr, NodeAbstract $errorNode): string
{
$target = $this->preparePropertyWriteTarget($expr);
$objectExpr = $target !== null && $target->isDynamicObjectProperty()
? $target->objectExpr
: $this->parseIdentifier($expr->var);
if (!$this->hasVar($objectExpr)) {
$this->fatalError($errorNode, 'Undefined variable `$' . $objectExpr . '`');
}
if ($target !== null && $target->isDynamicObjectProperty()) {
return $this->emitDynamicPropertyTargetRef($target);
}
return $objectExpr . '.attrRef(' . $this->identifierToStr($expr->name) . ')';
}
protected function getChainedFunc(string $op): string
{
return match ($op) {

@ -8,7 +8,7 @@
namespace PhpAot\Php\Parser;
use PhpAot\Php\Symbol;
use PhpAot\Php\Resolver\PropertyWriteTarget;
use PhpParser\Node;
use PhpParser\Node\ArrayItem;
use PhpParser\Node\Expr;
@ -44,16 +44,28 @@ trait AssignOpTrait
return $code . '((' . $tmp . ' = ' . $value . ', ' . "{$array}.offsetSet({$dim}, {$tmp})" . '), ' . $tmp . ')';
}
protected function parseAssignPropertyFetch(NodeAbstract $left, NodeAbstract $right): string
protected function parseAssignPropertyFetch(NodeAbstract $left, NodeAbstract $right, ?PropertyWriteTarget $target = null): string
{
$array = $this->parseIdentifier($left->var);
$propName = $this->identifierToStr($left->name, literal: true);
if ($target !== null) {
$this->assertCanAssignPropertyWrite($target, $right);
}
$rightExpr = $this->trimBrackets($this->parseExpr($right));
$rightExpr = $this->wrapObjectPropertyAssignTypeCheck($left, $right, $rightExpr);
if ($target !== null) {
$rightExpr = $this->wrapPropertyWriteTypeCheck($target, $right, $rightExpr);
} else {
$rightExpr = $this->wrapObjectPropertyAssignTypeCheck($left, $right, $rightExpr);
}
$tmp = $this->genTmpVarName();
$this->addLocalVar($tmp, self::TYPE_VAR);
// Comma expression: store RHS → execute side effect → evaluate to stored value
if ($target !== null && $target->isDynamicObjectProperty()) {
return '((' . $tmp . ' = ' . $rightExpr . ', ' . $this->emitDynamicPropertyTargetWrite($target, $tmp) . '), ' . $tmp . ')';
}
$array = $this->parseIdentifier($left->var);
$propName = $this->identifierToStr($left->name, literal: true);
return '((' . $tmp . ' = ' . $rightExpr . ', ' . $this->emitDynamicPropertyWrite($array, $propName, $tmp) . '), ' . $tmp . ')';
}
@ -82,18 +94,6 @@ trait AssignOpTrait
return implode(";\n" . $this->getIndent(), $list);
}
protected function parseAssignStaticProperty($left, $right): string
{
$value = $this->trimBrackets($this->parseExpr($right));
$native = $this->parseNativeStaticPropertyFetch($left);
if ($native !== null) {
return $native . ' = ' . $value;
}
$class = $this->identifierToStr($left->class);
$propName = $this->identifierToStr($left->name);
return Symbol::setStaticProperty() . "({$class}, {$propName}, {$value})";
}
protected function parseAssign(Expr\Assign $v): string
{
$left = $v->var;
@ -262,7 +262,7 @@ trait AssignOpTrait
}
}
} elseif ($this->isPropertyFetch($left) and !$this->isNativePropertyAccess($left)) {
return $this->parseAssignPropertyFetch($left, $right);
return $this->parseAssignPropertyFetch($left, $right, $propertyWriteTarget);
} elseif ($this->isArrayDimFetch($left) and $this->isVarExpr($left->var)) {
$tmp = $this->parseIdentifier($left->var);
if ($this->getVarType($tmp) === self::TYPE_STR and $left->dim === null) {
@ -401,12 +401,16 @@ trait AssignOpTrait
if ($propertyWriteTarget !== null) {
$this->assertCanAssignPropertyWrite($propertyWriteTarget, $node->expr);
}
$obj = $this->parseIdentifier($node->var->var);
$propName = $this->identifierToStr($node->var->name, literal: true);
$binaryOp = $this->removeAssignOp($op);
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$readProperty = $this->emitDynamicPropertyRead($obj, $propName);
if ($propertyWriteTarget !== null && $propertyWriteTarget->isDynamicObjectProperty()) {
$readProperty = $this->emitDynamicPropertyTargetRead($propertyWriteTarget);
} else {
$obj = $this->parseIdentifier($node->var->var);
$propName = $this->identifierToStr($node->var->name, literal: true);
$readProperty = $this->emitDynamicPropertyRead($obj, $propName);
}
if ($this->isAssignOpConcat($op)) {
$this->context->beforeStmtLines[] = "{$tmpVar} = php::concat({$readProperty}, {$expr});";
} elseif ($this->isAssignOpPow($op)) {
@ -414,7 +418,11 @@ trait AssignOpTrait
} else {
$this->context->beforeStmtLines[] = "{$tmpVar} = {$readProperty} {$binaryOp} ({$expr});";
}
$this->context->afterStmtLines[] = $this->emitDynamicPropertyWrite($obj, $propName, $tmpVar) . ';';
if ($propertyWriteTarget !== null && $propertyWriteTarget->isDynamicObjectProperty()) {
$this->context->afterStmtLines[] = $this->emitDynamicPropertyTargetWrite($propertyWriteTarget, $tmpVar) . ';';
} else {
$this->context->afterStmtLines[] = $this->emitDynamicPropertyWrite($obj, $propName, $tmpVar) . ';';
}
return $tmpVar;
}
@ -554,9 +562,14 @@ trait AssignOpTrait
$rightExpr = $tmpVar . ' = ' . $this->parseIdentifier($expr->expr) . '.toReference()';
} elseif ($this->isPropertyFetch($expr->expr)) {
$left = $this->parseIdentifier($expr->var);
$object = $this->parseExpr($expr->expr->var);
$prop = $this->identifierToStr($expr->expr->name);
$rightExpr = $tmpVar . ' = ' . $object . '.attrRef(' . $prop . ')';
$propertyWriteTarget = $this->preparePropertyWriteTarget($expr->expr);
if ($propertyWriteTarget !== null && $propertyWriteTarget->isDynamicObjectProperty()) {
$rightExpr = $tmpVar . ' = ' . $this->emitDynamicPropertyTargetRef($propertyWriteTarget);
} else {
$object = $this->parseExpr($expr->expr->var);
$prop = $this->identifierToStr($expr->expr->name);
$rightExpr = $tmpVar . ' = ' . $object . '.attrRef(' . $prop . ')';
}
} elseif ($this->isArrayDimFetch($expr->expr)) {
$left = $this->parseIdentifier($expr->var);
$array = $this->parseIdentifier($expr->expr->var);
@ -574,8 +587,14 @@ trait AssignOpTrait
protected function parseAssignPropertyArrayDim(NodeAbstract $left, NodeAbstract $right): string
{
$obj = $this->parseIdentifier($left->var->var);
$propName = $this->identifierToStr($left->var->name);
$propertyWriteTarget = $this->preparePropertyWriteTarget($left->var);
if ($propertyWriteTarget !== null && $propertyWriteTarget->isDynamicObjectProperty()) {
$obj = $propertyWriteTarget->objectExpr;
$propName = $propertyWriteTarget->propertyExpr;
} else {
$obj = $this->parseIdentifier($left->var->var);
$propName = $this->identifierToStr($left->var->name);
}
$code = '';
$value = $this->trimBrackets($this->parseExpr($right));

@ -15,6 +15,13 @@ final readonly class PropertyWriteTarget
public function __construct(
public NodeAbstract $node,
public string $label,
public ?string $objectExpr = null,
public ?string $propertyExpr = null,
) {
}
public function isDynamicObjectProperty(): bool
{
return $this->objectExpr !== null && $this->propertyExpr !== null;
}
}

Loading…
Cancel
Save