1 /*
2 * Copyright (c) 2021-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 "etsEnumType.h"
17
18 #include "checker/ETSchecker.h"
19 #include "checker/ets/conversion.h"
20 #include "checker/ets/unboxingConverter.h"
21 #include "ir/expressions/identifier.h"
22 #include "ir/expressions/literals/numberLiteral.h"
23 #include "ir/expressions/memberExpression.h"
24 #include "ir/ts/tsEnumMember.h"
25
26 namespace ark::es2panda::checker {
ETSEnumType(const ir::TSEnumDeclaration * const enumDecl,UType ordinal,const ir::TSEnumMember * const member,TypeFlag const typeFlag)27 ETSEnumType::ETSEnumType(const ir::TSEnumDeclaration *const enumDecl, UType ordinal,
28 const ir::TSEnumMember *const member, TypeFlag const typeFlag)
29 : Type(typeFlag), decl_(enumDecl), ordinal_ {ordinal}, member_(member)
30 {
31 }
32
AssignmentSource(TypeRelation * const relation,Type * const target)33 bool ETSEnumType::AssignmentSource(TypeRelation *const relation, Type *const target)
34 {
35 bool result = false;
36
37 if (target->IsETSEnumType()) {
38 result = IsSameEnumType(target->AsETSEnumType());
39 } else if (target->IsETSStringType()) {
40 result = IsETSStringEnumType();
41 if (result) {
42 relation->GetNode()->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
43 }
44 } else if (target->IsETSObjectType()) {
45 if (target->AsETSObjectType()->IsGlobalETSObjectType()) {
46 result = true;
47 relation->GetNode()->AddBoxingUnboxingFlags(ir::BoxingUnboxingFlags::BOX_TO_ENUM);
48 } else if (target->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_NUMERIC) && IsETSIntEnumType()) {
49 result = true;
50 relation->GetNode()->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
51 }
52 } else if (target->HasTypeFlag(TypeFlag::ETS_NUMERIC)) {
53 result = IsETSIntEnumType();
54 if (result) {
55 relation->GetNode()->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
56 }
57 } else if (target->IsETSBooleanType()) {
58 result = true;
59 relation->GetNode()->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
60 } else if (target->IsETSUnionType()) {
61 auto &unionConstituentTypes = target->AsETSUnionType()->ConstituentTypes();
62 for (auto *constituentType : unionConstituentTypes) {
63 if (constituentType == GetDecl()->BoxedClass()->TsType()) {
64 result = true;
65 relation->GetNode()->AddBoxingUnboxingFlags(ir::BoxingUnboxingFlags::BOX_TO_ENUM);
66 break;
67 }
68 }
69 }
70 relation->Result(result);
71 return relation->IsTrue();
72 }
73
AssignmentTarget(TypeRelation * const relation,Type * const source)74 void ETSEnumType::AssignmentTarget(TypeRelation *const relation, Type *const source)
75 {
76 auto checker = relation->GetChecker()->AsETSChecker();
77
78 Type *const unboxedSource = checker->MaybeUnboxType(source);
79 if (unboxedSource != source && !relation->OnlyCheckBoxingUnboxing()) {
80 relation->GetNode()->AddBoxingUnboxingFlags(ir::BoxingUnboxingFlags::UNBOX_TO_ENUM);
81 }
82
83 relation->Result(false);
84 if (unboxedSource->IsETSEnumType()) {
85 relation->Result(IsSameEnumType(unboxedSource->AsETSEnumType()));
86 } else if (unboxedSource->IsETSStringType()) {
87 relation->Result(IsSameEnumType(unboxedSource->AsETSStringEnumType()));
88 }
89 }
90
Cast(TypeRelation * relation,Type * target)91 void ETSEnumType::Cast(TypeRelation *relation, Type *target)
92 {
93 if (target->IsETSEnumType()) {
94 conversion::Identity(relation, this, target);
95 return;
96 }
97
98 if (target->HasTypeFlag(TypeFlag::ETS_NUMERIC)) {
99 relation->Result(true);
100 return;
101 }
102
103 // NOTE(vpukhov): #20510 conversion rules
104 if (target->IsETSObjectType()) {
105 if (target->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BOXED_ENUM)) {
106 conversion::Boxing(relation, this);
107 return;
108 }
109
110 if (target->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_TYPE)) {
111 auto unboxedTarget = relation->GetChecker()->AsETSChecker()->MaybeUnboxInRelation(target);
112 if (unboxedTarget == nullptr) {
113 conversion::Forbidden(relation);
114 return;
115 }
116 Cast(relation, unboxedTarget);
117 if (relation->IsTrue()) {
118 conversion::Boxing(relation, unboxedTarget);
119 return;
120 }
121 conversion::Forbidden(relation);
122 return;
123 }
124
125 conversion::BoxingWideningReference(relation, this, target->AsETSObjectType());
126 return;
127 }
128 conversion::Forbidden(relation);
129 }
130
Instantiate(ArenaAllocator * allocator,TypeRelation * relation,GlobalTypesHolder * globalTypes)131 Type *ETSEnumType::Instantiate([[maybe_unused]] ArenaAllocator *allocator, [[maybe_unused]] TypeRelation *relation,
132 [[maybe_unused]] GlobalTypesHolder *globalTypes)
133 {
134 return this;
135 }
136
Identical(TypeRelation * const relation,Type * const other)137 void ETSEnumType::Identical(TypeRelation *const relation, Type *const other)
138 {
139 relation->Result(other->IsETSEnumType() && IsSameEnumType(other->AsETSEnumType()));
140 }
141
ToAssemblerType(std::stringstream & ss) const142 void ETSEnumType::ToAssemblerType(std::stringstream &ss) const
143 {
144 ToAssemblerTypeImpl<UType>(ss);
145 }
146
ToDebugInfoType(std::stringstream & ss) const147 void ETSEnumType::ToDebugInfoType(std::stringstream &ss) const
148 {
149 ToDebugInfoTypeImpl<UType>(ss);
150 }
151
ToString(std::stringstream & ss,bool precise) const152 void ETSEnumType::ToString(std::stringstream &ss, [[maybe_unused]] bool precise) const
153 {
154 ss << decl_->Key()->Name();
155 }
156
GetDecl() const157 const ir::TSEnumDeclaration *ETSEnumType::GetDecl() const noexcept
158 {
159 return decl_;
160 }
161
BoxedType() const162 Type *ETSEnumType::BoxedType() const noexcept
163 {
164 return GetDecl()->BoxedClass()->TsType();
165 }
166
GetMembers() const167 const ArenaVector<ir::AstNode *> &ETSEnumType::GetMembers() const noexcept
168 {
169 return decl_->Members();
170 }
171
GetMemberVar() const172 varbinder::LocalVariable *ETSEnumType::GetMemberVar() const noexcept
173 {
174 ASSERT(IsLiteralType());
175 return member_->Key()->AsIdentifier()->Variable()->AsLocalVariable();
176 }
177
GetName() const178 util::StringView ETSEnumType::GetName() const noexcept
179 {
180 return decl_->Key()->Name();
181 }
182
GetOrdinal() const183 ETSEnumType::UType ETSEnumType::GetOrdinal() const noexcept
184 {
185 ASSERT(IsLiteralType());
186 return ordinal_;
187 }
188
LookupConstant(ETSChecker * const checker,const ir::Expression * const expression,const ir::Identifier * const prop) const189 ETSEnumType *ETSEnumType::LookupConstant(ETSChecker *const checker, const ir::Expression *const expression,
190 const ir::Identifier *const prop) const
191 {
192 if (!IsEnumTypeExpression(expression)) {
193 if (expression->IsIdentifier() &&
194 expression->AsIdentifier()->Variable()->HasFlag(varbinder::VariableFlags::TYPE_ALIAS)) {
195 checker->LogTypeError({"Cannot refer to enum members through type alias."}, prop->Start());
196 } else if (IsLiteralType()) {
197 checker->LogTypeError({"Cannot refer to enum members through variable."}, prop->Start());
198 } else {
199 checker->LogTypeError({"Enum constant does not have property '", prop->Name(), "'."}, prop->Start());
200 }
201 return nullptr;
202 }
203
204 auto *const member = FindMember(prop->Name());
205 if (member == nullptr) {
206 checker->LogTypeError({"No enum constant named '", prop->Name(), "' in enum '", this, "'"}, prop->Start());
207 return nullptr;
208 }
209
210 auto *const enumInterface = member->Key()->AsIdentifier()->Variable()->TsType()->AsETSEnumType();
211 ASSERT(enumInterface->IsLiteralType());
212 return enumInterface;
213 }
214
LookupMethod(ETSChecker * checker,const ir::Expression * const expression,const ir::Identifier * const prop) const215 ETSFunctionType *ETSEnumType::LookupMethod(ETSChecker *checker, const ir::Expression *const expression,
216 const ir::Identifier *const prop) const
217 {
218 if (IsEnumTypeExpression(expression)) {
219 return LookupTypeMethod(checker, prop);
220 }
221
222 ASSERT(IsEnumInstanceExpression(expression));
223 return LookupConstantMethod(checker, prop);
224 }
225
IsSameEnumType(const ETSEnumType * const other) const226 bool ETSEnumType::IsSameEnumType(const ETSEnumType *const other) const noexcept
227 {
228 return other->decl_ == decl_;
229 }
230
IsSameEnumLiteralType(const ETSEnumType * const other) const231 bool ETSEnumType::IsSameEnumLiteralType(const ETSEnumType *const other) const noexcept
232 {
233 ASSERT(IsLiteralType() && IsSameEnumType(other));
234 return member_ == other->member_;
235 }
236
IsEnumInstanceExpression(const ir::Expression * const expression) const237 bool ETSEnumType::IsEnumInstanceExpression(const ir::Expression *const expression) const noexcept
238 {
239 ASSERT(IsSameEnumType(expression->TsType()->AsETSEnumType()));
240
241 return IsEnumLiteralExpression(expression) || !IsEnumTypeExpression(expression);
242 }
243
IsEnumLiteralExpression(const ir::Expression * const expression) const244 bool ETSEnumType::IsEnumLiteralExpression(const ir::Expression *const expression) const noexcept
245 {
246 ASSERT(IsSameEnumType(expression->TsType()->AsETSEnumType()));
247
248 if (expression->IsMemberExpression()) {
249 const auto *const memberExpr = expression->AsMemberExpression();
250 return memberExpr->Kind() == ir::MemberExpressionKind::PROPERTY_ACCESS &&
251 IsEnumTypeExpression(memberExpr->Object());
252 }
253
254 return false;
255 }
256
IsEnumTypeExpression(const ir::Expression * const expression) const257 bool ETSEnumType::IsEnumTypeExpression(const ir::Expression *const expression) const noexcept
258 {
259 if (expression->TsType()->IsETSEnumType()) {
260 ASSERT(IsSameEnumType(expression->TsType()->AsETSEnumType()));
261 } else {
262 return false;
263 }
264
265 if (expression->IsCallExpression()) {
266 return false;
267 }
268
269 // clang-format off
270 const auto *const localVar = [expression]() -> const varbinder::LocalVariable* {
271 if (expression->IsMemberExpression()) {
272 const auto *const memberExpr = expression->AsMemberExpression();
273 return memberExpr->PropVar() != nullptr
274 ? memberExpr->PropVar()
275 : memberExpr->Object()->AsIdentifier()->Variable()->AsLocalVariable();
276 }
277 return expression->AsIdentifier()->Variable()->AsLocalVariable();
278 }();
279 // clang-format on
280 ASSERT(localVar->Declaration() == decl_->Key()->AsIdentifier()->Variable()->Declaration() ||
281 !localVar->HasFlag(varbinder::VariableFlags::ENUM_LITERAL));
282 return localVar->HasFlag(varbinder::VariableFlags::ENUM_LITERAL);
283 }
284
FromIntMethod() const285 ETSEnumType::Method ETSEnumType::FromIntMethod() const noexcept
286 {
287 ASSERT(fromIntMethod_.globalSignature != nullptr && fromIntMethod_.memberProxyType == nullptr);
288 return fromIntMethod_;
289 }
290
BoxedFromIntMethod() const291 ETSEnumType::Method ETSEnumType::BoxedFromIntMethod() const noexcept
292 {
293 ASSERT(boxedFromIntMethod_.globalSignature != nullptr && boxedFromIntMethod_.memberProxyType == nullptr);
294 return boxedFromIntMethod_;
295 }
296
UnboxMethod() const297 ETSEnumType::Method ETSEnumType::UnboxMethod() const noexcept
298 {
299 ASSERT(unboxMethod_.globalSignature != nullptr && unboxMethod_.memberProxyType != nullptr);
300 return unboxMethod_;
301 }
302
ValueOfMethod() const303 ETSEnumType::Method ETSEnumType::ValueOfMethod() const noexcept
304 {
305 ASSERT(valueOfMethod_.globalSignature != nullptr && valueOfMethod_.memberProxyType != nullptr);
306 return valueOfMethod_;
307 }
308
GetNameMethod() const309 ETSEnumType::Method ETSEnumType::GetNameMethod() const noexcept
310 {
311 ASSERT(getNameMethod_.globalSignature != nullptr && getNameMethod_.memberProxyType != nullptr);
312 return getNameMethod_;
313 }
314
ToStringMethod() const315 ETSEnumType::Method ETSEnumType::ToStringMethod() const noexcept
316 {
317 ASSERT(toStringMethod_.globalSignature != nullptr && toStringMethod_.memberProxyType != nullptr);
318 return toStringMethod_;
319 }
320
GetValueOfMethod() const321 ETSEnumType::Method ETSEnumType::GetValueOfMethod() const noexcept
322 {
323 ASSERT(getValueOfMethod_.globalSignature != nullptr && getValueOfMethod_.memberProxyType != nullptr);
324 return getValueOfMethod_;
325 }
326
ValuesMethod() const327 ETSEnumType::Method ETSEnumType::ValuesMethod() const noexcept
328 {
329 ASSERT(valuesMethod_.globalSignature != nullptr && valuesMethod_.memberProxyType != nullptr);
330 return valuesMethod_;
331 }
332
IsLiteralType() const333 bool ETSEnumType::IsLiteralType() const noexcept
334 {
335 return member_ != nullptr;
336 }
337
FindMember(const util::StringView & name) const338 ir::TSEnumMember *ETSEnumType::FindMember(const util::StringView &name) const noexcept
339 {
340 ASSERT(!IsLiteralType());
341 const auto &members = GetMembers();
342 auto memberIt = std::find_if(members.begin(), members.end(), [name](const ir::AstNode *const node) {
343 return node->AsTSEnumMember()->Key()->AsIdentifier()->Name() == name;
344 });
345 if (memberIt != members.end()) {
346 return (*memberIt)->AsTSEnumMember();
347 }
348
349 return nullptr;
350 }
351
LookupConstantMethod(ETSChecker * const checker,const ir::Identifier * const prop) const352 ETSFunctionType *ETSEnumType::LookupConstantMethod(ETSChecker *const checker, const ir::Identifier *const prop) const
353 {
354 if (prop->Name() == TO_STRING_METHOD_NAME) {
355 ASSERT(toStringMethod_.memberProxyType != nullptr);
356 return toStringMethod_.memberProxyType;
357 }
358
359 if (prop->Name() == VALUE_OF_METHOD_NAME) {
360 ASSERT(valueOfMethod_.memberProxyType != nullptr);
361 return valueOfMethod_.memberProxyType;
362 }
363
364 if (prop->Name() == GET_NAME_METHOD_NAME) {
365 ASSERT(getNameMethod_.memberProxyType != nullptr);
366 return getNameMethod_.memberProxyType;
367 }
368
369 checker->LogTypeError({"No enum item method called '", prop->Name(), "'"}, prop->Start());
370 return nullptr;
371 }
372
LookupTypeMethod(ETSChecker * const checker,const ir::Identifier * const prop) const373 ETSFunctionType *ETSEnumType::LookupTypeMethod(ETSChecker *const checker, const ir::Identifier *const prop) const
374 {
375 if (prop->Name() == VALUES_METHOD_NAME) {
376 ASSERT(valuesMethod_.memberProxyType != nullptr);
377 return valuesMethod_.memberProxyType;
378 }
379
380 if (prop->Name() == GET_VALUE_OF_METHOD_NAME) {
381 ASSERT(getValueOfMethod_.memberProxyType != nullptr);
382 return getValueOfMethod_.memberProxyType;
383 }
384
385 checker->LogTypeError({"No enum type method called '", prop->Name(), "'"}, prop->Start());
386 return nullptr;
387 }
388
389 } // namespace ark::es2panda::checker
390