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 
22 namespace ark::es2panda::compiler {
23 
Name() const24 std::string_view ObjectLiteralLowering::Name() const
25 {
26     return "ObjectLiteralLowering";
27 }
28 
MaybeAllowConstAssign(checker::Type *targetType, ArenaVector<ir::Statement *> &statements)29 static 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 
49 static constexpr std::string_view NESTED_BLOCK_EXPRESSION = "_$NESTED_BLOCK_EXPRESSION$_";
50 
RestoreNestedBlockExpression(const ArenaVector<ir::Statement *> &statements, std::deque<ir::BlockExpression *> &nestedBlckExprs, varbinder::Scope *scope)51 static 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 
AllowRequiredTypeInstantiation(const ir::Expression *const loweringResult)76 static 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 
GenerateNewStatements(checker::ETSChecker *checker, ir::ObjectExpression *objExpr, std::stringstream &ss, std::vector<ir::AstNode *> &newStmts, std::deque<ir::BlockExpression *> &nestedBlckExprs)105 static 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 
HandleObjectLiteralLowering(public_lib::Context *ctx, ir::ObjectExpression *objExpr)152 static 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 
Perform(public_lib::Context *ctx, parser::Program *program)201 bool 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 
ExternalSourcesPostcondition(public_lib::Context *ctx, const parser::Program *program)226 bool 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 
Postcondition(public_lib::Context *ctx, const parser::Program *program)239 bool 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