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