1/**
2 * Copyright (c) 2023-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#include "tupleLowering.h"
17
18#include "checker/ETSchecker.h"
19#include "checker/types/ets/etsTupleType.h"
20#include "compiler/lowering/util.h"
21#include "ir/expressions/assignmentExpression.h"
22#include "ir/expressions/identifier.h"
23#include "ir/expressions/memberExpression.h"
24#include "ir/expressions/sequenceExpression.h"
25#include "ir/expressions/updateExpression.h"
26#include "ir/opaqueTypeNode.h"
27#include "ir/statements/blockStatement.h"
28#include "ir/ts/tsAsExpression.h"
29
30namespace ark::es2panda::compiler {
31class TupleUpdateConverter {
32public:
33    TupleUpdateConverter(checker::ETSChecker *const checker, ir::UpdateExpression *const update)
34        : checker_(checker), update_(update)
35    {
36    }
37
38    std::optional<checker::Type *const> CheckUpdateArgument()
39    {
40        auto *const argument = update_->Argument();
41        const bool isArgumentMemberExpression = argument->IsMemberExpression();
42        auto *const argumentType =
43            isArgumentMemberExpression ? argument->AsMemberExpression()->Object()->TsType() : nullptr;
44
45        if ((argumentType == nullptr) || (!argumentType->IsETSTupleType())) {
46            return std::nullopt;
47        }
48        return {argumentType};
49    }
50
51    checker::Type *SetArgumentType(checker::Type *const argumentType)
52    {
53        auto *const savedType = argument_->TsType();
54        argument_->SetTsType(argumentType->AsETSTupleType()->ElementType());
55        return savedType;
56    }
57
58    void ComputeTypes(checker::Type *const argumentType)
59    {
60        tupleTypeAtIdx_ = argumentType->AsETSTupleType()->GetTypeAtIndex(
61
62            // After the checker, we are guranteed that the index is correct.
63            *checker_->GetTupleElementAccessValue(argument_->AsMemberExpression()->Property()->TsType(),
64                                                  argument_->AsMemberExpression()->Property()->Start()));
65
66        tupleElementTypeNode_ = checker_->AllocNode<ir::OpaqueTypeNode>(argumentType->AsETSTupleType()->ElementType());
67        tupleTypeAtIdxNode_ = checker_->AllocNode<ir::OpaqueTypeNode>(tupleTypeAtIdx_);
68    }
69
70    ArenaVector<ir::Expression *> GenerateExpressions()
71    {
72        // Clone argument of update expression (conversion flag might be added to it, so we need to duplicate it to not
73        // make
74        // conversions on 'line 3', that belongs to 'line 1' )
75        auto [memberExpr, argumentClone] = CloneArgument(argument_);
76        // --------------
77
78        // Generate temporary symbols
79        auto [gensym, tmpVar] = GenerateSymbol(tupleTypeAtIdx_);
80        auto [gensym2, tmpVar2] = GenerateSymbol(tupleTypeAtIdx_);
81        // --------------
82        // make node: let gensym = tuple[n] as <tuple type at index n>;
83        auto *const gensymTsAs = checker_->AllocNode<ir::TSAsExpression>(argumentClone, tupleTypeAtIdxNode_, false);
84        auto *const tupleAsType = checker_->AllocNode<ir::AssignmentExpression>(
85            gensym, gensymTsAs, lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
86        // --------------
87
88        // make node: let gensym2 = (gensym)++;
89        auto *identClone = gensym->Clone(checker_->Allocator(), nullptr);
90        identClone->SetTsType(tmpVar->TsType());
91        auto *gensymUpdate =
92            checker_->AllocNode<ir::UpdateExpression>(identClone, update_->OperatorType(), update_->IsPrefix());
93        auto *const gensym2Assignment = checker_->AllocNode<ir::AssignmentExpression>(
94            gensym2, gensymUpdate, lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
95        // --------------
96
97        // make node: tuple[n] = (gensym as <tuple type at index n>) as <tuple element_type>;
98        identClone = gensym->Clone(checker_->Allocator(), nullptr);
99        identClone->SetTsType(tmpVar->TsType());
100        auto *gensymAs = checker_->AllocNode<ir::TSAsExpression>(
101            identClone, tupleTypeAtIdxNode_->Clone(checker_->Allocator(), nullptr), false);
102        auto *gensymAsTupleTypeAtIdx = checker_->AllocNode<ir::TSAsExpression>(gensymAs, tupleElementTypeNode_, false);
103        auto *const tupleAssignment = checker_->AllocNode<ir::AssignmentExpression>(
104            argument_, gensymAsTupleTypeAtIdx, lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
105        // --------------
106
107        // make node: gensym2 as <tuple type at index n>;
108        identClone = gensym2->Clone(checker_->Allocator(), nullptr);
109        identClone->SetTsType(tmpVar2->TsType());
110        auto *const finalTupleNode = checker_->AllocNode<ir::TSAsExpression>(
111            identClone, tupleTypeAtIdxNode_->Clone(checker_->Allocator(), nullptr), false);
112        // --------------
113
114        // Construct sequence expression order
115        ArenaVector<ir::Expression *> expressionList(checker_->Allocator()->Adapter());
116        expressionList.push_back(tupleAsType);
117        expressionList.push_back(gensym2Assignment);
118        expressionList.push_back(tupleAssignment);
119        expressionList.push_back(finalTupleNode);
120        // --------------
121
122        return expressionList;
123    }
124
125private:
126    std::tuple<ir::Identifier *, varbinder::LocalVariable *const> GenerateSymbol(checker::Type *const type) const
127    {
128        auto *gensym = Gensym(checker_->Allocator());
129        auto *const tmpVar = NearestScope(update_)->AddDecl<varbinder::LetDecl, varbinder::LocalVariable>(
130            checker_->Allocator(), gensym->Name(), varbinder::VariableFlags::LOCAL);
131        tmpVar->SetTsType(type);
132        gensym->SetVariable(tmpVar);
133        gensym->SetTsType(tmpVar->TsType());
134        return std::make_tuple(gensym, tmpVar);
135    }
136
137    std::tuple<ir::MemberExpression *const, ir::MemberExpression *const> CloneArgument(
138        ir::Expression *const argument) const
139    {
140        auto *const memberExpr = argument->AsMemberExpression();
141        auto *const argumentClone = memberExpr->Clone(checker_->Allocator(), memberExpr->Parent());
142        argumentClone->Object()->SetTsType(memberExpr->Object()->TsType());
143        if (argumentClone->Object()->IsIdentifier()) {
144            argumentClone->Object()->AsIdentifier()->SetVariable(memberExpr->Object()->AsIdentifier()->Variable());
145        }
146        argumentClone->Property()->SetTsType(memberExpr->Property()->TsType());
147        if (argumentClone->Property()->IsIdentifier()) {
148            argumentClone->Property()->AsIdentifier()->SetVariable(memberExpr->Property()->AsIdentifier()->Variable());
149        }
150        argumentClone->SetTsType(memberExpr->TsType());
151        return std::make_tuple(memberExpr, argumentClone);
152    };
153
154    checker::ETSChecker *const checker_;
155    ir::UpdateExpression *const update_;
156    ir::Expression *const argument_ {nullptr};
157    checker::Type *tupleTypeAtIdx_ {nullptr};
158    ir::OpaqueTypeNode *tupleElementTypeNode_ {nullptr};
159    ir::OpaqueTypeNode *tupleTypeAtIdxNode_ {nullptr};
160};
161
162static ir::Expression *ConvertTupleUpdate(checker::ETSChecker *const checker, ir::UpdateExpression *const update)
163{
164    // Converts `tuple[n]++` to
165    // ```
166    // let gensym = tuple[n] as <tuple type at index n>;                            // line 1
167    // let gensym2 = (gensym)++;                                                    // line 2
168    // tuple[n] = (gensym as <tuple type at index n>) as <tuple element_type>;      // line 3
169    // gensym2 as <tuple type at index n>;                                          // line 4
170    // ```
171    // Notes:
172    // ---
173    // Because we can modify only 1 expression in the lowering (we don't want to add statements to the enclosing block),
174    // the expressions will be in a wrapper SequenceExpression
175    // ---
176    // At line 3 the double as expression is needed. If we simply write `gensym as <tuple type at index n>`, then a
177    // boxing flag may be put on the `gensym` identifier node. It'll be boxed in 'line 2' instead of 'line 3', which
178    // cause error. If we put another as expression inside (which won't do any conversion, because the type of `gensym`
179    // is already <tuple type at index n>), the boxing flag will be on the as expression, instead of the identifier, so
180    // the identifier node won't be unboxed at 'line 2'.
181
182    auto converter = TupleUpdateConverter {checker, update};
183
184    // Check if argument of update expression is tuple
185    auto const argumentType = converter.CheckUpdateArgument();
186    if (!argumentType) {
187        return update;
188    }
189    // --------------
190
191    // Set tuple type to Object (because we'll need implicit boxing)
192    auto *const savedType = converter.SetArgumentType(*argumentType);
193    // --------------
194
195    // Compute necessary types and OpaqueTypeNodes
196    converter.ComputeTypes(*argumentType);
197    // --------------
198
199    auto expressions = converter.GenerateExpressions();
200
201    // Check the new sequence expression
202    auto *const sequenceExpr = checker->AllocNode<ir::SequenceExpression>(std::move(expressions));
203    sequenceExpr->SetParent(update->Parent());
204    sequenceExpr->Check(checker);
205    // --------------
206
207    // Set back TsType of argument (not necessarily needed now, but there can be a phase later, that need to get the
208    // right type of it)
209    [[maybe_unused]] auto _ = converter.SetArgumentType(savedType);
210    // --------------
211
212    return sequenceExpr;
213}
214
215static ir::AssignmentExpression *ConvertTupleAssignment(checker::ETSChecker *const checker,
216                                                        ir::AssignmentExpression *const assignment)
217{
218    // Converts `tuple[n] = variable;` to
219    // `tuple[n] = ((variable as <tuple type at index n>) as <tuple element_type>)`
220    // This lowering is necessary to handle `an unboxing conversion followed by a widening primitive
221    // conversion`, eg. when `tuple[n]` has type of `int`, and assignment::right_ has type of `Short`. Because every
222    // type is stored as the LUB type in the tuple (which can be Object), then the following conversions need to be done
223    // for this case: Short->short->int->Int->Object which can't be made implicitly, hence lowering is needed
224
225    // Check if the left side of an assignment expression is a tuple element access
226    auto *const left = assignment->Left();
227    auto *const leftObjectType = left->AsMemberExpression()->Object()->TsType();
228
229    if ((leftObjectType == nullptr) || (!leftObjectType->IsETSTupleType())) {
230        return assignment;
231    }
232    // --------------
233
234    // Set tuple type to <tuple element_type> (because we may need implicit boxing)
235    auto *const savedLeftType = left->TsType();
236    left->SetTsType(leftObjectType->AsETSTupleType()->ElementType());
237    // --------------
238
239    // Compute necessary types and OpaqueTypeNodes
240    auto *const elementTypeTypeNode =
241        checker->AllocNode<ir::OpaqueTypeNode>(leftObjectType->AsETSTupleType()->ElementType());
242    auto *const tupleTypeAtIdxTypeNode = checker->AllocNode<ir::OpaqueTypeNode>(savedLeftType);
243    // --------------
244
245    // make node: tuple[n] = ((variable as <tuple type at index n>) as <tuple element_type>)
246    auto *const tsAsExpressionLeft =
247        checker->AllocNode<ir::TSAsExpression>(assignment->Right(), tupleTypeAtIdxTypeNode, false);
248
249    auto *const tsAsExpression = checker->AllocNode<ir::TSAsExpression>(tsAsExpressionLeft, elementTypeTypeNode, false);
250    auto *const newAssignment =
251        checker->AllocNode<ir::AssignmentExpression>(left, tsAsExpression, assignment->OperatorType());
252    // --------------
253
254    // Check the new assignment
255    newAssignment->SetParent(assignment->Parent());
256    newAssignment->Check(checker);
257    left->SetTsType(savedLeftType);
258    // --------------
259
260    return newAssignment;
261}
262
263bool TupleLowering::Perform(public_lib::Context *const ctx, parser::Program *const program)
264{
265    for (const auto &[_, ext_programs] : program->ExternalSources()) {
266        (void)_;
267        for (auto *const extProg : ext_programs) {
268            Perform(ctx, extProg);
269        }
270    }
271
272    checker::ETSChecker *const checker = ctx->checker->AsETSChecker();
273
274    program->Ast()->TransformChildrenRecursively(
275        [checker](ir::AstNode *const ast) -> ir::AstNode * {
276            // Check if node is an 'assignment expression', with a member expression on the left (potentially tuple)
277            if (ast->IsAssignmentExpression() && ast->AsAssignmentExpression()->Left()->IsMemberExpression()) {
278                return ConvertTupleAssignment(checker, ast->AsAssignmentExpression());
279            }
280
281            // Check if node is an 'update expression', with a member expression as an argument (potentially tuple)
282            if (ast->IsUpdateExpression() && ast->AsUpdateExpression()->Argument()->IsMemberExpression()) {
283                return ConvertTupleUpdate(checker, ast->AsUpdateExpression());
284            }
285
286            return ast;
287        },
288        Name());
289
290    return true;
291}
292
293bool TupleLowering::Postcondition(public_lib::Context *const ctx, const parser::Program *const program)
294{
295    for (const auto &[_, ext_programs] : program->ExternalSources()) {
296        (void)_;
297        for (const auto *const extProg : ext_programs) {
298            if (!Postcondition(ctx, extProg)) {
299                return false;
300            }
301        }
302    }
303
304    return !program->Ast()->IsAnyChild([](const ir::AstNode *const ast) {
305        const bool isLeftMemberExpr =
306            ast->IsAssignmentExpression() && ast->AsAssignmentExpression()->Left()->IsMemberExpression();
307        const bool isLeftTuple =
308            isLeftMemberExpr
309                ? (ast->AsAssignmentExpression()->Left()->AsMemberExpression()->TsType() != nullptr) &&
310                      ast->AsAssignmentExpression()->Left()->AsMemberExpression()->TsType()->IsETSTupleType()
311                : false;
312        // Check if there is an 'assignment expression' with a 'member expression' on it's left, which is a tuple. If
313        // yes, then the right hand side must be a type of the element type.
314        return isLeftMemberExpr && isLeftTuple &&
315               (ast->AsAssignmentExpression()->Right()->TsType() ==
316                ast->AsAssignmentExpression()->Left()->AsMemberExpression()->TsType()->AsETSTupleType()->ElementType());
317    });
318}
319
320}  // namespace ark::es2panda::compiler
321