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