diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index d08b7f23..283e610b 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -161,9 +161,10 @@ class CompilerBase extends \PhpAot\Core\Translator */ protected array $classCeList = []; protected array $classCeInfo = []; - protected FunctionDef $functionDef; - protected ClassDef $classDef; - protected InterfaceDef $interfaceDef; + protected ?FunctionDef $functionDef = null; + protected ?ClassDef $classDef = null; + protected ?MethodDef $methodDef = null; + protected ?InterfaceDef $interfaceDef = null; protected array $superGlobalVars = [ '_GET' => self::TYPE_ARRAY, '_POST' => self::TYPE_ARRAY, @@ -539,6 +540,13 @@ class CompilerBase extends \PhpAot\Core\Translator $this->tmpVarIndex = 0; $this->inLoop = false; $this->function = ''; + $this->functionDef = null; + } + + protected function resetMethod(): void + { + $this->method = ''; + $this->methodDef = null; } protected function resetClass(): void @@ -546,6 +554,7 @@ class CompilerBase extends \PhpAot\Core\Translator $this->class = ''; $this->interface = ''; $this->method = ''; + $this->classDef = null; } protected function resetFile(): void @@ -697,18 +706,18 @@ class CompilerBase extends \PhpAot\Core\Translator } if ($this->class) { - $this->arguments['this_'] = self::TYPE_OBJECT; - $this->addLocalVar('this_', self::TYPE_OBJECT); + $this->addArgument('this_', self::TYPE_OBJECT); + if ($this->methodDef) { + $this->functionDef->method = true; + $this->methodDef->functionDef = $this->functionDef; + } } else { $this->functions[$name] = $this->functionDef; $this->functionDefineInFile[$name] = $this->functionDef; } foreach ($this->functionDef->argInfoList as $argInfo) { - $this->arguments[$argInfo->name] = $argInfo->type; - if (!$this->hasLocalVar($argInfo->name)) { - $this->addLocalVar($argInfo->name, $argInfo->type); - } + $this->addArgument($argInfo->name, $argInfo->type); } if ($v->stmts) { @@ -718,6 +727,9 @@ class CompilerBase extends \PhpAot\Core\Translator } catch (Skip $e) { $stmts = ''; } + if (!$this->isReturnStmtInLastLine($v->stmts)) { + $stmts .= $this->genReturnCode(); + } $this->indentLevel--; } else { $stmts = ''; @@ -734,16 +746,7 @@ class CompilerBase extends \PhpAot\Core\Translator $code = $functionDeclCode . ' {' . PHP_EOL; $this->indentLevel++; - foreach ($this->localVars as $name => $type) { - if (isset($this->arguments[$name])) { - continue; - } - $code .= $this->getIndent() . $type . ' ' . $name; - if ($type === self::TYPE_INT or $type === self::TYPE_FLOAT or $type === self::TYPE_BOOL) { - $code .= ' = 0'; - } - $code .= ';' . PHP_EOL; - } + $code .= $this->genLocalVarDecl(); $code .= "\n"; $this->indentLevel--; $code .= $this->genDebugInfo(); @@ -1282,6 +1285,12 @@ class CompilerBase extends \PhpAot\Core\Translator $this->localVars[$name] = $type; } + protected function addArgument(string $name, string $type): void + { + $this->arguments[$name] = $type; + $this->addLocalVar($name, $type); + } + protected function addLiteralString(string $value): int { $index = $this->literalStringIndex++; @@ -3473,30 +3482,82 @@ class CompilerBase extends \PhpAot\Core\Translator return $code; } + protected function genLocalVarDecl(): string + { + $code = ''; + foreach ($this->localVars as $name => $type) { + if (isset($this->arguments[$name])) { + continue; + } + $code .= $this->getIndent() . $type . ' ' . $name; + if ($type === self::TYPE_INT or $type === self::TYPE_FLOAT or $type === self::TYPE_BOOL) { + $code .= ' = 0'; + } + $code .= ';' . PHP_EOL; + } + return $code; + } + + protected function genReturnCode(): string + { + if ($this->functionDef->returnType === self::TYPE_VOID) { + return ''; + } elseif ($this->functionDef->returnType === self::TYPE_INT + or $this->functionDef->returnType === self::TYPE_FLOAT + or $this->functionDef->returnType === self::TYPE_BOOL) { + return $this->getIndent() . 'return 0;'; + } else { + return $this->getIndent() . 'return php::null;'; + } + } + protected function parseClosure(Node\Expr\Closure $expr): string { $tmpVar = $this->genTmpVarName(); - $fnCode = $this->getIndent() . 'php::ClosureFn ' . $tmpVar . ' = [](INTERNAL_FUNCTION_PARAMETERS, ' . self::TYPE_OBJECT . ' &this_, ' . self::TYPE_ARGS . ' &vars_) {' . PHP_EOL; + $fnCode = $this->getIndent() . + 'php::ClosureFn ' . $tmpVar . ' = [](' + . 'INTERNAL_FUNCTION_PARAMETERS, ' + . self::TYPE_OBJECT . ' &this_, ' + . self::TYPE_ARGS . ' &vars_) ' . + '-> ' . self::TYPE_VAR . ' {' . PHP_EOL; $oriLocalVars = $this->localVars; $this->localVars = []; + $oriArgs = $this->arguments; + $this->arguments = []; $this->indentLevel++; + + $fnBodyCode = ''; foreach ($expr->params as $i => $param) { $var = $this->parseIdentifier($param->var); - $fnCode .= 'auto ' . $var . ' = php::getCallArg(' . $i . ');' . PHP_EOL; - $this->addLocalVar($var, self::TYPE_VAR); + $fnBodyCode .= 'auto ' . $var . ' = php::getCallArg(' . $i . ');' . PHP_EOL; + $this->addArgument($var, self::TYPE_VAR); } + foreach ($expr->uses as $i => $useItem) { $var = $this->parseIdentifier($useItem->var); - $fnCode .= 'auto ' . $var . ' = vars_.get(' . $i . ');' . PHP_EOL; - $this->addLocalVar($var, self::TYPE_VAR); + $fnBodyCode .= 'auto ' . $var . ' = vars_.get(' . $i . ');' . PHP_EOL; + $this->addArgument($var, self::TYPE_VAR); + } + + if ($this->methodDef) { + $this->addArgument('this_', self::TYPE_OBJECT); + } + + $fnBodyCode .= $this->parseStmts($expr->stmts);; + $fnBodyCode = $this->genLocalVarDecl() . $fnBodyCode; + $fnCode .= $fnBodyCode; + + if (!$this->isReturnStmtInLastLine($expr->stmts)) { + $fnCode .= $this->genReturnCode(); } - $fnCode .= $this->parseStmts($expr->stmts); + $this->indentLevel--; $fnCode .= '};' . PHP_EOL; $this->beforeStmtLines[] = $fnCode; $this->localVars = $oriLocalVars; + $this->arguments = $oriArgs; $useVars = []; foreach ($expr->uses as $useItem) { @@ -3511,7 +3572,11 @@ class CompilerBase extends \PhpAot\Core\Translator } } - return 'php::newClosure(' . $tmpVar . ', { ' . implode(', ', $useVars) . ' })'; + if ($this->methodDef) { + return 'php::newClosure(' . $tmpVar . ', { ' . implode(', ', $useVars) . ' }, this_)'; + } else { + return 'php::newClosure(' . $tmpVar . ', { ' . implode(', ', $useVars) . ' })'; + } } protected function parseAssignOpCoalesce(Node\Expr\AssignOp\Coalesce $expr): string @@ -3535,4 +3600,9 @@ class CompilerBase extends \PhpAot\Core\Translator $this->getIndent() . $var . ' = ' . $right . ';' . PHP_EOL . '}' . PHP_EOL; } + + protected function isReturnStmtInLastLine(array $stmts): bool + { + return $stmts[array_key_last($stmts)] instanceof Node\Stmt\Return_; + } } diff --git a/src/Php/Entity/MethodDef.php b/src/Php/Entity/MethodDef.php index ce78aafd..e2cfabcd 100644 --- a/src/Php/Entity/MethodDef.php +++ b/src/Php/Entity/MethodDef.php @@ -12,14 +12,12 @@ class MethodDef { public int $flags; public string $name; - public FunctionDef $functionDef; + public ?FunctionDef $functionDef = null; - public function __construct(int $flags, string $name, FunctionDef $def) + public function __construct(int $flags, string $name) { - $this->flags = $flags; - $this->name = $name; - $this->functionDef = $def; - $this->functionDef->method = true; + $this->flags = $flags; + $this->name = $name; } public function getReturnType(): string diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 843152ef..e7f26630 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -874,17 +874,20 @@ class Translator extends Preprocessor protected function parseClassMethod(Node\Stmt\ClassMethod $v, array &$methodCodes): void { - $name = $this->getMethodName($v); - $this->method = $name; - $methodCodes[$name] = $this->parseFunction($v); - $flags = $v->flags; + $name = $this->getMethodName($v); + $this->method = $name; + + $flags = $v->flags; if (!($flags & Modifiers::PRIVATE) and !($flags & Modifiers::PROTECTED)) { $flags |= Modifiers::PUBLIC; } - $methodDef = new MethodDef($flags, $name, $this->functionDef); - $this->checkRequiredArgNum($name, $methodDef, $v); - $this->classDef->methods[$name] = $methodDef; - $this->method = ''; + + $this->methodDef = new MethodDef($flags, $name); + $methodCodes[$name] = $this->parseFunction($v); + + $this->checkRequiredArgNum($name, $this->methodDef, $v); + $this->classDef->methods[$name] = $this->methodDef; + $this->resetMethod(); } protected function parseIdentifierList(array $implements): array diff --git a/tests/zend/closures/closure_005.phpt b/tests/zend/closures/closure_005.phpt new file mode 100644 index 00000000..b98ab54f --- /dev/null +++ b/tests/zend/closures/closure_005.phpt @@ -0,0 +1,76 @@ +--TEST-- +Closure 005: Lambda inside class, lifetime of $this +--FILE-- +x = $x; + } + + function __destruct() { + echo "Destroyed\n"; + } + + function getIncer($val) { + return function() use ($val) { + $this->x += $val; + }; + } + + function getPrinter() { + return function() { + echo $this->x."\n"; + }; + } + + function getError() { + return static function() { + echo $this->x."\n"; + }; + } + + function printX() { + echo $this->x."\n"; + } +} + +function main() { + $a = new A(3); + $incer = $a->getIncer(2); + $printer = $a->getPrinter(); + $error = $a->getError(); + + $a->printX(); + $printer(); + $incer(); + $a->printX(); + $printer(); + + unset($a); + + $incer(); + $printer(); + + unset($incer); + $printer(); + + unset($printer); + + $error(); + + echo "Done\n"; +} +?> +--EXPECTF-- +3 +3 +5 +5 +7 +7 +7 +Done +Destroyed \ No newline at end of file diff --git a/tests/zend/closures/closure_006.phpt b/tests/zend/closures/closure_006.phpt new file mode 100644 index 00000000..2fbffc1f --- /dev/null +++ b/tests/zend/closures/closure_006.phpt @@ -0,0 +1,20 @@ +--TEST-- +Closure 006: Nested lambdas +--FILE-- + +--EXPECT-- +Hello World: 2! +Done