diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index ae2ca1b5..6660e877 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.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 diff --git a/src/Php/Generator/AnonClassGenerator.php b/src/Php/Generator/AnonClassGenerator.php index d65509fd..b33f27b0 100644 --- a/src/Php/Generator/AnonClassGenerator.php +++ b/src/Php/Generator/AnonClassGenerator.php @@ -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 */ 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)) { diff --git a/src/Php/Generator/ClosureGenerator.php b/src/Php/Generator/ClosureGenerator.php index 60c4cc3f..5381ea71 100644 --- a/src/Php/Generator/ClosureGenerator.php +++ b/src/Php/Generator/ClosureGenerator.php @@ -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 diff --git a/src/Php/Generator/ResourceFileGenerator.php b/src/Php/Generator/ResourceFileGenerator.php index d21f8e6c..9e44749b 100644 --- a/src/Php/Generator/ResourceFileGenerator.php +++ b/src/Php/Generator/ResourceFileGenerator.php @@ -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) { diff --git a/src/Php/Parser/BinaryOpTrait.php b/src/Php/Parser/BinaryOpTrait.php index 976cebc6..5637f3b5 100644 --- a/src/Php/Parser/BinaryOpTrait.php +++ b/src/Php/Parser/BinaryOpTrait.php @@ -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) . ';'; diff --git a/src/Php/Platform/UnixPlatform.php b/src/Php/Platform/UnixPlatform.php index 225c14df..18b9570a 100644 --- a/src/Php/Platform/UnixPlatform.php +++ b/src/Php/Platform/UnixPlatform.php @@ -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; } /** diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 5b95d499..0e5bac32 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -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) {