• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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