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 "defaultParameterLowering.h" 17#include <iostream> 18#include "checker/ETSchecker.h" 19#include "parser/ETSparser.h" 20#include "parser/parserImpl.h" 21#include "lexer.h" 22#include "utils/arena_containers.h" 23#include "ir/statement.h" 24#include "varbinder/ETSBinder.h" 25 26namespace ark::es2panda::compiler { 27 28std::pair<bool, std::size_t> DefaultParameterLowering::HasDefaultParam(const ir::ScriptFunction *function, 29 parser::Program *program) 30{ 31 bool hasDefaultParameter = false; 32 bool hasRestParameter = false; 33 std::size_t requiredParametersNumber = 0U; 34 35 for (auto *const it : function->Params()) { 36 auto const *const param = it->AsETSParameterExpression(); 37 38 if (param->IsRestParameter()) { 39 hasRestParameter = true; 40 continue; 41 } 42 43 if (hasRestParameter) { 44 ThrowSyntaxError("Rest parameter should be the last one.", param->Start(), program); 45 } 46 47 if (param->IsDefault()) { 48 hasDefaultParameter = true; 49 continue; 50 } 51 52 if (hasDefaultParameter) { 53 ThrowSyntaxError("Required parameter follows default parameter(s).", param->Start(), program); 54 } 55 56 ++requiredParametersNumber; 57 } 58 59 if (hasDefaultParameter && hasRestParameter) { 60 ThrowSyntaxError("Both optional and rest parameters are not allowed in function's parameter list.", 61 function->Start(), program); 62 } 63 64 return std::make_pair(hasDefaultParameter, requiredParametersNumber); 65} 66 67ir::TSTypeParameterDeclaration *DefaultParameterLowering::CreateParameterDeclaraion(ir::MethodDefinition *method, 68 public_lib::Context *ctx) 69{ 70 auto *checker = ctx->checker->AsETSChecker(); 71 if (method->Function()->TypeParams() == nullptr || method->Function()->TypeParams()->Params().empty()) { 72 return nullptr; 73 } 74 75 ArenaVector<ir::TSTypeParameter *> typeParams(checker->Allocator()->Adapter()); 76 77 auto parentParams = method->Function()->TypeParams()->Params(); 78 std::for_each(parentParams.begin(), parentParams.end(), [&typeParams, checker](ir::TSTypeParameter *par) { 79 ir::Identifier *ident = par->Name()->Clone(checker->Allocator(), nullptr)->AsIdentifier(); 80 auto *constraint = par->Constraint() != nullptr 81 ? par->Constraint()->Clone(checker->Allocator(), nullptr)->AsTypeNode() 82 : nullptr; 83 auto *defaultType = par->DefaultType() != nullptr 84 ? par->DefaultType()->Clone(checker->Allocator(), nullptr)->AsTypeNode() 85 : nullptr; 86 auto *typeParam = checker->AllocNode<ir::TSTypeParameter>(ident, constraint, defaultType); 87 typeParams.push_back(typeParam); 88 }); 89 return checker->AllocNode<ir::TSTypeParameterDeclaration>(std::move(typeParams), typeParams.size()); 90} 91 92ir::FunctionSignature DefaultParameterLowering::CreateFunctionSignature(ir::MethodDefinition *method, 93 ArenaVector<ir::Expression *> funcParam, 94 public_lib::Context *ctx) 95{ 96 auto *checker = ctx->checker->AsETSChecker(); 97 98 ir::TSTypeParameterDeclaration *typeParamDecl = CreateParameterDeclaraion(method, ctx); 99 auto *returnTypeAnnotation = 100 method->Function()->ReturnTypeAnnotation() != nullptr 101 ? method->Function()->ReturnTypeAnnotation()->Clone(checker->Allocator(), nullptr)->AsTypeNode() 102 : nullptr; 103 104 return ir::FunctionSignature(typeParamDecl, std::move(funcParam), returnTypeAnnotation); 105} 106 107ir::TSTypeParameterInstantiation *DefaultParameterLowering::CreateTypeParameterInstantiation( 108 ir::MethodDefinition *method, public_lib::Context *ctx) 109{ 110 auto *checker = ctx->checker->AsETSChecker(); 111 ArenaVector<ir::TypeNode *> params(checker->Allocator()->Adapter()); 112 113 if (method->Function()->TypeParams() == nullptr || method->Function()->TypeParams()->Params().empty()) { 114 return nullptr; 115 } 116 ArenaVector<ir::TypeNode *> selfParams(checker->Allocator()->Adapter()); 117 ir::ETSTypeReferencePart *referencePart = nullptr; 118 119 for (const auto ¶m : method->Function()->TypeParams()->Params()) { 120 auto *identRef = 121 checker->AllocNode<ir::Identifier>(param->AsTSTypeParameter()->Name()->Name(), checker->Allocator()); 122 identRef->AsIdentifier()->SetReference(); 123 124 referencePart = checker->AllocNode<ir::ETSTypeReferencePart>(identRef, nullptr, nullptr); 125 126 auto *typeReference = checker->AllocNode<ir::ETSTypeReference>(referencePart); 127 128 selfParams.push_back(typeReference); 129 } 130 131 return checker->AllocNode<ir::TSTypeParameterInstantiation>(std::move(selfParams)); 132} 133 134ir::BlockStatement *DefaultParameterLowering::CreateFunctionBody(ir::MethodDefinition *method, public_lib::Context *ctx, 135 ArenaVector<ir::Expression *> funcCallArgs) 136{ 137 auto *checker = ctx->checker->AsETSChecker(); 138 ArenaVector<ir::Statement *> funcStatements(checker->Allocator()->Adapter()); 139 140 ir::CallExpression *callExpression = nullptr; 141 ir::Expression *id = nullptr; 142 ir::Expression *accessor = nullptr; 143 auto *const callee = checker->AllocNode<ir::Identifier>(method->Id()->Name(), checker->Allocator()); 144 callee->SetReference(); 145 146 if (method->IsConstructor()) { 147 accessor = checker->AllocNode<ir::ThisExpression>(); 148 } else { 149 if (method->Parent()->IsClassDefinition() && (!method->Parent()->AsClassDefinition()->IsGlobal())) { 150 if (method->IsStatic()) { 151 id = checker->AllocNode<ir::Identifier>(method->Parent()->AsClassDefinition()->Ident()->Name(), 152 checker->Allocator()); 153 id->AsIdentifier()->SetReference(); 154 } else { 155 id = checker->AllocNode<ir::ThisExpression>(); 156 } 157 accessor = checker->AllocNode<ir::MemberExpression>(id, callee, ir::MemberExpressionKind::PROPERTY_ACCESS, 158 false, false); 159 } 160 } 161 auto *paramInst = CreateTypeParameterInstantiation(method, ctx); 162 callExpression = checker->AllocNode<ir::CallExpression>(accessor != nullptr ? accessor : callee, 163 std::move(funcCallArgs), paramInst, false, false); 164 callExpression->SetRange(method->Range()); // NOTE: Used to locate the original node when an error occurs 165 ir::Statement *stmt = nullptr; 166 if ((method->Function()->ReturnTypeAnnotation() != nullptr) || 167 ((method->Function()->AsScriptFunction()->Flags() & ir::ScriptFunctionFlags::HAS_RETURN) != 0)) { 168 if ((method->Function()->ReturnTypeAnnotation() != nullptr) && 169 method->Function()->ReturnTypeAnnotation()->IsTSThisType()) { 170 // NOTE: special case if parent function has return type set as 'this' 171 // so we need to putu only explciit 'return this' to overload, 172 // but call parent function with default parameter before it. 173 stmt = checker->AllocNode<ir::ExpressionStatement>(callExpression); 174 funcStatements.push_back(stmt); 175 176 // build 'return this;' expression. 177 auto *thisExpr = checker->AllocNode<ir::ThisExpression>(); 178 stmt = checker->AllocNode<ir::ReturnStatement>(thisExpr); 179 } else { 180 stmt = checker->AllocNode<ir::ReturnStatement>(callExpression); 181 } 182 } else { 183 stmt = checker->AllocNode<ir::ExpressionStatement>(callExpression); 184 } 185 funcStatements.push_back(stmt); 186 187 return checker->AllocNode<ir::BlockStatement>(checker->Allocator(), std::move(funcStatements)); 188} 189 190ir::FunctionExpression *DefaultParameterLowering::CreateFunctionExpression( 191 ir::MethodDefinition *method, public_lib::Context *ctx, ArenaVector<ir::Expression *> funcDefinitionArgs, 192 ArenaVector<ir::Expression *> funcCallArgs) 193{ 194 lexer::SourcePosition startLoc(method->Start().line, method->Start().index); 195 lexer::SourcePosition endLoc = startLoc; 196 ir::FunctionSignature signature = CreateFunctionSignature(method, std::move(funcDefinitionArgs), ctx); 197 198 auto *checker = ctx->checker->AsETSChecker(); 199 ir::Identifier *id = nullptr; 200 201 ir::BlockStatement *body = nullptr; 202 if (!(method->IsNative() || method->IsDeclare() || method->IsAbstract())) { 203 body = CreateFunctionBody(method, ctx, std::move(funcCallArgs)); 204 } 205 auto *funcNode = checker->AllocNode<ir::ScriptFunction>( 206 checker->Allocator(), 207 ir::ScriptFunction::ScriptFunctionData { 208 body, std::move(signature), method->Function()->Flags(), {}, false, method->Function()->Language()}); 209 funcNode->AddModifier(method->Function()->Modifiers()); 210 funcNode->SetRange({startLoc, endLoc}); 211 212 id = method->Id()->Clone(checker->Allocator(), nullptr)->AsIdentifier(); 213 funcNode->SetIdent(id); 214 return checker->AllocNode<ir::FunctionExpression>(funcNode); 215} 216 217void DefaultParameterLowering::CreateOverloadFunction(ir::MethodDefinition *method, 218 ArenaVector<ir::Expression *> funcCallArgs, 219 ArenaVector<ir::Expression *> funcDefinitionArgs, 220 public_lib::Context *ctx) 221{ 222 auto *checker = ctx->checker->AsETSChecker(); 223 auto *funcExpression = 224 CreateFunctionExpression(method, ctx, std::move(funcDefinitionArgs), std::move(funcCallArgs)); 225 auto *ident = funcExpression->Function()->Id()->Clone(checker->Allocator(), nullptr); 226 auto *const overloadMethod = checker->AllocNode<ir::MethodDefinition>( 227 method->Kind(), ident, funcExpression, method->Modifiers(), checker->Allocator(), false); 228 ident->SetReference(); 229 230 overloadMethod->Function()->AddFlag(ir::ScriptFunctionFlags::OVERLOAD); 231 overloadMethod->SetRange(funcExpression->Range()); 232 233 if (method->Parent()->IsTSInterfaceBody()) { 234 overloadMethod->Function()->Body()->AsBlockStatement()->Statements().clear(); 235 } 236 237 method->AddOverload(overloadMethod); 238 overloadMethod->SetStart(method->Start()); // NOTE: Used to locate the original node when an error occurs 239 overloadMethod->SetParent(method); // NOTE(aleksisch): It's incorrect and don't exist in class body 240} 241 242void DefaultParameterLowering::RemoveInitializers(ArenaVector<ir::Expression *> params) 243{ 244 std::for_each(params.begin(), params.end(), [](ir::Expression *expr) { 245 if (expr->AsETSParameterExpression()->IsDefault()) { 246 expr->AsETSParameterExpression()->SetInitializer(); 247 } 248 }); 249} 250 251void DefaultParameterLowering::ProcessGlobalFunctionDefinition(ir::MethodDefinition *method, public_lib::Context *ctx) 252{ 253 auto *checker = ctx->checker->AsETSChecker(); 254 auto params = method->Function()->Params(); 255 256 // go through default parameters list and create overloading for each combination of them 257 // i.e. each new overload method has less actual paramaters than previous one and more 258 // default parameters values used to call original method. 259 260 // NOTE: args counter (i), intentionally starts with 1 as we would need to process at least 1 argument with 261 // initializer. 262 for (auto [it, i] = std::tuple {params.rbegin(), 1}; it != params.rend(); ++it, i++) { 263 if (!((*it)->AsETSParameterExpression()->IsDefault())) { 264 // do not process regular arguments; 265 break; 266 } 267 268 ArenaVector<ir::Expression *> defaultArgs(checker->Allocator()->Adapter()); // will have Initializers 269 ArenaVector<ir::Expression *> funcDefinitionArgs( 270 checker->Allocator()->Adapter()); // will have ETSParameterExpression 271 ArenaVector<ir::Expression *> funcCallArgs(checker->Allocator()->Adapter()); // will have ir::Identifier 272 273 // create function/method definition with less mandatory args than overloaded one 274 // 1. create copy of found function arguemnts 275 // 2. move out of them optional ones (one by one),and each time the one 276 // optional is moved out we need to create new overload method with the rest of 277 // arguments (as new method args) and move the optional one(s) to the explicit 278 // call to the original method 279 // 280 // foo(x : int = 0, y : int = 1, z : int = 2) 281 // 282 // 1. loop step 1 283 // foo (x : int, y : int) 284 // calls foo(x, y, 2) 285 // 2. loop step 2 286 // foo (x :int) 287 // calls foo(x, 1, 2) 288 // 3. loop step 3 289 // foo () 290 // calls foo(0, 1, 2) 291 auto pt = it; 292 do { 293 // extract default value from pt and make the function call argument out of it 294 // for now simple put whole parameter node to vector 295 ir::Expression *clone = nullptr; 296 auto *par = (*pt)->AsETSParameterExpression(); 297 if (par->Initializer()->IsArrowFunctionExpression()) { 298 clone = par->Initializer(); 299 } else { 300 clone = par->Initializer()->Clone(checker->Allocator(), nullptr)->AsExpression(); 301 } 302 if (clone != nullptr) { 303 defaultArgs.push_back(clone); 304 } 305 } while (params.rbegin() != pt--); 306 307 // ok, now we need to copy the 'valid' (for now) arguments from original function 308 // and make the arguments for current overload out of them. 309 310 funcCallArgs.reserve(params.size()); 311 funcDefinitionArgs.reserve(params.size() - i); 312 std::for_each( 313 params.begin(), params.end() - i, [&funcCallArgs, &funcDefinitionArgs, checker](ir::Expression *expr) { 314 // NOTE: we don't need Initializer here, as overload-method will have strict list of parameters 315 // will reset all of them once parsing loop completes 316 auto *funcArg = 317 expr->AsETSParameterExpression()->Ident()->Clone(checker->Allocator(), nullptr)->AsIdentifier(); 318 319 // update list of functional call arguments 320 funcCallArgs.push_back(funcArg); 321 322 auto *ident = 323 expr->AsETSParameterExpression()->Ident()->Clone(checker->Allocator(), nullptr)->AsIdentifier(); 324 auto *funcParam = checker->AllocNode<ir::ETSParameterExpression>(ident->AsIdentifier(), nullptr); 325 326 ASSERT(ident->TypeAnnotation()->Parent() == ident); 327 // prepare args list for overloade method definition 328 funcDefinitionArgs.push_back(funcParam); 329 }); 330 331 // finally append arguemnts list with hard-coded literals, 332 // so eventually we have list of call expression arguments 333 funcCallArgs.insert(funcCallArgs.end(), defaultArgs.begin(), defaultArgs.end()); 334 CreateOverloadFunction(method, std::move(funcCallArgs), std::move(funcDefinitionArgs), ctx); 335 } 336 337 // done with overloads, now need to cleanup all initializers, 338 // to make parent function signature strict 339 RemoveInitializers(std::move(params)); 340} 341 342bool DefaultParameterLowering::Perform(public_lib::Context *ctx, parser::Program *program) 343{ 344 for (auto &[_, extPrograms] : program->ExternalSources()) { 345 (void)_; 346 for (auto *extProg : extPrograms) { 347 Perform(ctx, extProg); 348 } 349 } 350 351 checker::ETSChecker *checker = ctx->checker->AsETSChecker(); 352 ArenaVector<ir::MethodDefinition *> foundNodes(checker->Allocator()->Adapter()); 353 program->Ast()->IterateRecursively([&foundNodes, this, program](ir::AstNode *ast) { 354 if (ast->IsMethodDefinition()) { 355 auto [hasDefaultParam, requiredParamsCount] = 356 HasDefaultParam(ast->AsMethodDefinition()->Function(), program); 357 if (hasDefaultParam) { 358 // store all nodes (which is function definition with default/optional parameters) 359 // to specific list, to process them later, as for now we can't modify AST in the 360 // middle of walking through it 361 foundNodes.push_back(ast->AsMethodDefinition()); 362 } 363 } 364 }); 365 366 for (auto &it : foundNodes) { 367 ProcessGlobalFunctionDefinition(it, ctx); 368 } 369 return true; 370} 371 372} // namespace ark::es2panda::compiler 373