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
32namespace ark::es2panda::compiler {
33ir::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
57void 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
68ir::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
95static 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
132using 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
143bool 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
202bool 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