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