• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &param : 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