refactor(php): 重构PHP编译器代码结构和方法实现

- 在AnonClassGenerator中优化匿名类方法访问逻辑,移除闭包传递改为直接对象引用
- 将BinaryOpTrait中的语句行追加逻辑提取为统一的formatCapturedStmtLines方法
- 重构ClosureGenerator中的闭包生成逻辑,分离箭头函数和匿名闭包的处理方式
- 提取CompilerBase中的通用方法如formatCapturedStmtLines、formatTernaryReturn等
- 优化返回类型检查逻辑,将重复的临时变量分配代码封装为通用方法
- 在ResourceFileGenerator中将版本号处理从函数映射改为循环处理
- 重构Translator中的文件过滤和命令参数处理逻辑为显式循环
- 将方法重写兼容性检查中的错误抛出逻辑提取为专用方法
-
pull/4/head
韩天峰 2 months ago
parent 903af8c477
commit b13fdc60fa
  1. 323
      src/Php/CompilerBase.php
  2. 10
      src/Php/Generator/AnonClassGenerator.php
  3. 62
      src/Php/Generator/ClosureGenerator.php
  4. 6
      src/Php/Generator/ResourceFileGenerator.php
  5. 10
      src/Php/Parser/BinaryOpTrait.php
  6. 17
      src/Php/Platform/UnixPlatform.php
  7. 49
      src/Php/Translator.php

