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