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.
208 lines
9.0 KiB
208 lines
9.0 KiB
<?php
|
|
/**
|
|
* This file is part of TypePHP.
|
|
*
|
|
* Resolves pipe targets and ordinary function calls.
|
|
*/
|
|
|
|
namespace TypePhp\Parser;
|
|
|
|
use TypePhp\Type;
|
|
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\Node\Expr\CallLike;
|
|
use PhpParser\Node\Expr\Variable;
|
|
use PhpParser\NodeAbstract;
|
|
use TypePhp\Metadata\Constants;
|
|
use TypePhp\Exception\PlaceHolder;
|
|
|
|
trait FunctionCallTrait
|
|
{
|
|
protected function parsePipeOperator(Expr\BinaryOp\Pipe $expr): string
|
|
{
|
|
$this->assertExprCanBeUsedAsValue($expr->left, 'pipe left operand');
|
|
$this->assertExprCanBeUsedAsValue($expr->right, 'pipe callable');
|
|
|
|
[$leftExpr, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr->left);
|
|
$this->appendCapturedStmtLinesToContext($beforeStmts);
|
|
$value = $this->addTmpVar(Type::VAR);
|
|
$this->context->beforeStmtLines[] = $value . ' = ' . $leftExpr . ';';
|
|
$this->appendCapturedStmtLinesToContext($afterStmts);
|
|
|
|
$directCall = $this->parsePipeFirstClassCallable($expr->right, $value);
|
|
if ($directCall !== null) {
|
|
return $directCall;
|
|
}
|
|
|
|
$callable = $this->parseExprAsValue($expr->right);
|
|
return 'php::call(' . $callable . ', {' . $value . '})';
|
|
}
|
|
|
|
/**
|
|
* Lower a first-class callable used as a pipe target to its direct call.
|
|
*
|
|
* `trim(...)`, `ClassName::method(...)`, and `$object->method(...)` do
|
|
* not need a Closure when the pipe immediately invokes them. Reusing the
|
|
* ordinary call parsers preserves native-call optimization, argument
|
|
* validation, visibility checks, and the left-to-right evaluation order.
|
|
*/
|
|
protected function parsePipeFirstClassCallable(NodeAbstract $callable, string $value): ?string
|
|
{
|
|
if (!$callable instanceof CallLike || !$callable->isFirstClassCallable()) {
|
|
return null;
|
|
}
|
|
|
|
$directCall = clone $callable;
|
|
$directCall->args = [new Node\Arg(new Variable($value))];
|
|
|
|
if ($directCall instanceof Expr\FuncCall) {
|
|
return $this->parseFuncCall($directCall);
|
|
}
|
|
if ($directCall instanceof Expr\StaticCall) {
|
|
return $this->parseStaticCall($directCall);
|
|
}
|
|
if ($directCall instanceof Expr\MethodCall) {
|
|
return $this->parseMethodCall($directCall);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function parseFuncCall(Expr\FuncCall $expr): string
|
|
{
|
|
$pythonCall = $this->parsePythonFunctionCall($expr);
|
|
if ($pythonCall !== null) {
|
|
return $pythonCall;
|
|
}
|
|
$pythonObjectCall = $this->parsePythonObjectCall($expr);
|
|
if ($pythonObjectCall !== null) {
|
|
return $pythonObjectCall;
|
|
}
|
|
|
|
$callableClass = $this->detectClassOfExpr($expr->name);
|
|
if ($this->isNativeObjectClass($callableClass)) {
|
|
if ($expr->isFirstClassCallable()) {
|
|
$this->fatalError($expr, 'Native object callables cannot be converted to Zend closures');
|
|
}
|
|
return $this->parseMethodCall(new Expr\MethodCall(
|
|
$expr->name,
|
|
new Node\Identifier('__invoke'),
|
|
$expr->args,
|
|
));
|
|
}
|
|
|
|
if ($this->isVarExpr($expr->name)) {
|
|
$fn = $this->parseIdentifier($expr->name);
|
|
$placeHolder = $fn;
|
|
$name = '';
|
|
} elseif ($expr->name->getType() === 'Name' or $expr->name->getType() === 'Name_FullyQualified') {
|
|
$name = $this->parseIdentifier($expr->name);
|
|
$globalName = ltrim($name, '\\');
|
|
if ($globalName === 'get_called_class' && $this->classDef?->nativeObject) {
|
|
$this->fatalError(
|
|
$expr,
|
|
'Native classes do not support late static binding; use `self::class` or a concrete class name',
|
|
);
|
|
}
|
|
if (($globalName === 'get_class' || $globalName === 'get_parent_class')
|
|
&& (($expr->args === [] && $this->classDef?->nativeObject)
|
|
|| ($expr->args !== []
|
|
&& $this->isNativeObjectClass($this->detectClassOfExpr($expr->args[0]->value))))
|
|
) {
|
|
$replacement = $globalName === 'get_class'
|
|
? '`self::class` or a concrete class name'
|
|
: '`parent::class` or a concrete class name';
|
|
$this->fatalError(
|
|
$expr,
|
|
"Native classes do not support runtime class introspection; use {$replacement}",
|
|
);
|
|
}
|
|
if ($this->isInternalFunction($globalName)) {
|
|
$this->assertWasiFunctionSupported($expr, $globalName);
|
|
$this->markInternalFunctionCallbackCall($globalName, $expr->args);
|
|
}
|
|
if (in_array($globalName, Constants::UNSUPPORTED_FUNCTIONS, true)) {
|
|
$this->fatalError($expr, 'Unsupported function: `' . $globalName . '`');
|
|
}
|
|
if ($name === 'any') {
|
|
if (count($expr->args) !== 1 || $expr->args[0]->unpack) {
|
|
$this->fatalError($expr, 'The any function expects exactly one non-unpacked argument');
|
|
}
|
|
$value = $expr->args[0]->value;
|
|
if ($this->isNativeObjectClass($this->detectClassOfExpr($value))) {
|
|
$this->fatalError(
|
|
$value,
|
|
'Native objects cannot be converted to mixed with any(); use an explicitly typed Native variable',
|
|
);
|
|
}
|
|
if ($this->isVarExpr($value)) {
|
|
$this->assertStdContainerDoesNotEscapeNativeObjects(
|
|
$value,
|
|
$this->parseIdentifier($value),
|
|
);
|
|
}
|
|
return $this->parseExprAsValue($value);
|
|
}
|
|
if ($globalName === 'expected' || $globalName === 'unexpected') {
|
|
if (count($expr->args) !== 1 || $expr->args[0]->unpack) {
|
|
$this->fatalError($expr, "The {$globalName} function expects exactly one non-unpacked argument");
|
|
}
|
|
$condition = $this->parseExprAsValue($expr->args[0]->value);
|
|
return 'static_cast<bool>(' . strtoupper($globalName) . '((' . $condition . ')))';
|
|
}
|
|
if ($name === 'objval') {
|
|
return $this->genObjvalCall($expr);
|
|
}
|
|
$nativeFn = $this->findNativeFunction($name);
|
|
if ($nativeFn) {
|
|
$expr->setAttribute('nativeCall', $nativeFn);
|
|
if ($expr->isFirstClassCallable()
|
|
&& $this->functionUsesNativeObject($this->getFunction($nativeFn))
|
|
) {
|
|
$this->fatalError($expr, 'Native ABI functions cannot be converted to Zend closures');
|
|
}
|
|
// 函数调用占位符,不是真实的函数调用
|
|
if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) {
|
|
return $this->genPlaceHolder($this->identifierToStr($expr->name));
|
|
}
|
|
$this->checkNativeCallArgs($expr, $this->getFunction($nativeFn), $expr->args, $name);
|
|
if ($this->shouldUseDynamicCallForNativeArgs($nativeFn, $expr->args)) {
|
|
$functionDef = $this->getFunction($nativeFn);
|
|
return $this->genRuntimeFunctionCall($this->getFuncPtr($functionDef->getNamespacedName()), $expr->args, $name);
|
|
}
|
|
try {
|
|
$callee = $expr->getAttribute(self::ATTR_MULTI_RETURN_IMPL, false)
|
|
? $this->getMultiReturnImplName($nativeFn)
|
|
: self::PREFIX . $nativeFn;
|
|
return $callee . '(' . $this->parseNativeCallArgs($expr->args, $nativeFn) . ')';
|
|
} catch (PlaceHolder) {
|
|
return $this->genPlaceHolder($this->identifierToStr($expr->name));
|
|
}
|
|
}
|
|
// 动态调用的函数,转换函数名为带有命名空间的全限定名称
|
|
$name = $this->getNamespacedFuncName($name);
|
|
$this->checkInternalFunctionArgCount($name, $expr);
|
|
$code = $this->parseFuncCallWithOptimizer($name, $expr);
|
|
if ($code !== false) {
|
|
return $code;
|
|
}
|
|
$placeHolder = $this->identifierToStr($expr->name);
|
|
$fn = $this->getFuncPtr($name);
|
|
$this->context->beforeStmtLines[] = $this->formatCppLineComment('Func Call: ', $name . '()');
|
|
} else {
|
|
$tmpVar = $this->addTmpVar(Type::VAR);
|
|
$this->context->beforeStmtLines[] = $tmpVar . ' = ' . $this->parseExpr($expr->name) . ';';
|
|
$placeHolder = $fn = $tmpVar;
|
|
$name = '';
|
|
}
|
|
if (empty($expr->args)) {
|
|
return 'php::call(' . $fn . ')';
|
|
}
|
|
try {
|
|
return $this->genRuntimeFunctionCall($fn, $expr->args, $name);
|
|
} catch (PlaceHolder) {
|
|
return $this->genPlaceHolder($placeHolder);
|
|
}
|
|
}
|
|
}
|
|
|