@ -1466,10 +1466,18 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
protected function appendCapturedStmtLines(string &$code, array $stmts): void protected function appendCapturedStmtLines(string &$code, array $stmts): void
{ {
if ($stmts) { if ($stmts) {
$code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts) . PHP_EOL; $code .= $this->formatCapturedStmtLines($stmts);
} }
} }
protected function formatCapturedStmtLines(array $stmts): string
{
if (!$stmts) {
return '';
}
return $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts) . PHP_EOL;
}
protected function genConditionWithCapturedStmts(NodeAbstract $cond, string $openPrefix): string protected function genConditionWithCapturedStmts(NodeAbstract $cond, string $openPrefix): string
{ {
$this->assertExprCanBeUsedAsCondition($cond); $this->assertExprCanBeUsedAsCondition($cond);
@ -1772,20 +1780,10 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
if ($v->expr === null) { if ($v->expr === null) {
if ($this->functionDef->returnType === self::TYPE_VOID and !$this->context->inClosure) { if ($this->functionDef->returnType === self::TYPE_VOID and !$this->context->inClosure) {
return 'return;'; return 'return;';
} elseif ($this->context->inClosure && $this->context->closureReturnTypeCheck) { } elseif ($this->shouldCheckClosureReturnType()) {
$tmpVar = $this->genTmpVarName(); return $this->genClosureCheckedReturn(self::VALUE_NULL);
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . self::VALUE_NULL . ';' . PHP_EOL;
$code .= $this->genClosureReturnCheck($tmpVar);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
return $code;
} elseif ($this->functionDef->returnTypeCheck && !$this->context->inClosure) { } elseif ($this->functionDef->returnTypeCheck && !$this->context->inClosure) {
$tmpVar = $this->genTmpVarName(); return $this->genUnionCheckedReturn(self::VALUE_NULL);
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . self::VALUE_NULL . ';' . PHP_EOL;
$code .= $this->genUnionReturnCheck($tmpVar);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
return $code;
} else { } else {
return 'return ' . self::VALUE_NULL . ';'; return 'return ' . self::VALUE_NULL . ';';
} }
@ -1832,17 +1830,11 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$exprCode = $this->convertExprType($expr, $returnType, $type); $exprCode = $this->convertExprType($expr, $returnType, $type);
// Union/nullable return type: always use tmpVar for runtime check // Union/nullable return type: always use tmpVar for runtime check
if ($this->context->inClosure && $this->context->closureReturnTypeCheck) { if ($this->shouldCheckClosureReturnType()) {
$tmpVar = $this->genTmpVarName(); [$code, $tmpVar] = $this->genClosureCheckedReturnAssignment($exprCode);
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . $exprCode . ';' . PHP_EOL;
$code .= $this->genClosureReturnCheck($tmpVar);
$this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $tmpVar . ';'; $this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $tmpVar . ';';
} elseif ($this->functionDef->returnTypeCheck && !$this->context->inClosure) { } elseif ($this->functionDef->returnTypeCheck && !$this->context->inClosure) {
$tmpVar = $this->genTmpVarName(); [$code, $tmpVar] = $this->genUnionCheckedReturnAssignment($exprCode);
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . $exprCode . ';' . PHP_EOL;
$code .= $this->genUnionReturnCheck($tmpVar);
$this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $tmpVar . ';'; $this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $tmpVar . ';';
} elseif (!$this->isVarExpr($v->expr) and !$this->isScalar($v->expr)) { } elseif (!$this->isVarExpr($v->expr) and !$this->isScalar($v->expr)) {
// return 如果使用了 Indirect 语句,可能会导致变量提前析构,出现悬空指针 // return 如果使用了 Indirect 语句,可能会导致变量提前析构,出现悬空指针
@ -1860,6 +1852,57 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
return $code; return $code;
} }
protected function genClosureCheckedReturn(string $exprCode): string
{
[$code, $tmpVar] = $this->genClosureCheckedReturnAssignment($exprCode);
return $code . $this->getIndent() . 'return ' . $tmpVar . ';';
}
protected function genClosureReturnValue(string $exprCode): string
{
if ($this->context->closureReturnTypeCheck) {
return $this->genClosureCheckedReturn($exprCode);
}
return 'return ' . $exprCode . ';';
}
protected function genClosureReturnNull(): string
{
return $this->genClosureReturnValue(self::VALUE_NULL);
}
protected function genUnionCheckedReturn(string $exprCode): string
{
[$code, $tmpVar] = $this->genUnionCheckedReturnAssignment($exprCode);
return $code . $this->getIndent() . 'return ' . $tmpVar . ';';
}
protected function genClosureCheckedReturnAssignment(string $exprCode): array
{
return $this->genCheckedReturnAssignment($exprCode, true);
}
protected function genUnionCheckedReturnAssignment(string $exprCode): array
{
return $this->genCheckedReturnAssignment($exprCode, false);
}
protected function genCheckedReturnAssignment(string $exprCode, bool $closure): array
{
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . $exprCode . ';' . PHP_EOL;
$code .= $closure ? $this->genClosureReturnCheck($tmpVar) : $this->genUnionReturnCheck($tmpVar);
return [$code, $tmpVar];
}
protected function shouldCheckClosureReturnType(): bool
{
return $this->context->inClosure && $this->context->closureReturnTypeCheck;
}
protected function addLocalVar(string $name, string $type): void protected function addLocalVar(string $name, string $type): void
{ {
$this->context->localVars[$name] = $type; $this->context->localVars[$name] = $type;
@ -4000,36 +4043,18 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$else = 'php::Var(' . $else . ')'; $else = 'php::Var(' . $else . ')';
} }
if ($hasBranchStmts) { if ($hasBranchStmts) {
$appendStmtLines = function (array $stmts): string {
if (!$stmts) {
return '';
}
return $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts) . PHP_EOL;
};
$appendReturn = function (string $value, array $beforeStmts, array $afterStmts) use ($appendStmtLines): string {
$code = $appendStmtLines($beforeStmts);
if ($afterStmts) {
$tmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . "{$tmpVar} = {$value};";
$code .= $appendStmtLines($afterStmts);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
} else {
$code .= $this->getIndent() . 'return php::Var(' . $value . ');';
}
return $code;
};
$code = '[&]() -> ' . self::TYPE_VAR . '{'; $code = '[&]() -> ' . self::TYPE_VAR . '{';
$code .= $appendStmtLines($condBeforeStmts); $code .= $this->formatCapturedStmtLines($condBeforeStmts);
if ($condAfterStmts) { if ($condAfterStmts) {
$condTmpVar = $this->addTmpVar(self::TYPE_VAR); $condTmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . "{$condTmpVar} = {$cond};"; $code .= $this->getIndent() . "{$condTmpVar} = {$cond};";
$code .= $appendStmtLines($condAfterStmts); $code .= $this->formatCapturedStmtLines($condAfterStmts);
$cond = $condTmpVar; $cond = $condTmpVar;
} }
$code .= $this->getIndent() . 'if (' . $cond . ') {'; $code .= $this->getIndent() . 'if (' . $cond . ') {';
$code .= $appendReturn($if, $ifBeforeStmts, $ifAfterStmts); $code .= $this->formatTernaryReturn($if, $ifBeforeStmts, $ifAfterStmts);
$code .= $this->getIndent() . '} else {'; $code .= $this->getIndent() . '} else {';
$code .= $appendReturn($else, $elseBeforeStmts, $elseAfterStmts); $code .= $this->formatTernaryReturn($else, $elseBeforeStmts, $elseAfterStmts);
$code .= $this->getIndent() . '}'; $code .= $this->getIndent() . '}';
$code .= $this->getIndent() . '}()'; $code .= $this->getIndent() . '}()';
return $code; return $code;
@ -4037,6 +4062,20 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
return '(' . $cond . ') ? (' . $if . ') : (' . $else . ')'; return '(' . $cond . ') ? (' . $if . ') : (' . $else . ')';
} }
protected function formatTernaryReturn(string $value, array $beforeStmts, array $afterStmts): string
{
$code = $this->formatCapturedStmtLines($beforeStmts);
if ($afterStmts) {
$tmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . "{$tmpVar} = {$value};";
$code .= $this->formatCapturedStmtLines($afterStmts);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
} else {
$code .= $this->getIndent() . 'return php::Var(' . $value . ');';
}
return $code;
}
protected function parseMatch(Expr\Match_ $expr): string protected function parseMatch(Expr\Match_ $expr): string
{ {
$this->assertExprCanBeUsedAsValue($expr->cond, 'match condition'); $this->assertExprCanBeUsedAsValue($expr->cond, 'match condition');
@ -4051,39 +4090,6 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$var = $tmpVar; $var = $tmpVar;
} }
$parseExprWithStmts = function (NodeAbstract $node): array {
$beforeStmtCount = count($this->context->beforeStmtLines);
$afterStmtCount = count($this->context->afterStmtLines);
$value = $this->parseExpr($node);
$beforeStmts = array_slice($this->context->beforeStmtLines, $beforeStmtCount);
$afterStmts = array_slice($this->context->afterStmtLines, $afterStmtCount);
$this->context->beforeStmtLines = array_slice($this->context->beforeStmtLines, 0, $beforeStmtCount);
$this->context->afterStmtLines = array_slice($this->context->afterStmtLines, 0, $afterStmtCount);
return [$value, $beforeStmts, $afterStmts];
};
$appendStmtLines = function (array $stmts): string {
if (!$stmts) {
return '';
}
return $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts) . PHP_EOL;
};
$appendMatchReturn = function (NodeAbstract $body) use ($parseExprWithStmts, $appendStmtLines): string {
$this->assertExprCanBeUsedAsValue($body, 'match arm');
[$value, $beforeStmts, $afterStmts] = $parseExprWithStmts($body);
$code = $appendStmtLines($beforeStmts);
if ($afterStmts) {
$tmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . "{$tmpVar} = {$value};";
$code .= $appendStmtLines($afterStmts);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
} else {
$code .= $this->getIndent() . 'return ' . $value . ';';
}
return $code;
};
$code = '[&]() -> ' . self::TYPE_VAR . '{'; $code = '[&]() -> ' . self::TYPE_VAR . '{';
$default = null; $default = null;
foreach ($expr->arms as $arm) { foreach ($expr->arms as $arm) {
@ -4098,26 +4104,26 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$this->fatalError($arm, 'Match expression cannot be used as a condition'); $this->fatalError($arm, 'Match expression cannot be used as a condition');
} }
$this->assertExprCanBeUsedAsValue($cond, 'match arm condition'); $this->assertExprCanBeUsedAsValue($cond, 'match arm condition');
[$condValue, $beforeStmts, $afterStmts] = $parseExprWithStmts($cond); [$condValue, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($cond);
$code .= $this->getIndent() . 'if (!' . $matched . ') {'; $code .= $this->getIndent() . 'if (!' . $matched . ') {';
$code .= $appendStmtLines($beforeStmts); $code .= $this->formatCapturedStmtLines($beforeStmts);
if ($afterStmts) { if ($afterStmts) {
$condTmpVar = $this->addTmpVar(self::TYPE_VAR); $condTmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . "{$condTmpVar} = {$condValue};"; $code .= $this->getIndent() . "{$condTmpVar} = {$condValue};";
$code .= $appendStmtLines($afterStmts); $code .= $this->formatCapturedStmtLines($afterStmts);
$condValue = $condTmpVar; $condValue = $condTmpVar;
} }
$code .= $this->getIndent() . $matched . ' = php::same(' . $var . ', ' . $condValue . ');'; $code .= $this->getIndent() . $matched . ' = php::same(' . $var . ', ' . $condValue . ');';
$code .= $this->getIndent() . '}'; $code .= $this->getIndent() . '}';
} }
$code .= $this->getIndent() . 'if (' . $matched . ') {'; $code .= $this->getIndent() . 'if (' . $matched . ') {';
$code .= $appendMatchReturn($arm->body); $code .= $this->formatMatchReturn($arm->body);
$code .= $this->getIndent() . '}'; $code .= $this->getIndent() . '}';
} }
if ($default) { if ($default) {
$code .= $this->getIndent() . '{'; $code .= $this->getIndent() . '{';
$code .= $appendMatchReturn($default); $code .= $this->formatMatchReturn($default);
$code .= $this->getIndent() . '}'; $code .= $this->getIndent() . '}';
} else { } else {
$code .= $this->getIndent() . '{ return php::throwException("UnhandledMatchError", "Unhandled match case"); }'; $code .= $this->getIndent() . '{ return php::throwException("UnhandledMatchError", "Unhandled match case"); }';
@ -4127,6 +4133,22 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
return $code; return $code;
} }
protected function formatMatchReturn(NodeAbstract $body): string
{
$this->assertExprCanBeUsedAsValue($body, 'match arm');
[$value, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($body);
$code = $this->formatCapturedStmtLines($beforeStmts);
if ($afterStmts) {
$tmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . "{$tmpVar} = {$value};";
$code .= $this->formatCapturedStmtLines($afterStmts);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
} else {
$code .= $this->getIndent() . 'return ' . $value . ';';
}
return $code;
}
protected function parsePreDec(Expr\PreDec $expr): string protected function parsePreDec(Expr\PreDec $expr): string
{ {
$this->assertNotNullsafeWriteContext($expr->var); $this->assertNotNullsafeWriteContext($expr->var);
@ -4166,27 +4188,28 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$arms[] = [$elseif->cond, $elseif->stmts]; $arms[] = [$elseif->cond, $elseif->stmts];
} }
$emitIfChain = function (int $index) use (&$emitIfChain, $arms, $v): string { return $this->parseBeforeStmtLines() . PHP_EOL . $this->parseIfChain($arms, $v->else, 0) . PHP_EOL;
if (!isset($arms[$index])) { }
if (!$v->else) {
return '';
}
return $this->parseBlockStmts($v->else->stmts);
}
[$cond, $stmts] = $arms[$index]; protected function parseIfChain(array $arms, ?Node\Stmt\Else_ $else, int $index): string
$code = $this->genConditionWithCapturedStmts($cond, 'if '); {
$code .= $this->parseBlockStmts($stmts); if (!isset($arms[$index])) {
$tail = $emitIfChain($index + 1); if (!$else) {
if ($tail !== '') { return '';
$code .= $this->getIndent() . '} else {' . PHP_EOL;
$code .= $tail;
} }
$code .= $this->getIndent() . '}'; return $this->parseBlockStmts($else->stmts);
return $code; }
};
return $this->parseBeforeStmtLines() . PHP_EOL . $emitIfChain(0) . PHP_EOL; [$cond, $stmts] = $arms[$index];
$code = $this->genConditionWithCapturedStmts($cond, 'if ');
$code .= $this->parseBlockStmts($stmts);
$tail = $this->parseIfChain($arms, $else, $index + 1);
if ($tail !== '') {
$code .= $this->getIndent() . '} else {' . PHP_EOL;
$code .= $tail;
}
$code .= $this->getIndent() . '}';
return $code;
} }
/** /**
@ -5355,9 +5378,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$initCode .= $this->getIndent() . "if (!{$initState}) { \n"; $initCode .= $this->getIndent() . "if (!{$initState}) { \n";
$this->indentLevel++; $this->indentLevel++;
$initCode .= $this->getIndent() . "{$initState} = true;\n"; $initCode .= $this->getIndent() . "{$initState} = true;\n";
$initCode .= $this->genLambdaCall(function () use ($var, $varName) { $initCode .= $this->genStaticVarInitLambda($var, $varName);
return $this->getIndent() . $varName . ' = ' . $this->parseExpr($var->default) . ';';
});
$this->indentLevel--; $this->indentLevel--;
$initCode .= $this->getIndent() . '}'; $initCode .= $this->getIndent() . '}';
$list[] = $initCode; $list[] = $initCode;
@ -5367,6 +5388,26 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
return implode(PHP_EOL . $this->getIndent(), $list); return implode(PHP_EOL . $this->getIndent(), $list);
} }
protected function genStaticVarInitLambda(Node\Stmt\StaticVar $var, string $varName): string
{
$oriCtx = $this->context;
$this->context = new FunctionContext();
$this->context->arguments = $oriCtx->localVars;
$code = '([&](){' . PHP_EOL;
$body = $this->getIndent() . $varName . ' = ' . $this->parseExpr($var->default) . ';';
$code .= $this->genScopeVarDecl();
$code .= $this->parseBeforeStmtLines();
$code .= $body;
$code .= $this->parseAfterStmtLines();
$code .= '})();' . PHP_EOL;
$this->context = $oriCtx;
return $code;
}
protected function parseEnum(Node\Stmt\Enum_ $v): string protected function parseEnum(Node\Stmt\Enum_ $v): string
{ {
return 'php::eval("' . $this->escapeString($this->genEmbeddedCode($v)) . '");'; return 'php::eval("' . $this->escapeString($this->genEmbeddedCode($v)) . '");';
@ -6883,24 +6924,14 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
protected function genReturnCode(): string protected function genReturnCode(): string
{ {
if ($this->context->inClosure && $this->context->closureReturnTypeCheck) { if ($this->shouldCheckClosureReturnType()) {
$tmpVar = $this->genTmpVarName(); return $this->genClosureCheckedReturn(self::VALUE_NULL);
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . self::VALUE_NULL . ';' . PHP_EOL;
$code .= $this->genClosureReturnCheck($tmpVar);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
return $code;
} }
if ($this->functionDef->returnType === self::TYPE_VOID) { if ($this->functionDef->returnType === self::TYPE_VOID) {
return ''; return '';
} }
if ($this->functionDef->returnTypeCheck && !$this->context->inClosure) { if ($this->functionDef->returnTypeCheck && !$this->context->inClosure) {
$tmpVar = $this->genTmpVarName(); return $this->genUnionCheckedReturn(self::VALUE_NULL);
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . self::VALUE_NULL . ';' . PHP_EOL;
$code .= $this->genUnionReturnCheck($tmpVar);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
return $code;
} }
if ($this->functionDef->returnType === self::TYPE_INT if ($this->functionDef->returnType === self::TYPE_INT
or $this->functionDef->returnType === self::TYPE_FLOAT or $this->functionDef->returnType === self::TYPE_FLOAT
@ -6939,66 +6970,12 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
} }
$uses = array_values($uses); $uses = array_values($uses);
$cb = function () use ($expr) { return $this->genClosure($expr, $expr->params, $uses);
$code = $this->parseExpr($expr->expr);
if ($this->context->beforeStmtLines) {
$beforeCode = implode(PHP_EOL, $this->context->beforeStmtLines);
} else {
$beforeCode = '';
}
if ($this->isCallExpr($expr->expr)) {
$nativeCall = $expr->expr->getAttribute('nativeCall');
if ($nativeCall and $this->getFunction($nativeCall)->returnType === self::TYPE_VOID) {
if ($this->context->closureReturnTypeCheck) {
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
return $beforeCode . PHP_EOL . $code . ';' . PHP_EOL
. $tmpVar . ' = ' . self::VALUE_NULL . ';' . PHP_EOL
. $this->genClosureReturnCheck($tmpVar)
. $this->getIndent() . 'return ' . $tmpVar . ';';
}
return $beforeCode . PHP_EOL . $code . ';' . PHP_EOL . 'return ' . self::VALUE_NULL . ';';
}
}
if ($this->detectTypeOfExpr($expr->expr) === self::TYPE_VOID) {
if ($this->context->closureReturnTypeCheck) {
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
return $beforeCode . PHP_EOL . $code . ';' . PHP_EOL
. $tmpVar . ' = ' . self::VALUE_NULL . ';' . PHP_EOL
. $this->genClosureReturnCheck($tmpVar)
. $this->getIndent() . 'return ' . $tmpVar . ';';
}
return $beforeCode . PHP_EOL . $code . ';' . PHP_EOL . 'return ' . self::VALUE_NULL . ';';
}
if ($this->context->closureReturnTypeCheck) {
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
return $beforeCode . PHP_EOL
. $tmpVar . ' = ' . $code . ';' . PHP_EOL
. $this->genClosureReturnCheck($tmpVar)
. $this->getIndent() . 'return ' . $tmpVar . ';';
}
return $beforeCode . PHP_EOL . 'return ' . $code . ';';
};
return $this->genClosure($expr, $expr->params, $cb, $uses);
} }
protected function parseClosure(Expr\Closure $expr): string protected function parseClosure(Expr\Closure $expr): string
{ {
$cb = function () use ($expr) { return $this->genClosure($expr, $expr->params, $expr->uses);
$fnCode = $this->parseStmts($expr->stmts);
if (!$this->isReturnStmtInLastLine($expr->stmts)) {
if ($this->context->closureReturnTypeCheck) {
$fnCode .= $this->genReturnCode() . PHP_EOL;
} else {
$fnCode .= 'return ' . self::VALUE_NULL . ';' . PHP_EOL;
}
}
return $fnCode;
};
return $this->genClosure($expr, $expr->params, $cb, $expr->uses);
} }
protected function isReturnStmtInLastLine(array $stmts): bool protected function isReturnStmtInLastLine(array $stmts): bool

@ -93,14 +93,12 @@ trait AnonClassGenerator
{ {
if ($stmt instanceof Class_) { if ($stmt instanceof Class_) {
$stmt = clone $stmt; $stmt = clone $stmt;
$shouldAddMixedReturn = fn (Class_ $class, ClassMethod $method): bool =>
$this->shouldAddMixedReturnToEmbeddedClassMethod($class, $method);
$traverser = new NodeTraverser(); $traverser = new NodeTraverser();
$traverser->addVisitor(new class($shouldAddMixedReturn) extends NodeVisitorAbstract { $traverser->addVisitor(new class($this) extends NodeVisitorAbstract {
/** @var list<Class_> */ /** @var list<Class_> */
private array $classStack = []; private array $classStack = [];
public function __construct(private \Closure $shouldAddMixedReturn) public function __construct(private object $compiler)
{ {
} }
@ -113,7 +111,7 @@ trait AnonClassGenerator
if ($node instanceof ClassMethod && $node->returnType === null) { if ($node instanceof ClassMethod && $node->returnType === null) {
$class = $this->classStack[count($this->classStack) - 1] ?? null; $class = $this->classStack[count($this->classStack) - 1] ?? null;
if ($class !== null && ($this->shouldAddMixedReturn)($class, $node)) { if ($class !== null && $this->compiler->shouldAddMixedReturnToEmbeddedClassMethod($class, $node)) {
$node->returnType = new Identifier('mixed'); $node->returnType = new Identifier('mixed');
} }
} }
@ -133,7 +131,7 @@ trait AnonClassGenerator
return $this->printer->prettyPrint([$stmt]); return $this->printer->prettyPrint([$stmt]);
} }
protected function shouldAddMixedReturnToEmbeddedClassMethod(Class_ $class, ClassMethod $method): bool public function shouldAddMixedReturnToEmbeddedClassMethod(Class_ $class, ClassMethod $method): bool
{ {
$methodName = strtolower($method->name->toString()); $methodName = strtolower($method->name->toString());
if ($this->isEmbeddedMagicMethodReturnSensitive($methodName)) { if ($this->isEmbeddedMagicMethodReturnSensitive($methodName)) {

@ -11,6 +11,7 @@ namespace PhpAot\Php\Generator;
use PhpAot\Php\ArgInfo; use PhpAot\Php\ArgInfo;
use PhpAot\Php\Context\FunctionContext; use PhpAot\Php\Context\FunctionContext;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\IntersectionType; use PhpParser\Node\IntersectionType;
use PhpParser\Node\NullableType; use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType; use PhpParser\Node\UnionType;
@ -26,7 +27,7 @@ trait ClosureGenerator
return $code; return $code;
} }
protected function genClosure(NodeAbstract $expr, array $params, callable $bodyGenCb, array $uses = []): string protected function genClosure(Expr\ArrowFunction|Expr\Closure $expr, array $params, array $uses = []): string
{ {
$tmpVar = $this->genTmpVarName(); $tmpVar = $this->genTmpVarName();
@ -106,7 +107,7 @@ trait ClosureGenerator
$this->addArgument('this_', self::TYPE_OBJECT); $this->addArgument('this_', self::TYPE_OBJECT);
} }
$body = $bodyGenCb(); $body = $this->genClosureBody($expr);
$code .= $this->genScopeVarDecl() . $body; $code .= $this->genScopeVarDecl() . $body;
$this->indentLevel--; $this->indentLevel--;
@ -143,27 +144,50 @@ trait ClosureGenerator
} }
} }
protected function genLambdaCall(callable $cb): string protected function genClosureBody(NodeAbstract $expr): string
{ {
$code = ''; if ($expr instanceof Node\Expr\ArrowFunction) {
$oriCtx = $this->context; return $this->genArrowFunctionBody($expr);
}
// 使用 lambda 函数来对 static 变量进行赋值 if ($expr instanceof Node\Expr\Closure) {
$this->context = new FunctionContext(); return $this->genAnonymousClosureBody($expr);
// C++ lambda 使用 & 捕获了当前函数的所有局部变量,可直接使用,不需要再声明,将其作为 arguments 来处理,隐式使用 }
$this->context->arguments = $oriCtx->localVars; $this->fatalError($expr, 'Unsupported closure expression');
}
$code .= '([&](){' . PHP_EOL; protected function genArrowFunctionBody(Node\Expr\ArrowFunction $expr): string
$body = $cb(); {
$code .= $this->genScopeVarDecl(); $code = $this->parseExpr($expr->expr);
$code .= $this->parseBeforeStmtLines(); if ($this->context->beforeStmtLines) {
$code .= $body; $beforeCode = implode(PHP_EOL, $this->context->beforeStmtLines);
$code .= $this->parseAfterStmtLines(); } else {
$code .= '})();' . PHP_EOL; $beforeCode = '';
}
if ($this->isCallExpr($expr->expr)) {
$nativeCall = $expr->expr->getAttribute('nativeCall');
if ($nativeCall and $this->getFunction($nativeCall)->returnType === self::TYPE_VOID) {
return $this->genArrowFunctionVoidReturn($beforeCode, $code);
}
}
if ($this->detectTypeOfExpr($expr->expr) === self::TYPE_VOID) {
return $this->genArrowFunctionVoidReturn($beforeCode, $code);
}
return $beforeCode . PHP_EOL . $this->genClosureReturnValue($code);
}
$this->context = $oriCtx; protected function genArrowFunctionVoidReturn(string $beforeCode, string $exprCode): string
{
$code = $beforeCode . PHP_EOL . $exprCode . ';' . PHP_EOL;
return $code . $this->genClosureReturnNull();
}
return $code; protected function genAnonymousClosureBody(Node\Expr\Closure $expr): string
{
$fnCode = $this->parseStmts($expr->stmts);
if (!$this->isReturnStmtInLastLine($expr->stmts)) {
$fnCode .= $this->genClosureReturnNull() . PHP_EOL;
}
return $fnCode;
} }
private function genClosureParamTypeCheck(Node\Param $param, string $var, string $phpName, int $index, bool $variadic): string private function genClosureParamTypeCheck(Node\Param $param, string $var, string $phpName, int $index, bool $variadic): string

