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 &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
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