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(' . 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); } } }