TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
6.8 KiB
174 lines
6.8 KiB
<?php
|
|
/**
|
|
* This file is part of TypePHP.
|
|
*
|
|
* Lowers nullsafe property and method access chains.
|
|
*/
|
|
|
|
namespace TypePhp\Parser;
|
|
|
|
use TypePhp\Type;
|
|
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\NodeAbstract;
|
|
|
|
trait NullsafeAccessTrait
|
|
{
|
|
protected function parseNullsafePropertyFetch(Expr\NullsafePropertyFetch $expr): string
|
|
{
|
|
return $this->parseNullsafeExpr($expr);
|
|
}
|
|
|
|
protected function parseNullsafePropertyFetchUpdate(Expr\NullsafePropertyFetch $expr): string
|
|
{
|
|
return $this->parseNodeWithUpdateAttribute(
|
|
$expr,
|
|
self::ATTR_PROPERTY_FETCH_UPDATE,
|
|
true,
|
|
fn() => $this->parseNullsafePropertyFetch($expr)
|
|
);
|
|
}
|
|
|
|
protected function parseNullsafeMethodCall(Expr\NullsafeMethodCall $expr): string
|
|
{
|
|
return $this->parseNullsafeExpr($expr);
|
|
}
|
|
|
|
protected function parseNullsafeExpr(
|
|
Expr\PropertyFetch|Expr\MethodCall|Expr\NullsafePropertyFetch|Expr\NullsafeMethodCall $expr
|
|
): string
|
|
{
|
|
$list = [];
|
|
$comment = $this->formatCppLineComment('Nullsafe Operator: ', $this->printer->prettyPrint([$expr]));
|
|
|
|
while (1) {
|
|
if ($expr instanceof Expr\NullsafePropertyFetch) {
|
|
$list[] = ['property', $this->propertyNameToStr($expr->name, literal: true), $expr, true];
|
|
$expr = $expr->var;
|
|
} elseif ($expr instanceof Expr\NullsafeMethodCall) {
|
|
$list[] = ['method', $this->identifierToStr($expr->name, literal: true), $expr->args, true, $expr];
|
|
$expr = $expr->var;
|
|
} elseif ($expr instanceof Expr\PropertyFetch) {
|
|
$list[] = ['property', $this->propertyNameToStr($expr->name, literal: true), $expr, false];
|
|
$expr = $expr->var;
|
|
} elseif ($expr instanceof Expr\MethodCall) {
|
|
$list[] = ['method', $this->identifierToStr($expr->name, literal: true), $expr->args, false, $expr];
|
|
$expr = $expr->var;
|
|
} else {
|
|
if ($this->isVarExpr($expr)) {
|
|
$object = $this->parseIdentifier($expr);
|
|
if (!$this->hasVar($object)) {
|
|
$this->errorUndefinedVariable($expr);
|
|
}
|
|
$type = $this->getVarType($object);
|
|
if ($type === Type::OBJECT) {
|
|
break;
|
|
}
|
|
}
|
|
$object = $this->addTmpVar(Type::OBJECT);
|
|
$this->context->beforeStmtLines[] = $this->getIndent() . $object . ' = ' . $this->parseIdentifier($expr) . ';';
|
|
break;
|
|
}
|
|
}
|
|
|
|
$list = array_reverse($list);
|
|
$this->checkNullsafePropertyAccesses($expr, $list);
|
|
$last = array_key_last($list);
|
|
$tmpFn = $this->genTmpVarName();
|
|
|
|
$code = $comment . PHP_EOL . 'auto ' . $tmpFn . ' = [&]() -> ' . Type::VAR . '{' . PHP_EOL;
|
|
|
|
foreach ($list as $key => $item) {
|
|
$tmpVar = $this->addTmpVar($key !== $last ? Type::OBJECT : Type::VAR);
|
|
if ($item[3]) {
|
|
$code .= "if ({$object}.isNull()) { return " . self::VALUE_NULL . '; }';
|
|
}
|
|
if ($item[0] == 'property') {
|
|
$update = $this->escapeAttrMode($this->isPropertyFetchUpdate($item[2]));
|
|
$code .= $this->getIndent() . "{$tmpVar} = {$object}.attr({$item[1]}, {$update});";
|
|
} else {
|
|
$methodName = $this->isNamedMethod($item[4]->name)
|
|
? $this->parseIdentifier($item[4]->name)
|
|
: '';
|
|
$receiverClass = $this->detectClassOfExpr($item[4]->var);
|
|
$requiresDynamicScope = $this->runtimeMethodRequiresDynamicScope($receiverClass, $methodName);
|
|
$beforeStmtCount = count($this->context->beforeStmtLines);
|
|
$afterStmtCount = count($this->context->afterStmtLines);
|
|
$args = $this->parseCallArgs($item[2]);
|
|
$argBeforeStmts = array_slice($this->context->beforeStmtLines, $beforeStmtCount);
|
|
$argAfterStmts = 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);
|
|
if ($argBeforeStmts) {
|
|
$code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argBeforeStmts) . PHP_EOL;
|
|
}
|
|
if ($requiresDynamicScope && $this->methodDef) {
|
|
$code .= $this->getIndent()
|
|
. "{$tmpVar} = php::callScoped({$object}, {$item[1]}, "
|
|
. $this->getCallableScopeExpr() . ", {$args});";
|
|
} else {
|
|
$code .= $this->getIndent() . "{$tmpVar} = {$object}.call({$item[1]}, {$args});";
|
|
}
|
|
if ($argAfterStmts) {
|
|
$code .= $this->getIndent() . implode(PHP_EOL . $this->getIndent(), $argAfterStmts) . PHP_EOL;
|
|
}
|
|
}
|
|
$object = $tmpVar;
|
|
}
|
|
$code .= $this->getIndent() . "return {$object}; };";
|
|
$this->context->beforeStmtLines[] = $code;
|
|
return "{$tmpFn}()";
|
|
}
|
|
|
|
protected function containsNullsafeChain(NodeAbstract $expr): bool
|
|
{
|
|
while ($expr instanceof Expr\PropertyFetch
|
|
|| $expr instanceof Expr\MethodCall
|
|
|| $expr instanceof Expr\NullsafePropertyFetch
|
|
|| $expr instanceof Expr\NullsafeMethodCall) {
|
|
if ($expr instanceof Expr\NullsafePropertyFetch || $expr instanceof Expr\NullsafeMethodCall) {
|
|
return true;
|
|
}
|
|
$expr = $expr->var;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function checkNullsafePropertyAccesses(NodeAbstract $baseExpr, array $list): void
|
|
{
|
|
$properties = [];
|
|
foreach ($list as $item) {
|
|
if ($item[0] !== 'property') {
|
|
break;
|
|
}
|
|
|
|
/** @var Expr\NullsafePropertyFetch $node */
|
|
$node = $item[2];
|
|
if (!$this->isIdExpr($node->name)) {
|
|
break;
|
|
}
|
|
|
|
$properties[] = [
|
|
'node' => $node,
|
|
'property' => $this->parseIdentifier($node->name),
|
|
];
|
|
}
|
|
|
|
if (!$properties) {
|
|
return;
|
|
}
|
|
|
|
$scope = $this->class ? $this->getFullClassName() : '';
|
|
$results = $this->createPropertyAccessResolver()->resolveNullsafePropertyChain(
|
|
$this->detectClassOfExpr($baseExpr),
|
|
$properties,
|
|
$scope,
|
|
Type::OBJECT,
|
|
);
|
|
foreach ($results as $index => $result) {
|
|
$this->applyNativePropertyAccessResult($properties[$index]['node'], $result);
|
|
}
|
|
}
|
|
|
|
}
|
|
|