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
26 namespace ark::es2panda::compiler {
27
HasDefaultParam(const ir::ScriptFunction * function,parser::Program * program)28 std::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
CreateParameterDeclaraion(ir::MethodDefinition * method,public_lib::Context * ctx)67 ir::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
CreateFunctionSignature(ir::MethodDefinition * method,ArenaVector<ir::Expression * > funcParam,public_lib::Context * ctx)92 ir::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
CreateTypeParameterInstantiation(ir::MethodDefinition * method,public_lib::Context * ctx)107 ir::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
CreateFunctionBody(ir::MethodDefinition * method,public_lib::Context * ctx,ArenaVector<ir::Expression * > funcCallArgs)134 ir::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
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
CreateFunctionExpression(ir::MethodDefinition * method,public_lib::Context * ctx,ArenaVector<ir::Expression * > funcDefinitionArgs,ArenaVector<ir::Expression * > funcCallArgs)190 ir::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
215 return checker->AllocNode<ir::FunctionExpression>(funcNode);
216 }
217
CreateOverloadFunction(ir::MethodDefinition * method,ArenaVector<ir::Expression * > funcCallArgs,ArenaVector<ir::Expression * > funcDefinitionArgs,public_lib::Context * ctx)218 void DefaultParameterLowering::CreateOverloadFunction(ir::MethodDefinition *method,
219 ArenaVector<ir::Expression *> funcCallArgs,
220 ArenaVector<ir::Expression *> funcDefinitionArgs,
221 public_lib::Context *ctx)
222 {
223 auto *checker = ctx->checker->AsETSChecker();
224 auto *funcExpression =
225 CreateFunctionExpression(method, ctx, std::move(funcDefinitionArgs), std::move(funcCallArgs));
226 auto *ident = funcExpression->Function()->Id()->Clone(checker->Allocator(), nullptr);
227 auto *const overloadMethod = checker->AllocNode<ir::MethodDefinition>(
228 method->Kind(), ident, funcExpression, method->Modifiers(), checker->Allocator(), false);
229 ident->SetReference();
230
231 overloadMethod->Function()->AddFlag(ir::ScriptFunctionFlags::OVERLOAD);
232 overloadMethod->SetRange(funcExpression->Range());
233
234 if (method->Parent()->IsTSInterfaceBody()) {
235 overloadMethod->Function()->Body()->AsBlockStatement()->Statements().clear();
236 }
237
238 method->AddOverload(overloadMethod);
239 overloadMethod->SetParent(method); // NOTE(aleksisch): It's incorrect and don't exist in class body
240 }
241
RemoveInitializers(ArenaVector<ir::Expression * > params)242 void 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
ProcessGlobalFunctionDefinition(ir::MethodDefinition * method,public_lib::Context * ctx)251 void 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 // crate funciton/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
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 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