1/*
2 * Copyright (c) 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 "lambdaLowering.h"
17
18#include "checker/ets/typeRelationContext.h"
19#include "compiler/lowering/scopesInit/scopesInitPhase.h"
20#include "compiler/lowering/util.h"
21
22namespace ark::es2panda::compiler {
23
24struct LambdaInfo {
25    ir::ClassDeclaration *calleeClass = nullptr;
26    ir::ScriptFunction *enclosingFunction = nullptr;
27    util::StringView name = "";
28    ArenaSet<varbinder::Variable *> *capturedVars = nullptr;
29    ir::Expression *callReceiver = nullptr;
30};
31
32struct CalleeMethodInfo {
33    util::StringView calleeName;
34    ir::AstNode *body = nullptr;
35    checker::Type *forcedReturnType = nullptr;
36    ir::ModifierFlags auxModifierFlags = ir::ModifierFlags::NONE;
37    ir::ScriptFunctionFlags auxFunctionFlags = ir::ScriptFunctionFlags::NONE;
38};
39
40struct LambdaClassInvokeInfo {
41    checker::Signature *lambdaSignature = nullptr;
42    ir::MethodDefinition *callee = nullptr;
43    ir::ClassDefinition *classDefinition = nullptr;
44    checker::Substitution *substitution = nullptr;
45};
46
47struct CalleeParameterInfo {
48    ir::ArrowFunctionExpression *lambda = nullptr;
49    ArenaSet<varbinder::Variable *> const &captured;
50    varbinder::ParamScope *paramScope = nullptr;
51    checker::Substitution *substitution = nullptr;
52    size_t limit = 0;
53};
54
55static std::pair<ir::ClassDeclaration *, ir::ScriptFunction *> FindEnclosingClassAndFunction(ir::AstNode *ast)
56{
57    ir::ScriptFunction *function = nullptr;
58    for (ir::AstNode *curr = ast->Parent(); curr != nullptr; curr = curr->Parent()) {
59        if (curr->IsClassDeclaration()) {
60            return {curr->AsClassDeclaration(), function};
61        }
62        if (curr->IsScriptFunction()) {
63            function = curr->AsScriptFunction();
64        }
65    }
66    UNREACHABLE();
67}
68
69static bool CheckIfNeedThis(ir::ArrowFunctionExpression const *lambda)
70{
71    return lambda->IsAnyChild([](ir::AstNode *ast) { return ast->IsThisExpression(); });
72}
73
74static size_t g_calleeCount = 0;
75
76// Make calleeCount behaviour predictable
77static void ResetCalleeCount()
78{
79    g_calleeCount = 0;
80}
81
82static util::StringView CreateCalleeName(ArenaAllocator *allocator)
83{
84    auto name = util::UString(util::StringView("lambda$invoke$"), allocator);
85    name.Append(std::to_string(g_calleeCount++));
86    return name.View();
87}
88
89static std::pair<ir::TSTypeParameterDeclaration *, checker::Substitution *> CloneTypeParams(
90    public_lib::Context *ctx, ir::TSTypeParameterDeclaration *oldIrTypeParams, ir::ScriptFunction *enclosingFunction,
91    varbinder::Scope *enclosingScope)
92{
93    if (oldIrTypeParams == nullptr) {
94        return {nullptr, nullptr};
95    }
96
97    auto *allocator = ctx->allocator;
98    auto *checker = ctx->checker->AsETSChecker();
99
100    auto *newScope = allocator->New<varbinder::LocalScope>(allocator, enclosingScope);
101    auto newTypeParams = ArenaVector<checker::ETSTypeParameter *>(allocator->Adapter());
102    auto newTypeParamNodes = ArenaVector<ir::TSTypeParameter *>(allocator->Adapter());
103    auto *substitution = checker->NewSubstitution();
104
105    for (size_t ix = 0; ix < oldIrTypeParams->Params().size(); ix++) {
106        auto *oldTypeParamNode = oldIrTypeParams->Params()[ix];
107        auto *oldTypeParam = enclosingFunction->Signature()->TypeParams()[ix]->AsETSTypeParameter();
108        auto *newTypeParamId = allocator->New<ir::Identifier>(oldTypeParamNode->Name()->Name(), allocator);
109        auto *newTypeParamNode =
110            util::NodeAllocator::ForceSetParent<ir::TSTypeParameter>(allocator, newTypeParamId, nullptr, nullptr);
111        auto *newTypeParam = allocator->New<checker::ETSTypeParameter>();
112        newTypeParam->SetDeclNode(newTypeParamNode);
113
114        auto *newTypeParamDecl = allocator->New<varbinder::TypeParameterDecl>(newTypeParamId->Name());
115        newTypeParamDecl->BindNode(newTypeParamNode);
116        auto *newTypeParamVar =
117            allocator->New<varbinder::LocalVariable>(newTypeParamDecl, varbinder::VariableFlags::TYPE_PARAMETER);
118
119        newTypeParamVar->SetTsType(newTypeParam);
120        newScope->InsertBinding(newTypeParamId->Name(), newTypeParamVar);
121        newTypeParamId->SetVariable(newTypeParamVar);
122
123        newTypeParams.push_back(newTypeParam);
124        newTypeParamNodes.push_back(newTypeParamNode);
125        substitution->emplace(oldTypeParam, newTypeParam);
126    }
127
128    for (size_t ix = 0; ix < oldIrTypeParams->Params().size(); ix++) {
129        auto *oldTypeParam = enclosingFunction->Signature()->TypeParams()[ix]->AsETSTypeParameter();
130
131        if (auto *oldConstraint = oldTypeParam->GetConstraintType(); oldConstraint != nullptr) {
132            auto *newConstraint = oldConstraint->Substitute(checker->Relation(), substitution);
133            newTypeParams[ix]->SetConstraintType(newConstraint);
134            newTypeParamNodes[ix]->SetConstraint(allocator->New<ir::OpaqueTypeNode>(newConstraint));
135            newTypeParamNodes[ix]->Constraint()->SetParent(newTypeParamNodes[ix]);
136        }
137        if (auto *oldDefault = oldTypeParam->GetDefaultType(); oldDefault != nullptr) {
138            auto *newDefault = oldDefault->Substitute(checker->Relation(), substitution);
139            newTypeParams[ix]->SetDefaultType(newDefault);
140            newTypeParamNodes[ix]->SetDefaultType(allocator->New<ir::OpaqueTypeNode>(newDefault));
141            newTypeParamNodes[ix]->DefaultType()->SetParent(newTypeParamNodes[ix]);
142        }
143    }
144
145    auto *newIrTypeParams = util::NodeAllocator::ForceSetParent<ir::TSTypeParameterDeclaration>(
146        allocator, std::move(newTypeParamNodes), oldIrTypeParams->RequiredParams());
147    newIrTypeParams->SetScope(newScope);
148
149    return {newIrTypeParams, substitution};
150}
151
152using ParamsAndVarMap =
153    std::pair<ArenaVector<ir::Expression *>, ArenaMap<varbinder::Variable *, varbinder::Variable *>>;
154ParamsAndVarMap CreateLambdaCalleeParameters(public_lib::Context *ctx, const CalleeParameterInfo &calleeParameterInfo)
155{
156    auto allocator = ctx->allocator;
157    auto checker = ctx->checker->AsETSChecker();
158    auto varBinder = ctx->checker->VarBinder();
159    auto resParams = ArenaVector<ir::Expression *>(allocator->Adapter());
160    auto varMap = ArenaMap<varbinder::Variable *, varbinder::Variable *>(allocator->Adapter());
161
162    auto paramLexScope =
163        varbinder::LexicalScope<varbinder::ParamScope>::Enter(varBinder, calleeParameterInfo.paramScope);
164
165    for (auto capturedVar : calleeParameterInfo.captured) {
166        auto *newType = capturedVar->TsType()->Substitute(checker->Relation(), calleeParameterInfo.substitution);
167        auto newId = util::NodeAllocator::ForceSetParent<ir::Identifier>(
168            allocator, capturedVar->Name(), allocator->New<ir::OpaqueTypeNode>(newType), allocator);
169        auto param = util::NodeAllocator::ForceSetParent<ir::ETSParameterExpression>(allocator, newId, nullptr);
170        auto [_, var] = varBinder->AddParamDecl(param);
171        (void)_;
172        var->SetTsType(newType);
173        var->SetScope(calleeParameterInfo.paramScope);
174        param->SetVariable(var);
175        param->SetTsType(newType);
176        resParams.push_back(param);
177        varMap[capturedVar] = var;
178    }
179
180    size_t i = 0;
181
182    for (auto *oldParam : calleeParameterInfo.lambda->Function()->Params()) {
183        if (i > calleeParameterInfo.limit) {
184            break;
185        }
186
187        auto *oldParamType = oldParam->AsETSParameterExpression()->Ident()->TypeAnnotation()->TsType();
188        auto *newParamType = oldParamType->Substitute(checker->Relation(), calleeParameterInfo.substitution);
189        auto *newParam = oldParam->AsETSParameterExpression()->Clone(allocator, nullptr);
190        newParam->Ident()->SetVariable(nullptr);  // Remove the cloned variable.
191        auto [_, var] = varBinder->AddParamDecl(newParam);
192        (void)_;
193        var->SetTsType(newParamType);
194        var->SetScope(calleeParameterInfo.paramScope);
195        newParam->SetVariable(var);
196        newParam->SetTsType(newParamType);
197        newParam->Ident()->SetTsType(newParamType);
198        resParams.push_back(newParam);
199        varMap[oldParam->AsETSParameterExpression()->Variable()] = var;
200        i++;
201    }
202
203    return {resParams, varMap};
204}
205
206static void ProcessCalleeMethodBody(ir::AstNode *body, checker::ETSChecker *checker, varbinder::Scope *paramScope,
207                                    checker::Substitution *substitution,
208                                    ArenaMap<varbinder::Variable *, varbinder::Variable *> const &varMap)
209{
210    if (body == nullptr || body->Scope() == nullptr) {
211        return;
212    }
213    body->Scope()->SetParent(paramScope);
214    body->IterateRecursively([&](ir::AstNode *node) {
215        if (node->IsIdentifier()) {
216            auto *id = node->AsIdentifier();
217            if (auto ref = varMap.find(id->Variable()); ref != varMap.end()) {
218                id->SetVariable(ref->second);
219            }
220        }
221        if (substitution == nullptr) {
222            return;
223        }
224        if (node->IsTyped() && node->AsTyped()->TsType() != nullptr) {
225            node->AsTyped()->SetTsType(node->AsTyped()->TsType()->Substitute(checker->Relation(), substitution));
226        }
227        if (node->IsCallExpression()) {
228            node->AsCallExpression()->SetSignature(
229                node->AsCallExpression()->Signature()->Substitute(checker->Relation(), substitution));
230        }
231        if (node->IsETSNewClassInstanceExpression()) {
232            node->AsETSNewClassInstanceExpression()->SetSignature(
233                node->AsETSNewClassInstanceExpression()->GetSignature()->Substitute(checker->Relation(), substitution));
234        }
235        if (node->IsScriptFunction()) {
236            node->AsScriptFunction()->SetSignature(
237                node->AsScriptFunction()->Signature()->Substitute(checker->Relation(), substitution));
238        }
239        if (node->IsVariableDeclarator()) {
240            auto *id = node->AsVariableDeclarator()->Id();
241            id->Variable()->SetTsType(id->Variable()->TsType()->Substitute(checker->Relation(), substitution));
242        }
243    });
244}
245
246static ir::MethodDefinition *SetUpCalleeMethod(public_lib::Context *ctx, LambdaInfo const *info,
247                                               CalleeMethodInfo const *cmInfo, ir::ScriptFunction *func,
248                                               varbinder::Scope *scopeForMethod, varbinder::Variable *variable)
249{
250    auto *allocator = ctx->allocator;
251    auto *varBinder = ctx->checker->VarBinder()->AsETSBinder();
252
253    auto *calleeClass = info->calleeClass;
254    auto *funcScope = func->Scope();
255    auto *paramScope = funcScope->ParamScope();
256    auto modifierFlags = ir::ModifierFlags::PUBLIC |
257                         (info->callReceiver != nullptr ? ir::ModifierFlags::NONE : ir::ModifierFlags::STATIC) |
258                         cmInfo->auxModifierFlags;
259
260    auto *calleeNameId = allocator->New<ir::Identifier>(cmInfo->calleeName, allocator);
261    func->SetIdent(calleeNameId);
262    calleeNameId->SetParent(func);
263
264    auto *calleeNameClone = calleeNameId->Clone(allocator, nullptr);
265    auto *funcExpr = util::NodeAllocator::ForceSetParent<ir::FunctionExpression>(allocator, func);
266    auto *method = util::NodeAllocator::ForceSetParent<ir::MethodDefinition>(
267        allocator, ir::MethodDefinitionKind::METHOD, calleeNameClone, funcExpr, modifierFlags, allocator, false);
268    calleeClass->Definition()->Body().push_back(method);
269    method->SetParent(calleeClass->Definition());
270
271    if (variable == nullptr) {
272        auto [_, var] =
273            varBinder->NewVarDecl<varbinder::FunctionDecl>(func->Start(), allocator, cmInfo->calleeName, func);
274        (void)_;
275        var->AddFlag(varbinder::VariableFlags::METHOD);
276        var->SetScope(scopeForMethod);
277        func->Id()->SetVariable(var);
278        method->Id()->SetVariable(var);
279        if (info->callReceiver != nullptr) {
280            auto paramScopeCtx = varbinder::LexicalScope<varbinder::FunctionParamScope>::Enter(varBinder, paramScope);
281            varBinder->AddMandatoryParam(varbinder::TypedBinder::MANDATORY_PARAM_THIS);
282            calleeClass->Definition()->TsType()->AsETSObjectType()->AddProperty<checker::PropertyType::INSTANCE_METHOD>(
283                var->AsLocalVariable());
284        } else {
285            calleeClass->Definition()->TsType()->AsETSObjectType()->AddProperty<checker::PropertyType::STATIC_METHOD>(
286                var->AsLocalVariable());
287        }
288
289        varbinder::BoundContext bctx {varBinder->GetRecordTable(), calleeClass->Definition(), true};
290        varBinder->ResolveReferencesForScopeWithContext(func, funcScope);
291
292        auto checkerCtx = checker::SavedCheckerContext(ctx->checker, checker::CheckerStatus::IN_CLASS,
293                                                       calleeClass->Definition()->TsType()->AsETSObjectType());
294        method->Check(ctx->checker->AsETSChecker());
295    } else {
296        func->Id()->SetVariable(variable);
297        method->Id()->SetVariable(variable);
298        method->Function()->AddFlag(ir::ScriptFunctionFlags::OVERLOAD);
299    }
300
301    return method;
302}
303
304static varbinder::FunctionScope *GetAndApplyFunctionScope(public_lib::Context *ctx, LambdaInfo const *info,
305                                                          CalleeMethodInfo const *cmInfo,
306                                                          varbinder::ParamScope *paramScope, ir::ScriptFunction *func)
307{
308    auto *allocator = ctx->allocator;
309    auto *funcScope = cmInfo->body == nullptr ? allocator->New<varbinder::FunctionScope>(allocator, paramScope)
310                                              : (cmInfo->body->Scope() == nullptr
311                                                     ? allocator->New<varbinder::FunctionScope>(allocator, paramScope)
312                                                     : cmInfo->body->Scope()->AsFunctionScope());
313    funcScope->BindName(info->calleeClass->Definition()->TsType()->AsETSObjectType()->AssemblerName());
314    func->SetScope(funcScope);
315
316    if (cmInfo->body != nullptr) {
317        cmInfo->body->AsBlockStatement()->SetScope(funcScope);
318        cmInfo->body->SetParent(func);
319    }
320
321    return funcScope;
322}
323
324static ir::MethodDefinition *CreateCalleeMethod(public_lib::Context *ctx, ir::ArrowFunctionExpression *lambda,
325                                                LambdaInfo const *info, CalleeMethodInfo const *cmInfo,
326                                                size_t limit = std::numeric_limits<size_t>::max(),
327                                                varbinder::Variable *variable = nullptr)
328{
329    auto *allocator = ctx->allocator;
330    auto *varBinder = ctx->checker->VarBinder()->AsETSBinder();
331    auto *checker = ctx->checker->AsETSChecker();
332
333    auto *classScope = info->calleeClass->Definition()->Scope()->AsClassScope();
334
335    auto *oldTypeParams = (info->enclosingFunction != nullptr) ? info->enclosingFunction->TypeParams() : nullptr;
336    auto enclosingScope =
337        info->callReceiver != nullptr ? classScope->InstanceMethodScope() : classScope->StaticMethodScope();
338
339    auto [newTypeParams, subst0] = CloneTypeParams(ctx, oldTypeParams, info->enclosingFunction, enclosingScope);
340    auto *substitution = subst0;  // NOTE(gogabr): needed to capture in a lambda later.
341    auto *scopeForMethod = newTypeParams != nullptr ? newTypeParams->Scope() : enclosingScope;
342
343    auto lexScope = varbinder::LexicalScope<varbinder::LocalScope>::Enter(varBinder, enclosingScope);
344    auto paramScope = allocator->New<varbinder::FunctionParamScope>(allocator, scopeForMethod);
345
346    CalleeParameterInfo cpi {lambda, *info->capturedVars, paramScope, substitution, limit};
347
348    auto [params, vMap] = CreateLambdaCalleeParameters(ctx, cpi);
349    auto varMap = std::move(vMap);
350
351    auto *returnType =
352        cmInfo->forcedReturnType != nullptr
353            ? cmInfo->forcedReturnType
354            : lambda->Function()->Signature()->ReturnType()->Substitute(checker->Relation(), substitution);
355    auto returnTypeAnnotation = allocator->New<ir::OpaqueTypeNode>(returnType);
356
357    auto funcFlags = ir::ScriptFunctionFlags::METHOD | cmInfo->auxFunctionFlags;
358    auto modifierFlags = ir::ModifierFlags::PUBLIC |
359                         (info->callReceiver != nullptr ? ir::ModifierFlags::NONE : ir::ModifierFlags::STATIC) |
360                         cmInfo->auxModifierFlags;
361
362    auto func = util::NodeAllocator::ForceSetParent<ir::ScriptFunction>(
363        allocator, allocator,
364        ir::ScriptFunction::ScriptFunctionData {
365            cmInfo->body, ir::FunctionSignature(newTypeParams, std::move(params), returnTypeAnnotation), funcFlags,
366            modifierFlags});
367    auto funcScope = GetAndApplyFunctionScope(ctx, info, cmInfo, paramScope, func);
368    ProcessCalleeMethodBody(cmInfo->body, checker, paramScope, substitution, varMap);
369
370    for (auto *param : func->Params()) {
371        param->SetParent(func);
372    }
373
374    // Bind the scopes
375    funcScope->BindNode(func);
376    paramScope->BindNode(func);
377    funcScope->AssignParamScope(paramScope);
378    paramScope->BindFunctionScope(funcScope);
379
380    /* NOTE(gogabr): Why does function scope need to replicate bindings from param scope?.
381       Keeping it for now.
382    */
383    for (auto [ov, nv] : varMap) {
384        ASSERT(ov->Name() == nv->Name());
385        auto name = ov->Name();
386        funcScope->EraseBinding(name);
387        funcScope->InsertBinding(name, nv);
388    }
389
390    return SetUpCalleeMethod(ctx, info, cmInfo, func, scopeForMethod, variable);
391}
392
393static ir::MethodDefinition *CreateCalleeDefault(public_lib::Context *ctx, ir::ArrowFunctionExpression *lambda,
394                                                 LambdaInfo const *info)
395{
396    auto *allocator = ctx->allocator;
397    auto *checker = ctx->checker->AsETSChecker();
398    auto *body = lambda->Function()->Body()->AsBlockStatement();
399    auto calleeName = lambda->Function()->IsAsyncFunc()
400                          ? (util::UString {checker::ETSChecker::GetAsyncImplName(info->name), allocator}).View()
401                          : info->name;
402    auto *forcedReturnType = lambda->Function()->IsAsyncFunc() ? checker->GlobalETSNullishObjectType() : nullptr;
403
404    CalleeMethodInfo cmInfo;
405    cmInfo.calleeName = calleeName;
406    cmInfo.body = body;
407    cmInfo.forcedReturnType = forcedReturnType;
408    auto *method = CreateCalleeMethod(ctx, lambda, info, &cmInfo);
409
410    if (lambda->Function()->IsAsyncFunc()) {
411        CalleeMethodInfo cmInfoAsync;
412        cmInfoAsync.calleeName = info->name;
413        cmInfoAsync.body = nullptr;
414        cmInfoAsync.forcedReturnType = nullptr;
415        cmInfoAsync.auxModifierFlags = ir::ModifierFlags::NATIVE;
416        cmInfoAsync.auxFunctionFlags = ir::ScriptFunctionFlags::ASYNC;
417        auto *asyncMethod = CreateCalleeMethod(ctx, lambda, info, &cmInfoAsync);
418        return asyncMethod;
419    }
420
421    return method;
422}
423
424static ArenaVector<ir::Expression *> CreateArgsForOptionalCall(public_lib::Context *ctx,
425                                                               ir::ArrowFunctionExpression *lambda,
426                                                               ir::MethodDefinition *defaultMethod)
427{
428    auto *checker = ctx->checker->AsETSChecker();
429
430    ArenaVector<ir::Expression *> funcCallArgs(checker->Allocator()->Adapter());
431    funcCallArgs.reserve(defaultMethod->Function()->Params().size());
432
433    size_t i = 0;
434
435    for (auto *param : defaultMethod->Function()->Params()) {
436        if (!param->AsETSParameterExpression()->IsDefault()) {
437            auto *paramName =
438                param->AsETSParameterExpression()->Ident()->Clone(checker->Allocator(), nullptr)->AsIdentifier();
439            funcCallArgs.push_back(paramName);
440        } else {
441            break;
442        }
443        i++;
444    }
445
446    for (; i < lambda->Function()->Params().size(); i++) {
447        auto *param = lambda->Function()->Params()[i]->AsETSParameterExpression();
448        if (param->Initializer() == nullptr) {
449            checker->ThrowTypeError({"Expected initializer for parameter ", param->Ident()->Name(), "."},
450                                    param->Start());
451        } else {
452            funcCallArgs.push_back(param->Initializer());
453        }
454    }
455
456    return funcCallArgs;
457}
458
459static ir::BlockStatement *CreateFunctionBody(public_lib::Context *ctx, ir::ArrowFunctionExpression *lambda,
460                                              ir::MethodDefinition *defaultMethod)
461{
462    auto *checker = ctx->checker->AsETSChecker();
463    ArenaVector<ir::Statement *> statements(checker->Allocator()->Adapter());
464    ir::CallExpression *callExpression = nullptr;
465    ir::Expression *id = nullptr;
466    ir::Expression *accessor = nullptr;
467    auto *const callee = checker->AllocNode<ir::Identifier>(defaultMethod->Id()->Name(), checker->Allocator());
468    callee->SetReference();
469
470    if (defaultMethod->IsStatic() && defaultMethod->Parent()->IsClassDefinition() &&
471        (!defaultMethod->Parent()->AsClassDefinition()->IsGlobal())) {
472        id = checker->AllocNode<ir::Identifier>(defaultMethod->Parent()->AsClassDefinition()->Ident()->Name(),
473                                                checker->Allocator());
474        id->AsIdentifier()->SetReference();
475        accessor = checker->AllocNode<ir::MemberExpression>(id, callee, ir::MemberExpressionKind::PROPERTY_ACCESS,
476                                                            false, false);
477        callee->SetParent(accessor);
478    }
479
480    callExpression = checker->AllocNode<ir::CallExpression>(accessor != nullptr ? accessor : callee,
481                                                            CreateArgsForOptionalCall(ctx, lambda, defaultMethod),
482                                                            nullptr, false, false);
483    callee->SetParent(callExpression);
484    callExpression->SetSignature(defaultMethod->Function()->Signature());
485
486    if ((defaultMethod->Function()->ReturnTypeAnnotation() != nullptr) ||
487        ((defaultMethod->Function()->AsScriptFunction()->Flags() & ir::ScriptFunctionFlags::HAS_RETURN) != 0)) {
488        statements.push_back(checker->AllocNode<ir::ReturnStatement>(callExpression));
489    } else {
490        statements.push_back(checker->AllocNode<ir::ExpressionStatement>(callExpression));
491    }
492
493    auto blockStatement = checker->AllocNode<ir::BlockStatement>(checker->Allocator(), std::move(statements));
494    blockStatement->Statements().front()->SetParent(blockStatement);
495
496    return blockStatement;
497}
498
499static ir::MethodDefinition *CreateCallee(public_lib::Context *ctx, ir::ArrowFunctionExpression *lambda,
500                                          LambdaInfo const *info, ir::MethodDefinition *defaultMethod,
501                                          size_t limit = std::numeric_limits<size_t>::max())
502{
503    auto *allocator = ctx->allocator;
504    auto *checker = ctx->checker->AsETSChecker();
505
506    auto calleeName = lambda->Function()->IsAsyncFunc()
507                          ? (util::UString {checker::ETSChecker::GetAsyncImplName(info->name), allocator}).View()
508                          : info->name;
509    auto *forcedReturnType = lambda->Function()->IsAsyncFunc() ? checker->GlobalETSNullishObjectType() : nullptr;
510
511    CalleeMethodInfo cmInfo;
512    cmInfo.calleeName = calleeName;
513    cmInfo.body = CreateFunctionBody(ctx, lambda, defaultMethod);
514    cmInfo.forcedReturnType = forcedReturnType;
515
516    auto *method = CreateCalleeMethod(ctx, lambda, info, &cmInfo, limit, defaultMethod->Id()->Variable());
517
518    if (lambda->Function()->IsAsyncFunc()) {
519        CalleeMethodInfo cmInfoAsync;
520        cmInfoAsync.calleeName = info->name;
521        cmInfoAsync.body = nullptr;
522        cmInfoAsync.forcedReturnType = nullptr;
523        cmInfoAsync.auxModifierFlags = ir::ModifierFlags::NATIVE;
524        cmInfoAsync.auxFunctionFlags = ir::ScriptFunctionFlags::ASYNC;
525        auto *asyncMethod = CreateCalleeMethod(ctx, lambda, info, &cmInfoAsync);
526        return asyncMethod;
527    }
528
529    return method;
530}
531
532// The name "=t" used in extension methods has special meaning for the code generator;
533// avoid it as parameter and field name in our generated code.
534static util::StringView AvoidMandatoryThis(util::StringView name)
535{
536    return (name == varbinder::TypedBinder::MANDATORY_PARAM_THIS) ? "$extensionThis" : name;
537}
538
539static void CreateLambdaClassFields(public_lib::Context *ctx, ir::ClassDefinition *classDefinition,
540                                    LambdaInfo const *info, checker::Substitution *substitution)
541{
542    auto *allocator = ctx->allocator;
543    auto *parser = ctx->parser->AsETSParser();
544    auto *checker = ctx->checker->AsETSChecker();
545    auto props = ArenaVector<ir::AstNode *>(allocator->Adapter());
546
547    if (info->callReceiver != nullptr) {
548        auto *outerThisDeclaration = parser->CreateFormattedClassFieldDefinition(
549            "@@I1: @@T2", "$this",
550            info->calleeClass->Definition()->TsType()->Substitute(checker->Relation(), substitution));
551        props.push_back(outerThisDeclaration);
552    }
553
554    for (auto *captured : *info->capturedVars) {
555        auto *varDeclaration = parser->CreateFormattedClassFieldDefinition(
556            "@@I1: @@T2", AvoidMandatoryThis(captured->Name()),
557            captured->TsType()->Substitute(checker->Relation(), substitution));
558        props.push_back(varDeclaration);
559    }
560
561    classDefinition->AddProperties(std::move(props));
562}
563
564static void CreateLambdaClassConstructor(public_lib::Context *ctx, ir::ClassDefinition *classDefinition,
565                                         LambdaInfo const *info, checker::Substitution *substitution)
566{
567    auto *allocator = ctx->allocator;
568    auto *parser = ctx->parser->AsETSParser();
569    auto *checker = ctx->checker->AsETSChecker();
570
571    auto params = ArenaVector<ir::Expression *>(allocator->Adapter());
572    auto makeParam = [checker, allocator, substitution, &params](util::StringView name, checker::Type *type) {
573        auto *substitutedType = type->Substitute(checker->Relation(), substitution);
574        auto *id = util::NodeAllocator::ForceSetParent<ir::Identifier>(
575            allocator, name, allocator->New<ir::OpaqueTypeNode>(substitutedType), allocator);
576        auto *param = util::NodeAllocator::ForceSetParent<ir::ETSParameterExpression>(allocator, id, nullptr);
577        params.push_back(param);
578    };
579
580    if (info->callReceiver != nullptr) {
581        makeParam("$this", info->calleeClass->Definition()->TsType());
582    }
583    for (auto *var : *info->capturedVars) {
584        makeParam(AvoidMandatoryThis(var->Name()), var->TsType());
585    }
586
587    auto bodyStmts = ArenaVector<ir::Statement *>(allocator->Adapter());
588    auto makeStatement = [&parser, &bodyStmts](util::StringView name) {
589        auto adjustedName = AvoidMandatoryThis(name);
590        auto *statement = parser->CreateFormattedStatement("this.@@I1 = @@I2", adjustedName, adjustedName);
591        bodyStmts.push_back(statement);
592    };
593    if (info->callReceiver != nullptr) {
594        makeStatement("$this");
595    }
596    for (auto *var : *info->capturedVars) {
597        makeStatement(var->Name());
598    }
599    auto *body = util::NodeAllocator::ForceSetParent<ir::BlockStatement>(allocator, allocator, std::move(bodyStmts));
600
601    auto *constructorId = allocator->New<ir::Identifier>("constructor", allocator);
602    auto *constructorIdClone = constructorId->Clone(allocator, nullptr);
603
604    auto *func = util::NodeAllocator::ForceSetParent<ir::ScriptFunction>(
605        allocator, allocator,
606        ir::ScriptFunction::ScriptFunctionData {body, ir::FunctionSignature(nullptr, std::move(params), nullptr),
607                                                ir::ScriptFunctionFlags::CONSTRUCTOR |
608                                                    ir::ScriptFunctionFlags::IMPLICIT_SUPER_CALL_NEEDED});
609    func->SetIdent(constructorId);
610    auto *funcExpr = util::NodeAllocator::ForceSetParent<ir::FunctionExpression>(allocator, func);
611
612    auto *ctor = util::NodeAllocator::ForceSetParent<ir::MethodDefinition>(
613        allocator, ir::MethodDefinitionKind::CONSTRUCTOR, constructorIdClone, funcExpr, ir::ModifierFlags::NONE,
614        allocator, false);
615
616    classDefinition->Body().push_back(ctor);
617    ctor->SetParent(classDefinition);
618}
619
620static ir::CallExpression *CreateCallForLambdaClassInvoke(public_lib::Context *ctx, LambdaInfo const *info,
621                                                          LambdaClassInvokeInfo const *lciInfo, bool wrapToObject)
622{
623    auto *allocator = ctx->allocator;
624    auto *parser = ctx->parser->AsETSParser();
625    auto *checker = ctx->checker->AsETSChecker();
626
627    auto callArguments = ArenaVector<ir::Expression *>(allocator->Adapter());
628    for (auto *captured : *info->capturedVars) {
629        auto *arg = parser->CreateFormattedExpression("this.@@I1", AvoidMandatoryThis(captured->Name()));
630        callArguments.push_back(arg);
631    }
632    for (auto *lambdaParam : lciInfo->lambdaSignature->Params()) {
633        auto argName = lambdaParam->Name();
634        auto *type = lambdaParam->TsType()->Substitute(checker->Relation(), lciInfo->substitution);
635        auto *arg = wrapToObject ? parser->CreateFormattedExpression("@@I1 as @@T2 as @@T3", argName,
636                                                                     checker->MaybePromotedBuiltinType(type), type)
637                                 : allocator->New<ir::Identifier>(argName, allocator);
638        callArguments.push_back(arg);
639    }
640
641    ir::Expression *calleeReceiver;
642    if (info->callReceiver != nullptr) {
643        calleeReceiver = parser->CreateFormattedExpression("this.@@I1", "$this");
644    } else {
645        calleeReceiver = lciInfo->callee->Parent()->AsClassDefinition()->Ident()->Clone(allocator, nullptr);
646    }
647
648    auto *calleeMemberExpr = util::NodeAllocator::ForceSetParent<ir::MemberExpression>(
649        allocator, calleeReceiver, lciInfo->callee->Key()->Clone(allocator, nullptr)->AsExpression(),
650        ir::MemberExpressionKind::PROPERTY_ACCESS, false, false);
651    auto *call = parser->CreateFormattedExpression("@@E1(@@[E2)", calleeMemberExpr, std::move(callArguments))
652                     ->AsCallExpression();
653
654    if (lciInfo->classDefinition->TypeParams() != nullptr) {
655        auto typeArgs = ArenaVector<ir::TypeNode *>(allocator->Adapter());
656        for (auto *tp : lciInfo->classDefinition->TypeParams()->Params()) {
657            typeArgs.push_back(allocator->New<ir::OpaqueTypeNode>(tp->Name()->AsIdentifier()->Variable()->TsType()));
658        }
659        auto *typeArg =
660            util::NodeAllocator::ForceSetParent<ir::TSTypeParameterInstantiation>(allocator, std::move(typeArgs));
661        call->SetTypeParams(typeArg);
662        typeArg->SetParent(call);
663    }
664
665    return call;
666}
667
668static void CreateLambdaClassInvoke(public_lib::Context *ctx, LambdaInfo const *info,
669                                    LambdaClassInvokeInfo const *lciInfo, util::StringView methodName,
670                                    bool wrapToObject)
671{
672    auto *allocator = ctx->allocator;
673    auto *parser = ctx->parser->AsETSParser();
674    auto *checker = ctx->checker->AsETSChecker();
675    auto *anyType = checker->GlobalETSNullishObjectType();
676
677    auto params = ArenaVector<ir::Expression *>(allocator->Adapter());
678    for (auto *lparam : lciInfo->lambdaSignature->Params()) {
679        auto *type = wrapToObject ? anyType : lparam->TsType()->Substitute(checker->Relation(), lciInfo->substitution);
680        auto *id = util::NodeAllocator::ForceSetParent<ir::Identifier>(
681            allocator, lparam->Name(), allocator->New<ir::OpaqueTypeNode>(type), allocator);
682        auto *param = util::NodeAllocator::ForceSetParent<ir::ETSParameterExpression>(allocator, id, nullptr);
683        params.push_back(param);
684    }
685
686    auto *call = CreateCallForLambdaClassInvoke(ctx, info, lciInfo, wrapToObject);
687
688    auto bodyStmts = ArenaVector<ir::Statement *>(allocator->Adapter());
689    if (lciInfo->lambdaSignature->ReturnType() == checker->GlobalVoidType()) {
690        auto *callStmt = util::NodeAllocator::ForceSetParent<ir::ExpressionStatement>(allocator, call);
691        bodyStmts.push_back(callStmt);
692        if (wrapToObject) {
693            auto *returnStmt = util::NodeAllocator::ForceSetParent<ir::ReturnStatement>(
694                allocator, allocator->New<ir::UndefinedLiteral>());
695            bodyStmts.push_back(returnStmt);
696        }
697    } else {
698        auto *returnExpr = wrapToObject ? parser->CreateFormattedExpression("@@E1 as @@T2", call, anyType) : call;
699        auto *returnStmt = util::NodeAllocator::ForceSetParent<ir::ReturnStatement>(allocator, returnExpr);
700        bodyStmts.push_back(returnStmt);
701    }
702
703    auto body = util::NodeAllocator::ForceSetParent<ir::BlockStatement>(allocator, allocator, std::move(bodyStmts));
704    auto *returnType2 = allocator->New<ir::OpaqueTypeNode>(
705        wrapToObject ? anyType
706                     : lciInfo->lambdaSignature->ReturnType()->Substitute(checker->Relation(), lciInfo->substitution));
707    auto *func = util::NodeAllocator::ForceSetParent<ir::ScriptFunction>(
708        allocator, allocator,
709        ir::ScriptFunction::ScriptFunctionData {body, ir::FunctionSignature(nullptr, std::move(params), returnType2),
710                                                ir::ScriptFunctionFlags::METHOD});
711
712    auto *invokeId = allocator->New<ir::Identifier>(methodName, allocator);
713    func->SetIdent(invokeId);
714
715    auto *funcExpr = util::NodeAllocator::ForceSetParent<ir::FunctionExpression>(allocator, func);
716
717    auto *invokeIdClone = invokeId->Clone(allocator, nullptr);
718    auto *invokeMethod = util::NodeAllocator::ForceSetParent<ir::MethodDefinition>(
719        allocator, ir::MethodDefinitionKind::METHOD, invokeIdClone, funcExpr, ir::ModifierFlags::NONE, allocator,
720        false);
721
722    lciInfo->classDefinition->Body().push_back(invokeMethod);
723    invokeMethod->SetParent(lciInfo->classDefinition);
724}
725
726static std::string BuildLambdaClass(public_lib::Context *ctx, ArenaVector<checker::Signature *> &lambdaSigs,
727                                    checker::Substitution *substitution, ArenaVector<checker::Type *> &funcInterfaces)
728{
729    auto *checker = ctx->checker->AsETSChecker();
730
731    std::string stringBuilder = "final class @@I1 implements ";
732    constexpr uint8_t OFF_SET = 2;
733
734    for (size_t i = 0; i < lambdaSigs.size(); i++) {
735        funcInterfaces.push_back(checker->FunctionTypeToFunctionalInterfaceType(
736            lambdaSigs[i]->Substitute(checker->Relation(), substitution)));
737        stringBuilder += "@@T" + std::to_string(i + OFF_SET) + ", ";
738    }
739
740    stringBuilder.pop_back();
741    stringBuilder.pop_back();
742    stringBuilder += " {}";
743
744    return stringBuilder;
745}
746
747static ir::ClassDeclaration *CreateLambdaClass(public_lib::Context *ctx, ArenaVector<checker::Signature *> &lambdaSigs,
748                                               ir::MethodDefinition *callee, LambdaInfo const *info)
749{
750    auto *allocator = ctx->allocator;
751    auto *parser = ctx->parser->AsETSParser();
752    auto *checker = ctx->checker->AsETSChecker();
753    auto *varBinder = ctx->checker->VarBinder()->AsETSBinder();
754
755    auto *oldTypeParams = (info->enclosingFunction != nullptr) ? info->enclosingFunction->TypeParams() : nullptr;
756    auto [newTypeParams, subst0] =
757        CloneTypeParams(ctx, oldTypeParams, info->enclosingFunction, ctx->parserProgram->GlobalClassScope());
758    auto *substitution = subst0;  // NOTE(gogabr): needed to capture in a lambda later.
759
760    auto lexScope = varbinder::LexicalScope<varbinder::Scope>::Enter(varBinder, ctx->parserProgram->GlobalClassScope());
761
762    auto lambdaClassName = util::UString {std::string_view {"LambdaObject-"}, allocator};
763    lambdaClassName.Append(info->calleeClass->Definition()->Ident()->Name());
764    lambdaClassName.Append("$");
765    lambdaClassName.Append(info->name);
766
767    ArenaVector<checker::Type *> funcInterfaces(allocator->Adapter());
768
769    auto *classDeclaration =
770        parser
771            ->CreateFormattedTopLevelStatement(BuildLambdaClass(ctx, lambdaSigs, substitution, funcInterfaces),
772                                               lambdaClassName, funcInterfaces)
773            ->AsClassDeclaration();
774    auto *classDefinition = classDeclaration->Definition();
775
776    // Adjust the class definition compared to what the parser gives.
777    classDefinition->Body().clear();  // remove the default empty constructor
778    classDefinition->AddModifier(ir::ModifierFlags::PUBLIC | ir::ModifierFlags::FUNCTIONAL);
779    if (newTypeParams != nullptr) {
780        classDefinition->SetTypeParams(newTypeParams);
781        newTypeParams->SetParent(classDefinition);
782    }
783
784    auto *program = varBinder->GetRecordTable()->Program();
785    program->Ast()->Statements().push_back(classDeclaration);
786    classDeclaration->SetParent(program->Ast());
787
788    CreateLambdaClassFields(ctx, classDefinition, info, substitution);
789    CreateLambdaClassConstructor(ctx, classDefinition, info, substitution);
790
791    LambdaClassInvokeInfo lciInfo;
792    lciInfo.callee = callee;
793    lciInfo.classDefinition = classDefinition;
794    lciInfo.substitution = substitution;
795
796    for (auto it : lambdaSigs) {
797        lciInfo.lambdaSignature = it;
798        CreateLambdaClassInvoke(ctx, info, &lciInfo, "invoke0", true);
799        CreateLambdaClassInvoke(ctx, info, &lciInfo, "invoke", false);
800    }
801
802    InitScopesPhaseETS::RunExternalNode(classDeclaration, varBinder);
803    varBinder->ResolveReferencesForScopeWithContext(classDeclaration, varBinder->TopScope());
804    classDeclaration->Check(checker);
805
806    return classDeclaration;
807}
808
809static ir::ETSNewClassInstanceExpression *CreateConstructorCall(public_lib::Context *ctx, ir::AstNode *lambdaOrFuncRef,
810                                                                ir::ClassDeclaration *lambdaClass,
811                                                                LambdaInfo const *info)
812{
813    auto *allocator = ctx->allocator;
814    auto *varBinder = ctx->checker->VarBinder()->AsETSBinder();
815    auto *checker = ctx->checker->AsETSChecker();
816
817    auto args = ArenaVector<ir::Expression *>(allocator->Adapter());
818    if (info->callReceiver != nullptr) {
819        args.push_back(info->callReceiver);
820    }
821    for (auto captured : *info->capturedVars) {
822        auto *id = allocator->New<ir::Identifier>(captured->Name(), allocator);
823        args.push_back(id);
824    }
825
826    checker::ETSObjectType *constructedType = lambdaClass->Definition()->TsType()->AsETSObjectType();
827    if (info->enclosingFunction != nullptr) {
828        constructedType = constructedType->SubstituteArguments(checker->Relation(),
829                                                               info->enclosingFunction->Signature()->TypeParams());
830    }
831    auto *newExpr = util::NodeAllocator::ForceSetParent<ir::ETSNewClassInstanceExpression>(
832        allocator, allocator->New<ir::OpaqueTypeNode>(constructedType), std::move(args), nullptr);
833    auto *lambdaOrFuncRefParent = lambdaOrFuncRef->Parent();
834    newExpr->SetParent(lambdaOrFuncRefParent);
835    // NOTE(dslynko, #19869): Required for correct debug-info generation
836    newExpr->SetRange(lambdaOrFuncRefParent != nullptr ? lambdaOrFuncRefParent->Range() : lambdaOrFuncRef->Range());
837
838    auto *nearestScope = NearestScope(lambdaOrFuncRef);
839    auto lexScope = varbinder::LexicalScope<varbinder::Scope>::Enter(varBinder, nearestScope);
840    varBinder->ResolveReferencesForScopeWithContext(newExpr, nearestScope);
841
842    auto checkerCtx = checker::SavedCheckerContext(ctx->checker, checker::CheckerStatus::IN_CLASS,
843                                                   info->calleeClass->Definition()->TsType()->AsETSObjectType());
844    auto scopeCtx = checker::ScopeContext(ctx->checker, nearestScope);
845    newExpr->Check(checker);
846
847    return newExpr;
848}
849
850static ir::AstNode *ConvertLambda(public_lib::Context *ctx, ir::ArrowFunctionExpression *lambda)
851{
852    auto *allocator = ctx->allocator;
853
854    auto firstDefaultIndex = lambda->Function()->DefaultParamIndex();
855
856    LambdaInfo info;
857    std::tie(info.calleeClass, info.enclosingFunction) = FindEnclosingClassAndFunction(lambda);
858    info.name = CreateCalleeName(allocator);
859    auto capturedVars = FindCaptured(allocator, lambda);
860    info.capturedVars = &capturedVars;
861    info.callReceiver = CheckIfNeedThis(lambda) ? allocator->New<ir::ThisExpression>() : nullptr;
862
863    auto *callee = CreateCalleeDefault(ctx, lambda, &info);
864
865    if (firstDefaultIndex < lambda->Function()->Params().size()) {
866        for (size_t i = firstDefaultIndex; i < lambda->Function()->Params().size(); i++) {
867            auto overload = CreateCallee(ctx, lambda, &info, callee, firstDefaultIndex);
868            callee->AddOverload(overload);
869        }
870    }
871
872    ASSERT(lambda->TsType()->IsETSFunctionType());
873    auto *lambdaType = lambda->TsType()->AsETSFunctionType();
874    auto *lambdaClass = CreateLambdaClass(ctx, lambdaType->CallSignatures(), callee, &info);
875    auto *constructorCall = CreateConstructorCall(ctx, lambda, lambdaClass, &info);
876    return constructorCall;
877}
878
879static checker::Signature *GuessSignature(checker::ETSChecker *checker, ir::Expression *ast)
880{
881    ASSERT(ast->TsType()->IsETSFunctionType());
882    auto *type = ast->TsType()->AsETSFunctionType();
883
884    if (type->CallSignatures().size() == 1) {
885        return type->CallSignatures()[0];
886    }
887
888    if (!ast->Parent()->IsCallExpression()) {
889        checker->ThrowTypeError(
890            std::initializer_list<checker::TypeErrorMessageElement> {"Cannot deduce call signature"}, ast->Start());
891    }
892
893    auto &args = ast->Parent()->AsCallExpression()->Arguments();
894    for (size_t ix = 0; ix < args.size(); ix++) {
895        if (args[ix] != ast) {
896            continue;
897        }
898
899        auto *argType = ast->Parent()->AsCallExpression()->Signature()->Params()[ix]->TsType();
900        checker::Signature *sigFound = nullptr;
901
902        for (auto *sig : type->CallSignatures()) {
903            auto *tmpFunType = checker->Allocator()->New<checker::ETSFunctionType>("", sig, checker->Allocator());
904            checker::AssignmentContext actx {
905                checker->Relation(), ast, tmpFunType, argType, ast->Start(), {}, checker::TypeRelationFlag::NO_THROW};
906            if (!actx.IsAssignable()) {
907                continue;
908            }
909            if (sigFound != nullptr) {
910                // ambiguiuty
911                checker->ThrowTypeError(
912                    std::initializer_list<checker::TypeErrorMessageElement> {"Cannot deduce call signature"},
913                    ast->Start());
914            }
915            sigFound = sig;
916        }
917        if (sigFound != nullptr) {
918            return sigFound;
919        }
920    }
921
922    checker->ThrowTypeError({"Cannot deduce call signature"}, ast->Start());
923}
924
925static ir::ArrowFunctionExpression *CreateWrappingLambda(public_lib::Context *ctx, ir::Expression *funcRef)
926{
927    auto *allocator = ctx->allocator;
928    auto *varBinder = ctx->checker->VarBinder()->AsETSBinder();
929    auto *signature = GuessSignature(ctx->checker->AsETSChecker(), funcRef);
930
931    auto *parent = funcRef->Parent();
932
933    ArenaVector<ir::Expression *> params {allocator->Adapter()};
934    for (auto *p : signature->Params()) {
935        params.push_back(util::NodeAllocator::ForceSetParent<ir::ETSParameterExpression>(
936            allocator,
937            allocator->New<ir::Identifier>(p->Name(), allocator->New<ir::OpaqueTypeNode>(p->TsType()), allocator),
938            nullptr));
939    }
940    auto *func = util::NodeAllocator::ForceSetParent<ir::ScriptFunction>(
941        allocator, allocator,
942        ir::ScriptFunction::ScriptFunctionData {
943            nullptr,
944            ir::FunctionSignature {nullptr, std::move(params),
945                                   allocator->New<ir::OpaqueTypeNode>(signature->ReturnType())},
946            ir::ScriptFunctionFlags::ARROW});
947
948    ArenaVector<ir::Statement *> bodyStmts {allocator->Adapter()};
949    ArenaVector<ir::Expression *> callArgs {allocator->Adapter()};
950
951    for (auto *p : func->Params()) {
952        callArgs.push_back(p->AsETSParameterExpression()->Ident()->Clone(allocator, nullptr));
953    }
954    auto *callExpr = util::NodeAllocator::ForceSetParent<ir::CallExpression>(allocator, funcRef, std::move(callArgs),
955                                                                             nullptr, false);
956    ir::Statement *stmt;
957    if (signature->ReturnType() == ctx->checker->AsETSChecker()->GlobalVoidType()) {
958        stmt = util::NodeAllocator::ForceSetParent<ir::ExpressionStatement>(allocator, callExpr);
959    } else {
960        stmt = util::NodeAllocator::ForceSetParent<ir::ReturnStatement>(allocator, callExpr);
961    }
962    bodyStmts.push_back(stmt);
963    func->SetBody(util::NodeAllocator::ForceSetParent<ir::BlockStatement>(allocator, allocator, std::move(bodyStmts)));
964    func->Body()->SetParent(func);
965    auto *lambda = util::NodeAllocator::ForceSetParent<ir::ArrowFunctionExpression>(allocator, func);
966    lambda->SetParent(parent);
967
968    auto *nearestScope = NearestScope(lambda);
969    auto lexScope = varbinder::LexicalScope<varbinder::Scope>::Enter(varBinder, nearestScope);
970    InitScopesPhaseETS::RunExternalNode(lambda, varBinder);
971    varBinder->ResolveReferencesForScopeWithContext(lambda, nearestScope);
972
973    auto [enclosingClass, enclosingFun] = FindEnclosingClassAndFunction(parent);
974    (void)enclosingFun;
975
976    auto checkerCtx = checker::SavedCheckerContext(ctx->checker, checker::CheckerStatus::IN_CLASS,
977                                                   enclosingClass->Definition()->TsType()->AsETSObjectType());
978    auto scopeCtx = checker::ScopeContext(ctx->checker, nearestScope);
979    lambda->Check(ctx->checker->AsETSChecker());
980
981    return lambda;
982}
983
984static ir::AstNode *ConvertFunctionReference(public_lib::Context *ctx, ir::Expression *funcRef)
985{
986    auto *allocator = ctx->allocator;
987    ASSERT(funcRef->IsIdentifier() ||
988           (funcRef->IsMemberExpression() &&
989            funcRef->AsMemberExpression()->Kind() == ir::MemberExpressionKind::PROPERTY_ACCESS &&
990            funcRef->AsMemberExpression()->Property()->IsIdentifier()));
991    varbinder::Variable *var;
992    if (funcRef->IsIdentifier()) {
993        var = funcRef->AsIdentifier()->Variable();
994    } else {
995        auto *mexpr = funcRef->AsMemberExpression();
996        // NOTE(gogabr): mexpr->PropVar() is a synthetic variable wwith no reference to the method definition. Why?
997        var = mexpr->Object()->TsType()->AsETSObjectType()->GetProperty(
998            mexpr->Property()->AsIdentifier()->Name(),
999            checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD | checker::PropertySearchFlags::SEARCH_STATIC_METHOD |
1000                checker::PropertySearchFlags::DISALLOW_SYNTHETIC_METHOD_CREATION);
1001        ASSERT(var != nullptr);
1002    }
1003
1004    ASSERT(var->Declaration()->Node()->IsMethodDefinition());
1005    auto *method = var->Declaration()->Node()->AsMethodDefinition();
1006
1007    if (method->IsPrivate() || method->IsProtected()) {
1008        // Direct reference to method will be impossible from the lambda class, so replace func ref with a lambda
1009        // that will translate to a proxy method
1010        auto *lam = CreateWrappingLambda(ctx, funcRef);
1011        return ConvertLambda(ctx, lam);
1012    }
1013
1014    LambdaInfo info;
1015    info.calleeClass = method->Parent()->Parent()->AsClassDeclaration();
1016    info.enclosingFunction = nullptr;
1017    info.name = CreateCalleeName(allocator);
1018    auto emptySet = ArenaSet<varbinder::Variable *>(allocator->Adapter());
1019    info.capturedVars = &emptySet;
1020    if (method->IsStatic()) {
1021        info.callReceiver = nullptr;
1022    } else {
1023        ASSERT(funcRef->IsMemberExpression());
1024        info.callReceiver = funcRef->AsMemberExpression()->Object();
1025    }
1026
1027    auto *signature = GuessSignature(ctx->checker->AsETSChecker(), funcRef);
1028    ArenaVector<checker::Signature *> signatures(allocator->Adapter());
1029    signatures.push_back(signature);
1030    auto *lambdaClass = CreateLambdaClass(ctx, signatures, method, &info);
1031    auto *constructorCall = CreateConstructorCall(ctx, funcRef, lambdaClass, &info);
1032    return constructorCall;
1033}
1034
1035static bool IsFunctionOrMethodCall(ir::AstNode const *node)
1036{
1037    ASSERT(node->IsCallExpression());
1038    auto const *callee = node->AsCallExpression()->Callee();
1039
1040    if (callee->TsType() != nullptr && callee->TsType()->IsETSExtensionFuncHelperType()) {
1041        return true;
1042    }
1043
1044    if (callee->IsMemberExpression() && callee->AsMemberExpression()->Object()->TsType() != nullptr &&
1045        (callee->AsMemberExpression()->Object()->TsType()->IsETSEnumType())) {
1046        return true;
1047    }
1048
1049    varbinder::Variable *var = nullptr;
1050    if (callee->IsMemberExpression() &&
1051        callee->AsMemberExpression()->Kind() == ir::MemberExpressionKind::PROPERTY_ACCESS) {
1052        var = callee->AsMemberExpression()->Property()->Variable();
1053    } else if (callee->IsIdentifier()) {
1054        var = callee->AsIdentifier()->Variable();
1055    }
1056    return var != nullptr && !checker::ETSChecker::IsVariableGetterSetter(var) &&
1057           (var->Flags() & varbinder::VariableFlags::METHOD) != 0;
1058}
1059
1060static ir::AstNode *InsertInvokeCall(public_lib::Context *ctx, ir::CallExpression *call)
1061{
1062    auto *allocator = ctx->allocator;
1063    auto *checker = ctx->checker->AsETSChecker();
1064    auto *varBinder = checker->VarBinder()->AsETSBinder();
1065
1066    auto *oldCallee = call->Callee();
1067    auto *ifaceType = oldCallee->TsType() != nullptr && oldCallee->TsType()->IsETSObjectType()
1068                          ? oldCallee->TsType()->AsETSObjectType()
1069                          : checker->FunctionTypeToFunctionalInterfaceType(call->Signature());
1070    if (ifaceType->IsETSDynamicType()) {
1071        return call;
1072    }
1073    auto *prop = ifaceType->GetProperty(checker::FUNCTIONAL_INTERFACE_INVOKE_METHOD_NAME,
1074                                        checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD |
1075                                            checker::PropertySearchFlags::SEARCH_IN_INTERFACES);
1076    ASSERT(prop != nullptr);
1077    auto *invoke0Id = allocator->New<ir::Identifier>(checker::FUNCTIONAL_INTERFACE_INVOKE_METHOD_NAME, allocator);
1078    invoke0Id->SetTsType(prop->TsType());
1079    invoke0Id->SetVariable(prop);
1080
1081    auto *newCallee = util::NodeAllocator::ForceSetParent<ir::MemberExpression>(
1082        allocator, oldCallee, invoke0Id, ir::MemberExpressionKind::PROPERTY_ACCESS, false, false);
1083    newCallee->SetTsType(prop->TsType());
1084    newCallee->SetObjectType(ifaceType);
1085
1086    call->SetCallee(newCallee);
1087    call->SetSignature(prop->TsType()->AsETSFunctionType()->CallSignatures()[0]);
1088
1089    /* NOTE(gogabr): argument types may have been spoiled by widening/narrowing conversions.
1090       Repair them here.
1091       In the future, make sure those conversions behave appropriately.
1092    */
1093    for (auto *arg : call->Arguments()) {
1094        auto boxingFlags = arg->GetBoxingUnboxingFlags();
1095        Recheck(varBinder, checker, arg);
1096        arg->SetBoxingUnboxingFlags(boxingFlags);
1097        // NOTE (psiket) Temporal solution
1098        if (arg->TsType()->IsETSEnumType()) {
1099            arg->SetBoxingUnboxingFlags(ir::BoxingUnboxingFlags::BOX_TO_ENUM);
1100        }
1101    }
1102
1103    return call;
1104}
1105
1106static bool IsRedirectingConstructorCall(ir::CallExpression *expr)
1107{
1108    return expr->Callee()->IsThisExpression() || expr->Callee()->IsSuperExpression();
1109}
1110
1111static bool IsInCalleePosition(ir::Expression *expr)
1112{
1113    return expr->Parent()->IsCallExpression() && expr->Parent()->AsCallExpression()->Callee() == expr;
1114}
1115
1116static ir::AstNode *BuildLambdaClassWhenNeeded(public_lib::Context *ctx, ir::AstNode *node)
1117{
1118    if (node->IsArrowFunctionExpression()) {
1119        return ConvertLambda(ctx, node->AsArrowFunctionExpression());
1120    }
1121    if (node->IsIdentifier()) {
1122        auto *id = node->AsIdentifier();
1123        auto *var = id->Variable();
1124        if (id->IsReference() && id->TsType() != nullptr && id->TsType()->IsETSFunctionType() && var != nullptr &&
1125            var->Declaration()->IsFunctionDecl() && !IsInCalleePosition(id)) {
1126            return ConvertFunctionReference(ctx, id);
1127        }
1128    }
1129    if (node->IsMemberExpression()) {
1130        auto *mexpr = node->AsMemberExpression();
1131        if (mexpr->Kind() == ir::MemberExpressionKind::PROPERTY_ACCESS && mexpr->TsType() != nullptr &&
1132            mexpr->TsType()->IsETSFunctionType() && mexpr->Object()->TsType()->IsETSObjectType()) {
1133            ASSERT(mexpr->Property()->IsIdentifier());
1134            auto *var = mexpr->Object()->TsType()->AsETSObjectType()->GetProperty(
1135                mexpr->Property()->AsIdentifier()->Name(),
1136                checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD |
1137                    checker::PropertySearchFlags::SEARCH_STATIC_METHOD |
1138                    checker::PropertySearchFlags::DISALLOW_SYNTHETIC_METHOD_CREATION);
1139            if (var != nullptr && var->Declaration()->IsFunctionDecl() && !IsInCalleePosition(mexpr)) {
1140                return ConvertFunctionReference(ctx, mexpr);
1141            }
1142        }
1143    }
1144    return node;
1145}
1146
1147static void CallPerformForExtSources(LambdaConversionPhase *phase, public_lib::Context *ctx, parser::Program *program)
1148{
1149    auto *varBinder = ctx->checker->VarBinder()->AsETSBinder();
1150    for (auto &[_, extPrograms] : program->ExternalSources()) {
1151        (void)_;
1152        for (auto *extProg : extPrograms) {
1153            varbinder::RecordTableContext bctx {varBinder, extProg};
1154            phase->Perform(ctx, extProg);
1155        }
1156    }
1157}
1158
1159bool LambdaConversionPhase::Perform(public_lib::Context *ctx, parser::Program *program)
1160{
1161    parser::SavedFormattingFileName savedFormattingName(ctx->parser->AsETSParser(), "lambda-conversion");
1162
1163    // For reproducibility of results when several compilation sessions are executed during
1164    // the same process's lifetime.
1165    if (program == ctx->parserProgram) {
1166        ResetCalleeCount();
1167    }
1168
1169    if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) {
1170        CallPerformForExtSources(this, ctx, program);
1171    }
1172
1173    program->Ast()->TransformChildrenRecursivelyPostorder(
1174        [ctx](ir::AstNode *node) { return BuildLambdaClassWhenNeeded(ctx, node); }, Name());
1175
1176    auto insertInvokeIfNeeded = [ctx](ir::AstNode *node) {
1177        if (node->IsCallExpression() && !IsFunctionOrMethodCall(node) &&
1178            !IsRedirectingConstructorCall(node->AsCallExpression())) {
1179            return InsertInvokeCall(ctx, node->AsCallExpression());
1180        }
1181        return node;
1182    };
1183    program->Ast()->TransformChildrenRecursively(insertInvokeIfNeeded, Name());
1184
1185    return true;
1186}
1187
1188bool LambdaConversionPhase::Postcondition([[maybe_unused]] public_lib::Context *ctx, parser::Program const *program)
1189{
1190    return !program->Ast()->IsAnyChild([](ir::AstNode const *node) { return node->IsArrowFunctionExpression(); });
1191}
1192
1193}  // namespace ark::es2panda::compiler
1194