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