• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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 "promiseVoid.h"
17 #include "checker/ETSchecker.h"
18 
19 namespace ark::es2panda::compiler {
HandleAsyncScriptFunctionBody(public_lib::Context * ctx,ir::BlockStatement * body)20 ir::BlockStatement *PromiseVoidInferencePhase::HandleAsyncScriptFunctionBody(public_lib::Context *ctx,
21                                                                              ir::BlockStatement *body)
22 {
23     (void)ctx;
24     body->TransformChildrenRecursively(
25         [ctx](checker::AstNodePtr ast) -> checker::AstNodePtr {
26             if (ast->IsReturnStatement()) {
27                 auto *returnStmt = ast->AsReturnStatement();
28                 const auto *arg = returnStmt->Argument();
29                 if (arg == nullptr) {
30                     auto *voidId = ctx->AllocNode<ir::Identifier>(compiler::Signatures::UNDEFINED, ctx->Allocator());
31                     const auto &returnLoc = returnStmt->Range();
32                     voidId->SetRange({returnLoc.end, returnLoc.end});
33                     returnStmt->SetArgument(voidId);
34                 }
35             }
36             return ast;
37         },
38         Name());
39 
40     return body;
41 }
42 
SetRangeRecursively(ir::TypeNode * node,const lexer::SourceRange & loc)43 void PromiseVoidInferencePhase::SetRangeRecursively(ir::TypeNode *node, const lexer::SourceRange &loc)
44 {
45     node->SetRange(loc);
46     node->TransformChildrenRecursively(
47         // CC-OFFNXT(G.FMT.14-CPP) project code style
48         [loc](ir::AstNode *ast) -> ir::AstNode * {
49             ast->SetRange(loc);
50             return ast;
51         },
52         Name());
53 }
54 
CreatePromiseVoidType(public_lib::Context * ctx,const lexer::SourceRange & loc)55 ir::TypeNode *PromiseVoidInferencePhase::CreatePromiseVoidType(public_lib::Context *ctx, const lexer::SourceRange &loc)
56 {
57     auto *voidParam = [ctx]() {
58         auto paramsVector = ArenaVector<ir::TypeNode *>(ctx->Allocator()->Adapter());
59         paramsVector.push_back(ctx->AllocNode<ir::ETSPrimitiveType>(ir::PrimitiveType::VOID, ctx->Allocator()));
60         auto *params = ctx->AllocNode<ir::TSTypeParameterInstantiation>(std::move(paramsVector));
61         return params;
62     }();
63 
64     auto *promiseVoidType = [ctx, voidParam]() {
65         auto *promiseId = ctx->AllocNode<ir::Identifier>(compiler::Signatures::BUILTIN_PROMISE_CLASS, ctx->Allocator());
66         auto *part = ctx->AllocNode<ir::ETSTypeReferencePart>(promiseId, voidParam, nullptr, ctx->Allocator());
67         auto *type = ctx->AllocNode<ir::ETSTypeReference>(part, ctx->Allocator());
68         return type;
69     }();
70 
71     SetRangeRecursively(promiseVoidType, loc);
72 
73     return promiseVoidType;
74 }
75 
CheckForPromiseVoid(const ir::TypeNode * type)76 static bool CheckForPromiseVoid(const ir::TypeNode *type)
77 {
78     if (type == nullptr || !type->IsETSTypeReference()) {
79         return false;
80     }
81 
82     auto *typeRef = type->AsETSTypeReference();
83     auto *typePart = typeRef->Part();
84     if (typePart->Previous() != nullptr) {
85         return false;
86     }
87 
88     if (typePart->TypeParams() == nullptr) {
89         return false;
90     }
91     const auto &params = typePart->TypeParams()->Params();
92     if (params.size() != 1) {
93         return false;
94     }
95 
96     const auto &param = params.at(0);
97     if (!param->IsETSUndefinedType()) {
98         return false;
99     }
100 
101     const auto isTypePromise = typePart->Name()->AsIdentifier()->Name() == compiler::Signatures::BUILTIN_PROMISE_CLASS;
102     return isTypePromise;
103 }
104 
105 using AstNodePtr = ir::AstNode *;
106 
107 /*
108  * Transformation is basically syntactical: it adds relevant return type and return statements to methods and function
109  * NOTE: but not for lambdas, at least for now
110  * So, the code
111  * async function f() {}
112  * transforms to
113  * async function f(): Promise<void> { return Void; }
114  * */
115 
PerformForModule(public_lib::Context * ctx,parser::Program * program)116 bool PromiseVoidInferencePhase::PerformForModule(public_lib::Context *ctx, parser::Program *program)
117 {
118     auto genTypeLocation = [](ir::ScriptFunction *function) -> lexer::SourceRange {
119         const auto &params = function->Params();
120         const auto &id = function->Id();
121         const auto &body = function->Body();
122         if (!params.empty()) {
123             const auto &last = params.back();
124             const auto &loc = last->Range();
125             return {loc.end, loc.end};
126         }
127 
128         if (id != nullptr) {
129             const auto &loc = id->Range();
130             return {loc.end, loc.end};
131         }
132 
133         if (function->HasBody()) {
134             const auto &loc = body->Range();
135             return {loc.start, loc.start};
136         }
137 
138         const auto &loc = function->Range();
139         return {loc.end, loc.end};
140     };
141 
142     const auto transformer = [this, ctx, genTypeLocation](ir::AstNode *ast) -> AstNodePtr {
143         if (!(ast->IsScriptFunction() && ast->AsScriptFunction()->IsAsyncFunc())) {
144             return ast;
145         }
146 
147         auto *function = ast->AsScriptFunction();
148         auto *returnAnn = function->ReturnTypeAnnotation();
149         const auto hasReturnAnn = returnAnn != nullptr;
150         const auto hasPromiseVoid = CheckForPromiseVoid(returnAnn);
151 
152         if (!hasReturnAnn) {
153             if (!function->HasReturnStatement()) {
154                 const auto &loc = genTypeLocation(function);
155                 function->SetReturnTypeAnnotation(CreatePromiseVoidType(ctx, loc));
156             }
157 
158             if (function->HasBody() && function->Body()->IsBlockStatement()) {
159                 HandleAsyncScriptFunctionBody(ctx, function->Body()->AsBlockStatement());
160             }
161         } else if (hasPromiseVoid && function->HasBody() && function->Body()->IsBlockStatement()) {
162             HandleAsyncScriptFunctionBody(ctx, function->Body()->AsBlockStatement());
163         }
164 
165         return ast;
166     };
167 
168     program->Ast()->TransformChildrenRecursively(transformer, Name());
169 
170     return true;
171 }
172 
PostconditionForModule(public_lib::Context * ctx,const parser::Program * program)173 bool PromiseVoidInferencePhase::PostconditionForModule(public_lib::Context *ctx, const parser::Program *program)
174 {
175     (void)ctx;
176 
177     auto checkFunctionBody = [](const ir::BlockStatement *body) -> bool {
178         if (!body->IsReturnStatement()) {
179             return true;
180         }
181         auto *returnStmt = body->AsReturnStatement();
182         const auto *arg = returnStmt->Argument();
183 
184         if (!arg->IsIdentifier()) {
185             return false;
186         }
187 
188         const auto *id = arg->AsIdentifier();
189         return id->Name() == compiler::Signatures::UNDEFINED;
190     };
191 
192     auto isOk = true;
193     auto transformer = [checkFunctionBody, &isOk](ir::AstNode *ast) {
194         if (!(ast->IsScriptFunction() && ast->AsScriptFunction()->IsAsyncFunc())) {
195             return;
196         }
197         auto *function = ast->AsScriptFunction();
198         auto *returnAnn = function->ReturnTypeAnnotation();
199         if (!CheckForPromiseVoid(returnAnn)) {
200             return;
201         }
202         if (function->HasBody() && function->Body()->IsBlockStatement()) {
203             if (!checkFunctionBody(function->Body()->AsBlockStatement())) {
204                 isOk = false;
205                 return;
206             }
207         }
208     };
209     program->Ast()->IterateRecursively(transformer);
210 
211     return isOk;
212 }
213 }  // namespace ark::es2panda::compiler
214