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
{
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
{
$this->assertExprCanBeUsedAsCondition($cond);
@ -1772,20 +1780,10 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
if ($v->expr === null) {
if ($this->functionDef->returnType === self::TYPE_VOID and !$this->context->inClosure) {
return 'return;';
} elseif ($this->context->inClosure && $this->context->closureReturnTypeCheck) {
$tmpVar = $this->genTmpVarName();
$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->shouldCheckClosureReturnType()) {
return $this->genClosureCheckedReturn(self::VALUE_NULL);
} elseif ($this->functionDef->returnTypeCheck && !$this->context->inClosure) {
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . self::VALUE_NULL . ';' . PHP_EOL;
$code .= $this->genUnionReturnCheck($tmpVar);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
return $code;
return $this->genUnionCheckedReturn(self::VALUE_NULL);
} else {
return 'return ' . self::VALUE_NULL . ';';
}
@ -1832,17 +1830,11 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$exprCode = $this->convertExprType($expr, $returnType, $type);
// Union/nullable return type: always use tmpVar for runtime check
if ($this->context->inClosure && $this->context->closureReturnTypeCheck) {
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . $exprCode . ';' . PHP_EOL;
$code .= $this->genClosureReturnCheck($tmpVar);
if ($this->shouldCheckClosureReturnType()) {
[$code, $tmpVar] = $this->genClosureCheckedReturnAssignment($exprCode);
$this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $tmpVar . ';';
} elseif ($this->functionDef->returnTypeCheck && !$this->context->inClosure) {
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . $exprCode . ';' . PHP_EOL;
$code .= $this->genUnionReturnCheck($tmpVar);
[$code, $tmpVar] = $this->genUnionCheckedReturnAssignment($exprCode);
$this->context->afterStmtLines[] = $this->getIndent() . 'return ' . $tmpVar . ';';
} elseif (!$this->isVarExpr($v->expr) and !$this->isScalar($v->expr)) {
// return 如果使用了 Indirect 语句,可能会导致变量提前析构,出现悬空指针
@ -1860,6 +1852,57 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
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
{
$this->context->localVars[$name] = $type;
@ -4000,36 +4043,18 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$else = 'php::Var(' . $else . ')';
}
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 .= $appendStmtLines($condBeforeStmts);
$code .= $this->formatCapturedStmtLines($condBeforeStmts);
if ($condAfterStmts) {
$condTmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . "{$condTmpVar} = {$cond};";
$code .= $appendStmtLines($condAfterStmts);
$code .= $this->formatCapturedStmtLines($condAfterStmts);
$cond = $condTmpVar;
}
$code .= $this->getIndent() . 'if (' . $cond . ') {';
$code .= $appendReturn($if, $ifBeforeStmts, $ifAfterStmts);
$code .= $this->formatTernaryReturn($if, $ifBeforeStmts, $ifAfterStmts);
$code .= $this->getIndent() . '} else {';
$code .= $appendReturn($else, $elseBeforeStmts, $elseAfterStmts);
$code .= $this->formatTernaryReturn($else, $elseBeforeStmts, $elseAfterStmts);
$code .= $this->getIndent() . '}';
$code .= $this->getIndent() . '}()';
return $code;
@ -4037,6 +4062,20 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
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
{
$this->assertExprCanBeUsedAsValue($expr->cond, 'match condition');
@ -4051,39 +4090,6 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$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 . '{';
$default = null;
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->assertExprCanBeUsedAsValue($cond, 'match arm condition');
[$condValue, $beforeStmts, $afterStmts] = $parseExprWithStmts($cond);
[$condValue, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($cond);
$code .= $this->getIndent() . 'if (!' . $matched . ') {';
$code .= $appendStmtLines($beforeStmts);
$code .= $this->formatCapturedStmtLines($beforeStmts);
if ($afterStmts) {
$condTmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . "{$condTmpVar} = {$condValue};";
$code .= $appendStmtLines($afterStmts);
$code .= $this->formatCapturedStmtLines($afterStmts);
$condValue = $condTmpVar;
}
$code .= $this->getIndent() . $matched . ' = php::same(' . $var . ', ' . $condValue . ');';
$code .= $this->getIndent() . '}';
}
$code .= $this->getIndent() . 'if (' . $matched . ') {';
$code .= $appendMatchReturn($arm->body);
$code .= $this->formatMatchReturn($arm->body);
$code .= $this->getIndent() . '}';
}
if ($default) {
$code .= $this->getIndent() . '{';
$code .= $appendMatchReturn($default);
$code .= $this->formatMatchReturn($default);
$code .= $this->getIndent() . '}';
} else {
$code .= $this->getIndent() . '{ return php::throwException("UnhandledMatchError", "Unhandled match case"); }';
@ -4127,6 +4133,22 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
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
{
$this->assertNotNullsafeWriteContext($expr->var);
@ -4166,27 +4188,28 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$arms[] = [$elseif->cond, $elseif->stmts];
}
$emitIfChain = function (int $index) use (&$emitIfChain, $arms, $v): string {
if (!isset($arms[$index])) {
if (!$v->else) {
return '';
}
return $this->parseBlockStmts($v->else->stmts);
}
return $this->parseBeforeStmtLines() . PHP_EOL . $this->parseIfChain($arms, $v->else, 0) . PHP_EOL;
}
[$cond, $stmts] = $arms[$index];
$code = $this->genConditionWithCapturedStmts($cond, 'if ');
$code .= $this->parseBlockStmts($stmts);
$tail = $emitIfChain($index + 1);
if ($tail !== '') {
$code .= $this->getIndent() . '} else {' . PHP_EOL;
$code .= $tail;
protected function parseIfChain(array $arms, ?Node\Stmt\Else_ $else, int $index): string
{
if (!isset($arms[$index])) {
if (!$else) {
return '';
}
$code .= $this->getIndent() . '}';
return $code;
};
return $this->parseBlockStmts($else->stmts);
}
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";
$this->indentLevel++;
$initCode .= $this->getIndent() . "{$initState} = true;\n";
$initCode .= $this->genLambdaCall(function () use ($var, $varName) {
return $this->getIndent() . $varName . ' = ' . $this->parseExpr($var->default) . ';';
});
$initCode .= $this->genStaticVarInitLambda($var, $varName);
$this->indentLevel--;
$initCode .= $this->getIndent() . '}';
$list[] = $initCode;
@ -5367,6 +5388,26 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
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
{
return 'php::eval("' . $this->escapeString($this->genEmbeddedCode($v)) . '");';
@ -6883,24 +6924,14 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
protected function genReturnCode(): string
{
if ($this->context->inClosure && $this->context->closureReturnTypeCheck) {
$tmpVar = $this->genTmpVarName();
$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->shouldCheckClosureReturnType()) {
return $this->genClosureCheckedReturn(self::VALUE_NULL);
}
if ($this->functionDef->returnType === self::TYPE_VOID) {
return '';
}
if ($this->functionDef->returnTypeCheck && !$this->context->inClosure) {
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_VAR);
$code = $tmpVar . ' = ' . self::VALUE_NULL . ';' . PHP_EOL;
$code .= $this->genUnionReturnCheck($tmpVar);
$code .= $this->getIndent() . 'return ' . $tmpVar . ';';
return $code;
return $this->genUnionCheckedReturn(self::VALUE_NULL);
}
if ($this->functionDef->returnType === self::TYPE_INT
or $this->functionDef->returnType === self::TYPE_FLOAT
@ -6939,66 +6970,12 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
}
$uses = array_values($uses);
$cb = function () use ($expr) {
$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);
return $this->genClosure($expr, $expr->params, $uses);
}
protected function parseClosure(Expr\Closure $expr): string
{
$cb = function () use ($expr) {
$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);
return $this->genClosure($expr, $expr->params, $expr->uses);
}
protected function isReturnStmtInLastLine(array $stmts): bool

@ -93,14 +93,12 @@ trait AnonClassGenerator
{
if ($stmt instanceof Class_) {
$stmt = clone $stmt;
$shouldAddMixedReturn = fn (Class_ $class, ClassMethod $method): bool =>
$this->shouldAddMixedReturnToEmbeddedClassMethod($class, $method);
$traverser = new NodeTraverser();
$traverser->addVisitor(new class($shouldAddMixedReturn) extends NodeVisitorAbstract {
$traverser->addVisitor(new class($this) extends NodeVisitorAbstract {
/** @var list<Class_> */
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) {
$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');
}
}
@ -133,7 +131,7 @@ trait AnonClassGenerator
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());
if ($this->isEmbeddedMagicMethodReturnSensitive($methodName)) {

@ -11,6 +11,7 @@ namespace PhpAot\Php\Generator;
use PhpAot\Php\ArgInfo;
use PhpAot\Php\Context\FunctionContext;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
@ -26,7 +27,7 @@ trait ClosureGenerator
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();
@ -106,7 +107,7 @@ trait ClosureGenerator
$this->addArgument('this_', self::TYPE_OBJECT);
}
$body = $bodyGenCb();
$body = $this->genClosureBody($expr);
$code .= $this->genScopeVarDecl() . $body;
$this->indentLevel--;
@ -143,27 +144,50 @@ trait ClosureGenerator
}
}
protected function genLambdaCall(callable $cb): string
protected function genClosureBody(NodeAbstract $expr): string
{
$code = '';
$oriCtx = $this->context;
// 使用 lambda 函数来对 static 变量进行赋值
$this->context = new FunctionContext();
// C++ lambda 使用 & 捕获了当前函数的所有局部变量,可直接使用,不需要再声明,将其作为 arguments 来处理,隐式使用
$this->context->arguments = $oriCtx->localVars;
if ($expr instanceof Node\Expr\ArrowFunction) {
return $this->genArrowFunctionBody($expr);
}
if ($expr instanceof Node\Expr\Closure) {
return $this->genAnonymousClosureBody($expr);
}
$this->fatalError($expr, 'Unsupported closure expression');
}
$code .= '([&](){' . PHP_EOL;
$body = $cb();
$code .= $this->genScopeVarDecl();
$code .= $this->parseBeforeStmtLines();
$code .= $body;
$code .= $this->parseAfterStmtLines();
$code .= '})();' . PHP_EOL;
protected function genArrowFunctionBody(Node\Expr\ArrowFunction $expr): string
{
$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) {
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

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

@ -427,22 +427,16 @@ trait BinaryOpTrait
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';
$rightCondition = $op === '&&' ? $leftBool : '!(' . $leftBool . ')';
$code = '[&]() -> bool {';
$code .= $this->getIndent() . 'if (' . $rightCondition . ') {';
$code .= $appendStmtLines($rightBeforeStmts);
$code .= $this->formatCapturedStmtLines($rightBeforeStmts);
if ($rightAfterStmts) {
$rightTmpVar = $this->addTmpVar(self::TYPE_VAR);
$code .= $this->getIndent() . $rightTmpVar . ' = ' . $rightExpr . ';';
$code .= $appendStmtLines($rightAfterStmts);
$code .= $this->formatCapturedStmtLines($rightAfterStmts);
$rightExpr = $rightTmpVar;
}
$code .= $this->getIndent() . 'return ' . $this->convertBoolExpr((string) $rightExpr) . ';';

@ -131,7 +131,13 @@ abstract class UnixPlatform extends PlatformBase
if ($includes) {
preg_match_all('/-I([^\s]+)/', $includes, $matches);
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',
];
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 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
@ -1682,7 +1689,11 @@ CODE;
$targetArgs = $this->getTargetArgs();
$command = escapeshellcmd($targetFile);
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");
@ -3225,20 +3236,15 @@ CODE;
string $parentClass
): void {
$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),
// but forbids narrowing it.
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)) {
$error('static mismatch');
$this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
}
$childFuncDef = $childMethodDef->functionDef;
@ -3248,29 +3254,29 @@ CODE;
}
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
// require more arguments than the parent contract.
if ($childFuncDef->argCountRequired > $parentFuncDef->argCountRequired) {
$error('required parameter count mismatch');
$this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
}
// Compare each parent-declared parameter position.
foreach ($parentFuncDef->argInfoList as $i => $parentArg) {
if (!isset($childFuncDef->argInfoList[$i])) {
$error("missing parameter #{$i}");
$this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
}
$childArg = $childFuncDef->argInfoList[$i];
if (!$this->isParameterTypeOverrideCompatible($childArg, $parentArg)) {
$error("parameter #{$i} type mismatch");
$this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
}
if ($childArg->byRef !== $parentArg->byRef) {
$error("parameter #{$i} by-reference mismatch");
$this->fatalMethodOverrideIncompatible($v, $className, $methodName, $parentClass);
}
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++) {
$childArg = $childFuncDef->argInfoList[$i];
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
{
if ($parentFuncDef->returnTypeUndeclared) {

Loading…
Cancel
Save