1 /*
2 * Copyright (c) 2021-2024 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 //
17 // This is a sample lowering, of little value by itself.
18 //
19 // desc: A compound assignment expression of the form E1 op= E2 is equivalent to E1 =
20 // ((E1) op (E2)) as T, where T is the type of E1, except that E1 is evaluated only
21 // once.
22 //
23
24 #include "opAssignment.h"
25
26 #include "parser/ETSparser.h"
27 #include "varbinder/ETSBinder.h"
28 #include "checker/ETSchecker.h"
29 #include "compiler/lowering/util.h"
30 #include "compiler/lowering/scopesInit/scopesInitPhase.h"
31 #include "ir/opaqueTypeNode.h"
32 #include "ir/expressions/assignmentExpression.h"
33 #include "ir/expressions/identifier.h"
34 #include "ir/expressions/memberExpression.h"
35 #include "ir/expressions/blockExpression.h"
36 #include "ir/statements/blockStatement.h"
37 #include "ir/statements/expressionStatement.h"
38
39 namespace ark::es2panda::compiler {
40
41 struct Conversion {
42 lexer::TokenType from;
43 lexer::TokenType to;
44 };
45
46 // NOLINTNEXTLINE(readability-magic-numbers)
47 static constexpr std::array<Conversion, 18> OP_TRANSLATION {{
48 {lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT_EQUAL, lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT},
49 {lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT_EQUAL, lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT},
50 {lexer::TokenType::PUNCTUATOR_LEFT_SHIFT_EQUAL, lexer::TokenType::PUNCTUATOR_LEFT_SHIFT},
51 {lexer::TokenType::PUNCTUATOR_PLUS_EQUAL, lexer::TokenType::PUNCTUATOR_PLUS},
52 {lexer::TokenType::PUNCTUATOR_MINUS_EQUAL, lexer::TokenType::PUNCTUATOR_MINUS},
53 {lexer::TokenType::PUNCTUATOR_MULTIPLY_EQUAL, lexer::TokenType::PUNCTUATOR_MULTIPLY},
54 {lexer::TokenType::PUNCTUATOR_DIVIDE_EQUAL, lexer::TokenType::PUNCTUATOR_DIVIDE},
55 {lexer::TokenType::PUNCTUATOR_MOD_EQUAL, lexer::TokenType::PUNCTUATOR_MOD},
56 {lexer::TokenType::PUNCTUATOR_BITWISE_AND_EQUAL, lexer::TokenType::PUNCTUATOR_BITWISE_AND},
57 {lexer::TokenType::PUNCTUATOR_BITWISE_OR_EQUAL, lexer::TokenType::PUNCTUATOR_BITWISE_OR},
58 {lexer::TokenType::PUNCTUATOR_BITWISE_XOR_EQUAL, lexer::TokenType::PUNCTUATOR_BITWISE_XOR},
59 {lexer::TokenType::PUNCTUATOR_LOGICAL_AND_EQUAL, lexer::TokenType::PUNCTUATOR_LOGICAL_AND},
60 {lexer::TokenType::PUNCTUATOR_LOGICAL_OR_EQUAL, lexer::TokenType::PUNCTUATOR_LOGICAL_OR},
61 {lexer::TokenType::PUNCTUATOR_LOGICAL_NULLISH_EQUAL, lexer::TokenType::PUNCTUATOR_NULLISH_COALESCING},
62 {lexer::TokenType::PUNCTUATOR_EXPONENTIATION_EQUAL, lexer::TokenType::PUNCTUATOR_EXPONENTIATION},
63 {lexer::TokenType::PUNCTUATOR_PLUS_PLUS, lexer::TokenType::PUNCTUATOR_PLUS},
64 {lexer::TokenType::PUNCTUATOR_MINUS_MINUS, lexer::TokenType::PUNCTUATOR_MINUS},
65 }};
66
CombinedOpToOp(const lexer::TokenType combinedOp)67 static lexer::TokenType CombinedOpToOp(const lexer::TokenType combinedOp)
68 {
69 for (const auto &conv : OP_TRANSLATION) {
70 if (conv.from == combinedOp) {
71 return conv.to;
72 }
73 }
74 UNREACHABLE();
75 }
76
AdjustBoxingUnboxingFlags(ir::Expression * loweringResult,const ir::Expression * oldExpr)77 void AdjustBoxingUnboxingFlags(ir::Expression *loweringResult, const ir::Expression *oldExpr)
78 {
79 ir::Expression *exprToProcess = nullptr;
80 if (loweringResult->IsAssignmentExpression()) {
81 exprToProcess = loweringResult->AsAssignmentExpression();
82 } else if (loweringResult->IsBlockExpression() && !loweringResult->AsBlockExpression()->Statements().empty()) {
83 auto *statement = loweringResult->AsBlockExpression()->Statements().back();
84 if (statement->IsExpressionStatement()) {
85 exprToProcess = statement->AsExpressionStatement()->GetExpression();
86 }
87 } else {
88 UNREACHABLE();
89 }
90
91 // NOTE: gogabr. make sure that the checker never puts both a boxing and an unboxing flag on the same node.
92 // Then this function will become unnecessary.
93 const ir::BoxingUnboxingFlags oldBoxingFlag {oldExpr->GetBoxingUnboxingFlags() &
94 ir::BoxingUnboxingFlags::BOXING_FLAG};
95 const ir::BoxingUnboxingFlags oldUnboxingFlag {oldExpr->GetBoxingUnboxingFlags() &
96 ir::BoxingUnboxingFlags::UNBOXING_FLAG};
97
98 if (exprToProcess->TsType()->IsETSPrimitiveType()) {
99 loweringResult->SetBoxingUnboxingFlags(oldBoxingFlag);
100 } else if (exprToProcess->TsType()->IsETSObjectType()) {
101 loweringResult->SetBoxingUnboxingFlags(oldUnboxingFlag);
102 }
103 }
104
CreateProxyTypeNode(checker::ETSChecker * checker,ir::Expression * expr)105 static ir::OpaqueTypeNode *CreateProxyTypeNode(checker::ETSChecker *checker, ir::Expression *expr)
106 {
107 auto *lcType = expr->TsType();
108 if (auto *lcTypeAsPrimitive = checker->MaybeUnboxInRelation(lcType); lcTypeAsPrimitive != nullptr) {
109 lcType = lcTypeAsPrimitive;
110 }
111 return checker->AllocNode<ir::OpaqueTypeNode>(lcType);
112 }
113
GenFormatForExpression(ir::Expression * expr,size_t ix1,size_t ix2)114 static std::string GenFormatForExpression(ir::Expression *expr, size_t ix1, size_t ix2)
115 {
116 std::string res = "@@I" + std::to_string(ix1);
117
118 if (expr->IsTSNonNullExpression()) {
119 expr = expr->AsTSNonNullExpression()->Expr();
120 }
121
122 if (expr->IsMemberExpression()) {
123 auto const kind = expr->AsMemberExpression()->Kind();
124 if (kind == ir::MemberExpressionKind::PROPERTY_ACCESS) {
125 res += ".@@I" + std::to_string(ix2);
126 } else if (kind == ir::MemberExpressionKind::ELEMENT_ACCESS) {
127 res += "[@@I" + std::to_string(ix2) + "]";
128 }
129 }
130 return res;
131 }
132
GenerateStringForLoweredAssignment(lexer::TokenType opEqual,ir::Expression * expr)133 static std::string GenerateStringForLoweredAssignment(lexer::TokenType opEqual, ir::Expression *expr)
134 {
135 std::string leftHand = GenFormatForExpression(expr, 5, 6);
136 std::string rightHand = GenFormatForExpression(expr, 7, 8);
137
138 return leftHand + " = (" + rightHand + ' ' + std::string {lexer::TokenToString(CombinedOpToOp(opEqual))} +
139 " (@@E9)) as @@T10";
140 }
141
GetClone(ArenaAllocator * allocator,ir::Identifier * node)142 static ir::Identifier *GetClone(ArenaAllocator *allocator, ir::Identifier *node)
143 {
144 return node == nullptr ? nullptr : node->Clone(allocator, nullptr);
145 }
146
ConstructOpAssignmentResult(public_lib::Context * ctx,ir::AssignmentExpression * assignment)147 static ir::Expression *ConstructOpAssignmentResult(public_lib::Context *ctx, ir::AssignmentExpression *assignment)
148 {
149 auto *allocator = ctx->allocator;
150 auto *parser = ctx->parser->AsETSParser();
151 auto *checker = ctx->checker->AsETSChecker();
152
153 const auto opEqual = assignment->OperatorType();
154 ASSERT(opEqual != lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
155
156 auto *const left = assignment->Left();
157 auto *const right = assignment->Right();
158
159 std::string newAssignmentStatements {};
160
161 ir::Identifier *ident1;
162 ir::Identifier *ident2 = nullptr;
163 ir::Expression *object = nullptr;
164 ir::Expression *property = nullptr;
165
166 // Create temporary variable(s) if left hand of assignment is not defined by simple identifier[s]
167 if (left->IsIdentifier()) {
168 ident1 = left->AsIdentifier();
169 } else if (left->IsMemberExpression()) {
170 auto *const memberExpression = left->AsMemberExpression();
171
172 if (object = memberExpression->Object(); object->IsIdentifier()) {
173 ident1 = object->AsIdentifier();
174 } else {
175 ident1 = Gensym(allocator);
176 newAssignmentStatements = "const @@I1 = (@@E2); ";
177 }
178
179 if (property = memberExpression->Property(); property->IsIdentifier()) {
180 ident2 = property->AsIdentifier();
181 } else {
182 ident2 = Gensym(allocator);
183 newAssignmentStatements += "const @@I3 = (@@E4); ";
184 }
185 } else {
186 UNREACHABLE();
187 }
188
189 auto *exprType = CreateProxyTypeNode(checker, left);
190
191 // Generate ArkTS code string for new lowered assignment expression:
192 newAssignmentStatements += GenerateStringForLoweredAssignment(opEqual, left);
193
194 // Parse ArkTS code string and create corresponding AST node(s)
195 return parser->CreateFormattedExpression(newAssignmentStatements, ident1, object, ident2, property,
196 GetClone(allocator, ident1), GetClone(allocator, ident2),
197 GetClone(allocator, ident1), GetClone(allocator, ident2), right, exprType);
198 }
199
HandleOpAssignment(public_lib::Context * ctx,ir::AssignmentExpression * assignment)200 ir::AstNode *HandleOpAssignment(public_lib::Context *ctx, ir::AssignmentExpression *assignment)
201 {
202 auto *checker = ctx->checker->AsETSChecker();
203
204 if (assignment->TsType() == nullptr) { // hasn't been through checker
205 return assignment;
206 }
207
208 auto *loweringResult = ConstructOpAssignmentResult(ctx, assignment);
209
210 loweringResult->SetParent(assignment->Parent());
211 // NOTE(dslynko, #19200): required for correct debug-info
212 auto rng = assignment->Range();
213 loweringResult->SetRange(rng);
214 loweringResult->TransformChildrenRecursively(
215 [rng](auto *node) {
216 node->SetRange(rng);
217 return node;
218 },
219 "");
220
221 auto *const scope = NearestScope(assignment);
222
223 auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
224 InitScopesPhaseETS::RunExternalNode(loweringResult, ctx->parserProgram->VarBinder());
225 checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult, scope);
226
227 checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY, ContainingClass(assignment)};
228 checker::ScopeContext sc {checker, scope};
229
230 loweringResult->Check(checker);
231
232 AdjustBoxingUnboxingFlags(loweringResult, assignment);
233
234 return loweringResult;
235 }
236
237 struct ArgumentInfo {
238 std::string newAssignmentStatements {};
239 ir::Identifier *id1 = nullptr;
240 ir::Identifier *id2 = nullptr;
241 ir::Identifier *id3 = nullptr;
242 ir::Expression *object = nullptr;
243 ir::Expression *property = nullptr;
244 checker::Type *objType = nullptr;
245 checker::Type *propType = nullptr;
246 };
247
ParseArgument(public_lib::Context * ctx,ir::Expression * argument,ArgumentInfo & info)248 static void ParseArgument(public_lib::Context *ctx, ir::Expression *argument, ArgumentInfo &info)
249 {
250 auto *allocator = ctx->allocator;
251
252 if (argument->IsTSNonNullExpression()) {
253 argument = argument->AsTSNonNullExpression()->Expr();
254 }
255
256 if (argument->IsIdentifier()) {
257 info.id1 = GetClone(allocator, argument->AsIdentifier());
258 } else if (argument->IsMemberExpression()) {
259 auto *memberExpression = argument->AsMemberExpression();
260
261 if (info.object = memberExpression->Object(); info.object != nullptr && info.object->IsIdentifier()) {
262 info.id1 = GetClone(allocator, info.object->AsIdentifier());
263 } else if (info.object != nullptr) {
264 info.id1 = Gensym(allocator);
265 info.newAssignmentStatements = "const @@I1 = (@@E2) as @@T3; ";
266 info.objType = info.object->TsType();
267 }
268
269 if (info.property = memberExpression->Property(); info.property != nullptr && info.property->IsIdentifier()) {
270 info.id2 = GetClone(allocator, info.property->AsIdentifier());
271 } else if (info.property != nullptr) {
272 info.id2 = Gensym(allocator);
273 info.newAssignmentStatements += "const @@I4 = (@@E5) as @@T6; ";
274 info.propType = info.property->TsType();
275 }
276 }
277 }
278
ConstructUpdateResult(public_lib::Context * ctx,ir::UpdateExpression * upd)279 static ir::Expression *ConstructUpdateResult(public_lib::Context *ctx, ir::UpdateExpression *upd)
280 {
281 auto *allocator = ctx->allocator;
282 auto *parser = ctx->parser->AsETSParser();
283 auto *argument = upd->Argument();
284 auto *checker = ctx->checker->AsETSChecker();
285
286 ArgumentInfo argInfo {};
287 argInfo.objType = checker->GlobalVoidType();
288 argInfo.propType = checker->GlobalVoidType();
289
290 // Parse ArkTS code string and create the corresponding AST node(s)
291 // We have to use extra caution with types and `as` conversions because of smart types, which we cannot reproduce in
292 // re-checking.
293 ParseArgument(ctx, argument, argInfo);
294 std::string opSign = lexer::TokenToString(CombinedOpToOp(upd->OperatorType()));
295
296 std::string suffix = argument->TsType()->IsETSBigIntType() ? "n" : "";
297
298 // NOLINTBEGIN(readability-magic-numbers)
299 if (upd->IsPrefix()) {
300 argInfo.newAssignmentStatements += GenFormatForExpression(argument, 7U, 8U) + " = (" +
301 GenFormatForExpression(argument, 9U, 10U) + opSign + " 1" + suffix +
302 ") as @@T11;";
303 return parser->CreateFormattedExpression(
304 argInfo.newAssignmentStatements, argInfo.id1, argInfo.object, argInfo.objType, argInfo.id2,
305 argInfo.property, argInfo.propType, GetClone(allocator, argInfo.id1), GetClone(allocator, argInfo.id2),
306 GetClone(allocator, argInfo.id1), GetClone(allocator, argInfo.id2), argument->TsType());
307 }
308
309 // upd is postfix
310 argInfo.id3 = Gensym(allocator);
311 argInfo.newAssignmentStatements += "const @@I7 = " + GenFormatForExpression(argument, 8, 9) + " as @@T10;" +
312 GenFormatForExpression(argument, 11U, 12U) + " = (@@I13 " + opSign + " 1" +
313 suffix + ") as @@T14; @@I15;";
314 return parser->CreateFormattedExpression(
315 argInfo.newAssignmentStatements, argInfo.id1, argInfo.object, argInfo.objType, argInfo.id2, argInfo.property,
316 argInfo.propType, argInfo.id3, GetClone(allocator, argInfo.id1), GetClone(allocator, argInfo.id2),
317 argument->TsType(), GetClone(allocator, argInfo.id1), GetClone(allocator, argInfo.id2),
318 GetClone(allocator, argInfo.id3), argument->TsType(), GetClone(allocator, argInfo.id3));
319 // NOLINTEND(readability-magic-numbers)
320 }
321
HandleUpdate(public_lib::Context * ctx,ir::UpdateExpression * upd)322 static ir::AstNode *HandleUpdate(public_lib::Context *ctx, ir::UpdateExpression *upd)
323 {
324 auto *checker = ctx->checker->AsETSChecker();
325
326 auto *const scope = NearestScope(upd);
327
328 ir::Expression *loweringResult = ConstructUpdateResult(ctx, upd);
329
330 auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
331 checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY, ContainingClass(upd)};
332 checker::ScopeContext sc {checker, scope};
333
334 loweringResult->SetParent(upd->Parent());
335 // NOTE(dslynko, #19200): required for correct debug-info
336 auto rng = upd->Range();
337 loweringResult->SetRange(rng);
338 loweringResult->TransformChildrenRecursively(
339 [rng](auto *node) {
340 node->SetRange(rng);
341 return node;
342 },
343 "");
344 InitScopesPhaseETS::RunExternalNode(loweringResult, ctx->checker->VarBinder());
345
346 checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult,
347 NearestScope(loweringResult));
348 loweringResult->Check(checker);
349
350 AdjustBoxingUnboxingFlags(loweringResult, upd);
351
352 return loweringResult;
353 }
354
Perform(public_lib::Context * ctx,parser::Program * program)355 bool OpAssignmentLowering::Perform(public_lib::Context *ctx, parser::Program *program)
356 {
357 if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) {
358 for (auto &[_, ext_programs] : program->ExternalSources()) {
359 (void)_;
360 for (auto *extProg : ext_programs) {
361 Perform(ctx, extProg);
362 }
363 }
364 }
365
366 program->Ast()->TransformChildrenRecursively(
367 [ctx](ir::AstNode *ast) {
368 if (ast->IsAssignmentExpression() &&
369 ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) {
370 return HandleOpAssignment(ctx, ast->AsAssignmentExpression());
371 }
372 if (ast->IsUpdateExpression()) {
373 return HandleUpdate(ctx, ast->AsUpdateExpression());
374 }
375
376 return ast;
377 },
378 Name());
379
380 return true;
381 }
382
Postcondition(public_lib::Context * ctx,const parser::Program * program)383 bool OpAssignmentLowering::Postcondition(public_lib::Context *ctx, const parser::Program *program)
384 {
385 auto checkExternalPrograms = [this, ctx](const ArenaVector<parser::Program *> &programs) {
386 for (auto *p : programs) {
387 if (!Postcondition(ctx, p)) {
388 return false;
389 }
390 }
391 return true;
392 };
393
394 if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) {
395 for (auto &[_, extPrograms] : program->ExternalSources()) {
396 (void)_;
397 if (!checkExternalPrograms(extPrograms)) {
398 return false;
399 };
400 }
401 }
402
403 return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) {
404 return (ast->IsAssignmentExpression() && ast->AsAssignmentExpression()->TsType() != nullptr &&
405 ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) ||
406 ast->IsUpdateExpression();
407 });
408 }
409
410 } // namespace ark::es2panda::compiler
411