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