1 /*
2 * Copyright (c) 2024-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 "spreadLowering.h"
17 #include "checker/ETSchecker.h"
18 #include "checker/types/ets/etsTupleType.h"
19 #include "compiler/lowering/util.h"
20
21 namespace ark::es2panda::compiler {
22
23 using AstNodePtr = ir::AstNode *;
24
SetPossibleTupleType(ir::Identifier * arrIdent,ir::Expression * spreadArgument)25 void SetPossibleTupleType(ir::Identifier *arrIdent, ir::Expression *spreadArgument)
26 {
27 // Tuple types are used when referenceing the generated identifier of the spread argument node, as tuples don't have
28 // 'length' property, and cannot be iterated by for-of statements
29 auto *const spreadType = spreadArgument->TsType();
30 if (spreadType->IsETSTupleType()) {
31 arrIdent->SetTsType(spreadType);
32 }
33 }
34
CreateSpreadArrayDeclareStatements(public_lib::Context * ctx,ir::ArrayExpression * array,std::vector<ir::Identifier * > & spreadArrayIds,ArenaVector<ir::Statement * > & statements)35 void CreateSpreadArrayDeclareStatements(public_lib::Context *ctx, ir::ArrayExpression *array,
36 std::vector<ir::Identifier *> &spreadArrayIds,
37 ArenaVector<ir::Statement *> &statements)
38 {
39 auto *const allocator = ctx->allocator;
40 auto *const parser = ctx->parser->AsETSParser();
41 for (auto element : array->Elements()) {
42 if (element->Type() != ir::AstNodeType::SPREAD_ELEMENT) {
43 continue;
44 }
45 ir::Identifier *const arrIdent = Gensym(allocator);
46 auto *const spreadArgument = element->AsSpreadElement()->Argument();
47 auto *const initExpr = spreadArgument->Clone(allocator, nullptr);
48 SetPossibleTupleType(arrIdent, spreadArgument);
49 spreadArrayIds.emplace_back(arrIdent);
50 statements.emplace_back(parser->CreateFormattedStatement("let @@I1 = (@@E2);", arrIdent, initExpr));
51 }
52 }
53
CreateNewArrayLengthStatement(public_lib::Context * ctx,ir::ArrayExpression * array,std::vector<ir::Identifier * > & spreadArrayIds,ArenaVector<ir::Statement * > & statements)54 ir::Identifier *CreateNewArrayLengthStatement(public_lib::Context *ctx, ir::ArrayExpression *array,
55 std::vector<ir::Identifier *> &spreadArrayIds,
56 ArenaVector<ir::Statement *> &statements)
57 {
58 auto *const allocator = ctx->allocator;
59 auto *const parser = ctx->parser->AsETSParser();
60 ir::Identifier *newArrayLengthId = Gensym(allocator);
61 ES2PANDA_ASSERT(newArrayLengthId != nullptr);
62 std::vector<ir::AstNode *> nodesWaitingInsert {newArrayLengthId->Clone(allocator, nullptr)};
63 size_t argumentCount = 1;
64 std::stringstream lengthString;
65 const size_t normalElementCount = array->Elements().size() - spreadArrayIds.size();
66 lengthString << "let @@I" << (argumentCount++) << " : int = " << normalElementCount << " + ";
67 for (auto *const spaId : spreadArrayIds) {
68 if (spaId->TsType() != nullptr && spaId->TsType()->IsETSTupleType()) {
69 lengthString << "(" << spaId->TsType()->AsETSTupleType()->GetTupleSize() << ") + ";
70 } else {
71 lengthString << "(@@I" << (argumentCount++) << ".length as int) + ";
72 nodesWaitingInsert.emplace_back(spaId->Clone(allocator, nullptr));
73 }
74 }
75 lengthString << "0;";
76
77 ir::Statement *newArrayLengthStatement = parser->CreateFormattedStatement(lengthString.str(), nodesWaitingInsert);
78 statements.emplace_back(newArrayLengthStatement);
79 return newArrayLengthId;
80 }
81
CreateNewArrayDeclareStatement(public_lib::Context * ctx,ir::ArrayExpression * array,ArenaVector<ir::Statement * > & statements,ir::Identifier * newArrayLengthId)82 static ir::Identifier *CreateNewArrayDeclareStatement(public_lib::Context *ctx, ir::ArrayExpression *array,
83 ArenaVector<ir::Statement *> &statements,
84 ir::Identifier *newArrayLengthId)
85 {
86 auto *const checker = ctx->checker->AsETSChecker();
87 auto *const allocator = ctx->allocator;
88 auto *const parser = ctx->parser->AsETSParser();
89 ir::Identifier *newArrayId = Gensym(allocator);
90 checker::Type *arrayElementType = checker->GetElementTypeOfArray(array->TsType());
91
92 // NOTE: If arrayElementType is ETSUnionType(String|Int) or ETSObjectType(private constructor) or ..., we cannot
93 // use "new Type[]" to declare an array, so we generate a new UnionType "arrayElementType|null" to solve
94 // array initialization problems temporarily.
95 // We probably need to use cast Expression in the end of the generated source code to remove "|null", such as
96 // "newArrayName as arrayType[]".
97 // But now cast Expression doesn't support built-in array (cast fatherType[] to sonType[]), so "newArrayName
98 // as arrayType" should be added after cast Expression is implemented completely.
99 // Related issue: #issue20162
100 if (checker::ETSChecker::IsReferenceType(arrayElementType)) {
101 arrayElementType = checker->CreateETSUnionType({arrayElementType, checker->GlobalETSUndefinedType()});
102 }
103
104 std::stringstream newArrayDeclareStr;
105 if (array->TsType()->IsETSResizableArrayType()) {
106 newArrayDeclareStr << "let @@I1: Array<@@T2> = new Array<@@T3>(@@I4);" << std::endl;
107 } else {
108 newArrayDeclareStr << "let @@I1: FixedArray<@@T2> = new (@@T3)[@@I4];" << std::endl;
109 }
110
111 ES2PANDA_ASSERT(newArrayLengthId != nullptr);
112 ir::Statement *newArrayDeclareSt = parser->CreateFormattedStatement(
113 newArrayDeclareStr.str(), newArrayId->Clone(allocator, nullptr), arrayElementType, arrayElementType,
114 newArrayLengthId->Clone(allocator, nullptr));
115 statements.emplace_back(newArrayDeclareSt);
116
117 return newArrayId;
118 }
119
GenerateNewTupleInitList(ArenaAllocator * allocator,ir::ArrayExpression * array,std::vector<ir::AstNode * > & elementNodes)120 static std::string GenerateNewTupleInitList(ArenaAllocator *allocator, ir::ArrayExpression *array,
121 std::vector<ir::AstNode *> &elementNodes)
122 {
123 std::string tupleInitList {};
124
125 for (auto *element : array->Elements()) {
126 if (element->IsSpreadElement()) {
127 // Only a tuple type can be spread into a new tuple type
128 ES2PANDA_ASSERT(element->AsSpreadElement()->Argument()->TsType()->IsETSTupleType());
129 auto *const argumentTupleType = element->AsSpreadElement()->Argument()->TsType()->AsETSTupleType();
130
131 // NOTE (smartin): make a distinct variable for the spread argument. Now if the argument is a function call
132 // (that returns a tuple), it'll run every time when inserted into the new initializer. It should however
133 // run once, and index the element from that result.
134
135 for (std::size_t idx = 0; idx < argumentTupleType->GetTupleSize(); idx++) {
136 tupleInitList +=
137 "@@E" + std::to_string(elementNodes.size() + 1) + "[" + std::to_string(idx) + "]" + ", ";
138 elementNodes.emplace_back(element->Clone(allocator, nullptr)->AsSpreadElement()->Argument());
139 }
140 } else {
141 tupleInitList += "@@E" + std::to_string(elementNodes.size() + 1) + ", ";
142 elementNodes.emplace_back(element->Clone(allocator, nullptr));
143 }
144 }
145
146 tupleInitList.pop_back();
147 return tupleInitList;
148 }
149
GenerateTupleInitExpr(public_lib::Context * ctx,ir::ArrayExpression * array)150 static ir::Expression *GenerateTupleInitExpr(public_lib::Context *ctx, ir::ArrayExpression *array)
151 {
152 auto *const parser = ctx->parser->AsETSParser();
153 auto *const allocator = ctx->allocator;
154
155 std::vector<ir::AstNode *> arrayExprElementNodes {};
156 std::stringstream newTupleExpr;
157
158 newTupleExpr << "[";
159 newTupleExpr << GenerateNewTupleInitList(allocator, array, arrayExprElementNodes);
160 newTupleExpr << "];";
161
162 return parser->CreateFormattedExpression(newTupleExpr.str(), arrayExprElementNodes);
163 }
164
CreateNewTupleDeclareStatement(public_lib::Context * ctx,ir::ArrayExpression * array,ArenaVector<ir::Statement * > & statements)165 static ir::Identifier *CreateNewTupleDeclareStatement(public_lib::Context *ctx, ir::ArrayExpression *array,
166 ArenaVector<ir::Statement *> &statements)
167 {
168 auto *const allocator = ctx->allocator;
169 auto *const parser = ctx->parser->AsETSParser();
170 ir::Identifier *newTupleId = Gensym(allocator);
171 ES2PANDA_ASSERT(newTupleId != nullptr);
172 checker::ETSTupleType *tupleType = array->TsType()->AsETSTupleType();
173
174 std::stringstream newArrayDeclareStr;
175 newArrayDeclareStr << "let @@I1: (@@T2) = (@@E3);" << std::endl;
176
177 ir::Expression *tupleCreationExpr = GenerateTupleInitExpr(ctx, array);
178
179 ir::Statement *newTupleInitStmt = parser->CreateFormattedStatement(
180 newArrayDeclareStr.str(), newTupleId->Clone(allocator, nullptr), tupleType, tupleCreationExpr);
181 statements.emplace_back(newTupleInitStmt);
182
183 return newTupleId;
184 }
185
CreateElementsAssignStatementBySpreadArr(public_lib::Context * ctx,ir::Identifier * spId,std::vector<ir::AstNode * > & newArrayAndIndex,ir::Identifier * spreadArrIterator,checker::Type * arrayElementType)186 static ir::Statement *CreateElementsAssignStatementBySpreadArr(public_lib::Context *ctx, ir::Identifier *spId,
187 std::vector<ir::AstNode *> &newArrayAndIndex,
188 ir::Identifier *spreadArrIterator,
189 checker::Type *arrayElementType)
190 {
191 auto *const allocator = ctx->allocator;
192 auto *const parser = ctx->parser->AsETSParser();
193 auto *const newArrayId = newArrayAndIndex[0];
194 auto *const newArrayIndexId = newArrayAndIndex[1];
195
196 std::stringstream elementsAssignStr;
197 elementsAssignStr << "for (let @@I1 of @@I2) {";
198 elementsAssignStr << "@@I3[@@I4] = @@I5 as @@T6;";
199 elementsAssignStr << "@@I7++;";
200 elementsAssignStr << "}";
201
202 ES2PANDA_ASSERT(spreadArrIterator != nullptr);
203 ir::Statement *elementsAssignStatement = parser->CreateFormattedStatement(
204 elementsAssignStr.str(), spreadArrIterator->Clone(allocator, nullptr), spId->Clone(allocator, nullptr),
205 newArrayId->Clone(allocator, nullptr), newArrayIndexId->Clone(allocator, nullptr),
206 spreadArrIterator->Clone(allocator, nullptr), arrayElementType, newArrayIndexId->Clone(allocator, nullptr));
207
208 return elementsAssignStatement;
209 }
210
CreateElementsAssignStatementBySingle(public_lib::Context * ctx,ir::AstNode * element,std::vector<ir::AstNode * > & newArrayAndIndex)211 static ir::Statement *CreateElementsAssignStatementBySingle(public_lib::Context *ctx, ir::AstNode *element,
212 std::vector<ir::AstNode *> &newArrayAndIndex)
213 {
214 auto *const allocator = ctx->allocator;
215 auto *const parser = ctx->parser->AsETSParser();
216 auto *const newArrayId = newArrayAndIndex[0];
217 auto *const newArrayIndexId = newArrayAndIndex[1];
218 std::stringstream elementsAssignStr;
219 elementsAssignStr << "@@I1[@@I2] = (@@E3);";
220 elementsAssignStr << "@@I4++;";
221
222 ir::Statement *elementsAssignStatement = parser->CreateFormattedStatement(
223 elementsAssignStr.str(), newArrayId->Clone(allocator, nullptr), newArrayIndexId->Clone(allocator, nullptr),
224 element->Clone(allocator, nullptr), newArrayIndexId->Clone(allocator, nullptr));
225
226 return elementsAssignStatement;
227 }
228
CreateElementsAssignForTupleElements(public_lib::Context * ctx,ir::Identifier * spId,std::vector<ir::AstNode * > & newArrayAndIndex)229 static std::vector<ir::Statement *> CreateElementsAssignForTupleElements(public_lib::Context *ctx, ir::Identifier *spId,
230 std::vector<ir::AstNode *> &newArrayAndIndex)
231 {
232 auto *const allocator = ctx->allocator;
233 auto *const parser = ctx->parser->AsETSParser();
234 auto *const newArrayId = newArrayAndIndex[0];
235 auto *const newArrayIndexId = newArrayAndIndex[1];
236
237 ES2PANDA_ASSERT(spId->TsType()->IsETSTupleType());
238 const auto *const spreadType = spId->TsType()->AsETSTupleType();
239 std::vector<ir::Statement *> tupleAssignmentStatements {};
240
241 for (size_t idx = 0; idx < spreadType->GetTupleTypesList().size(); ++idx) {
242 std::stringstream tupleAssignmentsStr {};
243 auto *elementType = spreadType->GetTupleTypesList()[idx];
244 tupleAssignmentsStr << "@@I1[@@I2] = (@@I3[" << idx << "] as @@T4);";
245 tupleAssignmentsStr << "@@I5++;";
246 tupleAssignmentStatements.emplace_back(parser->CreateFormattedStatement(
247 tupleAssignmentsStr.str(), newArrayId->Clone(allocator, nullptr),
248 newArrayIndexId->Clone(allocator, nullptr), spId->Clone(allocator, nullptr), elementType,
249 newArrayIndexId->Clone(allocator, nullptr)));
250 }
251
252 return tupleAssignmentStatements;
253 }
254
CreateReturnStatement(public_lib::Context * ctx,ir::AstNode * newArrayId,ir::ArrayExpression * array)255 static ir::Statement *CreateReturnStatement(public_lib::Context *ctx, ir::AstNode *newArrayId,
256 ir::ArrayExpression *array)
257 {
258 auto *const allocator = ctx->allocator;
259 auto *const parser = ctx->parser->AsETSParser();
260
261 std::vector<ir::AstNode *> nodes {newArrayId};
262 std::stringstream ss;
263 ss << "@@I1 ";
264
265 if (array->TsType()->IsETSResizableArrayType()) {
266 ss << "as Object as Array<@@T2>;" << std::endl;
267 nodes.emplace_back(
268 allocator->New<ir::OpaqueTypeNode>(array->TsType()->AsETSResizableArrayType()->ElementType(), allocator));
269 }
270
271 ir::Statement *returnStatement = parser->CreateFormattedStatement(ss.str(), nodes);
272 return returnStatement;
273 }
274
CreateNewArrayElementsAssignStatement(public_lib::Context * ctx,ir::ArrayExpression * array,std::vector<ir::Identifier * > & spArrIds,ArenaVector<ir::Statement * > & statements,std::vector<ir::AstNode * > & newArrayAndIndex)275 static void CreateNewArrayElementsAssignStatement(public_lib::Context *ctx, ir::ArrayExpression *array,
276 std::vector<ir::Identifier *> &spArrIds,
277 ArenaVector<ir::Statement *> &statements,
278 std::vector<ir::AstNode *> &newArrayAndIndex)
279 {
280 auto *const allocator = ctx->allocator;
281 auto *const newArrayId = newArrayAndIndex[0];
282 size_t spArrIdx = 0;
283
284 for (auto *element : array->Elements()) {
285 if (element->IsSpreadElement()) {
286 if (element->AsSpreadElement()->Argument()->TsType()->IsETSTupleType()) {
287 const auto newTupleAssignmentStatements =
288 CreateElementsAssignForTupleElements(ctx, spArrIds[spArrIdx++], newArrayAndIndex);
289 statements.insert(statements.cend(), newTupleAssignmentStatements.cbegin(),
290 newTupleAssignmentStatements.cend());
291 } else {
292 ir::Identifier *spreadArrIterator = Gensym(allocator);
293 checker::Type *arrayElementType = ctx->checker->AsETSChecker()->GetElementTypeOfArray(array->TsType());
294 statements.emplace_back(CreateElementsAssignStatementBySpreadArr(
295 ctx, spArrIds[spArrIdx++], newArrayAndIndex, spreadArrIterator, arrayElementType));
296 }
297 } else {
298 statements.emplace_back(CreateElementsAssignStatementBySingle(ctx, element, newArrayAndIndex));
299 }
300 }
301
302 statements.emplace_back(CreateReturnStatement(ctx, newArrayId->Clone(allocator, nullptr), array));
303 }
304
305 /*
306 * NOTE: Expand the SpreadExpr to BlockExpr, the rules as follows :
307 * let spreadArrayId1 = spreadExpr1
308 * let length : int = normalExprCount + spreadArrayId1.length + 0
309 * type typeOfNewArray = arrayType
310 * let newArray: typeOfNewArray[] = new typeOfNewArray[length]
311 * let newArrayIndex = 0
312 * let elementOfSpreadArray1: arrayType
313 * for (elementOfSpreadArray1 of spreadArray1) {
314 * newArray[newArrayIndex] = elementOfSpreadArray1
315 * newArrayIndex++
316 * }
317 * newArray[newArrayIndex] = normalExpr1
318 * newArrayIndex++
319 * ...
320 * newArray;
321 */
CreateLoweredExpressionForArray(public_lib::Context * ctx,ir::ArrayExpression * array)322 static ir::BlockExpression *CreateLoweredExpressionForArray(public_lib::Context *ctx, ir::ArrayExpression *array)
323 {
324 auto *const parser = ctx->parser->AsETSParser();
325 auto *const allocator = ctx->allocator;
326 ArenaVector<ir::Statement *> statements(allocator->Adapter());
327 std::vector<ir::Identifier *> spreadArrayIds = {};
328
329 CreateSpreadArrayDeclareStatements(ctx, array, spreadArrayIds, statements);
330
331 ir::Identifier *newArrayLengthId = CreateNewArrayLengthStatement(ctx, array, spreadArrayIds, statements);
332
333 ir::Identifier *newArrayId = CreateNewArrayDeclareStatement(ctx, array, statements, newArrayLengthId);
334 ES2PANDA_ASSERT(newArrayId != nullptr);
335 ir::Identifier *newArrayIndexId = Gensym(allocator);
336 statements.emplace_back(
337 parser->CreateFormattedStatement("let @@I1 = 0", newArrayIndexId->Clone(allocator, nullptr)));
338 std::vector<ir::AstNode *> newArrayAndIndex {newArrayId->Clone(allocator, nullptr),
339 newArrayIndexId->Clone(allocator, nullptr)};
340
341 CreateNewArrayElementsAssignStatement(ctx, array, spreadArrayIds, statements, newArrayAndIndex);
342 return ctx->AllocNode<ir::BlockExpression>(std::move(statements));
343 }
344
345 /*
346 * NOTE: Expand the SpreadExpr to BlockExpr, the rules as follows :
347 * let newTuple: typeOfNewTuple = new std.core.TupleN(normalExpr1, ..., normalExprN);
348 */
CreateLoweredExpressionForTuple(public_lib::Context * ctx,ir::ArrayExpression * array)349 static ir::BlockExpression *CreateLoweredExpressionForTuple(public_lib::Context *ctx, ir::ArrayExpression *array)
350 {
351 auto *const checker = ctx->checker->AsETSChecker();
352 auto *const parser = ctx->parser->AsETSParser();
353 auto *const allocator = ctx->allocator;
354
355 ArenaVector<ir::Statement *> statements(allocator->Adapter());
356 ir::Identifier *newTupleId = CreateNewTupleDeclareStatement(ctx, array, statements);
357 ES2PANDA_ASSERT(newTupleId != nullptr);
358 statements.emplace_back(parser->CreateFormattedStatement("@@I1;", newTupleId->Clone(allocator, nullptr)));
359 return checker->AllocNode<ir::BlockExpression>(std::move(statements));
360 }
361
PerformForModule(public_lib::Context * ctx,parser::Program * program)362 bool SpreadConstructionPhase::PerformForModule(public_lib::Context *ctx, parser::Program *program)
363 {
364 checker::ETSChecker *const checker = ctx->checker->AsETSChecker();
365 varbinder::ETSBinder *const varbinder = checker->VarBinder()->AsETSBinder();
366
367 program->Ast()->TransformChildrenRecursively(
368 [&checker, &varbinder, &ctx](ir::AstNode *const node) -> AstNodePtr {
369 if (node->IsArrayExpression() &&
370 std::any_of(node->AsArrayExpression()->Elements().begin(), node->AsArrayExpression()->Elements().end(),
371 [](const auto *param) { return param->Type() == ir::AstNodeType::SPREAD_ELEMENT; })) {
372 auto scopeCtx =
373 varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), NearestScope(node));
374
375 const auto *const arrayExprType = node->AsArrayExpression()->TsType();
376 ir::BlockExpression *blockExpression =
377 arrayExprType->IsETSArrayType() || arrayExprType->IsETSResizableArrayType()
378 ? CreateLoweredExpressionForArray(ctx, node->AsArrayExpression())
379 : CreateLoweredExpressionForTuple(ctx, node->AsArrayExpression());
380 blockExpression->SetParent(node->Parent());
381
382 // NOTE: this blockExpression is a kind of formatted-dummy code, which is invisible to users,
383 // so, its source range should be same as the original code([element1, element2, ...spreadExpr])
384 blockExpression->SetRange(node->Range());
385 for (auto st : blockExpression->Statements()) {
386 SetSourceRangesRecursively(st, node->Range());
387 }
388 Recheck(ctx->phaseManager, varbinder, checker, blockExpression);
389
390 return blockExpression;
391 }
392
393 return node;
394 },
395 Name());
396 return true;
397 }
398
399 } // namespace ark::es2panda::compiler
400