feat(closure): 实现闭包功能支持类内部方法访问$this

- 添加closure_005.phpt和closure_006.phpt测试用例验证闭包功能
- 修改CompilerBase.php中函数定义相关属性为可空类型并添加methodDef
- 实现resetMethod方法重置方法定义状态
- 优化函数参数和局部变量处理逻辑
- 添加genLocalVarDecl和genReturnCode方法生成变量声明和返回代码
- 改进闭包解析逻辑支持返回类型声明
- 更新newClosure调用传递this_参数支持类内闭包访问实例
- 重构MethodDef构造函数移除FunctionDef参数依赖
pull/1/head
韩天峰 7 months ago
parent 8bc3c66499
commit 288f1dddef
  1. 122
      src/Php/CompilerBase.php
  2. 10
      src/Php/Entity/MethodDef.php
  3. 19
      src/Php/Translator.php
  4. 76
      tests/zend/closures/closure_005.phpt
  5. 20
      tests/zend/closures/closure_006.phpt

@ -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_;
}
}

@ -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

@ -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

@ -0,0 +1,76 @@
--TEST--
Closure 005: Lambda inside class, lifetime of $this
--FILE--
<?php
class A {
private $x;
function __construct($x) {
$this->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

@ -0,0 +1,20 @@
--TEST--
Closure 006: Nested lambdas
--FILE--
<?php
$getClosure = function ($v) {
return function () use ($v) {
echo "Hello World: $v!\n";
return null;
};
};
$closure = $getClosure (2);
$closure ();
echo "Done\n";
?>
--EXPECT--
Hello World: 2!
Done
Loading…
Cancel
Save