• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024-2025 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 #include "checker/ETSchecker.h"
18 #include "compiler/lowering/scopesInit/scopesInitPhase.h"
19 #include "compiler/lowering/util.h"
20 
21 namespace ark::es2panda::compiler {
22 
Name() const23 std::string_view ObjectLiteralLowering::Name() const
24 {
25     return "ObjectLiteralLowering";
26 }
27 
MaybeAllowConstAssign(checker::Type * targetType,ArenaVector<ir::Statement * > & statements)28 static void MaybeAllowConstAssign(checker::Type *targetType, ArenaVector<ir::Statement *> &statements)
29 {
30     if (!targetType->IsETSObjectType()) {
31         return;
32     }
33     for (auto *const stmt : statements) {
34         if (!stmt->IsExpressionStatement() ||
35             !stmt->AsExpressionStatement()->GetExpression()->IsAssignmentExpression()) {
36             continue;
37         }
38 
39         auto *const assignmentExpr = stmt->AsExpressionStatement()->GetExpression()->AsAssignmentExpression();
40         assignmentExpr->SetIgnoreConstAssign();
41     }
42 }
43 
44 static constexpr std::string_view NESTED_BLOCK_EXPRESSION = "_$NESTED_BLOCK_EXPRESSION$_";
45 
RestoreNestedBlockExpression(const ArenaVector<ir::Statement * > & statements,std::deque<ir::BlockExpression * > & nestedBlckExprs,varbinder::Scope * scope)46 static void RestoreNestedBlockExpression(const ArenaVector<ir::Statement *> &statements,
47                                          std::deque<ir::BlockExpression *> &nestedBlckExprs, varbinder::Scope *scope)
48 {
49     if (!nestedBlckExprs.empty()) {
50         for (auto stmt : statements) {
51             if (!stmt->IsExpressionStatement() ||
52                 !stmt->AsExpressionStatement()->GetExpression()->IsAssignmentExpression()) {
53                 continue;
54             }
55 
56             auto *assign = stmt->AsExpressionStatement()->GetExpression()->AsAssignmentExpression();
57 
58             if (assign->Right()->IsStringLiteral() &&
59                 assign->Right()->AsStringLiteral()->Str().Is(NESTED_BLOCK_EXPRESSION)) {
60                 auto nestedBlckExpr = nestedBlckExprs.front();
61                 nestedBlckExprs.pop_front();
62                 nestedBlckExpr->Scope()->SetParent(scope);
63                 assign->SetRight(nestedBlckExpr);
64             }
65         }
66         // All nested block expressions should be restored
67         ES2PANDA_ASSERT(nestedBlckExprs.empty());
68     }
69 }
70 
AllowRequiredTypeInstantiation(const ir::Expression * const loweringResult)71 static void AllowRequiredTypeInstantiation(const ir::Expression *const loweringResult)
72 {
73     if (!loweringResult->IsBlockExpression()) {
74         return;
75     }
76 
77     const auto *const blockExpression = loweringResult->AsBlockExpression();
78     const auto *const firstStatement = blockExpression->Statements().front();
79     if (!firstStatement->IsVariableDeclaration() ||
80         !firstStatement->AsVariableDeclaration()->Declarators().front()->Init()->IsETSNewClassInstanceExpression()) {
81         return;
82     }
83 
84     const auto *const varDecl = firstStatement->AsVariableDeclaration()->Declarators().front()->Init();
85 
86     varDecl->AddAstNodeFlags(ir::AstNodeFlags::ALLOW_REQUIRED_INSTANTIATION);
87 
88     for (auto *const stmt : blockExpression->Statements()) {
89         if (!stmt->IsExpressionStatement() ||
90             !stmt->AsExpressionStatement()->GetExpression()->IsAssignmentExpression() ||
91             !stmt->AsExpressionStatement()->GetExpression()->AsAssignmentExpression()->Right()->IsBlockExpression()) {
92             continue;
93         }
94 
95         AllowRequiredTypeInstantiation(
96             stmt->AsExpressionStatement()->GetExpression()->AsAssignmentExpression()->Right()->AsBlockExpression());
97     }
98 }
99 
CheckReadonlyAndUpdateCtorArgs(const ir::Identifier * key,ir::Expression * value,std::map<util::StringView,ir::Expression * > & ctorArgumentsMap)100 static bool CheckReadonlyAndUpdateCtorArgs(const ir::Identifier *key, ir::Expression *value,
101                                            std::map<util::StringView, ir::Expression *> &ctorArgumentsMap)
102 {
103     ES2PANDA_ASSERT(key != nullptr);
104     auto varType = (key->Variable() != nullptr) ? key->Variable()->TsType() : nullptr;
105     if (varType == nullptr || varType->HasTypeFlag(checker::TypeFlag::SETTER)) {
106         return false;
107     }
108 
109     if (ctorArgumentsMap.find(key->Name()) == ctorArgumentsMap.end()) {
110         return false;
111     }
112 
113     ctorArgumentsMap[key->Name()] = value;
114     return true;
115 }
116 
PopulateCtorArgumentsFromMap(public_lib::Context * ctx,ir::ObjectExpression * objExpr,ArenaVector<ir::Expression * > & ctorArguments,std::map<util::StringView,ir::Expression * > & ctorArgumentsMap)117 static void PopulateCtorArgumentsFromMap(public_lib::Context *ctx, ir::ObjectExpression *objExpr,
118                                          ArenaVector<ir::Expression *> &ctorArguments,
119                                          std::map<util::StringView, ir::Expression *> &ctorArgumentsMap)
120 {
121     if (ctorArgumentsMap.empty()) {
122         return;
123     }
124     auto *const allocator = ctx->Allocator();
125     auto *const classType = objExpr->TsType()->AsETSObjectType();
126 
127     for (auto param : classType->ConstructSignatures().front()->Params()) {
128         auto ctorArgument = ctorArgumentsMap[param->Declaration()->Name()];
129         if (ctorArgument == nullptr && objExpr->PreferredType()->AsETSObjectType()->IsPartial()) {
130             ctorArguments.push_back(allocator->New<ir::UndefinedLiteral>());
131             continue;
132         }
133         if (ctorArgument == nullptr && param->TsType()->PossiblyETSUndefined()) {
134             ctorArguments.push_back(allocator->New<ir::UndefinedLiteral>());
135             continue;
136         }
137         ES2PANDA_ASSERT(ctorArgument != nullptr);
138         ctorArguments.push_back(ctorArgument);
139     }
140 }
141 
SetInstanceArguments(ArenaVector<ir::Statement * > & statements,ArenaVector<ir::Expression * > & ctorArguments)142 static void SetInstanceArguments(ArenaVector<ir::Statement *> &statements, ArenaVector<ir::Expression *> &ctorArguments)
143 {
144     if (statements.empty() || ctorArguments.empty()) {
145         return;
146     }
147 
148     const auto *const firstStatement = statements.front();
149     if (!firstStatement->IsVariableDeclaration()) {
150         return;
151     }
152 
153     auto declarator = firstStatement->AsVariableDeclaration()->Declarators().front();
154     auto *initExpression = declarator->Init();
155     if (initExpression == nullptr || !initExpression->IsETSNewClassInstanceExpression()) {
156         return;
157     }
158 
159     auto *instance = initExpression->AsETSNewClassInstanceExpression();
160     for (auto *arg : ctorArguments) {
161         arg->SetParent(instance);
162     }
163     instance->SetArguments(std::move(ctorArguments));
164 }
165 
GenerateArgsForAnonymousClassType(const checker::ETSObjectType * classType,const bool & isAnonymous,std::map<util::StringView,ir::Expression * > & ctorArgumentsMap)166 static void GenerateArgsForAnonymousClassType(const checker::ETSObjectType *classType, const bool &isAnonymous,
167                                               std::map<util::StringView, ir::Expression *> &ctorArgumentsMap)
168 {
169     if (isAnonymous) {
170         checker::Signature *sig = classType->ConstructSignatures().front();
171         for (auto param : sig->Params()) {
172             ES2PANDA_ASSERT(param->Declaration() != nullptr);
173             ctorArgumentsMap.emplace(param->Declaration()->Name(), nullptr);
174         }
175     }
176 }
177 
GenerateNewStatements(public_lib::Context * ctx,ir::ObjectExpression * objExpr,std::stringstream & ss,std::vector<ir::AstNode * > & newStmts,std::deque<ir::BlockExpression * > & nestedBlckExprs,ArenaVector<ir::Expression * > & ctorArguments)178 static void GenerateNewStatements(public_lib::Context *ctx, ir::ObjectExpression *objExpr, std::stringstream &ss,
179                                   std::vector<ir::AstNode *> &newStmts,
180                                   std::deque<ir::BlockExpression *> &nestedBlckExprs,
181                                   ArenaVector<ir::Expression *> &ctorArguments)
182 {
183     auto *const allocator = ctx->Allocator();
184 
185     auto *const classType = objExpr->TsType()->AsETSObjectType();
186 
187     auto addNode = [&newStmts](ir::AstNode *node) -> int {
188         newStmts.emplace_back(node);
189         return newStmts.size();
190     };
191 
192     // Generating: let <genSym>: <TsType> = new <TsType>();
193     auto *genSymIdent = Gensym(allocator);
194     auto *type = ctx->AllocNode<ir::OpaqueTypeNode>(classType, allocator);
195     ss << "let @@I" << addNode(genSymIdent) << ": @@T" << addNode(type) << " = new @@T"
196        << addNode(type->Clone(allocator, nullptr)) << "();" << std::endl;
197 
198     // Generating: <genSym>.key_i = value_i      ( i <= [0, object_literal.properties.size) )
199     bool isAnonymous = IsAnonymousClassType(classType);
200 
201     std::map<util::StringView, ir::Expression *> ctorArgumentsMap;
202     GenerateArgsForAnonymousClassType(classType, isAnonymous, ctorArgumentsMap);
203 
204     for (auto *propExpr : objExpr->Properties()) {
205         //  Skip possibly invalid properties:
206         if (!propExpr->IsProperty()) {
207             ES2PANDA_ASSERT(ctx->checker->AsETSChecker()->IsAnyError());
208             continue;
209         }
210 
211         auto *prop = propExpr->AsProperty();
212         ir::Expression *key = prop->Key();
213         ir::Expression *value = prop->Value();
214 
215         //  Processing of possible invalid property key
216         ir::Identifier *keyIdent;
217         if (key->IsStringLiteral()) {
218             keyIdent = ctx->AllocNode<ir::Identifier>(key->AsStringLiteral()->Str(), allocator);
219         } else if (key->IsIdentifier()) {
220             keyIdent = key->AsIdentifier();
221         } else {
222             continue;
223         }
224 
225         if (isAnonymous && CheckReadonlyAndUpdateCtorArgs(keyIdent, value, ctorArgumentsMap)) {
226             continue;
227         }
228         ES2PANDA_ASSERT(genSymIdent != nullptr);
229         ss << "@@I" << addNode(genSymIdent->Clone(allocator, nullptr)) << ".@@I" << addNode(keyIdent);
230 
231         if (value->IsBlockExpression()) {
232             // Case of nested object literal (all nested object literals has already been processed)
233             // Corresponding nested block expressions should be stored somewhere and restored after ScopesPhase
234             // Because it has already processed them
235             // Predefined String Literal acts as placeholder
236             ss << " = \"" << NESTED_BLOCK_EXPRESSION << "\";" << std::endl;
237             nestedBlckExprs.emplace_back(value->AsBlockExpression());
238         } else {
239             ss << " = @@E" << addNode(value) << ";" << std::endl;
240         }
241     }
242 
243     PopulateCtorArgumentsFromMap(ctx, objExpr, ctorArguments, ctorArgumentsMap);
244 
245     ss << "(@@I" << addNode(genSymIdent->Clone(allocator, nullptr)) << ");" << std::endl;
246 }
247 
HandleObjectLiteralLowering(public_lib::Context * ctx,ir::ObjectExpression * objExpr)248 static ir::AstNode *HandleObjectLiteralLowering(public_lib::Context *ctx, ir::ObjectExpression *objExpr)
249 {
250     /*
251      * For given object literal of class type generates following block expression:
252      *
253      *  ({
254      *     let <genSym>: <TsType> = new <TsType>();
255      *     <genSym>.key_i = value_i      ( i <= [0, object_literal.properties.size) )
256      *     <genSym>;                     <-- NOTE: result of block expression
257      *  })
258      */
259 
260     if (objExpr->TsType() == nullptr) {
261         return objExpr;
262     }
263 
264     auto *const checker = ctx->checker->AsETSChecker();
265     auto *const parser = ctx->parser->AsETSParser();
266     auto *const varbinder = ctx->checker->VarBinder()->AsETSBinder();
267 
268     checker->CheckObjectLiteralKeys(objExpr->Properties());
269 
270     std::stringstream ss;
271     // Double-ended queue for storing nested block expressions that have already been processed earlier
272     std::deque<ir::BlockExpression *> nestedBlckExprs;
273     std::vector<ir::AstNode *> newStmts;
274     ArenaVector<ir::Expression *> ctorArguments(checker->Allocator()->Adapter());
275 
276     GenerateNewStatements(ctx, objExpr, ss, newStmts, nestedBlckExprs, ctorArguments);
277 
278     auto *loweringResult = parser->CreateFormattedExpression(ss.str(), newStmts);
279     ES2PANDA_ASSERT(loweringResult != nullptr);
280     SetInstanceArguments(loweringResult->AsBlockExpression()->Statements(), ctorArguments);
281 
282     loweringResult->SetParent(objExpr->Parent());
283 
284     MaybeAllowConstAssign(objExpr->TsType(), loweringResult->AsBlockExpression()->Statements());
285 
286     auto scopeCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder, NearestScope(objExpr));
287     InitScopesPhaseETS::RunExternalNode(loweringResult, varbinder);
288 
289     // Restoring nested block expressions
290     RestoreNestedBlockExpression(loweringResult->AsBlockExpression()->Statements(), nestedBlckExprs,
291                                  loweringResult->Scope());
292 
293     varbinder->ResolveReferencesForScope(loweringResult, NearestScope(loweringResult));
294 
295     AllowRequiredTypeInstantiation(loweringResult);
296 
297     checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY};
298     loweringResult->Check(checker);
299 
300     return loweringResult;
301 }
302 
PerformForModule(public_lib::Context * ctx,parser::Program * program)303 bool ObjectLiteralLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program)
304 {
305     program->Ast()->TransformChildrenRecursively(
306         // CC-OFFNXT(G.FMT.14-CPP) project code style
307         [ctx](ir::AstNode *ast) -> ir::AstNode * {
308             // Skip processing invalid and dynamic objects
309             if (ast->IsObjectExpression()) {
310                 auto *exprType = ast->AsObjectExpression()->TsType();
311                 if (exprType != nullptr && exprType->IsETSObjectType() &&
312                     !exprType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::DYNAMIC)) {
313                     return HandleObjectLiteralLowering(ctx, ast->AsObjectExpression());
314                 }
315             }
316             return ast;
317         },
318         Name());
319 
320     return true;
321 }
322 
PostconditionForModule(public_lib::Context * ctx,const parser::Program * program)323 bool ObjectLiteralLowering::PostconditionForModule([[maybe_unused]] public_lib::Context *ctx,
324                                                    const parser::Program *program)
325 {
326     // In all object literal contexts (except dynamic) a substitution should take place
327     return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) -> bool {
328         return ast->IsObjectExpression() && ast->AsObjectExpression()->TsType()->IsETSObjectType() &&
329                !ast->AsObjectExpression()->TsType()->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::DYNAMIC);
330     });
331 }
332 
333 }  // namespace ark::es2panda::compiler
334