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 ¶ms = typePart->TypeParams()->Params();
110 if (params.size() != 1) {
111 return false;
112 }
113
114 const auto ¶m = 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 ¶ms = 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