@ -202,9 +202,9 @@ class ResourceFileGenerator
// 用点号分隔 // 用点号分隔
$parts = explode('.', $version); $parts = explode('.', $version);
// 确保每个部分都是数字(过滤掉非数字字符) // 确保每个部分都是数字(过滤掉非数字字符)
$parts = array_map(function ($p) { foreach ($parts as $i => $part) {
return preg_replace('/[^0-9]/', '', $p) ?: '0'; $parts[$i] = preg_replace('/[^0-9]/', '', $part) ?: '0';
}, $parts); }
// 确保恰好有4个部分 // 确保恰好有4个部分
while (count($parts) < 4) { while (count($parts) < 4) {

@ -427,22 +427,16 @@ trait BinaryOpTrait
return '(' . $leftBool . ' ' . $op . ' ' . $this->convertBoolExpr((string) $rightExpr) . ')'; return '(' . $leftBool . ' ' . $op . ' ' . $this->convertBoolExpr((string) $rightExpr) . ')';
} }
$appendStmtLines = function (array $stmts): string {
if (!$stmts) {
return '';
}
return $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $stmts) . PHP_EOL;
};
$shortCircuitValue = $op === '&&' ? 'false' : 'true'; $shortCircuitValue = $op === '&&' ? 'false' : 'true';
$rightCondition = $op === '&&' ? $leftBool : '!(' . $leftBool . ')'; $rightCondition = $op === '&&' ? $leftBool : '!(' . $leftBool . ')';
$code = '[&]() -> bool {'; $code = '[&]() -> bool {';
$code .= $this->getIndent() . 'if (' . $rightCondition . ') {'; $code .= $this->getIndent() . 'if (' . $rightCondition . ') {';
$code .= $appendStmtLines($rightBeforeStmts); $code .= $this->formatCapturedStmtLines($rightBeforeStmts);
if ($rightAfterStmts) { if ($rightAfterStmts) {
$rightTmpVar = $this->addTmpVar(self::TYPE_VAR); $rightTmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . $rightTmpVar . ' = ' . $rightExpr . ';'; $code .= $this->getIndent() . $rightTmpVar . ' = ' . $rightExpr . ';';
$code .= $appendStmtLines($rightAfterStmts); $code .= $this->formatCapturedStmtLines($rightAfterStmts);
$rightExpr = $rightTmpVar; $rightExpr = $rightTmpVar;
} }
$code .= $this->getIndent() . 'return ' . $this->convertBoolExpr((string) $rightExpr) . ';'; $code .= $this->getIndent() . 'return ' . $this->convertBoolExpr((string) $rightExpr) . ';';

