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