1 /**
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "restArgsLowering.h"
17 #include "compiler/lowering/util.h"
18 #include "ir/astNode.h"
19 #include "ir/expressions/arrayExpression.h"
20
21 #include <checker/ETSchecker.h>
22 #include <sstream>
23
24 namespace ark::es2panda::compiler {
25
26 using AstNodePtr = ir::AstNode *;
27
CreateRestArgsBlockExpression(public_lib::Context * context,ir::SpreadElement * spreadElement)28 static ir::BlockExpression *CreateRestArgsBlockExpression(public_lib::Context *context,
29 ir::SpreadElement *spreadElement)
30 {
31 auto *allocator = context->allocator;
32 auto *parser = context->parser->AsETSParser();
33 auto *checker = context->checker->AsETSChecker();
34
35 ArenaVector<ir::Statement *> blockStatements(allocator->Adapter());
36 const auto arraySymbol = Gensym(allocator);
37 const auto argumentSymbol = Gensym(allocator);
38 const auto iteratorIndex = Gensym(allocator);
39 const auto iteratorSymbol = Gensym(allocator);
40 const auto elementType = checker->GetElementTypeOfArray(spreadElement->Argument()->TsType());
41 auto *typeNode = allocator->New<ir::OpaqueTypeNode>(elementType, allocator);
42 blockStatements.push_back(
43 parser->CreateFormattedStatement("let @@I1 = @@E2;", argumentSymbol, spreadElement->Argument()));
44 blockStatements.push_back(parser->CreateFormattedStatement("let @@I1 = 0;", iteratorIndex));
45 blockStatements.push_back(parser->CreateFormattedStatement("let @@I1 = new Array<@@T2>(@@I3.length);", arraySymbol,
46 typeNode, argumentSymbol->Clone(allocator, nullptr)));
47 std::vector<ir::AstNode *> args;
48 std::stringstream ss;
49 ss << "for (let @@I1 of @@I2){";
50 args.emplace_back(iteratorSymbol);
51 args.emplace_back(argumentSymbol->Clone(allocator, nullptr));
52 ss << "@@I3[@@I4] = @@I5;";
53 args.emplace_back(arraySymbol->Clone(allocator, nullptr));
54 ES2PANDA_ASSERT(iteratorIndex != nullptr);
55 args.emplace_back(iteratorIndex->Clone(allocator, nullptr));
56 args.emplace_back(iteratorSymbol->Clone(allocator, nullptr));
57 ss << "@@I6 = @@I7 + 1;";
58 args.emplace_back(iteratorIndex->Clone(allocator, nullptr));
59 args.emplace_back(iteratorIndex->Clone(allocator, nullptr));
60 ss << "}";
61 ir::Statement *loopStatement = parser->CreateFormattedStatement(ss.str(), args);
62
63 blockStatements.push_back(loopStatement);
64 blockStatements.push_back(parser->CreateFormattedStatement("@@I1", arraySymbol->Clone(allocator, nullptr)));
65 auto *blockExpr = util::NodeAllocator::ForceSetParent<ir::BlockExpression>(allocator, std::move(blockStatements));
66 return blockExpr;
67 }
68
ConvertSpreadToBlockExpression(public_lib::Context * context,ir::SpreadElement * spreadElement)69 static ir::BlockExpression *ConvertSpreadToBlockExpression(public_lib::Context *context,
70 ir::SpreadElement *spreadElement)
71 {
72 auto *blockExpression = CreateRestArgsBlockExpression(context, spreadElement);
73 ES2PANDA_ASSERT(blockExpression != nullptr);
74 blockExpression->SetParent(spreadElement->Parent());
75 blockExpression->SetRange(spreadElement->Range());
76
77 for (auto *statement : blockExpression->Statements()) {
78 SetSourceRangesRecursively(statement, spreadElement->Range());
79 }
80 return blockExpression;
81 }
82
ShouldProcessRestParameters(checker::Signature * signature,const ArenaVector<ir::Expression * > & arguments)83 static bool ShouldProcessRestParameters(checker::Signature *signature, const ArenaVector<ir::Expression *> &arguments)
84 {
85 return signature != nullptr && signature->HasRestParameter() && !signature->RestVar()->TsType()->IsETSArrayType() &&
86 arguments.size() >= signature->Params().size() && !signature->RestVar()->TsType()->IsETSTupleType();
87 }
88
CreateRestArgsArray(public_lib::Context * context,ArenaVector<ir::Expression * > & arguments,checker::Signature * signature)89 static ir::Expression *CreateRestArgsArray(public_lib::Context *context, ArenaVector<ir::Expression *> &arguments,
90 checker::Signature *signature)
91 {
92 auto *allocator = context->allocator;
93 auto *parser = context->parser->AsETSParser();
94 auto *checker = context->checker->AsETSChecker();
95
96 // Handle single spread element case
97 const size_t extraArgs = arguments.size() - signature->Params().size();
98 if (extraArgs == 1 && arguments.back()->IsSpreadElement()) {
99 return ConvertSpreadToBlockExpression(context, arguments.back()->AsSpreadElement());
100 }
101 // Determine array type
102 checker::Type *arrayType = signature->RestVar()->TsType();
103 auto *type = checker->AllocNode<ir::OpaqueTypeNode>(checker->GetElementTypeOfArray(arrayType), allocator);
104 if (extraArgs == 0) {
105 return parser->CreateFormattedExpression("new Array<@@T1>(0)", type);
106 }
107
108 ArenaVector<ir::Expression *> copiedArguments(arguments.begin() + signature->Params().size(), arguments.end(),
109 allocator->Adapter());
110
111 std::stringstream ss;
112 auto *genSymIdent = Gensym(allocator);
113 ss << "let @@I1 : FixedArray<@@T2> = @@E3;";
114 ss << "Array.from<@@T4>(@@I5);";
115 auto *arrayExpr = checker->AllocNode<ir::ArrayExpression>(std::move(copiedArguments), allocator);
116 ES2PANDA_ASSERT(type != nullptr);
117 auto *loweringResult =
118 parser->CreateFormattedExpression(ss.str(), genSymIdent, type, arrayExpr, type->Clone(allocator, nullptr),
119 genSymIdent->Clone(allocator, nullptr));
120 return loweringResult;
121 }
122
RebuildCallExpression(public_lib::Context * context,ir::CallExpression * originalCall,checker::Signature * signature,ir::Expression * restArgsArray)123 static ir::CallExpression *RebuildCallExpression(public_lib::Context *context, ir::CallExpression *originalCall,
124 checker::Signature *signature, ir::Expression *restArgsArray)
125 {
126 auto *allocator = context->allocator;
127 auto *varbinder = context->checker->VarBinder()->AsETSBinder();
128 ArenaVector<ir::Expression *> newArgs(allocator->Adapter());
129
130 for (size_t i = 0; i < signature->Params().size(); ++i) {
131 newArgs.push_back(originalCall->Arguments()[i]);
132 newArgs[i]->SetBoxingUnboxingFlags(ir::BoxingUnboxingFlags::NONE);
133 }
134
135 newArgs.push_back(restArgsArray);
136
137 auto *newCall = util::NodeAllocator::ForceSetParent<ir::CallExpression>(allocator, originalCall->Callee(),
138 std::move(newArgs), nullptr, false);
139 ES2PANDA_ASSERT(newCall != nullptr);
140 restArgsArray->SetParent(newCall);
141 newCall->SetParent(originalCall->Parent());
142 newCall->AddModifier(originalCall->Modifiers());
143 newCall->AddBoxingUnboxingFlags(originalCall->GetBoxingUnboxingFlags());
144 newCall->SetTypeParams(originalCall->TypeParams());
145 newCall->AddAstNodeFlags(ir::AstNodeFlags::RESIZABLE_REST);
146
147 auto *scope = NearestScope(newCall->Parent());
148 auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder, scope);
149 CheckLoweredNode(context->checker->VarBinder()->AsETSBinder(), context->checker->AsETSChecker(), newCall);
150 newCall->RemoveAstNodeFlags(ir::AstNodeFlags::RESIZABLE_REST);
151 return newCall;
152 }
153
RebuildNewClassInstanceExpression(public_lib::Context * context,ir::ETSNewClassInstanceExpression * originalCall,checker::Signature * signature,ir::Expression * restArgsArray)154 static ir::ETSNewClassInstanceExpression *RebuildNewClassInstanceExpression(
155 public_lib::Context *context, ir::ETSNewClassInstanceExpression *originalCall, checker::Signature *signature,
156 ir::Expression *restArgsArray)
157 {
158 auto *allocator = context->allocator;
159 ArenaVector<ir::Expression *> newArgs(allocator->Adapter());
160
161 for (size_t i = 0; i < signature->Params().size(); ++i) {
162 newArgs.push_back(originalCall->GetArguments()[i]);
163 }
164
165 newArgs.push_back(restArgsArray);
166
167 auto *newCall = util::NodeAllocator::ForceSetParent<ir::ETSNewClassInstanceExpression>(
168 allocator, originalCall->GetTypeRef()->Clone(allocator, nullptr)->AsETSTypeReference(), std::move(newArgs));
169
170 restArgsArray->SetParent(newCall);
171 newCall->SetParent(originalCall->Parent());
172 newCall->AddModifier(originalCall->Modifiers());
173 newCall->AddBoxingUnboxingFlags(originalCall->GetBoxingUnboxingFlags());
174 auto *scope = NearestScope(newCall->Parent());
175 auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(context->checker->VarBinder()->AsETSBinder(), scope);
176 CheckLoweredNode(context->checker->VarBinder()->AsETSBinder(), context->checker->AsETSChecker(), newCall);
177 return newCall;
178 }
179
TransformCallConstructWithRestArgs(ir::ETSNewClassInstanceExpression * expr,public_lib::Context * context)180 ir::ETSNewClassInstanceExpression *RestArgsLowering::TransformCallConstructWithRestArgs(
181 ir::ETSNewClassInstanceExpression *expr, public_lib::Context *context)
182 {
183 checker::Signature *signature = expr->GetSignature();
184 if (!ShouldProcessRestParameters(signature, expr->GetArguments())) {
185 return expr;
186 }
187
188 auto *restArgsArray = CreateRestArgsArray(context, expr->GetArguments(), signature);
189 restArgsArray->AddAstNodeFlags(ir::AstNodeFlags::RESIZABLE_REST);
190
191 return RebuildNewClassInstanceExpression(context, expr, signature, restArgsArray);
192 }
193
TransformCallExpressionWithRestArgs(ir::CallExpression * callExpr,public_lib::Context * context)194 ir::CallExpression *RestArgsLowering::TransformCallExpressionWithRestArgs(ir::CallExpression *callExpr,
195 public_lib::Context *context)
196 {
197 checker::Type *calleeType = callExpr->Callee()->TsType();
198 if (calleeType == nullptr || calleeType->IsETSDynamicType() || calleeType->IsETSArrowType()) {
199 return callExpr;
200 }
201
202 checker::Signature *signature = callExpr->Signature();
203 if (!ShouldProcessRestParameters(signature, callExpr->Arguments())) {
204 return callExpr;
205 }
206
207 auto *restArgsArray = CreateRestArgsArray(context, callExpr->Arguments(), signature);
208 restArgsArray->AddAstNodeFlags(ir::AstNodeFlags::RESIZABLE_REST);
209
210 return RebuildCallExpression(context, callExpr, signature, restArgsArray);
211 }
212
PerformForModule(public_lib::Context * ctx,parser::Program * program)213 bool RestArgsLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program)
214 {
215 program->Ast()->TransformChildrenRecursively(
216 [this, ctx](ir::AstNode *node) -> AstNodePtr {
217 if (node->IsCallExpression()) {
218 return TransformCallExpressionWithRestArgs(node->AsCallExpression(), ctx);
219 }
220 if (node->IsETSNewClassInstanceExpression()) {
221 return TransformCallConstructWithRestArgs(node->AsETSNewClassInstanceExpression(), ctx);
222 }
223 return node;
224 },
225 Name());
226 return true;
227 }
228 } // namespace ark::es2panda::compiler