* Copyright (c) 2025-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 "arrayLiteralLowering.h"
#include "checker/types/ets/etsTupleType.h"
#include "compiler/lowering/util.h"
namespace ark::es2panda::compiler {
using AstNodePtr = ir::AstNode *;
std::string_view ArrayLiteralLowering::Name() const
{
return "ArrayLiteralLowering";
}
ArenaVector<ir::Statement *> ArrayLiteralLowering::GenerateDefaultCallToConstructor(ir::Identifier *arraySymbol,
checker::Type *eleType,
checker::Signature *sig)
{
std::stringstream ss;
std::vector<ir::AstNode *> newStmts;
if (!eleType->IsETSUnionType() && !eleType->IsETSAnyType()) {
auto *indexSymbol = Gensym(Allocator());
auto *lengthSymbol = Gensym(Allocator());
auto *typeNode = checker_->AllocNode<ir::OpaqueTypeNode>(eleType, Allocator());
ES2PANDA_ASSERT(typeNode != nullptr);
ss << "let @@I1 : int = @@I2.length.toInt();";
newStmts.emplace_back(lengthSymbol);
newStmts.emplace_back(arraySymbol->Clone(Allocator(), nullptr));
ss << "for (let @@I3 = 0; @@I4 < @@E5; @@I6 = @@I7 + 1) {";
newStmts.emplace_back(indexSymbol);
newStmts.emplace_back(indexSymbol->Clone(Allocator(), nullptr));
newStmts.emplace_back(lengthSymbol->Clone(Allocator(), nullptr));
newStmts.emplace_back(indexSymbol->Clone(Allocator(), nullptr));
newStmts.emplace_back(indexSymbol->Clone(Allocator(), nullptr));
if (!sig->HasRestParameter()) {
ss << "@@I8[@@I9] = new @@T10();";
} else {
ss << "let restArgs = new Array<Any>();";
ss << "@@I8[@@I9] = new @@T10(restArgs);";
}
newStmts.emplace_back(arraySymbol->Clone(Allocator(), nullptr));
newStmts.emplace_back(indexSymbol->Clone(Allocator(), nullptr));
ES2PANDA_ASSERT(typeNode != nullptr);
newStmts.emplace_back(typeNode->Clone(Allocator(), nullptr));
ss << "}";
} else {
ArenaVector<ir::Statement *> emptyStatement(Allocator()->Adapter());
return emptyStatement;
}
return parser_->CreateFormattedStatements(ss.str(), newStmts);
}
static bool IsInAnnotationContext(ir::AstNode *node)
{
while (node != nullptr && !(node->IsClassDefinition() && node->AsClassDefinition()->IsGlobal())) {
if (node->IsAnnotationDeclaration() || node->IsAnnotationUsage()) {
return true;
}
node = node->Parent();
}
return false;
}
ir::AstNode *ArrayLiteralLowering::TryTransformTupleConstructor(ir::ArrayExpression *literalArray,
checker::ETSTupleType *tupleType)
{
std::size_t maxTupleTypes = checker_->GetGlobalTypesHolder()->VariadicTupleTypeThreshold();
if (tupleType->GetTupleSize() <= maxTupleTypes) {
tupleType->GetWrapperType()->ConstructSignatures();
return literalArray;
}
ES2PANDA_ASSERT_POS(literalArray->Elements().size() > maxTupleTypes, literalArray->Start());
auto const &typeList = tupleType->GetTupleTypesList();
std::size_t extraParamsNumber = literalArray->Elements().size() - maxTupleTypes;
std::vector<ir::AstNode *> nodes {};
auto *extraParams = Gensym(Allocator());
auto *helperArray = Gensym(Allocator());
auto *parent = literalArray->Parent();
auto const range = literalArray->Range();
std::string code = "let @@I1 : FixedArray<Any> = @@E2; ";
nodes.emplace_back(helperArray);
nodes.emplace_back(literalArray);
literalArray->SetTsType(nullptr);
code += "let @@I3 : Array<Any> = @@E4;";
nodes.emplace_back(extraParams);
nodes.emplace_back(CreateUninitializedResizableArray(
Context(), parser_->CreateFormattedExpression(std::to_string(extraParamsNumber)),
checker_->CreateETSResizableArrayType(checker_->GlobalETSAnyType())));
code += "for (let i = 0; i < " + std::to_string(extraParamsNumber) + "; i = i + 1) { @@I5[i] = @@I6[i+" +
std::to_string(maxTupleTypes) + "]} ";
nodes.emplace_back(extraParams->Clone(Allocator(), nullptr));
nodes.emplace_back(helperArray->Clone(Allocator(), nullptr));
--maxTupleTypes;
code += "new TupleN<";
for (std::size_t i = 0U; i < maxTupleTypes; ++i) {
code += typeList[i]->ToString() + ", ";
}
code += typeList[maxTupleTypes]->ToString() + ">(";
std::size_t j = 7U;
for (std::size_t i = 0U; i < maxTupleTypes; ++i, j += 2U) {
code += "@@I" + std::to_string(j) + '[' + std::to_string(i) + "] as @@T" + std::to_string(j + 1U) + ", ";
nodes.emplace_back(helperArray->Clone(Allocator(), nullptr));
nodes.emplace_back(checker_->AllocNode<ir::OpaqueTypeNode>(typeList[i], Allocator()));
}
code += "@@I" + std::to_string(j) + '[' + std::to_string(maxTupleTypes) + "] as @@T" + std::to_string(j + 1U) +
", @@I" + std::to_string(j + 2U) + ");";
nodes.emplace_back(helperArray->Clone(Allocator(), nullptr));
nodes.emplace_back(checker_->AllocNode<ir::OpaqueTypeNode>(typeList[maxTupleTypes], Allocator()));
nodes.emplace_back(extraParams->Clone(Allocator(), nullptr));
auto *loweringResult = parser_->CreateFormattedExpression(code, nodes);
loweringResult->SetParent(parent);
SetSourceRangesRecursively(loweringResult, range);
auto *scope = NearestScope(parent);
auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder_, scope);
CheckLoweredNode(varbinder_, checker_, loweringResult);
return loweringResult;
}
ir::AstNode *ArrayLiteralLowering::TryTransformLiteralArrayToRefArray(ir::ArrayExpression *literalArray)
{
if (IsInAnnotationContext(literalArray)) {
return literalArray;
}
auto literalArrayType = literalArray->TsType() != nullptr ? literalArray->TsType() : literalArray->PreferredType();
if (literalArrayType->IsETSTupleType()) {
return TryTransformTupleConstructor(literalArray, literalArrayType->AsETSTupleType());
}
if (!literalArrayType->IsETSResizableArrayType()) {
return literalArray;
}
auto *arrayType = literalArrayType->AsETSResizableArrayType()->ElementType();
std::vector<ir::AstNode *> newStmts;
std::stringstream ss;
ir::Identifier *genSymIdent = Gensym(Allocator());
auto *type = checker_->AllocNode<ir::OpaqueTypeNode>(arrayType, Allocator());
if (literalArray->Elements().empty()) {
ss << "let @@I1: Array<@@T2> = new Array<@@T3>(); @@I4;";
newStmts.emplace_back(genSymIdent);
newStmts.emplace_back(type);
newStmts.emplace_back(type->Clone(Allocator(), nullptr));
newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
} else {
ir::Identifier *genSymIdent2 = Gensym(Allocator());
bool elementIsUnboxable = arrayType->IsETSObjectType() && arrayType->AsETSObjectType()->IsBoxedPrimitive();
ss << "let @@I1: " << (elementIsUnboxable ? "ValueArray" : "FixedArray") << "<@@T2> = @@E3;";
ss << "let @@I4: Array<@@T5> = Array.create<@@T6>(@@I7.length, @@I8[0]);";
ss << "for (let i = 1; i < @@I9.length; i = i + 1) { @@I10[i] = @@I11[i]} @@I12";
newStmts.emplace_back(genSymIdent);
newStmts.emplace_back(type);
newStmts.emplace_back(literalArray);
literalArray->SetTsType(nullptr);
newStmts.emplace_back(genSymIdent2);
newStmts.emplace_back(type->Clone(Allocator(), nullptr));
newStmts.emplace_back(type->Clone(Allocator(), nullptr));
newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
newStmts.emplace_back(genSymIdent2->Clone(Allocator(), nullptr));
newStmts.emplace_back(genSymIdent->Clone(Allocator(), nullptr));
newStmts.emplace_back(genSymIdent2->Clone(Allocator(), nullptr));
}
auto *parent = literalArray->Parent();
auto *loweringResult = parser_->CreateFormattedExpression(ss.str(), newStmts);
ES2PANDA_ASSERT(loweringResult != nullptr);
loweringResult->SetRange(literalArray->Range());
loweringResult->SetParent(parent);
auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder_, NearestScope(parent));
CheckLoweredNode(varbinder_, checker_, loweringResult);
return loweringResult;
}
ir::AstNode *ArrayLiteralLowering::TryTransformNewArrayExprToRefArray(ir::ETSNewArrayInstanceExpression *newExpr)
{
if (newExpr->TsType()->IsETSArrayType()) {
return newExpr;
}
ES2PANDA_ASSERT(newExpr->TsType()->IsETSResizableArrayType());
auto *arrayType = newExpr->TsType()->AsETSResizableArrayType()->ElementType();
std::vector<ir::AstNode *> newStmts;
auto *genSymIdent = Gensym(Allocator());
std::stringstream ss;
ss << "let @@I1: Array<@@T2> = @@E3;";
auto *type = checker_->AllocNode<ir::OpaqueTypeNode>(arrayType, Allocator());
newStmts.emplace_back(genSymIdent);
newStmts.emplace_back(type);
CreateUninitializedResizableArray(Context(), newExpr->Dimension(), newExpr->TsType());
ArenaVector<ir::Statement *> statements(Allocator()->Adapter());
auto *newArrStatement = parser_->CreateFormattedStatement(ss.str(), newStmts);
statements.emplace_back(newArrStatement);
auto newArrElementStatements = GenerateDefaultCallToConstructor(genSymIdent, arrayType, newExpr->Signature());
statements.insert(statements.end(), newArrElementStatements.begin(), newArrElementStatements.end());
auto returnStmt = parser_->CreateFormattedStatement("@@I1", genSymIdent->Clone(Allocator(), nullptr));
statements.emplace_back(returnStmt);
auto *loweringResult = checker_->AllocNode<ir::BlockExpression>(std::move(statements));
loweringResult->SetRange(newExpr->Range());
loweringResult->SetParent(newExpr->Parent());
auto *scope = NearestScope(loweringResult->Parent());
auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder_, scope);
CheckLoweredNode(varbinder_, checker_, loweringResult);
return loweringResult;
}
ir::Statement *ArrayLiteralLowering::CreateNestedArrayCreationStatement(ArenaVector<ir::Identifier *> &identDims,
size_t currentDim, checker::Type *type,
ir::Expression *expr, checker::Signature *sig)
{
auto *genSymIdent = Gensym(Allocator());
auto *arraySymbol = Gensym(Allocator());
auto *lastDimIdent = identDims[currentDim - 1];
auto *currentDimIdent = identDims[currentDim];
auto *arrayType = type->AsETSResizableArrayType()->ElementType();
auto arrayAccessExpr = checker_->AllocNode<ir::MemberExpression>(
expr->Clone(Allocator(), nullptr)->AsExpression(), genSymIdent->Clone(Allocator(), nullptr),
ir::MemberExpressionKind::ELEMENT_ACCESS, true, false);
std::string creationTemplate =
"for (let @@I1 = 0; @@I2 < @@I3; @@I4 = @@I5 + 1) { let @@I6 : Array<@@T7> = @@E8; "
"@@E9 = @@I10}";
ir::Statement *forUpdateStmt = parser_->CreateFormattedStatement(
creationTemplate, genSymIdent, genSymIdent->Clone(Allocator(), nullptr),
lastDimIdent->Clone(Allocator(), nullptr), genSymIdent->Clone(Allocator(), nullptr),
genSymIdent->Clone(Allocator(), nullptr), arraySymbol, arrayType,
CreateUninitializedResizableArray(Context(), currentDimIdent, type), arrayAccessExpr,
arraySymbol->Clone(Allocator(), nullptr));
if (identDims.size() > currentDim + 1) {
auto consequentStmt =
CreateNestedArrayCreationStatement(identDims, currentDim + 1, arrayType, arrayAccessExpr, sig);
forUpdateStmt->AsForUpdateStatement()->Body()->AsBlockStatement()->AddStatement(consequentStmt);
} else if (identDims.size() == currentDim + 1) {
auto newArrElementStatements = GenerateDefaultCallToConstructor(arraySymbol, arrayType, sig);
forUpdateStmt->AsForUpdateStatement()->Body()->AsBlockStatement()->AddStatements(newArrElementStatements);
}
return forUpdateStmt;
}
ArenaVector<ir::Identifier *> ArrayLiteralLowering::TransformDimVectorToIdentVector(
ArenaVector<ir::Expression *> &dimVector, ArenaVector<ir::Statement *> &stmts)
{
std::vector<ir::AstNode *> statements;
ArenaVector<ir::Identifier *> idents(Allocator()->Adapter());
auto addNode = [&statements](ir::AstNode *node) -> size_t {
statements.emplace_back(node);
return statements.size();
};
std::stringstream ss;
for (size_t i = 0; i < dimVector.size(); ++i) {
idents.emplace_back(Gensym(Allocator()));
ss << "let @@I" << addNode(idents[i]) << " = @@E" << addNode(dimVector[i]->Clone(Allocator(), nullptr)) << ";";
}
auto parsedStatement = parser_->CreateFormattedStatements(ss.str(), statements);
stmts.insert(stmts.end(), parsedStatement.begin(), parsedStatement.end());
return idents;
}
ir::AstNode *ArrayLiteralLowering::TryTransformNewMultiDimArrayToRefArray(
ir::ETSNewMultiDimArrayInstanceExpression *newExpr)
{
if (newExpr->TsType()->IsETSArrayType()) {
return newExpr;
}
ES2PANDA_ASSERT(newExpr->TsType()->IsETSResizableArrayType());
ArenaVector<ir::Statement *> statements(Allocator()->Adapter());
auto arrayType = newExpr->TsType()->AsETSResizableArrayType()->ElementType();
auto *genSymIdent = Gensym(Allocator());
std::string newArray = "let @@I1 : Array<@@T2> = @@E3;";
auto idents = TransformDimVectorToIdentVector(newExpr->Dimensions(), statements);
auto newArraystatement = parser_->CreateFormattedStatements(
newArray, genSymIdent, arrayType,
CreateUninitializedResizableArray(Context(), idents[0]->Clone(Allocator(), nullptr), newExpr->TsType()));
auto nestedArrayCreationStmt =
CreateNestedArrayCreationStatement(idents, 1, arrayType, genSymIdent, newExpr->Signature());
auto returnStmt = parser_->CreateFormattedStatement("@@I1", genSymIdent->Clone(Allocator(), nullptr));
statements.insert(statements.end(), newArraystatement.begin(), newArraystatement.end());
statements.push_back(nestedArrayCreationStmt);
statements.push_back(returnStmt);
auto loweringResult = checker_->AllocNode<ir::BlockExpression>(std::move(statements));
loweringResult->SetRange(newExpr->Range());
loweringResult->SetParent(newExpr->Parent());
auto *scope = NearestScope(loweringResult->Parent());
auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder_, scope);
CheckLoweredNode(varbinder_, checker_, loweringResult);
return loweringResult;
}
void ArrayLiteralLowering::Setup()
{
parser_ = Context()->parser->AsETSParser();
varbinder_ = Context()->parserProgram->VarBinder()->AsETSBinder();
checker_ = Context()->GetChecker()->AsETSChecker();
}
bool ArrayLiteralLowering::PerformForProgram(parser::Program *program)
{
program->Ast()->TransformChildrenRecursively(
[this](ir::AstNode *ast) -> AstNodePtr {
if (ast->IsArrayExpression()) {
return TryTransformLiteralArrayToRefArray(ast->AsArrayExpression());
}
if (ast->IsETSNewArrayInstanceExpression()) {
return TryTransformNewArrayExprToRefArray(ast->AsETSNewArrayInstanceExpression());
}
if (ast->IsETSNewMultiDimArrayInstanceExpression()) {
return TryTransformNewMultiDimArrayToRefArray(ast->AsETSNewMultiDimArrayInstanceExpression());
}
return ast;
},
Name());
return true;
}
ArenaAllocator *ArrayLiteralLowering::Allocator()
{
return checker_->Allocator();
}
}