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