* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "restArgsLowering.h"
#include <algorithm>
#include "checker/types/ets/etsTupleType.h"
#include "compiler/lowering/ets/iterableSpreadLowering.h"
#include "compiler/lowering/util.h"
#include "ir/astNode.h"
#include "ir/base/spreadElement.h"
#include "ir/expression.h"
#include "ir/expressions/arrayExpression.h"
#include "ir/expressions/literals/undefinedLiteral.h"
#include "ir/opaqueTypeNode.h"
#include "ir/ts/tsAsExpression.h"
#include "ir/typeNode.h"
#include <checker/ETSchecker.h>
#include <cstddef>
#include <iterator>
#include <sstream>
namespace ark::es2panda::compiler {
using AstNodePtr = ir::AstNode *;
constexpr size_t ARRAY_INDEX_FOR_ACCESS_OFFSET = 1;
constexpr size_t ARGUMENT_SYMBOL_OFFSET = 2;
constexpr size_t ARRAY_INDEX_FOR_ASSIGNMENT_OFFSET = 3;
constexpr size_t ARRAY_INDEX_FOR_INCREMENT_OFFSET = 4;
constexpr size_t TUPLE_SPREAD_ARG_COUNT = 5;
struct RestArgsBlockExpressionData {
std::string spreadArgsArray;
ir::Identifier *arrayLength;
ir::Identifier *arraySymbol;
ir::Identifier *arrayIndex;
ir::Identifier *argumentIndex;
};
static RestArgsBlockExpressionData CreateRestArgsBlockExpressionData(public_lib::Context *context)
{
auto *allocator = context->allocator;
const auto spreadArgsArray = GenName();
const auto arrayLength = Gensym(allocator);
const auto arraySymbol = Gensym(allocator);
ES2PANDA_ASSERT(arraySymbol != nullptr);
const auto arrayIndex = Gensym(allocator);
ES2PANDA_ASSERT(arrayIndex != nullptr);
const auto argumentIndex = Gensym(allocator);
ES2PANDA_ASSERT(argumentIndex != nullptr);
return {spreadArgsArray, arrayLength, arraySymbol, arrayIndex, argumentIndex};
}
static ir::OpaqueTypeNode *CreateOpaqueTypeNode(public_lib::Context *context, checker::Type *type)
{
return context->AllocNode<ir::OpaqueTypeNode>(type, context->allocator);
}
static checker::Type *CreateResultArrayType(checker::ETSChecker *checker, checker::Type *constraintType,
checker::Type *arrayType, bool useConstraintType)
{
if (!useConstraintType) {
return arrayType;
}
ES2PANDA_ASSERT(constraintType != nullptr);
bool isValueArray = arrayType->IsETSArrayType() && arrayType->AsETSArrayType()->IsValueArray();
return checker->CreateETSArrayType(constraintType, isValueArray);
}
static ir::Statement *FillArrayWithSpreadElement(public_lib::Context *context, ir::Expression *argument,
ir::Identifier *arraySymbolWithoutTypeAnnotation,
ir::Identifier *argumentSymbol,
const RestArgsBlockExpressionData &data)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
auto *checker = context->GetChecker()->AsETSChecker();
std::vector<ir::AstNode *> args;
std::stringstream ss;
auto *spreadType = checker->NormalizeSpreadType(argument->AsSpreadElement()->Argument()->TsType());
if (spreadType->IsETSTupleType()) {
size_t index = 1;
for (size_t j = 0; j < spreadType->AsETSTupleType()->GetTupleSize(); j++) {
ss << "@@I" << index << "[@@I" << (index + ARRAY_INDEX_FOR_ACCESS_OFFSET) << "] = @@I"
<< (index + ARGUMENT_SYMBOL_OFFSET) << "[" << j << "];";
args.emplace_back(arraySymbolWithoutTypeAnnotation->CloneReference(allocator, nullptr));
args.emplace_back(data.arrayIndex->CloneReference(allocator, nullptr));
args.emplace_back(argumentSymbol->CloneReference(allocator, nullptr));
ss << "@@I" << (index + ARRAY_INDEX_FOR_ASSIGNMENT_OFFSET) << " = @@I"
<< (index + ARRAY_INDEX_FOR_INCREMENT_OFFSET) << " + 1;";
args.emplace_back(data.arrayIndex->CloneReference(allocator, nullptr));
args.emplace_back(data.arrayIndex->CloneReference(allocator, nullptr));
index += TUPLE_SPREAD_ARG_COUNT;
}
} else {
ss << "for (let @@I1: int = 0; @@I2 < @@I3.length; @@I4 = @@I5 + 1){";
args.emplace_back(data.argumentIndex->CloneReference(allocator, nullptr));
args.emplace_back(data.argumentIndex->CloneReference(allocator, nullptr));
args.emplace_back(argumentSymbol->CloneReference(allocator, nullptr));
args.emplace_back(data.argumentIndex->CloneReference(allocator, nullptr));
args.emplace_back(data.argumentIndex->CloneReference(allocator, nullptr));
ss << "@@I6[@@I7] = @@I8[@@I9];";
args.emplace_back(arraySymbolWithoutTypeAnnotation->CloneReference(allocator, nullptr));
args.emplace_back(data.arrayIndex->CloneReference(allocator, nullptr));
args.emplace_back(argumentSymbol->CloneReference(allocator, nullptr));
args.emplace_back(data.argumentIndex->CloneReference(allocator, nullptr));
ss << "@@I10 = @@I11 + 1;";
args.emplace_back(data.arrayIndex->CloneReference(allocator, nullptr));
args.emplace_back(data.arrayIndex->CloneReference(allocator, nullptr));
ss << "}";
}
return parser->CreateFormattedStatement(ss.str(), args);
}
static ir::Identifier *FillArrayWithArguments(public_lib::Context *context,
ArenaVector<ir::Statement *> &blockStatements,
const ArenaVector<ir::Expression *> &arguments,
const RestArgsBlockExpressionData &data)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
auto *arraySymbolWithoutTypeAnnotation = data.arraySymbol->CloneReference(allocator, nullptr);
blockStatements.push_back(parser->CreateFormattedStatement("let @@I1 = 0;", data.arrayIndex));
size_t spreadArgIndex = 0;
for (auto argument : arguments) {
const auto argumentSymbol = Gensym(allocator);
ES2PANDA_ASSERT(argumentSymbol != nullptr);
if (argument->IsSpreadElement()) {
auto *spreadType = context->GetChecker()->AsETSChecker()->NormalizeSpreadType(
argument->AsSpreadElement()->Argument()->TsType());
blockStatements.push_back(parser->CreateFormattedStatement(
"let @@I1 = @@I2[" + std::to_string(spreadArgIndex) + "] as @@T3;", argumentSymbol,
data.spreadArgsArray, CreateOpaqueTypeNode(context, spreadType)));
spreadArgIndex++;
blockStatements.push_back(
FillArrayWithSpreadElement(context, argument, arraySymbolWithoutTypeAnnotation, argumentSymbol, data));
} else {
blockStatements.push_back(parser->CreateFormattedStatement(
"@@I1[@@I2] = @@E3 as @@T4;", arraySymbolWithoutTypeAnnotation->CloneReference(allocator, nullptr),
data.arrayIndex->CloneReference(allocator, nullptr), argument->Clone(allocator, nullptr),
CreateOpaqueTypeNode(context, argument->TsType())));
blockStatements.push_back(parser->CreateFormattedStatement(
"@@I1 = @@I2 + 1;", data.arrayIndex->CloneReference(allocator, nullptr),
data.arrayIndex->CloneReference(allocator, nullptr)));
}
}
return arraySymbolWithoutTypeAnnotation;
}
static size_t GetNonSpreadArgCount(const ArenaVector<ir::Expression *> &arguments,
ArenaVector<ir::Expression *> &arrayElements)
{
size_t nonSpreadArgCount = 0;
for (auto *argExpr : arguments) {
if (argExpr->IsSpreadElement()) {
arrayElements.push_back(argExpr->AsSpreadElement()->Argument());
} else {
nonSpreadArgCount++;
}
}
return nonSpreadArgCount;
}
static void AppendLegacySpreadLengthCalculation(public_lib::Context *context,
ArenaVector<ir::Statement *> &blockStatements,
ArenaVector<ir::Expression *> &spreadElements,
const RestArgsBlockExpressionData &data)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
auto *checker = context->GetChecker()->AsETSChecker();
auto addStmt = [&parser, &blockStatements](std::string_view const fmt, auto... args) {
blockStatements.push_back(parser->CreateFormattedStatement(fmt, args...));
};
for (size_t i = 0; i < spreadElements.size(); ++i) {
auto *arg = spreadElements[i];
auto *argType = checker->NormalizeSpreadType(arg->TsType());
addStmt("@@I1[" + std::to_string(i) + "] = @@E2 as @@T3;", data.spreadArgsArray, arg,
CreateOpaqueTypeNode(context, argType));
if (argType->IsETSTupleType() && argType->AsETSTupleType()->GetTupleSize() > 0) {
addStmt("@@I1 = @@I2 + " + std::to_string(argType->AsETSTupleType()->GetTupleSize()) + " ;",
data.arrayLength->CloneReference(allocator, nullptr),
data.arrayLength->CloneReference(allocator, nullptr));
continue;
}
addStmt("@@I1 = @@I2 + (@@I3[" + std::to_string(i) + "] as @@T4).length;",
data.arrayLength->CloneReference(allocator, nullptr),
data.arrayLength->CloneReference(allocator, nullptr), data.spreadArgsArray,
CreateOpaqueTypeNode(context, argType));
}
}
static void AppendLegacyResultArrayDeclaration(public_lib::Context *context,
ArenaVector<ir::Statement *> &blockStatements,
const RestArgsBlockExpressionData &data, checker::Type *constraintType,
checker::Type *arrayType)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
auto *checker = context->GetChecker()->AsETSChecker();
bool isValueArray = arrayType->IsETSArrayType() && arrayType->AsETSArrayType()->IsValueArray();
bool useConstraintType = constraintType != nullptr;
if (useConstraintType || arrayType->IsETSArrayType()) {
auto *resultType = useConstraintType ? checker->CreateETSArrayType(constraintType, isValueArray) : arrayType;
blockStatements.push_back(parser->CreateFormattedStatement(
"let @@I1 = @@E2;", data.arraySymbol,
CreateUninitializedFixedArray(context, data.arrayLength->CloneReference(allocator, nullptr), resultType)));
return;
}
auto *elementType = checker->GetElementTypeOfArray(arrayType);
blockStatements.push_back(parser->CreateFormattedStatement(
"let @@I1: Array<@@T2> = @@E3;", data.arraySymbol, CreateOpaqueTypeNode(context, elementType),
CreateUninitializedResizableArray(context, data.arrayLength->CloneReference(allocator, nullptr),
checker->CreateETSResizableArrayType(elementType))));
}
static ir::BlockExpression *CreateLegacyRestArgsBlockExpression(public_lib::Context *context,
ArenaVector<ir::Expression *> &arguments,
checker::Type *constraintType, checker::Type *arrayType,
bool useConstraintType)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
auto *checker = context->GetChecker()->AsETSChecker();
ArenaVector<ir::Statement *> blockStatements(allocator->Adapter());
auto data = CreateRestArgsBlockExpressionData(context);
ArenaVector<ir::Expression *> spreadElements(allocator->Adapter());
auto nonSpreadArgCount = GetNonSpreadArgCount(arguments, spreadElements);
blockStatements.push_back(parser->CreateFormattedStatement(
"let @@I1 = @@E2;", data.spreadArgsArray,
CreateUninitializedFixedArray(context, parser->CreateFormattedExpression(std::to_string(spreadElements.size())),
checker->CreateETSArrayType(checker->GlobalETSAnyType(), false))));
blockStatements.push_back(
parser->CreateFormattedStatement("let @@I1: int = 0;", data.arrayLength->CloneReference(allocator, nullptr)));
AppendLegacySpreadLengthCalculation(context, blockStatements, spreadElements, data);
if (nonSpreadArgCount > 0) {
blockStatements.push_back(
parser->CreateFormattedStatement("@@I1 = @@I2 + " + std::to_string(nonSpreadArgCount) + ";",
data.arrayLength->CloneReference(allocator, nullptr),
data.arrayLength->CloneReference(allocator, nullptr)));
}
AppendLegacyResultArrayDeclaration(context, blockStatements, data, useConstraintType ? constraintType : nullptr,
arrayType);
auto *arraySymbolWithoutTypeAnnotation = FillArrayWithArguments(context, blockStatements, arguments, data);
blockStatements.push_back(
parser->CreateFormattedStatement("@@I1", arraySymbolWithoutTypeAnnotation->CloneReference(allocator, nullptr)));
return util::NodeAllocator::ForceSetParent<ir::BlockExpression>(allocator, std::move(blockStatements));
}
static void AppendArgumentPush(public_lib::Context *context, ArenaVector<ir::Statement *> &blockStatements,
ir::Identifier *targetArrayIdent, ir::Expression *argument, checker::Type *elementType)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
blockStatements.push_back(parser->CreateFormattedStatement(
"@@I1.push(@@E2 as @@T3);", targetArrayIdent->CloneReference(allocator, nullptr),
argument->Clone(allocator, nullptr), CreateOpaqueTypeNode(context, elementType)));
}
static ir::BlockExpression *CreateOrderedRestArgsBlockExpression(public_lib::Context *context,
ArenaVector<ir::Expression *> &arguments,
checker::Type *arrayType)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
auto *checker = context->GetChecker()->AsETSChecker();
ArenaVector<ir::Statement *> blockStatements(allocator->Adapter());
auto *elementType = checker->GetElementTypeOfArray(arrayType);
auto *tempArrayIdent = CreateSpreadTempResizableArray(context, elementType, blockStatements);
for (auto *argument : arguments) {
if (argument->IsSpreadElement()) {
AppendSpreadToArray(context, argument->AsSpreadElement(), tempArrayIdent, elementType, blockStatements);
continue;
}
AppendArgumentPush(context, blockStatements, tempArrayIdent, argument, elementType);
}
auto *resultArrayIdent = FinalizeSpreadTempArray(context, arrayType, tempArrayIdent, blockStatements);
blockStatements.push_back(
parser->CreateFormattedStatement("@@I1", resultArrayIdent->CloneReference(allocator, nullptr)));
return util::NodeAllocator::ForceSetParent<ir::BlockExpression>(allocator, std::move(blockStatements));
}
static ir::BlockExpression *CreateRestArgsBlockExpression(public_lib::Context *context,
ArenaVector<ir::Expression *> &arguments,
checker::Type *constraintType, checker::Type *arrayType,
bool useConstraintType)
{
auto *resultArrayType =
CreateResultArrayType(context->GetChecker()->AsETSChecker(), constraintType, arrayType, useConstraintType);
return CreateOrderedRestArgsBlockExpression(context, arguments, resultArrayType);
}
static bool HasMaterializedSpreadArgument(const ArenaVector<ir::Expression *> &arguments)
{
return std::any_of(arguments.begin(), arguments.end(), [](ir::Expression *argument) {
return argument->IsSpreadElement() &&
argument->AsSpreadElement()->GetResolvedSpreadKind() == ir::SpreadElement::ResolvedSpreadKind::ITERABLE;
});
}
bool ShouldSkipParamCheck(checker::Signature *signature, const ArenaVector<ir::Expression *> &args)
{
if (signature == nullptr) {
return false;
}
if (signature->Params().empty()) {
return false;
}
const auto ¶ms = signature->Params();
if (params.empty()) {
return false;
}
if (!signature->HasRestParameter()) {
return false;
}
auto *restVar = signature->RestVar();
if (restVar == nullptr) {
return false;
}
size_t nMandatory = 0;
size_t nOptional = 0;
for (auto *param : params) {
if (param == nullptr) {
return false;
}
auto *decl = param->Declaration();
if (decl == nullptr) {
return false;
}
auto *node = decl->Node();
if (node == nullptr || !node->IsETSParameterExpression()) {
return false;
}
auto *etsParam = node->AsETSParameterExpression();
if (etsParam == nullptr) {
return false;
}
if (etsParam->IsOptional()) {
++nOptional;
} else {
++nMandatory;
}
}
return args.size() < (nOptional + nMandatory) && nOptional > 0;
}
static bool ShouldProcessRestParameters(checker::Signature *signature, checker::Type *calleeType,
const ArenaVector<ir::Expression *> &arguments)
{
if (signature == nullptr || !signature->HasRestParameter()) {
return false;
}
auto *restVar = signature->RestVar();
if (restVar == nullptr) {
return false;
}
auto *restVarType = restVar->TsType();
if (restVarType == nullptr) {
return false;
}
bool isRestVarTupleType = restVarType->IsETSTupleType();
if (isRestVarTupleType) {
return false;
}
bool hasEnoughArguments = arguments.size() >= signature->Params().size();
bool shouldSkipParamCheck = ShouldSkipParamCheck(signature, arguments);
if (!hasEnoughArguments && !shouldSkipParamCheck) {
return false;
}
return calleeType->IsETSArrowType() || !signature->Function()->IsDynamic();
}
static ir::Expression *CreateRestArgsArrayWithoutSpread(public_lib::Context *context,
ArenaVector<ir::Expression *> &copiedArguments,
checker::Type *restParamType)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
auto *checker = context->GetChecker()->AsETSChecker();
const lexer::SourceRange range = {copiedArguments.front()->Start(), copiedArguments.back()->End()};
auto elemType = checker->GetElementTypeOfArray(restParamType);
auto arrayTypeNode = checker->AllocNode<ir::OpaqueTypeNode>(elemType, allocator);
auto *arrayExpr = checker->AllocNode<ir::ArrayExpression>(std::move(copiedArguments), allocator);
std::stringstream ss;
auto *genSymIdent = Gensym(allocator);
auto *genSymIdent2 = Gensym(allocator);
ES2PANDA_ASSERT(arrayTypeNode != nullptr);
ES2PANDA_ASSERT(genSymIdent != nullptr && genSymIdent2 != nullptr);
ss << "let @@I1 : FixedArray<@@T2> = @@E3;";
ir::Expression *loweringResult = nullptr;
if (restParamType->IsETSArrayType()) {
ss << "@@I4";
loweringResult = parser->CreateFormattedExpression(ss.str(), genSymIdent, arrayTypeNode, arrayExpr,
genSymIdent->CloneReference(allocator, nullptr));
} else {
ss << "let @@I4 : Array<@@T5> = @@E6;";
ss << "for (let i = 0; i < @@I7.length; i = i + 1) { @@I8[i] = @@I9[i]} @@I10";
loweringResult = parser->CreateFormattedExpression(
ss.str(), genSymIdent, arrayTypeNode->Clone(allocator, nullptr), arrayExpr, genSymIdent2, arrayTypeNode,
CreateUninitializedResizableArray(
context,
parser->CreateFormattedExpression("@@I1.length", genSymIdent->CloneReference(allocator, nullptr)),
checker->CreateETSResizableArrayType(elemType)),
genSymIdent->CloneReference(allocator, nullptr), genSymIdent2->CloneReference(allocator, nullptr),
genSymIdent->CloneReference(allocator, nullptr), genSymIdent2->CloneReference(allocator, nullptr));
}
loweringResult->SetRange(range);
return loweringResult;
}
static checker::Type *GetRestArgsConstraintType(checker::ETSChecker *checker, bool isArrowType,
checker::Type *restParamType)
{
if (isArrowType) {
return checker->GetElementTypeOfArray(restParamType);
}
return nullptr;
}
static bool CanSkipRestArgsLowering(checker::Type *restParamType, bool isArrowType, bool hasSpreadArg)
{
if (!hasSpreadArg) {
if (!isArrowType && (restParamType->IsETSResizableArrayType() || restParamType->IsETSReadonlyArrayType())) {
return false;
}
return true;
}
return false;
}
static ir::BlockExpression *CreateSpreadRestArgsArray(public_lib::Context *context,
ArenaVector<ir::Expression *> &copiedArguments,
checker::Type *restParamType, bool isArrowType,
bool hasMaterializedSpread)
{
auto *checker = context->GetChecker()->AsETSChecker();
auto *constraintType = GetRestArgsConstraintType(checker, isArrowType, restParamType);
bool needsConstraintType = constraintType != nullptr;
auto *blockExpression = hasMaterializedSpread
? CreateRestArgsBlockExpression(context, copiedArguments, constraintType, restParamType,
needsConstraintType)
: CreateLegacyRestArgsBlockExpression(context, copiedArguments, constraintType,
restParamType, needsConstraintType);
ES2PANDA_ASSERT(blockExpression != nullptr);
return blockExpression;
}
static void PreserveRestArgSmartTypes(public_lib::Context *context, ArenaVector<ir::Expression *> &copiedArguments)
{
auto *allocator = context->allocator;
for (auto *&arg : copiedArguments) {
if (arg->IsIdentifier() && arg->TsType() != arg->Variable()->TsType()) {
auto *const typeNode = context->AllocNode<ir::OpaqueTypeNode>(arg->TsType(), allocator);
arg = context->AllocNode<ir::TSAsExpression>(arg, typeNode, false);
}
}
}
static ir::Expression *CreateRestArgsArray(public_lib::Context *context, ir::Expression *expr, bool isArrowType = false)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
auto *checker = context->GetChecker()->AsETSChecker();
const auto &arguments = expr->IsCallExpression() ? expr->AsCallExpression()->Arguments()
: expr->AsETSNewClassInstanceExpression()->GetArguments();
const auto *signature = expr->IsCallExpression() ? expr->AsCallExpression()->Signature()
: expr->AsETSNewClassInstanceExpression()->Signature();
checker::Type *restParamType = signature->RestVar()->TsType();
auto arrayTypeNode =
checker->AllocNode<ir::OpaqueTypeNode>(checker->GetElementTypeOfArray(restParamType), allocator);
const size_t numRegularParams = signature->Params().size();
const size_t numExtraArgs = arguments.size() > numRegularParams ? arguments.size() - numRegularParams : 0;
if (!restParamType->IsETSArrayType() && numExtraArgs == 0) {
return parser->CreateFormattedExpression("new Array<@@T1>()", arrayTypeNode);
}
ArenaVector<ir::Expression *> copiedArguments(arguments.begin() + numRegularParams, arguments.end(),
allocator->Adapter());
bool hasSpreadArg = std::any_of(copiedArguments.begin(), copiedArguments.end(),
[](ir::Expression *arg) { return arg->IsSpreadElement(); });
if (CanSkipRestArgsLowering(restParamType, isArrowType, hasSpreadArg)) {
return nullptr;
}
if (hasSpreadArg) {
bool hasMaterializedSpread = HasMaterializedSpreadArgument(copiedArguments);
auto *blockExpression =
CreateSpreadRestArgsArray(context, copiedArguments, restParamType, isArrowType, hasMaterializedSpread);
const lexer::SourceRange range = {copiedArguments.front()->Start(), copiedArguments.back()->End()};
blockExpression->SetParent(arguments.back()->Parent());
blockExpression->SetRange(range);
if (hasMaterializedSpread) {
Recheck(context->phaseManager, checker->VarBinder()->AsETSBinder(), checker, blockExpression);
}
return blockExpression;
}
PreserveRestArgSmartTypes(context, copiedArguments);
return CreateRestArgsArrayWithoutSpread(context, copiedArguments, restParamType);
}
static ir::CallExpression *RebuildCallExpression(public_lib::Context *context, ir::CallExpression *originalCall,
checker::Signature *signature, ir::Expression *restArgsArray)
{
auto *allocator = context->allocator;
auto *varbinder = context->GetChecker()->VarBinder()->AsETSBinder();
ArenaVector<ir::Expression *> newArgs(allocator->Adapter());
if (!originalCall->Arguments().empty()) {
for (size_t i = 0; i < signature->Params().size() && i < originalCall->Arguments().size(); ++i) {
newArgs.push_back(originalCall->Arguments()[i]);
}
}
if (ShouldSkipParamCheck(signature, originalCall->Arguments())) {
auto *checker = context->GetChecker()->AsETSChecker();
auto *undefinedExpr = checker->AllocNode<ir::UndefinedLiteral>();
size_t nMissing = signature->Params().size() - originalCall->Arguments().size();
auto it = signature->Params().begin();
std::advance(it, originalCall->Arguments().size());
for (size_t i = 0; i < nMissing; ++i) {
undefinedExpr->SetTsType((*it)->TsType());
newArgs.push_back(undefinedExpr);
++it;
}
}
newArgs.push_back(restArgsArray);
auto *newCall = util::NodeAllocator::ForceSetParent<ir::CallExpression>(allocator, originalCall->Callee(),
std::move(newArgs), nullptr, false);
ES2PANDA_ASSERT(newCall != nullptr);
restArgsArray->SetParent(newCall);
newCall->SetParent(originalCall->Parent());
newCall->AddModifier(originalCall->Modifiers());
newCall->SetTypeParams(originalCall->TypeParams());
restArgsArray->AddAstNodeFlags(ir::AstNodeFlags::REST_ARGUMENT);
SetSourceRangesRecursively(newCall, originalCall->Range());
auto *scope = NearestScope(newCall->Parent());
auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder, scope);
CheckLoweredNode(context->GetChecker()->VarBinder()->AsETSBinder(), context->GetChecker()->AsETSChecker(), newCall);
return newCall;
}
static ir::ETSNewClassInstanceExpression *RebuildNewClassInstanceExpression(
public_lib::Context *context, ir::ETSNewClassInstanceExpression *originalCall, checker::Signature *signature,
ir::Expression *restArgsArray)
{
auto *allocator = context->allocator;
ArenaVector<ir::Expression *> newArgs(allocator->Adapter());
for (size_t i = 0; i < signature->Params().size(); ++i) {
newArgs.push_back(originalCall->GetArguments()[i]);
}
newArgs.push_back(restArgsArray);
auto *newCall = util::NodeAllocator::ForceSetParent<ir::ETSNewClassInstanceExpression>(
allocator, originalCall->GetTypeRef(), std::move(newArgs));
restArgsArray->AddAstNodeFlags(ir::AstNodeFlags::REST_ARGUMENT);
restArgsArray->SetParent(newCall);
newCall->SetParent(originalCall->Parent());
newCall->AddModifier(originalCall->Modifiers());
SetSourceRangesRecursively(newCall, originalCall->Range());
auto *scope = NearestScope(newCall->Parent());
auto bscope =
varbinder::LexicalScope<varbinder::Scope>::Enter(context->GetChecker()->VarBinder()->AsETSBinder(), scope);
CheckLoweredNode(context->GetChecker()->VarBinder()->AsETSBinder(), context->GetChecker()->AsETSChecker(), newCall);
return newCall;
}
static bool IsDelegatingConstructorCall(ir::ETSNewClassInstanceExpression *expr)
{
return expr->GetTypeRef()->IsThisExpression() || expr->GetTypeRef()->IsSuperExpression();
}
static ir::ETSNewClassInstanceExpression *TransformCallConstructWithRestArgs(ir::ETSNewClassInstanceExpression *expr,
public_lib::Context *context)
{
checker::Signature *signature = expr->Signature();
if (!ShouldProcessRestParameters(signature, expr->GetTypeRef()->TsType(), expr->GetArguments())) {
return expr;
}
auto *restArgsArray = CreateRestArgsArray(context, expr);
if (restArgsArray == nullptr) {
return expr;
}
auto arg =
context->AllocNode<ir::SpreadElement>(ir::AstNodeType::SPREAD_ELEMENT, context->allocator, restArgsArray);
return RebuildNewClassInstanceExpression(context, expr, signature, arg);
}
static ir::CallExpression *TransformCallExpressionWithRestArgs(ir::CallExpression *callExpr,
public_lib::Context *context)
{
checker::Type *calleeType = context->GetChecker()->AsETSChecker()->GetNormalizedType(callExpr->Callee()->TsType());
if (calleeType == nullptr) {
return callExpr;
}
bool isArrowType = calleeType->IsETSArrowType();
checker::Signature *signature = callExpr->Signature();
if (signature == nullptr || (!isArrowType && signature->Function()->IsDynamic())) {
return callExpr;
}
bool hasSpreadArg = std::any_of(callExpr->Arguments().begin(), callExpr->Arguments().end(),
[](ir::Expression *arg) { return arg->IsSpreadElement(); });
if (isArrowType && !hasSpreadArg) {
return callExpr;
}
if (!ShouldProcessRestParameters(signature, calleeType, callExpr->Arguments())) {
return callExpr;
}
auto *restArgsArray = CreateRestArgsArray(context, callExpr, calleeType->IsETSArrowType());
if (restArgsArray == nullptr) {
return callExpr;
}
auto arg =
context->AllocNode<ir::SpreadElement>(ir::AstNodeType::SPREAD_ELEMENT, context->allocator, restArgsArray);
arg->SetRange(restArgsArray->Range());
return RebuildCallExpression(context, callExpr, signature, arg);
}
static bool IsInsideSyntheticFunction(ir::AstNode *node)
{
for (ir::AstNode *curr = node->Parent(); curr != nullptr; curr = curr->Parent()) {
if (curr->IsScriptFunction()) {
auto *scriptFunc = curr->AsScriptFunction();
if ((scriptFunc->Flags() & ir::ScriptFunctionFlags::SYNTHETIC) != 0) {
return true;
}
}
}
return false;
}
static bool ShouldTransformCallWithSpreadTuple(ir::CallExpression *callExpr)
{
if (IsInsideSyntheticFunction(callExpr)) {
return false;
}
auto calleeType = callExpr->Callee()->TsType();
if (calleeType->IsETSArrowType()) {
return false;
}
if (callExpr->Signature() == nullptr || !callExpr->Signature()->HasRestParameter()) {
return false;
}
auto *restVar = callExpr->Signature()->RestVar();
if (restVar == nullptr || !restVar->TsType()->IsETSTupleType()) {
return false;
}
const auto &args = callExpr->Arguments();
if (args.empty() || !args.back()->IsSpreadElement()) {
return false;
}
return args.size() == callExpr->Signature()->Params().size() + 1;
}
static ir::BlockExpression *CreateTupleRestArgsBlockExpression(public_lib::Context *context,
ir::SpreadElement *spreadElement,
checker::Signature *signature)
{
auto *allocator = context->allocator;
auto *parser = context->parser->AsETSParser();
auto *checker = context->GetChecker()->AsETSChecker();
auto *restVar = signature->RestVar();
auto *tupleType = restVar->TsType()->AsETSTupleType();
auto *spreadArg = spreadElement->Argument();
auto *tupleVar = Gensym(allocator);
auto *tupleTypeNode = checker->AllocNode<ir::OpaqueTypeNode>(tupleType, allocator);
ArenaVector<ir::Statement *> blockStatements(allocator->Adapter());
blockStatements.push_back(
parser->CreateFormattedStatement("let @@I1 = @@E2 as @@T3", tupleVar, spreadArg, tupleTypeNode));
ArenaVector<ir::Expression *> tupleElements(allocator->Adapter());
for (size_t i = 0; i < tupleType->GetTupleSize(); ++i) {
auto *elemAccess = parser->CreateFormattedExpression("@@I1[" + std::to_string(i) + "]",
tupleVar->CloneReference(allocator, nullptr));
tupleElements.push_back(elemAccess);
}
auto *newTupleExpr = checker->AllocNode<ir::ArrayExpression>(std::move(tupleElements), allocator);
auto *asExpression =
context->AllocNode<ir::TSAsExpression>(newTupleExpr, tupleTypeNode->Clone(allocator, nullptr), false);
blockStatements.push_back(parser->CreateFormattedStatement("@@E1", asExpression));
auto *blockExpr = util::NodeAllocator::ForceSetParent<ir::BlockExpression>(allocator, std::move(blockStatements));
return blockExpr;
}
static bool ShouldTransformNewClassWithSpreadTuple(ir::ETSNewClassInstanceExpression *expr)
{
if (IsDelegatingConstructorCall(expr)) {
return false;
}
if (IsInsideSyntheticFunction(expr)) {
return false;
}
if (expr->Signature() == nullptr || !expr->Signature()->HasRestParameter()) {
return false;
}
auto *restVar = expr->Signature()->RestVar();
if (restVar == nullptr || !restVar->TsType()->IsETSTupleType()) {
return false;
}
const auto &args = expr->GetArguments();
if (args.empty() || !args.back()->IsSpreadElement()) {
return false;
}
return args.size() == expr->Signature()->Params().size() + 1;
}
static ir::ETSNewClassInstanceExpression *TransformNewClassWithSpreadTuple(public_lib::Context *ctx,
ir::ETSNewClassInstanceExpression *expr)
{
auto *allocator = ctx->allocator;
auto *signature = expr->Signature();
auto *spreadElement = expr->GetArguments().back()->AsSpreadElement();
auto *restArgsBlock = CreateTupleRestArgsBlockExpression(ctx, spreadElement, signature);
auto *spreadArg = ctx->AllocNode<ir::SpreadElement>(ir::AstNodeType::SPREAD_ELEMENT, allocator, restArgsBlock);
spreadArg->SetRange(restArgsBlock->Range());
return RebuildNewClassInstanceExpression(ctx, expr, signature, spreadArg);
}
static ir::CallExpression *TransformCallWithSpreadTuple(public_lib::Context *ctx, ir::CallExpression *callExpr)
{
auto *allocator = ctx->allocator;
auto *checker = ctx->GetChecker()->AsETSChecker();
auto *signature = callExpr->Signature();
auto *spreadElement = callExpr->Arguments().back()->AsSpreadElement();
auto *restArgsBlock = CreateTupleRestArgsBlockExpression(ctx, spreadElement, signature);
ArenaVector<ir::Expression *> newCallArgs(allocator->Adapter());
for (size_t i = 0; i < signature->Params().size(); ++i) {
newCallArgs.push_back(callExpr->Arguments()[i]);
}
auto *spreadArg = ctx->AllocNode<ir::SpreadElement>(ir::AstNodeType::SPREAD_ELEMENT, allocator, restArgsBlock);
newCallArgs.push_back(spreadArg);
auto *newCall =
ctx->AllocNode<ir::CallExpression>(callExpr->Callee(), std::move(newCallArgs), callExpr->TypeParams(), false);
newCall->SetParent(callExpr->Parent());
newCall->SetRange(callExpr->Range());
for (auto *arg : newCall->Arguments()) {
arg->SetParent(newCall);
}
auto *scope = NearestScope(newCall->Parent());
auto bscope =
varbinder::LexicalScope<varbinder::Scope>::Enter(ctx->GetChecker()->VarBinder()->AsETSBinder(), scope);
CheckLoweredNode(ctx->GetChecker()->VarBinder()->AsETSBinder(), checker, newCall);
return newCall;
}
bool RestArgsLowering::PerformForProgram(parser::Program *program)
{
program->Ast()->TransformChildrenRecursively(
[ctx = Context()](ir::AstNode *node) -> AstNodePtr {
if (node->IsCallExpression()) {
auto *callExpr = node->AsCallExpression();
if (ShouldTransformCallWithSpreadTuple(callExpr)) {
return TransformCallWithSpreadTuple(ctx, callExpr);
}
return TransformCallExpressionWithRestArgs(callExpr, ctx);
}
if (node->IsETSNewClassInstanceExpression()) {
auto *newClassExpr = node->AsETSNewClassInstanceExpression();
if (ShouldTransformNewClassWithSpreadTuple(newClassExpr)) {
return TransformNewClassWithSpreadTuple(ctx, newClassExpr);
}
return TransformCallConstructWithRestArgs(newClassExpr, ctx);
}
return node;
},
Name());
return true;
}
}