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
39namespace ark::es2panda::compiler {
40
41struct Conversion {
42    lexer::TokenType from;
43    lexer::TokenType to;
44};
45
46// NOLINTNEXTLINE(readability-magic-numbers)
47static 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
67static 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
77void 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
105static 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
114static 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
128static 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
137static ir::Identifier *GetClone(ArenaAllocator *allocator, ir::Identifier *node)
138{
139    return node == nullptr ? nullptr : node->Clone(allocator, nullptr);
140}
141
142static 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
195ir::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    // NOTE(dslynko, #19200): required for correct debug-info
207    auto rng = assignment->Range();
208    loweringResult->SetRange(rng);
209    loweringResult->TransformChildrenRecursively(
210        [rng](auto *node) {
211            node->SetRange(rng);
212            return node;
213        },
214        "");
215
216    auto *const scope = NearestScope(assignment);
217
218    auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
219    InitScopesPhaseETS::RunExternalNode(loweringResult, ctx->parserProgram->VarBinder());
220    checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult, scope);
221
222    checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY, ContainingClass(assignment)};
223    checker::ScopeContext sc {checker, scope};
224
225    loweringResult->Check(checker);
226
227    AdjustBoxingUnboxingFlags(loweringResult, assignment);
228
229    return loweringResult;
230}
231
232static ir::Expression *ConstructUpdateResult(public_lib::Context *ctx, ir::UpdateExpression *upd)
233{
234    auto *allocator = ctx->allocator;
235    auto *parser = ctx->parser->AsETSParser();
236    auto *argument = upd->Argument();
237    auto *checker = ctx->checker->AsETSChecker();
238
239    std::string newAssignmentStatements {};
240
241    ir::Identifier *id1;
242    ir::Identifier *id2 = nullptr;
243    ir::Identifier *id3 = nullptr;
244    ir::Expression *object = nullptr;
245    ir::Expression *property = nullptr;
246    checker::Type *objType = checker->GlobalVoidType();  // placeholder
247    checker::Type *propType = checker->GlobalVoidType();
248
249    // Parse ArkTS code string and create the corresponding AST node(s)
250    // We have to use extra caution with types and `as` conversions because of smart types, which we cannot reproduce in
251    // re-checking.
252
253    if (argument->IsIdentifier()) {
254        id1 = GetClone(allocator, argument->AsIdentifier());
255    } else if (argument->IsMemberExpression()) {
256        auto *memberExpression = argument->AsMemberExpression();
257
258        if (object = memberExpression->Object(); object != nullptr && object->IsIdentifier()) {
259            id1 = GetClone(allocator, object->AsIdentifier());
260        } else if (object != nullptr) {
261            id1 = Gensym(allocator);
262            newAssignmentStatements = "const @@I1 = (@@E2) as @@T3; ";
263            objType = object->TsType();
264        }
265
266        if (property = memberExpression->Property(); property != nullptr && property->IsIdentifier()) {
267            id2 = GetClone(allocator, property->AsIdentifier());
268        } else if (property != nullptr) {
269            id2 = Gensym(allocator);
270            newAssignmentStatements += "const @@I4 = (@@E5) as @@T6; ";
271            propType = property->TsType();
272        }
273    }
274
275    std::string opSign = lexer::TokenToString(CombinedOpToOp(upd->OperatorType()));
276
277    std::string suffix = (argument->TsType() == checker->GlobalETSBigIntType()) ? "n" : "";
278
279    // NOLINTBEGIN(readability-magic-numbers)
280    if (upd->IsPrefix()) {
281        newAssignmentStatements += GenFormatForExpression(argument, 7U, 8U) + " = (" +
282                                   GenFormatForExpression(argument, 9U, 10U) + opSign + " 1" + suffix + ") as @@T11;";
283        return parser->CreateFormattedExpression(
284            newAssignmentStatements, id1, object, objType, id2, property, propType, GetClone(allocator, id1),
285            GetClone(allocator, id2), GetClone(allocator, id1), GetClone(allocator, id2), argument->TsType());
286    }
287
288    // upd is postfix
289    id3 = Gensym(allocator);
290    newAssignmentStatements += "const @@I7 = " + GenFormatForExpression(argument, 8, 9) + " as @@T10;" +
291                               GenFormatForExpression(argument, 11U, 12U) + " = (@@I13 " + opSign + " 1" + suffix +
292                               ") as @@T14; @@I15;";
293    return parser->CreateFormattedExpression(newAssignmentStatements, id1, object, objType, id2, property, propType,
294                                             id3, GetClone(allocator, id1), GetClone(allocator, id2),
295                                             argument->TsType(), GetClone(allocator, id1), GetClone(allocator, id2),
296                                             GetClone(allocator, id3), argument->TsType(), GetClone(allocator, id3));
297    // NOLINTEND(readability-magic-numbers)
298}
299
300static ir::AstNode *HandleUpdate(public_lib::Context *ctx, ir::UpdateExpression *upd)
301{
302    auto *checker = ctx->checker->AsETSChecker();
303
304    auto *const scope = NearestScope(upd);
305
306    ir::Expression *loweringResult = ConstructUpdateResult(ctx, upd);
307
308    auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
309    checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY, ContainingClass(upd)};
310    checker::ScopeContext sc {checker, scope};
311
312    loweringResult->SetParent(upd->Parent());
313    // NOTE(dslynko, #19200): required for correct debug-info
314    auto rng = upd->Range();
315    loweringResult->SetRange(rng);
316    loweringResult->TransformChildrenRecursively(
317        [rng](auto *node) {
318            node->SetRange(rng);
319            return node;
320        },
321        "");
322    InitScopesPhaseETS::RunExternalNode(loweringResult, ctx->checker->VarBinder());
323
324    checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult,
325                                                                              NearestScope(loweringResult));
326    loweringResult->Check(checker);
327
328    AdjustBoxingUnboxingFlags(loweringResult, upd);
329
330    return loweringResult;
331}
332
333bool OpAssignmentLowering::Perform(public_lib::Context *ctx, parser::Program *program)
334{
335    if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) {
336        for (auto &[_, ext_programs] : program->ExternalSources()) {
337            (void)_;
338            for (auto *extProg : ext_programs) {
339                Perform(ctx, extProg);
340            }
341        }
342    }
343
344    program->Ast()->TransformChildrenRecursively(
345        [ctx](ir::AstNode *ast) {
346            if (ast->IsAssignmentExpression() &&
347                ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) {
348                return HandleOpAssignment(ctx, ast->AsAssignmentExpression());
349            }
350            if (ast->IsUpdateExpression()) {
351                return HandleUpdate(ctx, ast->AsUpdateExpression());
352            }
353
354            return ast;
355        },
356        Name());
357
358    return true;
359}
360
361bool OpAssignmentLowering::Postcondition(public_lib::Context *ctx, const parser::Program *program)
362{
363    auto checkExternalPrograms = [this, ctx](const ArenaVector<parser::Program *> &programs) {
364        for (auto *p : programs) {
365            if (!Postcondition(ctx, p)) {
366                return false;
367            }
368        }
369        return true;
370    };
371
372    if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) {
373        for (auto &[_, extPrograms] : program->ExternalSources()) {
374            (void)_;
375            if (!checkExternalPrograms(extPrograms)) {
376                return false;
377            };
378        }
379    }
380
381    return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) {
382        return (ast->IsAssignmentExpression() && ast->AsAssignmentExpression()->TsType() != nullptr &&
383                ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) ||
384               ast->IsUpdateExpression();
385    });
386}
387
388}  // namespace ark::es2panda::compiler
389