1/*
2 * Copyright (c) 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 "objectLiteralLowering.h"
17
18#include "checker/ETSchecker.h"
19#include "compiler/lowering/scopesInit/scopesInitPhase.h"
20#include "compiler/lowering/util.h"
21
22namespace ark::es2panda::compiler {
23
24std::string_view ObjectLiteralLowering::Name() const
25{
26    return "ObjectLiteralLowering";
27}
28
29static void MaybeAllowConstAssign(checker::Type *targetType, ArenaVector<ir::Statement *> &statements)
30{
31    if (!targetType->IsETSObjectType()) {
32        return;
33    }
34    for (auto *const stmt : statements) {
35        if (!stmt->IsExpressionStatement() ||
36            !stmt->AsExpressionStatement()->GetExpression()->IsAssignmentExpression()) {
37            continue;
38        }
39
40        auto *const assignmentExpr = stmt->AsExpressionStatement()->GetExpression()->AsAssignmentExpression();
41        auto *const variable = assignmentExpr->Left()->AsMemberExpression()->Property()->AsIdentifier()->Variable();
42
43        if (variable != nullptr && variable->HasFlag(varbinder::VariableFlags::READONLY)) {
44            assignmentExpr->SetIgnoreConstAssign();
45        }
46    }
47}
48
49static constexpr std::string_view NESTED_BLOCK_EXPRESSION = "_$NESTED_BLOCK_EXPRESSION$_";
50
51static void RestoreNestedBlockExpression(const ArenaVector<ir::Statement *> &statements,
52                                         std::deque<ir::BlockExpression *> &nestedBlckExprs, varbinder::Scope *scope)
53{
54    if (!nestedBlckExprs.empty()) {
55        for (auto stmt : statements) {
56            if (!stmt->IsExpressionStatement() ||
57                !stmt->AsExpressionStatement()->GetExpression()->IsAssignmentExpression()) {
58                continue;
59            }
60
61            auto *assign = stmt->AsExpressionStatement()->GetExpression()->AsAssignmentExpression();
62
63            if (assign->Right()->IsStringLiteral() &&
64                assign->Right()->AsStringLiteral()->Str().Is(NESTED_BLOCK_EXPRESSION)) {
65                auto nestedBlckExpr = nestedBlckExprs.front();
66                nestedBlckExprs.pop_front();
67                nestedBlckExpr->Scope()->SetParent(scope);
68                assign->SetRight(nestedBlckExpr);
69            }
70        }
71        // All nested block expressions should be restored
72        ASSERT(nestedBlckExprs.empty());
73    }
74}
75
76static void AllowRequiredTypeInstantiation(const ir::Expression *const loweringResult)
77{
78    if (!loweringResult->IsBlockExpression()) {
79        return;
80    }
81
82    const auto *const blockExpression = loweringResult->AsBlockExpression();
83    const auto *const firstStatement = blockExpression->Statements().front();
84    if (!firstStatement->IsVariableDeclaration() ||
85        !firstStatement->AsVariableDeclaration()->Declarators().front()->Init()->IsETSNewClassInstanceExpression()) {
86        return;
87    }
88
89    const auto *const varDecl = firstStatement->AsVariableDeclaration()->Declarators().front()->Init();
90
91    varDecl->AddAstNodeFlags(ir::AstNodeFlags::ALLOW_REQUIRED_INSTANTIATION);
92
93    for (auto *const stmt : blockExpression->Statements()) {
94        if (!stmt->IsExpressionStatement() ||
95            !stmt->AsExpressionStatement()->GetExpression()->IsAssignmentExpression() ||
96            !stmt->AsExpressionStatement()->GetExpression()->AsAssignmentExpression()->Right()->IsBlockExpression()) {
97            continue;
98        }
99
100        AllowRequiredTypeInstantiation(
101            stmt->AsExpressionStatement()->GetExpression()->AsAssignmentExpression()->Right()->AsBlockExpression());
102    }
103}
104
105static void GenerateNewStatements(checker::ETSChecker *checker, ir::ObjectExpression *objExpr, std::stringstream &ss,
106                                  std::vector<ir::AstNode *> &newStmts,
107                                  std::deque<ir::BlockExpression *> &nestedBlckExprs)
108{
109    auto *const allocator = checker->Allocator();
110
111    auto *const classType = objExpr->TsType()->AsETSObjectType();
112
113    auto addNode = [&newStmts](ir::AstNode *node) -> int {
114        newStmts.emplace_back(node);
115        return newStmts.size();
116    };
117
118    // Generating: let <genSym>: <TsType> = new <TsType>();
119    auto *genSymIdent = Gensym(allocator);
120    auto *type = checker->AllocNode<ir::OpaqueTypeNode>(classType);
121    ss << "let @@I" << addNode(genSymIdent) << ": @@T" << addNode(type) << " = new @@T"
122       << addNode(type->Clone(allocator, nullptr)) << "();" << std::endl;
123
124    // Generating: <genSym>.key_i = value_i      ( i <= [0, object_literal.properties.size) )
125    for (auto *propExpr : objExpr->Properties()) {
126        ASSERT(propExpr->IsProperty());
127        auto *prop = propExpr->AsProperty();
128        ir::Expression *key = prop->Key();
129        ir::Expression *value = prop->Value();
130
131        ir::Identifier *keyIdent = key->IsStringLiteral()
132                                       ? checker->AllocNode<ir::Identifier>(key->AsStringLiteral()->Str(), allocator)
133                                       : key->AsIdentifier();
134
135        ss << "@@I" << addNode(genSymIdent->Clone(allocator, nullptr)) << ".@@I" << addNode(keyIdent);
136
137        if (value->IsBlockExpression()) {
138            // Case of nested object literal (all nested object literals has already been processed)
139            // Corresponding nested block expressions should be stored somewhere and restored after ScopesPhase
140            // Because it has already processed them
141            // Predefined String Literal acts as placeholder
142            ss << " = \"" << NESTED_BLOCK_EXPRESSION << "\";" << std::endl;
143            nestedBlckExprs.emplace_back(value->AsBlockExpression());
144        } else {
145            ss << " = @@E" << addNode(value) << ";" << std::endl;
146        }
147    }
148
149    ss << "(@@I" << addNode(genSymIdent->Clone(allocator, nullptr)) << ");" << std::endl;
150}
151
152static ir::AstNode *HandleObjectLiteralLowering(public_lib::Context *ctx, ir::ObjectExpression *objExpr)
153{
154    /*
155     * For given object literal of class type generates following block expression:
156     *
157     *  ({
158     *     let <genSym>: <TsType> = new <TsType>();
159     *     <genSym>.key_i = value_i      ( i <= [0, object_literal.properties.size) )
160     *     <genSym>;                     <-- NOTE: result of block expression
161     *  })
162     */
163
164    if (objExpr->TsType() == nullptr) {
165        return objExpr;
166    }
167
168    auto *const checker = ctx->checker->AsETSChecker();
169    auto *const parser = ctx->parser->AsETSParser();
170    auto *const varbinder = ctx->checker->VarBinder()->AsETSBinder();
171
172    std::stringstream ss;
173    // Double-ended queue for storing nested block expressions that have already been processed earlier
174    std::deque<ir::BlockExpression *> nestedBlckExprs;
175    std::vector<ir::AstNode *> newStmts;
176
177    GenerateNewStatements(checker, objExpr, ss, newStmts, nestedBlckExprs);
178
179    auto *loweringResult = parser->CreateFormattedExpression(ss.str(), newStmts);
180    loweringResult->SetParent(objExpr->Parent());
181
182    MaybeAllowConstAssign(objExpr->TsType(), loweringResult->AsBlockExpression()->Statements());
183
184    auto scopeCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder, NearestScope(objExpr));
185    InitScopesPhaseETS::RunExternalNode(loweringResult, varbinder);
186
187    // Restoring nested block expressions
188    RestoreNestedBlockExpression(loweringResult->AsBlockExpression()->Statements(), nestedBlckExprs,
189                                 loweringResult->Scope());
190
191    varbinder->ResolveReferencesForScope(loweringResult, NearestScope(loweringResult));
192
193    AllowRequiredTypeInstantiation(loweringResult);
194
195    checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY};
196    loweringResult->Check(checker);
197
198    return loweringResult;
199}
200
201bool ObjectLiteralLowering::Perform(public_lib::Context *ctx, parser::Program *program)
202{
203    if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) {
204        for (auto &[_, extPrograms] : program->ExternalSources()) {
205            (void)_;
206            for (auto *extProg : extPrograms) {
207                Perform(ctx, extProg);
208            }
209        }
210    }
211
212    program->Ast()->TransformChildrenRecursively(
213        [ctx](ir::AstNode *ast) -> ir::AstNode * {
214            // Skip processing dynamic objects
215            if (ast->IsObjectExpression() && !ast->AsObjectExpression()->TsType()->AsETSObjectType()->HasObjectFlag(
216                                                 checker::ETSObjectFlags::DYNAMIC)) {
217                return HandleObjectLiteralLowering(ctx, ast->AsObjectExpression());
218            }
219            return ast;
220        },
221        Name());
222
223    return true;
224}
225
226bool ObjectLiteralLowering::ExternalSourcesPostcondition(public_lib::Context *ctx, const parser::Program *program)
227{
228    for (auto &[_, extPrograms] : program->ExternalSources()) {
229        (void)_;
230        for (auto *extProg : extPrograms) {
231            if (!Postcondition(ctx, extProg)) {
232                return false;
233            }
234        }
235    }
236    return true;
237}
238
239bool ObjectLiteralLowering::Postcondition(public_lib::Context *ctx, const parser::Program *program)
240{
241    if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB &&
242        !ExternalSourcesPostcondition(ctx, program)) {
243        return false;
244    }
245
246    // In all object literal contexts (except dynamic) a substitution should take place
247    return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) -> bool {
248        return ast->IsObjectExpression() &&
249               !ast->AsObjectExpression()->TsType()->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::DYNAMIC);
250    });
251}
252
253}  // namespace ark::es2panda::compiler
254