• 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 #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 &param : 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