• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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