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