@ -131,7 +131,13 @@ abstract class UnixPlatform extends PlatformBase
if ($includes) { if ($includes) {
preg_match_all('/-I([^\s]+)/', $includes, $matches); preg_match_all('/-I([^\s]+)/', $includes, $matches);
if (!empty($matches[1])) { if (!empty($matches[1])) {
return array_filter($matches[1], 'is_dir'); $includePaths = [];
foreach ($matches[1] as $path) {
if (is_dir($path)) {
$includePaths[] = $path;
}
}
return $includePaths;
} }
} }
} }
@ -144,7 +150,14 @@ abstract class UnixPlatform extends PlatformBase
$phpDir . '/include/php/ext', $phpDir . '/include/php/ext',
]; ];
return array_filter($paths, 'is_dir'); $includePaths = [];
foreach ($paths as $path) {
if (is_dir($path)) {
$includePaths[] = $path;
}
}
return $includePaths;
} }
/** /**

@ -669,7 +669,14 @@ class Translator extends Preprocessor
return $files; return $files;
} }
return array_values(array_filter($files, fn(string $file): bool => !$this->shouldIgnoreFile($file))); $filteredFiles = [];
foreach ($files as $file) {
if (!$this->shouldIgnoreFile($file)) {
$filteredFiles[] = $file;
}
}
return $filteredFiles;
} }
public function convert(array $files): array public function convert(array $files): array
@ -1682,7 +1689,11 @@ CODE;
$targetArgs = $this->getTargetArgs(); $targetArgs = $this->getTargetArgs();
$command = escapeshellcmd($targetFile); $command = escapeshellcmd($targetFile);
if (!empty($targetArgs)) { if (!empty($targetArgs)) {
$command .= ' ' . implode(' ', array_map('escapeshellarg', $targetArgs)); $escapedArgs = [];
foreach ($targetArgs as $targetArg) {
$escapedArgs[] = escapeshellarg($targetArg);
}
$command .= ' ' . implode(' ', $escapedArgs);
} }
fwrite(STDERR, "Running: {$command}\n"); fwrite(STDERR, "Running: {$command}\n");
@ -3225,20 +3236,15 @@ CODE;
string $parentClass string $parentClass
): void { ): void {
$className = $this->getFullClassName(); $className = $this->getFullClassName();
$error = function (string $detail) use ($v, $className, $methodName, $parentClass) {
$this->fatalError($v,
"Declaration of `{$className}::{$methodName}()` must be compatible " .
"with `{$parentClass}::{$methodName}()`");
};
// PHP allows widening visibility in overrides (e.g. protected -> public), // PHP allows widening visibility in overrides (e.g. protected -> public),
// but forbids narrowing it. // but forbids narrowing it.
if ($this->getVisibilityRank($childMethodDef->flags) < $this->getVisibilityRank($parentMethodDef->flags)) { if ($this->getVisibilityRank($childMethodDef->flags) < $this->getVisibilityRank($parentMethodDef->flags)) {
$error('visibility mismatch'); $this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
} }
if (($childMethodDef->flags & Modifiers::STATIC) !== ($parentMethodDef->flags & Modifiers::STATIC)) { if (($childMethodDef->flags & Modifiers::STATIC) !== ($parentMethodDef->flags & Modifiers::STATIC)) {
$error('static mismatch'); $this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
} }
$childFuncDef = $childMethodDef->functionDef; $childFuncDef = $childMethodDef->functionDef;
@ -3248,29 +3254,29 @@ CODE;
} }
if (!$this->isReturnTypeOverrideCompatible($childFuncDef, $parentFuncDef)) { if (!$this->isReturnTypeOverrideCompatible($childFuncDef, $parentFuncDef)) {
$error('return type mismatch'); $this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
} }
// Child methods may add optional trailing parameters, but they cannot // Child methods may add optional trailing parameters, but they cannot
// require more arguments than the parent contract. // require more arguments than the parent contract.
if ($childFuncDef->argCountRequired > $parentFuncDef->argCountRequired) { if ($childFuncDef->argCountRequired > $parentFuncDef->argCountRequired) {
$error('required parameter count mismatch'); $this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
} }
// Compare each parent-declared parameter position. // Compare each parent-declared parameter position.
foreach ($parentFuncDef->argInfoList as $i => $parentArg) { foreach ($parentFuncDef->argInfoList as $i => $parentArg) {
if (!isset($childFuncDef->argInfoList[$i])) { if (!isset($childFuncDef->argInfoList[$i])) {
$error("missing parameter #{$i}"); $this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
} }
$childArg = $childFuncDef->argInfoList[$i]; $childArg = $childFuncDef->argInfoList[$i];
if (!$this->isParameterTypeOverrideCompatible($childArg, $parentArg)) { if (!$this->isParameterTypeOverrideCompatible($childArg, $parentArg)) {
$error("parameter #{$i} type mismatch"); $this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
} }
if ($childArg->byRef !== $parentArg->byRef) { if ($childArg->byRef !== $parentArg->byRef) {
$error("parameter #{$i} by-reference mismatch"); $this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
} }
if ($childArg->variadic !== $parentArg->variadic) { if ($childArg->variadic !== $parentArg->variadic) {
$error("parameter #{$i} variadic mismatch"); $this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
} }
} }
@ -3278,11 +3284,22 @@ CODE;
for ($i = count($parentFuncDef->argInfoList); $i < count($childFuncDef->argInfoList); $i++) { for ($i = count($parentFuncDef->argInfoList); $i < count($childFuncDef->argInfoList); $i++) {
$childArg = $childFuncDef->argInfoList[$i]; $childArg = $childFuncDef->argInfoList[$i];
if (!$childArg->variadic && $childArg->defaultValue === null) { if (!$childArg->variadic && $childArg->defaultValue === null) {
$error("extra required parameter #{$i}"); $this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
} }
} }
} }
private function fatalMethodOverrideIncompatible(
NodeAbstract $v,
string $className,
string $methodName,
string $parentClass
): void {
$this->fatalError($v,
"Declaration of `{$className}::{$methodName}()` must be compatible " .
"with `{$parentClass}::{$methodName}()`");
}
private function isReturnTypeOverrideCompatible(FunctionDef $childFuncDef, FunctionDef $parentFuncDef): bool private function isReturnTypeOverrideCompatible(FunctionDef $childFuncDef, FunctionDef $parentFuncDef): bool
{ {
if ($parentFuncDef->returnTypeUndeclared) { if ($parentFuncDef->returnTypeUndeclared) {

Loading…
Cancel
Save