• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 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 "asyncMethodLowering.h"
17 
18 #include "checker/ETSchecker.h"
19 #include "checker/types/ets/etsAsyncFuncReturnType.h"
20 #include "compiler/lowering/util.h"
21 
22 namespace ark::es2panda::compiler {
23 
Name() const24 std::string_view AsyncMethodLowering::Name() const
25 {
26     return "AsyncMethodLowering";
27 }
28 
CreateFuncDecl(checker::ETSChecker * checker,ir::MethodDefinition * func,varbinder::LocalScope * scope)29 static void CreateFuncDecl(checker::ETSChecker *checker, ir::MethodDefinition *func, varbinder::LocalScope *scope)
30 {
31     auto *allocator = checker->Allocator();
32     auto *varBinder = checker->VarBinder();
33     // Add the function declarations to the lambda class scope
34     auto ctx = varbinder::LexicalScope<varbinder::LocalScope>::Enter(varBinder, scope);
35     ES2PANDA_ASSERT(func->Id() != nullptr);
36     varbinder::Variable *var = scope->FindLocal(func->Id()->Name(), varbinder::ResolveBindingOptions::ALL_DECLARATION);
37     if (var == nullptr) {
38         var = std::get<1>(
39             varBinder->NewVarDecl<varbinder::FunctionDecl>(func->Id()->Start(), allocator, func->Id()->Name(), func));
40     }
41     var->AddFlag(varbinder::VariableFlags::METHOD);
42     var->SetScope(ctx.GetScope());
43     func->Function()->Id()->SetVariable(var);
44 }
45 
CreateAsyncImplMethodReturnTypeAnnotation(checker::ETSChecker * checker,ir::ScriptFunction * asyncFunc)46 ir::ETSTypeReference *CreateAsyncImplMethodReturnTypeAnnotation(checker::ETSChecker *checker,
47                                                                 ir::ScriptFunction *asyncFunc)
48 {
49     // Set impl method return type "Object" because it may return Promise as well as Promise parameter's type
50     auto *objectId =
51         checker->AllocNode<ir::Identifier>(compiler::Signatures::BUILTIN_OBJECT_CLASS, checker->Allocator());
52     checker->VarBinder()->AsETSBinder()->LookupTypeReference(objectId, false);
53 
54     auto *returnTypeAnn = checker->AllocNode<ir::ETSTypeReference>(
55         checker->AllocNode<ir::ETSTypeReferencePart>(objectId, nullptr, nullptr, checker->Allocator()),
56         checker->Allocator());
57     ES2PANDA_ASSERT(returnTypeAnn != nullptr);
58     objectId->SetParent(returnTypeAnn->Part());
59     returnTypeAnn->Part()->SetParent(returnTypeAnn);
60 
61     auto *asyncFuncRetTypeAnn = asyncFunc->ReturnTypeAnnotation();
62     auto *promiseType = [checker](ir::TypeNode *type) {
63         if (type != nullptr) {
64             return type->GetType(checker)->AsETSObjectType();
65         }
66         return checker->GlobalBuiltinPromiseType()->AsETSObjectType();
67     }(asyncFuncRetTypeAnn);
68     auto *retType = checker->CreateETSAsyncFuncReturnTypeFromPromiseType(promiseType);
69     returnTypeAnn->SetTsType(retType);
70     return returnTypeAnn;
71 }
72 
CreateAsyncImplMethod(checker::ETSChecker * checker,ir::MethodDefinition * asyncMethod,ir::ClassDefinition * classDef)73 ir::MethodDefinition *CreateAsyncImplMethod(checker::ETSChecker *checker, ir::MethodDefinition *asyncMethod,
74                                             ir::ClassDefinition *classDef)
75 {
76     util::UString implName(checker->GetAsyncImplName(asyncMethod), checker->Allocator());
77     ir::ModifierFlags modifiers = asyncMethod->Modifiers();
78     // clear ASYNC flag for implementation
79     modifiers &= ~ir::ModifierFlags::ASYNC;
80     ir::ScriptFunction *asyncFunc = asyncMethod->Function();
81     ES2PANDA_ASSERT(asyncFunc != nullptr);
82     ir::ScriptFunctionFlags flags = ir::ScriptFunctionFlags::METHOD;
83 
84     if (asyncFunc->IsProxy()) {
85         flags |= ir::ScriptFunctionFlags::PROXY;
86     }
87 
88     if (asyncFunc->HasReturnStatement()) {
89         flags |= ir::ScriptFunctionFlags::HAS_RETURN;
90     }
91 
92     asyncMethod->AddModifier(ir::ModifierFlags::NATIVE);
93     asyncFunc->AddModifier(ir::ModifierFlags::NATIVE);
94     // Create async_impl method copied from CreateInvokeFunction
95     auto scopeCtx =
96         varbinder::LexicalScope<varbinder::ClassScope>::Enter(checker->VarBinder(), classDef->Scope()->AsClassScope());
97     auto *body = asyncFunc->Body();
98     ArenaVector<ir::Expression *> params(checker->Allocator()->Adapter());
99     ArenaUnorderedMap<varbinder::Variable *, varbinder::Variable *> oldParam2NewParamMap(
100         checker->Allocator()->Adapter());
101     varbinder::FunctionParamScope *paramScope = checker->CopyParams(asyncFunc->Params(), params, &oldParam2NewParamMap);
102     body->IterateRecursively([&oldParam2NewParamMap](ir::AstNode *astNode) -> void {
103         if (oldParam2NewParamMap.find(astNode->Variable()) != oldParam2NewParamMap.end()) {
104             astNode->SetVariable(oldParam2NewParamMap.at(astNode->Variable()));
105         }
106     });
107 
108     ir::ETSTypeReference *returnTypeAnn = nullptr;
109 
110     if (!asyncFunc->Signature()->HasSignatureFlag(checker::SignatureFlags::INFERRED_RETURN_TYPE)) {
111         returnTypeAnn = CreateAsyncImplMethodReturnTypeAnnotation(checker, asyncFunc);
112     }  // NOTE(vpukhov): #19874 - returnTypeAnn is not set
113 
114     ir::MethodDefinition *implMethod =
115         checker->CreateMethod(implName.View(), modifiers, flags, std::move(params), paramScope, returnTypeAnn, body);
116     asyncFunc->SetBody(nullptr);
117 
118     if (returnTypeAnn != nullptr) {
119         returnTypeAnn->SetParent(implMethod->Function());
120     }
121 
122     implMethod->Function()->AddFlag(ir::ScriptFunctionFlags::ASYNC_IMPL);
123     implMethod->SetParent(asyncMethod->Parent());
124     return implMethod;
125 }
126 
CreateAsyncProxy(checker::ETSChecker * checker,ir::MethodDefinition * asyncMethod,ir::ClassDefinition * classDef)127 ir::MethodDefinition *CreateAsyncProxy(checker::ETSChecker *checker, ir::MethodDefinition *asyncMethod,
128                                        ir::ClassDefinition *classDef)
129 {
130     ES2PANDA_ASSERT(asyncMethod != nullptr);
131     ir::ScriptFunction *asyncFunc = asyncMethod->Function();
132     ES2PANDA_ASSERT(asyncFunc != nullptr);
133     if (!asyncFunc->IsExternal()) {
134         checker->VarBinder()->AsETSBinder()->GetRecordTable()->Signatures().push_back(asyncFunc->Scope());
135     }
136 
137     ir::MethodDefinition *implMethod = CreateAsyncImplMethod(checker, asyncMethod, classDef);
138     ES2PANDA_ASSERT(implMethod != nullptr && implMethod->Function() != nullptr && implMethod->Id() != nullptr);
139     varbinder::FunctionScope *implFuncScope = implMethod->Function()->Scope();
140     for (auto *decl : asyncFunc->Scope()->Decls()) {
141         auto res = asyncFunc->Scope()->Bindings().find(decl->Name());
142         ES2PANDA_ASSERT(res != asyncFunc->Scope()->Bindings().end());
143         auto *const var = std::get<1>(*res);
144         var->SetScope(implFuncScope);
145         implFuncScope->Decls().push_back(decl);
146         implFuncScope->InsertBinding(decl->Name(), var);
147     }
148 
149     checker->ReplaceScope(implMethod->Function()->Body(), asyncFunc, implFuncScope);
150 
151     bool isStatic = asyncMethod->IsStatic();
152     if (isStatic) {
153         CreateFuncDecl(checker, implMethod, classDef->Scope()->AsClassScope()->StaticMethodScope());
154     } else {
155         CreateFuncDecl(checker, implMethod, classDef->Scope()->AsClassScope()->InstanceMethodScope());
156     }
157     implMethod->Id()->SetVariable(implMethod->Function()->Id()->Variable());
158 
159     checker->VarBinder()->AsETSBinder()->BuildProxyMethod(implMethod->Function(), classDef->InternalName(),
160                                                           asyncFunc->IsExternal());
161     implMethod->SetParent(asyncMethod->Parent());
162 
163     return implMethod;
164 }
165 
ComposeAsyncImplMethod(checker::ETSChecker * checker,ir::MethodDefinition * node)166 void ComposeAsyncImplMethod(checker::ETSChecker *checker, ir::MethodDefinition *node)
167 {
168     ES2PANDA_ASSERT(checker->FindAncestorGivenByType(node, ir::AstNodeType::CLASS_DEFINITION) != nullptr);
169     auto *classDef = checker->FindAncestorGivenByType(node, ir::AstNodeType::CLASS_DEFINITION)->AsClassDefinition();
170     ir::MethodDefinition *implMethod = CreateAsyncProxy(checker, node, classDef);
171 
172     implMethod->Check(checker);
173     node->SetAsyncPairMethod(implMethod);
174 
175     ES2PANDA_ASSERT(node->Function() != nullptr);
176     if (node->Function()->IsOverload() && node->BaseOverloadMethod()->AsyncPairMethod() != nullptr) {
177         auto *baseOverloadImplMethod = node->BaseOverloadMethod()->AsyncPairMethod();
178         ES2PANDA_ASSERT(implMethod->Function() != nullptr && baseOverloadImplMethod->Function() != nullptr);
179         implMethod->Function()->Id()->SetVariable(baseOverloadImplMethod->Function()->Id()->Variable());
180         baseOverloadImplMethod->AddOverload(implMethod);
181     } else if (node->Function()->IsOverload() && node->BaseOverloadMethod()->AsyncPairMethod() == nullptr) {
182         // If it's base overload function doesnot marked as async,
183         // then current AsyncImpl should be treated as AsyncPairMethod in base overload.
184         node->BaseOverloadMethod()->SetAsyncPairMethod(implMethod);
185         classDef->Body().push_back(implMethod);
186     } else {
187         classDef->Body().push_back(implMethod);
188     }
189 }
190 
HandleMethod(checker::ETSChecker * checker,ir::MethodDefinition * node)191 void HandleMethod(checker::ETSChecker *checker, ir::MethodDefinition *node)
192 {
193     ES2PANDA_ASSERT(!node->TsType()->IsTypeError() && node->Function() != nullptr);
194     if (util::Helpers::IsAsyncMethod(node) && !node->Function()->IsExternal()) {
195         ComposeAsyncImplMethod(checker, node);
196     }
197 
198     for (auto overload : node->Overloads()) {
199         HandleMethod(checker, overload);
200     }
201 }
202 
UpdateClassDefintion(checker::ETSChecker * checker,ir::ClassDefinition * classDef)203 void UpdateClassDefintion(checker::ETSChecker *checker, ir::ClassDefinition *classDef)
204 {
205     checker::SavedCheckerContext savedContext(checker, checker->Context().Status(),
206                                               classDef->TsType()->AsETSObjectType());
207     for (auto *it : classDef->Body()) {
208         if (it->IsMethodDefinition()) {
209             HandleMethod(checker, it->AsMethodDefinition());
210         }
211     }
212 }
213 
PerformForModule(public_lib::Context * ctx,parser::Program * program)214 bool AsyncMethodLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program)
215 {
216     checker::ETSChecker *const checker = ctx->checker->AsETSChecker();
217 
218     ir::NodeTransformer handleClassAsyncMethod = [checker](ir::AstNode *const ast) {
219         if (ast->IsClassDefinition()) {
220             UpdateClassDefintion(checker, ast->AsClassDefinition());
221         }
222         return ast;
223     };
224 
225     program->Ast()->TransformChildrenRecursively(handleClassAsyncMethod, Name());
226 
227     return true;
228 }
229 }  // namespace ark::es2panda::compiler
230