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 ¶ms = typePart->TypeParams()->Params();
92 if (params.size() != 1) {
93 return false;
94 }
95
96 const auto ¶m = 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 ¶ms = 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