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()->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE)) {
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->ETSBuiltinTypeAsPrimitiveType(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 if (expr->IsMemberExpression()) {
118 auto const kind = expr->AsMemberExpression()->Kind();
119 if (kind == ir::MemberExpressionKind::PROPERTY_ACCESS) {
120 res += ".@@I" + std::to_string(ix2);
121 } else if (kind == ir::MemberExpressionKind::ELEMENT_ACCESS) {
122 res += "[@@I" + std::to_string(ix2) + "]";
123 }
124 }
125 return res;
126 }
127
GenerateStringForLoweredAssignment(lexer::TokenType opEqual,ir::Expression * expr)128 static std::string GenerateStringForLoweredAssignment(lexer::TokenType opEqual, ir::Expression *expr)
129 {
130 std::string leftHand = GenFormatForExpression(expr, 5, 6);
131 std::string rightHand = GenFormatForExpression(expr, 7, 8);
132
133 return leftHand + " = (" + rightHand + ' ' + std::string {lexer::TokenToString(CombinedOpToOp(opEqual))} +
134 " (@@E9)) as @@T10";
135 }
136
GetClone(ArenaAllocator * allocator,ir::Identifier * node)137 static ir::Identifier *GetClone(ArenaAllocator *allocator, ir::Identifier *node)
138 {
139 return node == nullptr ? nullptr : node->Clone(allocator, nullptr);
140 }
141
ConstructOpAssignmentResult(public_lib::Context * ctx,ir::AssignmentExpression * assignment)142 static ir::Expression *ConstructOpAssignmentResult(public_lib::Context *ctx, ir::AssignmentExpression *assignment)
143 {
144 auto *allocator = ctx->allocator;
145 auto *parser = ctx->parser->AsETSParser();
146 auto *checker = ctx->checker->AsETSChecker();
147
148 const auto opEqual = assignment->OperatorType();
149 ASSERT(opEqual != lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
150
151 auto *const left = assignment->Left();
152 auto *const right = assignment->Right();
153
154 std::string newAssignmentStatements {};
155
156 ir::Identifier *ident1;
157 ir::Identifier *ident2 = nullptr;
158 ir::Expression *object = nullptr;
159 ir::Expression *property = nullptr;
160
161 // Create temporary variable(s) if left hand of assignment is not defined by simple identifier[s]
162 if (left->IsIdentifier()) {
163 ident1 = left->AsIdentifier();
164 } else if (left->IsMemberExpression()) {
165 auto *const memberExpression = left->AsMemberExpression();
166
167 if (object = memberExpression->Object(); object->IsIdentifier()) {
168 ident1 = object->AsIdentifier();
169 } else {
170 ident1 = Gensym(allocator);
171 newAssignmentStatements = "const @@I1 = (@@E2); ";
172 }
173
174 if (property = memberExpression->Property(); property->IsIdentifier()) {
175 ident2 = property->AsIdentifier();
176 } else {
177 ident2 = Gensym(allocator);
178 newAssignmentStatements += "const @@I3 = (@@E4); ";
179 }
180 } else {
181 UNREACHABLE();
182 }
183
184 auto *exprType = CreateProxyTypeNode(checker, left);
185
186 // Generate ArkTS code string for new lowered assignment expression:
187 newAssignmentStatements += GenerateStringForLoweredAssignment(opEqual, left);
188
189 // Parse ArkTS code string and create corresponding AST node(s)
190 return parser->CreateFormattedExpression(newAssignmentStatements, ident1, object, ident2, property,
191 GetClone(allocator, ident1), GetClone(allocator, ident2),
192 GetClone(allocator, ident1), GetClone(allocator, ident2), right, exprType);
193 }
194
HandleOpAssignment(public_lib::Context * ctx,ir::AssignmentExpression * assignment)195 ir::AstNode *HandleOpAssignment(public_lib::Context *ctx, ir::AssignmentExpression *assignment)
196 {
197 auto *checker = ctx->checker->AsETSChecker();
198
199 if (assignment->TsType() == nullptr) { // hasn't been through checker
200 return assignment;
201 }
202
203 auto *loweringResult = ConstructOpAssignmentResult(ctx, assignment);
204
205 loweringResult->SetParent(assignment->Parent());
206
207 auto *const scope = NearestScope(assignment);
208
209 auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
210 InitScopesPhaseETS::RunExternalNode(loweringResult, ctx->parserProgram->VarBinder());
211 checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult, scope);
212
213 checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY, ContainingClass(assignment)};
214 checker::ScopeContext sc {checker, scope};
215
216 loweringResult->Check(checker);
217
218 AdjustBoxingUnboxingFlags(loweringResult, assignment);
219
220 return loweringResult;
221 }
222
ConstructUpdateResult(public_lib::Context * ctx,ir::UpdateExpression * upd)223 static ir::Expression *ConstructUpdateResult(public_lib::Context *ctx, ir::UpdateExpression *upd)
224 {
225 auto *allocator = ctx->allocator;
226 auto *parser = ctx->parser->AsETSParser();
227 auto *argument = upd->Argument();
228 auto *checker = ctx->checker->AsETSChecker();
229
230 std::string newAssignmentStatements {};
231
232 ir::Identifier *id1;
233 ir::Identifier *id2 = nullptr;
234 ir::Identifier *id3 = nullptr;
235 ir::Expression *object = nullptr;
236 ir::Expression *property = nullptr;
237 checker::Type *objType = checker->GlobalVoidType(); // placeholder
238 checker::Type *propType = checker->GlobalVoidType();
239
240 // Parse ArkTS code string and create the corresponding AST node(s)
241 // We have to use extra caution with types and `as` conversions because of smart types, which we cannot reproduce in
242 // re-checking.
243
244 if (argument->IsIdentifier()) {
245 id1 = GetClone(allocator, argument->AsIdentifier());
246 } else if (argument->IsMemberExpression()) {
247 auto *memberExpression = argument->AsMemberExpression();
248
249 if (object = memberExpression->Object(); object != nullptr && object->IsIdentifier()) {
250 id1 = GetClone(allocator, object->AsIdentifier());
251 } else if (object != nullptr) {
252 id1 = Gensym(allocator);
253 newAssignmentStatements = "const @@I1 = (@@E2) as @@T3; ";
254 objType = object->TsType();
255 }
256
257 if (property = memberExpression->Property(); property != nullptr && property->IsIdentifier()) {
258 id2 = GetClone(allocator, property->AsIdentifier());
259 } else if (property != nullptr) {
260 id2 = Gensym(allocator);
261 newAssignmentStatements += "const @@I4 = (@@E5) as @@T6; ";
262 propType = property->TsType();
263 }
264 }
265
266 std::string opSign = lexer::TokenToString(CombinedOpToOp(upd->OperatorType()));
267
268 std::string suffix = (argument->TsType() == checker->GlobalETSBigIntType()) ? "n" : "";
269
270 // NOLINTBEGIN(readability-magic-numbers)
271 if (upd->IsPrefix()) {
272 newAssignmentStatements += GenFormatForExpression(argument, 7U, 8U) + " = (" +
273 GenFormatForExpression(argument, 9U, 10U) + opSign + " 1" + suffix + ") as @@T11;";
274 return parser->CreateFormattedExpression(
275 newAssignmentStatements, id1, object, objType, id2, property, propType, GetClone(allocator, id1),
276 GetClone(allocator, id2), GetClone(allocator, id1), GetClone(allocator, id2), argument->TsType());
277 }
278
279 // upd is postfix
280 id3 = Gensym(allocator);
281 newAssignmentStatements += "const @@I7 = " + GenFormatForExpression(argument, 8, 9) + " as @@T10;" +
282 GenFormatForExpression(argument, 11U, 12U) + " = (@@I13 " + opSign + " 1" + suffix +
283 ") as @@T14; @@I15;";
284 return parser->CreateFormattedExpression(newAssignmentStatements, id1, object, objType, id2, property, propType,
285 id3, GetClone(allocator, id1), GetClone(allocator, id2),
286 argument->TsType(), GetClone(allocator, id1), GetClone(allocator, id2),
287 GetClone(allocator, id3), argument->TsType(), GetClone(allocator, id3));
288 // NOLINTEND(readability-magic-numbers)
289 }
290
HandleUpdate(public_lib::Context * ctx,ir::UpdateExpression * upd)291 static ir::AstNode *HandleUpdate(public_lib::Context *ctx, ir::UpdateExpression *upd)
292 {
293 auto *checker = ctx->checker->AsETSChecker();
294
295 auto *const scope = NearestScope(upd);
296
297 ir::Expression *loweringResult = ConstructUpdateResult(ctx, upd);
298
299 auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
300 checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY, ContainingClass(upd)};
301 checker::ScopeContext sc {checker, scope};
302
303 loweringResult->SetParent(upd->Parent());
304 InitScopesPhaseETS::RunExternalNode(loweringResult, ctx->checker->VarBinder());
305
306 checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult,
307 NearestScope(loweringResult));
308 loweringResult->Check(checker);
309
310 AdjustBoxingUnboxingFlags(loweringResult, upd);
311
312 return loweringResult;
313 }
314
Perform(public_lib::Context * ctx,parser::Program * program)315 bool OpAssignmentLowering::Perform(public_lib::Context *ctx, parser::Program *program)
316 {
317 if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) {
318 for (auto &[_, ext_programs] : program->ExternalSources()) {
319 (void)_;
320 for (auto *extProg : ext_programs) {
321 Perform(ctx, extProg);
322 }
323 }
324 }
325
326 program->Ast()->TransformChildrenRecursively(
327 [ctx](ir::AstNode *ast) {
328 if (ast->IsAssignmentExpression() &&
329 ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) {
330 return HandleOpAssignment(ctx, ast->AsAssignmentExpression());
331 }
332 if (ast->IsUpdateExpression()) {
333 return HandleUpdate(ctx, ast->AsUpdateExpression());
334 }
335
336 return ast;
337 },
338 Name());
339
340 return true;
341 }
342
Postcondition(public_lib::Context * ctx,const parser::Program * program)343 bool OpAssignmentLowering::Postcondition(public_lib::Context *ctx, const parser::Program *program)
344 {
345 auto checkExternalPrograms = [this, ctx](const ArenaVector<parser::Program *> &programs) {
346 for (auto *p : programs) {
347 if (!Postcondition(ctx, p)) {
348 return false;
349 }
350 }
351 return true;
352 };
353
354 if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) {
355 for (auto &[_, extPrograms] : program->ExternalSources()) {
356 (void)_;
357 if (!checkExternalPrograms(extPrograms)) {
358 return false;
359 };
360 }
361 }
362
363 return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) {
364 return (ast->IsAssignmentExpression() && ast->AsAssignmentExpression()->TsType() != nullptr &&
365 ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) ||
366 ast->IsUpdateExpression();
367 });
368 }
369
370 } // namespace ark::es2panda::compiler
371