1 /**
2 * Copyright (c) 2023-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 "recordLowering.h"
17
18 #include "checker/ETSchecker.h"
19 #include "checker/types/ets/etsAsyncFuncReturnType.h"
20
21 #include "compiler/lowering/scopesInit/scopesInitPhase.h"
22 #include "compiler/lowering/util.h"
23
24 namespace ark::es2panda::compiler {
25
Name() const26 std::string_view RecordLowering::Name() const
27 {
28 static std::string const NAME = "RecordLowering";
29 return NAME;
30 }
31
TypeToString(checker::Type * type) const32 std::string RecordLowering::TypeToString(checker::Type *type) const
33 {
34 std::stringstream ss;
35 type->ToString(ss);
36 return ss.str();
37 }
38
TypeToKey(checker::Type * type) const39 RecordLowering::KeyType RecordLowering::TypeToKey(checker::Type *type) const
40 {
41 if (type->IsByteType()) {
42 return type->AsByteType()->GetValue();
43 }
44 if (type->IsShortType()) {
45 return type->AsShortType()->GetValue();
46 }
47 if (type->IsIntType()) {
48 return type->AsIntType()->GetValue();
49 }
50 if (type->IsLongType()) {
51 return type->AsLongType()->GetValue();
52 }
53 if (type->IsFloatType()) {
54 return type->AsFloatType()->GetValue();
55 }
56 if (type->IsDoubleType()) {
57 return type->AsDoubleType()->GetValue();
58 }
59 if (type->IsETSStringType()) {
60 return type->AsETSStringType()->GetValue();
61 }
62 ES2PANDA_UNREACHABLE();
63 return {};
64 }
65
PerformForModule(public_lib::Context * ctx,parser::Program * program)66 bool RecordLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program)
67 {
68 // Replace Record Object Expressions with Block Expressions
69 program->Ast()->TransformChildrenRecursively(
70 // CC-OFFNXT(G.FMT.14-CPP) project code style
71 [this, ctx](ir::AstNode *ast) -> ir::AstNode * {
72 if (ast->IsObjectExpression()) {
73 return UpdateObjectExpression(ast->AsObjectExpression(), ctx);
74 }
75
76 return ast;
77 },
78 Name());
79
80 return true;
81 }
82
CheckDuplicateKey(KeySetType & keySet,ir::ObjectExpression * expr,public_lib::Context * ctx)83 void RecordLowering::CheckDuplicateKey(KeySetType &keySet, ir::ObjectExpression *expr, public_lib::Context *ctx)
84 {
85 for (auto *it : expr->Properties()) {
86 auto *prop = it->AsProperty();
87 switch (prop->Key()->Type()) {
88 case ir::AstNodeType::NUMBER_LITERAL: {
89 auto number = prop->Key()->AsNumberLiteral()->Number();
90 if ((number.IsInt() && keySet.insert(number.GetInt()).second) ||
91 (number.IsLong() && keySet.insert(number.GetLong()).second) ||
92 (number.IsFloat() && keySet.insert(number.GetFloat()).second) ||
93 (number.IsDouble() && keySet.insert(number.GetDouble()).second)) {
94 continue;
95 }
96 ctx->checker->AsETSChecker()->LogError(diagnostic::OBJ_LIT_PROP_NAME_COLLISION, {}, expr->Start());
97 break;
98 }
99 case ir::AstNodeType::STRING_LITERAL: {
100 if (keySet.insert(prop->Key()->AsStringLiteral()->Str()).second) {
101 continue;
102 }
103 ctx->checker->AsETSChecker()->LogError(diagnostic::OBJ_LIT_PROP_NAME_COLLISION, {}, expr->Start());
104 break;
105 }
106 default: {
107 ctx->checker->AsETSChecker()->LogError(diagnostic::OBJ_LIT_UNKNOWN_PROP, {}, expr->Start());
108 break;
109 }
110 }
111 }
112 }
113
CheckLiteralsCompleteness(KeySetType & keySet,ir::ObjectExpression * expr,public_lib::Context * ctx)114 void RecordLowering::CheckLiteralsCompleteness(KeySetType &keySet, ir::ObjectExpression *expr, public_lib::Context *ctx)
115 {
116 auto *keyType = expr->PreferredType()->AsETSObjectType()->TypeArguments().front();
117 if (!keyType->IsETSUnionType()) {
118 return;
119 }
120 for (auto &ct : keyType->AsETSUnionType()->ConstituentTypes()) {
121 if (ct->IsConstantType() && keySet.find(TypeToKey(ct)) == keySet.end()) {
122 ctx->checker->AsETSChecker()->LogError(diagnostic::OBJ_LIT_NOT_COVERING_UNION, {}, expr->Start());
123 }
124 }
125 }
126
CreateStatement(const std::string & src,ir::Expression * ident,ir::Expression * key,ir::Expression * value,public_lib::Context * ctx)127 ir::Statement *RecordLowering::CreateStatement(const std::string &src, ir::Expression *ident, ir::Expression *key,
128 ir::Expression *value, public_lib::Context *ctx)
129 {
130 std::vector<ir::AstNode *> nodes;
131 if (ident != nullptr) {
132 nodes.push_back(ident);
133 }
134
135 if (key != nullptr) {
136 nodes.push_back(key);
137 }
138
139 if (value != nullptr) {
140 nodes.push_back(value);
141 }
142
143 auto parser = ctx->parser->AsETSParser();
144 auto statements = parser->CreateFormattedStatements(src, nodes);
145 if (!statements.empty()) {
146 return *statements.begin();
147 }
148
149 return nullptr;
150 }
151
UpdateObjectExpression(ir::ObjectExpression * expr,public_lib::Context * ctx)152 ir::Expression *RecordLowering::UpdateObjectExpression(ir::ObjectExpression *expr, public_lib::Context *ctx)
153 {
154 auto checker = ctx->checker->AsETSChecker();
155 if (expr->PreferredType()->IsETSAsyncFuncReturnType()) {
156 expr->SetPreferredType(expr->PreferredType()->AsETSAsyncFuncReturnType()->GetPromiseTypeArg());
157 }
158
159 if (!expr->PreferredType()->IsETSObjectType()) {
160 // Unexpected preferred type
161 return expr;
162 }
163
164 ES2PANDA_ASSERT(expr->TsType() != nullptr);
165 std::stringstream ss;
166 expr->TsType()->ToAssemblerType(ss);
167 if (!(ss.str() == compiler::Signatures::BUILTIN_RECORD || ss.str() == compiler::Signatures::BUILTIN_MAP)) {
168 // Only update object expressions for Map/Record types
169 return expr;
170 }
171
172 // Access type arguments
173 [[maybe_unused]] size_t constexpr NUM_ARGUMENTS = 2;
174 auto typeArguments = expr->PreferredType()->AsETSObjectType()->TypeArguments();
175 ES2PANDA_ASSERT(typeArguments.size() == NUM_ARGUMENTS);
176
177 // check keys correctness
178 KeySetType keySet;
179 CheckDuplicateKey(keySet, expr, ctx);
180 CheckLiteralsCompleteness(keySet, expr, ctx);
181
182 auto *const scope = NearestScope(expr);
183 checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY};
184 auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
185
186 // Create Block Expression
187 auto block = CreateBlockExpression(expr, typeArguments[0], typeArguments[1], ctx);
188 ES2PANDA_ASSERT(block != nullptr);
189 block->SetParent(expr->Parent());
190
191 // Run checks
192 InitScopesPhaseETS::RunExternalNode(block, ctx->checker->VarBinder());
193 checker->VarBinder()->AsETSBinder()->ResolveReferencesForScope(block, NearestScope(block));
194 block->Check(checker);
195
196 // Replace Object Expression with Block Expression
197 return block;
198 }
199
CreateBlockExpression(ir::ObjectExpression * expr,checker::Type * keyType,checker::Type * valueType,public_lib::Context * ctx)200 ir::Expression *RecordLowering::CreateBlockExpression(ir::ObjectExpression *expr, checker::Type *keyType,
201 checker::Type *valueType, public_lib::Context *ctx)
202 {
203 /* This function will create block expression in the following format
204 *
205 * let map = new Map<key_type, value_type>();
206 * map.set(k1, v1)
207 * map.set(k2, v2)
208 * ...
209 * map
210 */
211
212 // Initialize map with provided type arguments
213 auto *ident = Gensym(ctx->Allocator());
214 std::stringstream ss;
215 expr->TsType()->ToAssemblerType(ss);
216
217 ArenaVector<ir::Statement *> statements(ctx->allocator->Adapter());
218 auto &properties = expr->Properties();
219 // currently we only have Map and Record in this if branch
220 std::string containerType;
221 if (ss.str() == compiler::Signatures::BUILTIN_MAP) {
222 containerType = "Map";
223 } else {
224 containerType = "Record";
225 }
226
227 const std::string createSrc =
228 "let @@I1 = new " + containerType + "<" + TypeToString(keyType) + "," + "@@T2" + ">()";
229 statements.push_back(ctx->parser->AsETSParser()->CreateFormattedStatements(createSrc, ident, valueType).front());
230
231 // Build statements from properties
232
233 for (const auto &property : properties) {
234 ES2PANDA_ASSERT(property->IsProperty());
235 auto p = property->AsProperty();
236 statements.push_back(
237 CreateStatement("@@I1.set(@@E2, @@E3)", ident->Clone(ctx->allocator, nullptr), p->Key(), p->Value(), ctx));
238 }
239 statements.push_back(CreateStatement("@@I1", ident->Clone(ctx->allocator, nullptr), nullptr, nullptr, ctx));
240
241 // Create Block Expression
242 auto block = ctx->AllocNode<ir::BlockExpression>(std::move(statements));
243 return block;
244 }
245
246 } // namespace ark::es2panda::compiler
247