1 /*
2 * Copyright (c) 2021-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 "enumPostCheckLowering.h"
17 #include <cstddef>
18 #include <string>
19 #include "checker/ETSchecker.h"
20 #include "checker/types/type.h"
21 #include "checker/types/ets/etsEnumType.h"
22 #include "compiler/lowering/util.h"
23 #include "ir/astNode.h"
24 #include "ir/expressions/identifier.h"
25 #include "ir/statements/ifStatement.h"
26 #include "ir/ts/tsAsExpression.h"
27 #include "macros.h"
28 #include "parser/ETSparser.h"
29 #include "util.h"
30 #include "util/ustring.h"
31 #include "varbinder/ETSBinder.h"
32 #include "varbinder/variable.h"
33
34 namespace ark::es2panda::compiler {
35
FindEnclosingClass(ir::AstNode * ast)36 static ir::ClassDeclaration *FindEnclosingClass(ir::AstNode *ast)
37 {
38 for (ir::AstNode *curr = ast->Parent(); curr != nullptr; curr = curr->Parent()) {
39 if (curr->IsClassDeclaration()) {
40 return curr->AsClassDeclaration();
41 }
42 }
43 ES2PANDA_UNREACHABLE();
44 }
45
TypeToString(checker::Type * type)46 static std::string TypeToString(checker::Type *type)
47 {
48 std::stringstream ss;
49 type->ToString(ss);
50 return ss.str();
51 }
52
TypeAnnotationToString(ir::ETSTypeReference * typeAnnotation,public_lib::Context * ctx)53 static util::StringView TypeAnnotationToString(ir::ETSTypeReference *typeAnnotation, public_lib::Context *ctx)
54 {
55 std::stringstream ss;
56 std::function<void(ir::AstNode *)> typeAnnoToStringImpl = [&ss, &typeAnnoToStringImpl](ir::AstNode *node) -> void {
57 if (node->IsIdentifier()) {
58 ss << node->AsIdentifier()->ToString() << ".";
59 }
60 node->Iterate(typeAnnoToStringImpl);
61 };
62 typeAnnotation->Iterate(typeAnnoToStringImpl);
63 std::string res = ss.str();
64 res.erase(res.size() - 1);
65 return util::UString {res, ctx->Allocator()}.View();
66 }
67
IsValidEnumCasting(checker::Type * type,EnumCastType castType)68 static bool IsValidEnumCasting(checker::Type *type, EnumCastType castType)
69 {
70 switch (castType) {
71 case EnumCastType::NONE: {
72 return false;
73 }
74 case EnumCastType::CAST_TO_STRING: {
75 return type->IsETSStringEnumType();
76 }
77 case EnumCastType::CAST_TO_INT: {
78 return type->IsETSIntEnumType();
79 }
80 case EnumCastType::CAST_TO_INT_ENUM:
81 case EnumCastType::CAST_TO_STRING_ENUM: {
82 return true;
83 }
84 default: {
85 ES2PANDA_UNREACHABLE();
86 }
87 }
88 }
89
NeedHandleEnumCasting(ir::TSAsExpression * node)90 static EnumCastType NeedHandleEnumCasting(ir::TSAsExpression *node)
91 {
92 auto type = node->TsType();
93 EnumCastType castType = EnumCastType::NONE;
94 if (type->IsETSStringType()) {
95 castType = EnumCastType::CAST_TO_STRING;
96 } else if (type->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC) ||
97 (type->IsETSObjectType() &&
98 type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_NUMERIC))) {
99 castType = EnumCastType::CAST_TO_INT;
100 } else if (type->IsETSEnumType()) {
101 castType = type->IsETSIntEnumType() ? EnumCastType::CAST_TO_INT_ENUM : EnumCastType::CAST_TO_STRING_ENUM;
102 } else {
103 return castType;
104 }
105 auto expr = node->Expr();
106 if (expr->TsType()->IsETSUnionType()) {
107 for (auto *ct : expr->TsType()->AsETSUnionType()->ConstituentTypes()) {
108 if (ct->IsETSEnumType() && IsValidEnumCasting(ct, castType)) {
109 return castType;
110 }
111 }
112 }
113 return IsValidEnumCasting(expr->TsType(), castType) ? castType : EnumCastType::NONE;
114 }
115
CallStaticEnumMethod(public_lib::Context * ctx,parser::ETSParser * parser,std::string_view className,std::string_view methodName,ir::Expression * argument)116 static ir::CallExpression *CallStaticEnumMethod(public_lib::Context *ctx, parser::ETSParser *parser,
117 std::string_view className, std::string_view methodName,
118 ir::Expression *argument)
119 {
120 auto classId = parser->CreateExpression(className);
121 auto methodId = ctx->AllocNode<ir::Identifier>(methodName, ctx->Allocator());
122 auto callee = ctx->AllocNode<ir::MemberExpression>(classId, methodId, ir::MemberExpressionKind::PROPERTY_ACCESS,
123 false, false);
124 ArenaVector<ir::Expression *> callArguments({argument}, ctx->Allocator()->Adapter());
125 return ctx->AllocNode<ir::CallExpression>(callee, std::move(callArguments), nullptr, false);
126 }
127
CallInstanceEnumMethod(public_lib::Context * ctx,std::string_view methodName,ir::Expression * thisArg)128 static ir::CallExpression *CallInstanceEnumMethod(public_lib::Context *ctx, std::string_view methodName,
129 ir::Expression *thisArg)
130 {
131 auto methodId = ctx->AllocNode<ir::Identifier>(methodName, ctx->Allocator());
132 auto callee = ctx->AllocNode<ir::MemberExpression>(thisArg, methodId, ir::MemberExpressionKind::PROPERTY_ACCESS,
133 false, false);
134
135 ArenaVector<ir::Expression *> callArguments({}, ctx->Allocator()->Adapter());
136 auto callExpr = ctx->AllocNode<ir::CallExpression>(callee, std::move(callArguments), nullptr, false);
137 callExpr->SetRange(thisArg->Range());
138 return callExpr;
139 }
140
CreateCallInstanceEnumExpression(public_lib::Context * ctx,ir::AstNode * const node,std::string_view methodName)141 static ir::CallExpression *CreateCallInstanceEnumExpression(public_lib::Context *ctx, ir::AstNode *const node,
142 std::string_view methodName)
143 {
144 ES2PANDA_ASSERT(node->IsExpression());
145 auto expr = node->AsExpression();
146 auto parent = expr->Parent();
147
148 auto *callExpr = CallInstanceEnumMethod(ctx, methodName, expr);
149 callExpr->SetParent(parent);
150
151 auto *calleeClass = FindEnclosingClass(expr);
152
153 auto *checker = ctx->checker->AsETSChecker();
154 auto *varBinder = checker->VarBinder()->AsETSBinder();
155
156 auto *nearestScope = NearestScope(parent);
157 auto lexScope = varbinder::LexicalScope<varbinder::Scope>::Enter(varBinder, nearestScope);
158 varBinder->ResolveReferencesForScopeWithContext(callExpr, nearestScope);
159
160 auto checkerCtx = checker::SavedCheckerContext(checker, checker::CheckerStatus::IN_CLASS,
161 calleeClass->Definition()->TsType()->AsETSObjectType());
162 auto scopeCtx = checker::ScopeContext(checker, nearestScope);
163
164 callExpr->Check(checker);
165 return callExpr;
166 }
167
MakeTypeReference(public_lib::Context * ctx,const std::string & name)168 [[nodiscard]] static ir::ETSTypeReference *MakeTypeReference(public_lib::Context *ctx, const std::string &name)
169 {
170 auto allocator = ctx->Allocator();
171 auto *const ident = ctx->AllocNode<ir::Identifier>(util::UString(name, allocator).View(), allocator);
172 auto *const referencePart = ctx->AllocNode<ir::ETSTypeReferencePart>(ident, allocator);
173 return ctx->AllocNode<ir::ETSTypeReference>(referencePart, allocator);
174 }
175
CreateEnumIfStatement(public_lib::Context * ctx,ir::Identifier * ident,const std::string & enumName,ir::Statement * consequent)176 static ir::IfStatement *CreateEnumIfStatement(public_lib::Context *ctx, ir::Identifier *ident,
177 const std::string &enumName, ir::Statement *consequent)
178 {
179 auto enumType = MakeTypeReference(ctx, enumName);
180 auto clonedIdent = ident->Clone(ctx->Allocator(), nullptr);
181 auto ifTestExpr = ctx->AllocNode<ir::BinaryExpression>(clonedIdent, enumType, lexer::TokenType::KEYW_INSTANCEOF);
182 return ctx->AllocNode<ir::IfStatement>(ifTestExpr, consequent, nullptr);
183 }
184
CreateStatement(const std::string & src,ir::Expression * ident,ir::Expression * init)185 ir::Statement *EnumPostCheckLoweringPhase::CreateStatement(const std::string &src, ir::Expression *ident,
186 ir::Expression *init)
187 {
188 std::vector<ir::AstNode *> nodes;
189 if (ident != nullptr) {
190 nodes.push_back(ident->Clone(context_->Allocator(), nullptr));
191 }
192
193 if (init != nullptr) {
194 nodes.push_back(init->Clone(context_->Allocator(), nullptr));
195 }
196
197 auto statements = parser_->CreateFormattedStatements(src, nodes);
198 if (!statements.empty()) {
199 return *statements.begin();
200 }
201
202 return nullptr;
203 }
204
HandleEnumTypeCasting(checker::Type * type,ir::Expression * expr,ir::TSAsExpression * tsAsExpr)205 ir::Expression *EnumPostCheckLoweringPhase::HandleEnumTypeCasting(checker::Type *type, ir::Expression *expr,
206 ir::TSAsExpression *tsAsExpr)
207 {
208 ir::Expression *transformedExpr = nullptr;
209 // Generate fromValue call;
210 if (type->IsETSEnumType()) {
211 auto exprType = expr->TsType();
212 if (exprType->IsETSEnumType() || exprType->IsETSAnyType() ||
213 (exprType->IsETSObjectType() && exprType->AsETSObjectType()->IsGlobalETSObjectType())) {
214 return expr;
215 }
216 auto name = TypeAnnotationToString(tsAsExpr->TypeAnnotation()->AsETSTypeReference(), context_);
217 transformedExpr = GenerateFromValueCall(expr, name);
218 } else {
219 transformedExpr = CallInstanceEnumMethod(context_, checker::ETSEnumType::VALUE_OF_METHOD_NAME, expr);
220 }
221 transformedExpr->SetRange(expr->Range());
222 transformedExpr->SetParent(expr->Parent());
223 return transformedExpr;
224 }
225
226 // CC-OFFNXT(huge_method,G.FUD.05)
CreateStatementForUnionConstituentType(EnumCastType castType,ir::Identifier * ident,checker::Type * type,ir::TSAsExpression * tsAsExpr,ArenaVector<ir::Statement * > & statements)227 void EnumPostCheckLoweringPhase::CreateStatementForUnionConstituentType(EnumCastType castType, ir::Identifier *ident,
228 checker::Type *type,
229 ir::TSAsExpression *tsAsExpr,
230 ArenaVector<ir::Statement *> &statements)
231 {
232 auto createInstanceOfStatement = [this, &statements, &ident, &type](ir::Expression *callExpr) {
233 auto consequent = CreateStatement("@@I1 = @@E2;", ident, callExpr);
234 auto ifStatement = CreateEnumIfStatement(context_, ident, TypeToString(type), consequent);
235 auto prevStatement = statements.back();
236 if (prevStatement != nullptr && prevStatement->IsIfStatement()) {
237 prevStatement->AsIfStatement()->SetAlternate(ifStatement);
238 }
239 statements.push_back(ifStatement);
240 };
241 switch (castType) {
242 case EnumCastType::CAST_TO_STRING: {
243 if (type->IsETSStringEnumType()) {
244 auto callExpr = CallInstanceEnumMethod(context_, checker::ETSEnumType::VALUE_OF_METHOD_NAME,
245 ident->Clone(context_->Allocator(), nullptr)->AsExpression());
246 callExpr->SetRange(tsAsExpr->Expr()->Range());
247 createInstanceOfStatement(callExpr);
248 }
249 break;
250 }
251 case EnumCastType::CAST_TO_INT: {
252 if (type->IsETSIntEnumType()) {
253 auto callExpr = CallInstanceEnumMethod(context_, checker::ETSEnumType::VALUE_OF_METHOD_NAME,
254 ident->Clone(context_->Allocator(), nullptr)->AsExpression());
255 callExpr->SetRange(tsAsExpr->Expr()->Range());
256 createInstanceOfStatement(callExpr);
257 }
258 break;
259 }
260 case EnumCastType::CAST_TO_INT_ENUM: {
261 // int and Boxed Int can be casted to int enum
262 if (type->IsIntType() || (type->IsETSObjectType() &&
263 type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_INT))) {
264 auto name = TypeAnnotationToString(tsAsExpr->TypeAnnotation()->AsETSTypeReference(), context_);
265 auto callExpr =
266 GenerateFromValueCall(ident->Clone(context_->Allocator(), nullptr)->AsExpression(), name);
267 callExpr->SetRange(tsAsExpr->Expr()->Range());
268 createInstanceOfStatement(callExpr);
269 }
270 break;
271 }
272 case EnumCastType::CAST_TO_STRING_ENUM: {
273 if (type->IsETSStringType()) {
274 auto name = TypeAnnotationToString(tsAsExpr->TypeAnnotation()->AsETSTypeReference(), context_);
275 auto callExpr =
276 GenerateFromValueCall(ident->Clone(context_->Allocator(), nullptr)->AsExpression(), name);
277 callExpr->SetRange(tsAsExpr->Expr()->Range());
278 createInstanceOfStatement(callExpr);
279 }
280 break;
281 }
282 default: {
283 ES2PANDA_UNREACHABLE();
284 }
285 }
286 }
287
HandleUnionTypeForCalls(checker::ETSUnionType * unionType,ir::Expression * expr,ir::TSAsExpression * tsAsExpr,EnumCastType castType)288 ir::Expression *EnumPostCheckLoweringPhase::HandleUnionTypeForCalls(checker::ETSUnionType *unionType,
289 ir::Expression *expr, ir::TSAsExpression *tsAsExpr,
290 EnumCastType castType)
291 {
292 /*
293 * For given union type: number | string | Enum | otherTypes, this method generate instanceof trees to ensuring
294 * all union constituent types are handled correctly with enum related casting.
295 */
296 auto *const allocator = context_->Allocator();
297 auto *genSymIdent = Gensym(allocator);
298 ArenaVector<ir::Statement *> statements(allocator->Adapter());
299 const std::string createSrc = "let @@I1 = @@E2";
300 statements.push_back(CreateStatement(createSrc, genSymIdent, expr));
301 for (auto type : unionType->ConstituentTypes()) {
302 CreateStatementForUnionConstituentType(castType, genSymIdent, type, tsAsExpr, statements);
303 }
304 statements.push_back(CreateStatement("@@I1", genSymIdent, nullptr));
305 auto block = context_->AllocNode<ir::BlockExpression>(std::move(statements));
306 return block;
307 }
308
GenerateEnumCasting(ir::TSAsExpression * node,EnumCastType castType)309 ir::AstNode *EnumPostCheckLoweringPhase::GenerateEnumCasting(ir::TSAsExpression *node, EnumCastType castType)
310 {
311 auto expr = node->Expr();
312 if (expr->TsType()->IsETSUnionType()) {
313 auto newExpr = HandleUnionTypeForCalls(expr->TsType()->AsETSUnionType(), node->Expr(), node, castType);
314 node->SetExpr(newExpr);
315 } else {
316 auto newExpr = HandleEnumTypeCasting(node->TsType(), node->Expr(), node);
317 node->SetExpr(newExpr);
318 }
319 node->SetTsType(nullptr);
320 auto *scope = NearestScope(node);
321 auto bscope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder_, scope);
322 CheckLoweredNode(varbinder_, checker_, node);
323 return node;
324 }
325
GenerateValueOfCall(ir::AstNode * const node)326 ir::AstNode *EnumPostCheckLoweringPhase::GenerateValueOfCall(ir::AstNode *const node)
327 {
328 node->Parent()->AddAstNodeFlags(ir::AstNodeFlags::RECHECK);
329 if (!node->IsExpression()) {
330 node->RemoveAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
331 return node;
332 }
333 auto *callExpr = CreateCallInstanceEnumExpression(context_, node, checker::ETSEnumType::VALUE_OF_METHOD_NAME);
334 node->RemoveAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
335 return callExpr;
336 }
337
GenerateFromValueCall(ir::Expression * const node,util::StringView name)338 ir::Expression *EnumPostCheckLoweringPhase::GenerateFromValueCall(ir::Expression *const node, util::StringView name)
339 {
340 auto *callExpr =
341 CallStaticEnumMethod(context_, parser_, name.Utf8(), checker::ETSEnumType::FROM_VALUE_METHOD_NAME, node);
342 callExpr->SetRange(node->Range());
343 return callExpr;
344 }
345
GenerateGetOrdinalCallForSwitch(ir::SwitchStatement * const node)346 ir::SwitchStatement *EnumPostCheckLoweringPhase::GenerateGetOrdinalCallForSwitch(ir::SwitchStatement *const node)
347 {
348 node->AddAstNodeFlags(ir::AstNodeFlags::RECHECK);
349 auto *discrminant =
350 CreateCallInstanceEnumExpression(context_, node->Discriminant(), checker::ETSEnumType::GET_ORDINAL_METHOD_NAME);
351 node->SetDiscriminant(discrminant);
352
353 for (auto *ele : node->Cases()) {
354 // Default case will not handle.
355 if (ele->Test() == nullptr) {
356 continue;
357 }
358 auto *newTest =
359 CreateCallInstanceEnumExpression(context_, ele->Test(), checker::ETSEnumType::GET_ORDINAL_METHOD_NAME);
360 ele->SetTest(newTest);
361 }
362 return node;
363 }
364
PerformForModule(public_lib::Context * ctx,parser::Program * program)365 bool EnumPostCheckLoweringPhase::PerformForModule(public_lib::Context *ctx, parser::Program *program)
366 {
367 if (program->Extension() != ScriptExtension::ETS) {
368 return true;
369 }
370
371 context_ = ctx;
372 parser_ = ctx->parser->AsETSParser();
373 checker_ = ctx->checker->AsETSChecker();
374 varbinder_ = ctx->parserProgram->VarBinder()->AsETSBinder();
375
376 program->Ast()->TransformChildrenRecursivelyPostorder(
377 // clang-format off
378 [this](ir::AstNode *const node) -> ir::AstNode* {
379 if (node->HasAstNodeFlags(ir::AstNodeFlags::RECHECK)) {
380 if (node->IsExpression()) {
381 node->AsExpression()->SetTsType(nullptr); // force recheck
382 }
383 if (checker_->Context().ContainingClass() == nullptr) {
384 auto *parentClass = util::Helpers::FindAncestorGivenByType(node, ir::AstNodeType::CLASS_DEFINITION);
385 checker_->Context().SetContainingClass(
386 parentClass->AsClassDefinition()->TsType()->AsETSObjectType());
387 }
388 node->RemoveAstNodeFlags(ir::AstNodeFlags::RECHECK);
389 node->Check(checker_);
390 if (node->IsExpression() && node->AsExpression()->TsType() != nullptr &&
391 !node->AsExpression()->TsType()->IsETSIntEnumType()) {
392 node->RemoveAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
393 }
394 }
395 if (node->HasAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF)) {
396 return GenerateValueOfCall(node);
397 }
398 if (node->IsTSAsExpression()) {
399 auto castFlag = NeedHandleEnumCasting(node->AsTSAsExpression());
400 if (castFlag == EnumCastType::NONE) {
401 return node;
402 }
403 return GenerateEnumCasting(node->AsTSAsExpression(), castFlag);
404 }
405 if (node->IsSwitchStatement() && node->AsSwitchStatement()->Discriminant()->TsType()->IsETSEnumType()) {
406 return GenerateGetOrdinalCallForSwitch(node->AsSwitchStatement());
407 }
408 return node;
409 },
410 // clang-format on
411 Name());
412 return true;
413 }
414
415 } // namespace ark::es2panda::compiler
416