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
Perform(public_lib::Context * ctx,parser::Program * program)46 bool RecordLowering::Perform(public_lib::Context *ctx, parser::Program *program)
47 {
48 for (auto &[_, extPrograms] : program->ExternalSources()) {
49 (void)_;
50 for (auto *extProg : extPrograms) {
51 Perform(ctx, extProg);
52 }
53 }
54
55 // Replace Record Object Expressions with Block Expressions
56 program->Ast()->TransformChildrenRecursively(
57 [this, ctx](ir::AstNode *ast) -> ir::AstNode * {
58 if (ast->IsObjectExpression()) {
59 return UpdateObjectExpression(ast->AsObjectExpression(), ctx);
60 }
61
62 return ast;
63 },
64 Name());
65
66 return true;
67 }
68
CheckDuplicateKey(ir::ObjectExpression * expr,public_lib::Context * ctx)69 void RecordLowering::CheckDuplicateKey(ir::ObjectExpression *expr, public_lib::Context *ctx)
70 {
71 std::unordered_set<std::variant<int32_t, int64_t, float, double, util::StringView>> keySet;
72 for (auto *it : expr->Properties()) {
73 auto *prop = it->AsProperty();
74 switch (prop->Key()->Type()) {
75 case ir::AstNodeType::NUMBER_LITERAL: {
76 auto number = prop->Key()->AsNumberLiteral()->Number();
77 if ((number.IsInt() && keySet.insert(number.GetInt()).second) ||
78 (number.IsLong() && keySet.insert(number.GetLong()).second) ||
79 (number.IsFloat() && keySet.insert(number.GetFloat()).second) ||
80 (number.IsDouble() && keySet.insert(number.GetDouble()).second)) {
81 continue;
82 }
83 ctx->checker->AsETSChecker()->ThrowTypeError(
84 "An object literal cannot multiple properties with same name", expr->Start());
85 }
86 case ir::AstNodeType::STRING_LITERAL: {
87 if (keySet.insert(prop->Key()->AsStringLiteral()->Str()).second) {
88 continue;
89 }
90 ctx->checker->AsETSChecker()->ThrowTypeError(
91 "An object literal cannot multiple properties with same name", expr->Start());
92 }
93 case ir::AstNodeType::IDENTIFIER: {
94 ctx->checker->AsETSChecker()->ThrowTypeError("Object literal may only specify known properties",
95 expr->Start());
96 }
97 default: {
98 UNREACHABLE();
99 break;
100 }
101 }
102 }
103 }
104
CreateStatement(const std::string & src,ir::Expression * ident,ir::Expression * key,ir::Expression * value,public_lib::Context * ctx)105 ir::Statement *RecordLowering::CreateStatement(const std::string &src, ir::Expression *ident, ir::Expression *key,
106 ir::Expression *value, public_lib::Context *ctx)
107 {
108 std::vector<ir::AstNode *> nodes;
109 if (ident != nullptr) {
110 nodes.push_back(ident);
111 }
112
113 if (key != nullptr) {
114 nodes.push_back(key);
115 }
116
117 if (value != nullptr) {
118 nodes.push_back(value);
119 }
120
121 auto parser = ctx->parser->AsETSParser();
122 auto statements = parser->CreateFormattedStatements(src, nodes);
123 if (!statements.empty()) {
124 return *statements.begin();
125 }
126
127 return nullptr;
128 }
129
UpdateObjectExpression(ir::ObjectExpression * expr,public_lib::Context * ctx)130 ir::Expression *RecordLowering::UpdateObjectExpression(ir::ObjectExpression *expr, public_lib::Context *ctx)
131 {
132 auto checker = ctx->checker->AsETSChecker();
133 if (expr->TsType() == nullptr) {
134 // Hasn't been through checker
135 checker->ThrowTypeError("Unexpected type error in Record object literal", expr->Start());
136 }
137
138 if (!expr->PreferredType()->IsETSObjectType()) {
139 // Unexpected preferred type
140 return expr;
141 }
142
143 std::stringstream ss;
144 expr->TsType()->ToAssemblerType(ss);
145 if (!(ss.str() == "escompat.Record" || ss.str() == "escompat.Map")) {
146 // Only update object expressions for Map/Record types
147 return expr;
148 }
149
150 // Access type arguments
151 [[maybe_unused]] size_t constexpr NUM_ARGUMENTS = 2;
152 auto typeArguments = expr->PreferredType()->AsETSObjectType()->TypeArguments();
153 ASSERT(typeArguments.size() == NUM_ARGUMENTS);
154
155 // check Duplicate key
156 CheckDuplicateKey(expr, ctx);
157
158 auto *const scope = NearestScope(expr);
159 checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY};
160 auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope);
161
162 // Create Block Expression
163 auto block = CreateBlockExpression(expr, typeArguments[0], typeArguments[1], ctx);
164 block->SetParent(expr->Parent());
165
166 // Run checks
167 InitScopesPhaseETS::RunExternalNode(block, ctx->checker->VarBinder());
168 checker->VarBinder()->AsETSBinder()->ResolveReferencesForScope(block, NearestScope(block));
169 block->Check(checker);
170
171 // Replace Object Expression with Block Expression
172 return block;
173 }
174
CreateBlockExpression(ir::ObjectExpression * expr,checker::Type * keyType,checker::Type * valueType,public_lib::Context * ctx)175 ir::Expression *RecordLowering::CreateBlockExpression(ir::ObjectExpression *expr, checker::Type *keyType,
176 checker::Type *valueType, public_lib::Context *ctx)
177 {
178 /* This function will create block expression in the following format
179 *
180 * let map = new Map<key_type, value_type>();
181 * map.set(k1, v1)
182 * map.set(k2, v2)
183 * ...
184 * map
185 */
186 auto checker = ctx->checker->AsETSChecker();
187
188 // Initialize map with provided type arguments
189 auto *ident = Gensym(checker->Allocator());
190 std::stringstream ss;
191 expr->TsType()->ToAssemblerType(ss);
192
193 ArenaVector<ir::Statement *> statements(ctx->allocator->Adapter());
194 auto &properties = expr->Properties();
195 // currently we only have Map and Record in this if branch
196 std::string containerType;
197 if (ss.str() == "escompat.Map") {
198 containerType = "Map";
199 } else {
200 containerType = "Record";
201 }
202
203 const std::string createSrc =
204 "let @@I1 = new " + containerType + "<" + TypeToString(keyType) + "," + TypeToString(valueType) + ">()";
205 statements.push_back(CreateStatement(createSrc, ident, nullptr, nullptr, ctx));
206
207 // Build statements from properties
208
209 for (const auto &property : properties) {
210 ASSERT(property->IsProperty());
211 auto p = property->AsProperty();
212 statements.push_back(
213 CreateStatement("@@I1.set(@@E2, @@E3)", ident->Clone(ctx->allocator, nullptr), p->Key(), p->Value(), ctx));
214 }
215 statements.push_back(CreateStatement("@@I1", ident->Clone(ctx->allocator, nullptr), nullptr, nullptr, ctx));
216
217 // Create Block Expression
218 auto block = checker->AllocNode<ir::BlockExpression>(std::move(statements));
219 return block;
220 }
221
222 } // namespace ark::es2panda::compiler
223