/**
 * Copyright (c) 2021-2026 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstddef>

#include "checker/types/type.h"
#include "checker/types/typeRelation.h"
#include "lexer/token/sourceLocation.h"
#include "util/ustring.h"
#include "checker/ETSAnalyzerHelpers.h"
#include "checker/ETSchecker.h"
#include "checker/ets/typeRelationContext.h"
#include "checker/types/ets/etsAwaitedType.h"
#include "checker/types/ets/etsObjectType.h"
#include "checker/types/ets/etsPartialTypeParameter.h"
#include "checker/types/ets/etsResizableArrayType.h"
#include "checker/types/ets/etsReturnTypeUtilityType.h"
#include "checker/types/ets/etsTupleType.h"
#include "checker/types/typeError.h"
#include "compiler/lowering/scopesInit/scopesInitPhase.h"
#include "generated/signatures.h"
#include "ir/base/catchClause.h"
#include "ir/base/classDefinition.h"
#include "ir/base/classProperty.h"
#include "ir/base/methodDefinition.h"
#include "ir/base/scriptFunction.h"
#include "ir/base/spreadElement.h"
#include "ir/ets/etsFunctionType.h"
#include "ir/ets/etsParameterExpression.h"
#include "ir/expressions/arrowFunctionExpression.h"
#include "ir/expressions/assignmentExpression.h"
#include "ir/expressions/callExpression.h"
#include "ir/expressions/functionExpression.h"
#include "ir/expressions/identifier.h"
#include "ir/expressions/memberExpression.h"
#include "ir/expressions/objectExpression.h"
#include "ir/statements/blockStatement.h"
#include "ir/statements/doWhileStatement.h"
#include "ir/statements/expressionStatement.h"
#include "ir/statements/forInStatement.h"
#include "ir/statements/forOfStatement.h"
#include "ir/statements/forUpdateStatement.h"
#include "ir/statements/returnStatement.h"
#include "ir/statements/switchStatement.h"
#include "ir/statements/whileStatement.h"
#include "ir/ts/tsTypeAliasDeclaration.h"
#include "ir/ts/tsTypeParameter.h"
#include "ir/ts/tsTypeParameterInstantiation.h"
#include "parser/program/program.h"
#include "util/helpers.h"
#include "util/nameMangler.h"
#include "varbinder/ETSBinder.h"

namespace ark::es2panda::checker {

bool ETSChecker::IsOverloadDeclaration(ir::Expression *expr)
{
    while (expr->IsMemberExpression()) {
        expr = expr->AsMemberExpression()->Property();
    }

    if (expr->IsIdentifier() && expr->AsIdentifier()->Variable() != nullptr) {
        return expr->AsIdentifier()->Variable()->HasFlag(varbinder::VariableFlags::OVERLOAD);
    }
    return false;
}

static Signature *CheckDerivedStaticMethodCall(ETSChecker *checker, ir::CallExpression *expr, Signature *signature)
{
    if (signature == nullptr || !signature->HasFunction() || !signature->Function()->IsStatic() ||
        !expr->Callee()->IsMemberExpression()) {
        return signature;
    }

    auto *memberExpr = expr->Callee()->AsMemberExpression();
    if (!memberExpr->Property()->IsIdentifier()) {
        return signature;
    }

    const auto *targetRef = checker->GetTargetRef(memberExpr);
    if (!checker::IsClassStaticAccessTarget(targetRef)) {
        return signature;
    }

    auto *targetType = memberExpr->ObjType();
    if (targetType == nullptr || !targetType->IsETSObjectType()) {
        return signature;
    }

    auto *owner = signature->Owner();
    if (owner == nullptr || owner->IsSameBasedGeneric(checker->Relation(), targetType->AsETSObjectType())) {
        return signature;
    }

    auto propertyName = memberExpr->Property()->AsIdentifier()->Name();
    if (propertyName.Is(compiler::Signatures::STATIC_INSTANTIATE_METHOD) ||
        propertyName.Is(compiler::Signatures::STATIC_INVOKE_METHOD)) {
        return signature;
    }

    checker->LogDiagnostic(diagnostic::STATIC_PROP_INHERITANCE, {memberExpr->Property()->AsIdentifier()->Name(), owner},
                           memberExpr->Property()->Start());
    return nullptr;
}

static Type *GetParamTypeForArgument(Signature const *signature, ETSChecker *checker, const SignatureInfo *sigInfo,
                                     size_t argIndex)
{
    auto maybeRestParam = argIndex >= signature->ArgCount();
    if (!maybeRestParam) {
        return sigInfo->params[argIndex]->TsType();
    }

    if (sigInfo->restVar == nullptr) {
        return nullptr;
    }

    auto *restType = sigInfo->restVar->TsType();
    if (util::Helpers::IsArrayType(restType)) {
        return checker->GetElementTypeOfArray(restType);
    }

    return restType;
}

static Type *GetArgumentType(ETSChecker *checker, ir::Expression *arg)
{
    if (arg->IsSpreadElement()) {
        auto spreadArg = arg->AsSpreadElement()->Argument();
        auto type = spreadArg->Check(checker);
        if (type->IsETSTupleType()) {
            return type;
        }

        if (util::Helpers::IsArrayType(type)) {
            return checker->GetElementTypeOfArray(type);
        }

        return checker->GetElementTypeOfSpreadType(type);
    }

    return arg->Check(checker);
}

static std::pair<Type *, bool> GetArgumentTypeForInfer(ETSChecker *checker, ir::Expression *arg)
{
    if (arg->IsArrowFunctionExpression()) {
        InferMatchContext inferMatchContext(checker, util::DiagnosticType::SEMANTIC, arg->Range(), false);
        auto *argType = GetArgumentType(checker, arg);
        if (!inferMatchContext.ValidMatchStatus()) {
            arg->CleanCheckInformation();
            return {nullptr, true};
        }
        checker->Relation()->SetNode(arg);
        return {argType, false};
    }

    return {GetArgumentType(checker, arg), false};
}

static Substitution RenameParameters(ETSChecker *checker, Signature *signature)
{
    auto res = Substitution {};
    for (auto *tp : signature->TypeParams()) {
        auto *tpar = tp->AsETSTypeParameter();
        auto *newTpar = tpar->Clone(checker)->AsETSTypeParameter();
        newTpar->SetUnderInference();
        res.emplace(tpar, newTpar);
    }

    // Be careful about interdependenicies, thus the two-phase initialization.
    for (auto *tp : signature->TypeParams()) {
        auto *tpar = tp->AsETSTypeParameter();
        auto *newTpar = res[tpar]->AsETSTypeParameter();
        if (tpar->GetConstraintType() != nullptr) {
            newTpar->SetConstraintType(tpar->GetConstraintType()->Substitute(checker->Relation(), &res));
        }
        if (tpar->GetDefaultType() != nullptr) {
            newTpar->SetDefaultType(tpar->GetDefaultType()->Substitute(checker->Relation(), &res));
        }
    }
    return res;
}

static Substitution ComposeTemporarySubstitution(ETSChecker *checker, Substitution *one, Substitution *two)
{
    Substitution res {};
    for (auto [tpar, val] : *one) {
        auto nval = val->Substitute(checker->Relation(), two);
        if (nval != val) {
            res.emplace(tpar, nval);
        }
    }
    return res;
}

static void InferUntilFail(Signature const *const signature, const ArenaVector<ir::Expression *> &arguments,
                           ETSChecker *checker, Substitution *substitution)
{
    auto *sigInfo = signature->GetSignatureInfo();
    auto &sigParams = signature->GetSignatureInfo()->typeParams;
    ArenaVector<bool> inferStatus(checker->Allocator()->Adapter());
    inferStatus.assign(arguments.size(), false);
    bool anyChange = true;
    size_t lastSubsititutionSize = 0;

    checker->AddStatus(checker::CheckerStatus::IN_TYPE_INFER);
    // some ets lib files require type infer from arg index 0,1,... , not fit to build graph
    ES2PANDA_ASSERT(substitution != nullptr);
    while (anyChange && substitution->size() < sigParams.size()) {
        anyChange = false;
        for (size_t ix = 0; ix < arguments.size(); ++ix) {
            if (inferStatus[ix]) {
                continue;
            }

            auto *arg = arguments[ix];
            if (arg->IsObjectExpression()) {
                continue;
            }

            Type *paramType = GetParamTypeForArgument(signature, checker, sigInfo, ix);
            if (paramType == nullptr) {
                continue;
            }

            if (!substitution->empty()) {
                paramType = paramType->Substitute(checker->Relation(), substitution);
            }

            arg->SetPreferredType(paramType);
            auto [argType, needContinue] = GetArgumentTypeForInfer(checker, arg);

            if (needContinue) {
                continue;
            }

            if (checker->EnhanceSubstitutionForType(sigInfo->typeParams, paramType, argType, substitution)) {
                inferStatus[ix] = true;
            }
            if (lastSubsititutionSize != substitution->size()) {
                lastSubsititutionSize = substitution->size();
                anyChange = true;
            }
        }
    }
    checker->RemoveStatus(checker::CheckerStatus::IN_TYPE_INFER);
}

static bool IsCompatibleTypeArgument(ETSChecker *checker, ETSTypeParameter *typeParam, Type *typeArgument,
                                     const Substitution *substitution);

static bool CheckTypeParamsConstraint(ETSChecker *checker, const Substitution *substitution,
                                      const lexer::SourcePosition &pos)
{
    for (auto it = substitution->begin(); it != substitution->end(); ++it) {
        auto *constraintType = it->second;
        auto *typeParameter = it->first->AsETSTypeParameter();

        while (typeParameter->GetConstraintType() != nullptr &&
               typeParameter->GetConstraintType()->IsETSTypeParameter()) {
            typeParameter = typeParameter->GetConstraintType()->AsETSTypeParameter();
        }

        if (!IsCompatibleTypeArgument(checker, typeParameter, constraintType, substitution)) {
            checker->LogError(diagnostic::TYPEARG_TYPEPARAM_SUBTYPING,
                              {constraintType, typeParameter->GetConstraintType()}, pos);
            return false;
        }
    }
    return true;
}

static std::optional<Substitution> BuildImplicitSubstitutionForArguments(ETSChecker *checker, Signature *signature,
                                                                         const ArenaVector<ir::Expression *> &arguments,
                                                                         const lexer::SourcePosition &pos)
{
    auto substitution = Substitution {};
    auto paramRenamer = RenameParameters(checker, signature);
    auto sigRenamed = signature->Substitute(checker->Relation(), &paramRenamer);
    auto *sigInfo = sigRenamed->GetSignatureInfo();
    auto &sigParams = sigInfo->typeParams;

    InferUntilFail(sigRenamed, arguments, checker, &substitution);

    auto sigArgs = signature->Params();
    auto noNeverArg = std::all_of(sigArgs.begin(), sigArgs.end(),
                                  [](varbinder::LocalVariable *param) { return !param->TsType()->IsETSNeverType(); });
    if (substitution.size() == sigParams.size()) {
        if (!CheckTypeParamsConstraint(checker, &substitution, pos)) {
            return std::nullopt;
        }
        return ComposeTemporarySubstitution(checker, &paramRenamer, &substitution);
    }
    for (const auto typeParam : sigParams) {
        auto newTypeParam = typeParam->AsETSTypeParameter();
        if (auto it = substitution.find(newTypeParam); it != substitution.cend()) {
            continue;
        }
        if (newTypeParam->GetDefaultType() == nullptr) {
            if (sigArgs.empty() || noNeverArg) {
                checker->LogError(diagnostic::GENERIC_TYPE_PARAM_INFERENCE_FAILED,
                                  {typeParam, signature->Function()->Id()->Name()}, pos);
            }
            continue;
        }
        auto dflt = newTypeParam->GetDefaultType()->Substitute(checker->Relation(), &substitution);
        if (!checker->EnhanceSubstitutionForType(sigInfo->typeParams, newTypeParam, dflt, &substitution)) {
            return std::nullopt;
        }
    }
    if (substitution.size() != sigParams.size() &&
        (signature->Function()->ReturnTypeAnnotation() == nullptr ||
         sigRenamed->Function()->ReturnTypeAnnotation()->TsType() == nullptr || sigRenamed->ReturnType() == nullptr ||
         !checker->EnhanceSubstitutionForType(sigInfo->typeParams,
                                              sigRenamed->Function()->ReturnTypeAnnotation()->TsType(),
                                              sigRenamed->ReturnType(), &substitution))) {
        return std::nullopt;
    }

    if (!CheckTypeParamsConstraint(checker, &substitution, pos)) {
        return std::nullopt;
    }
    return ComposeTemporarySubstitution(checker, &paramRenamer, &substitution);
}

static std::optional<Substitution> BuildExplicitSubstitutionForArguments(ETSChecker *checker, Signature *signature,
                                                                         const ArenaVector<ir::TypeNode *> &params,
                                                                         const lexer::SourcePosition &pos)
{
    auto &sigParams = signature->GetSignatureInfo()->typeParams;
    auto substitution = Substitution {};
    auto constraintsSubstitution = Substitution {};
    ArenaVector<Type *> instArgs {checker->Allocator()->Adapter()};

    for (size_t ix = 0; ix < params.size(); ++ix) {
        instArgs.push_back(params[ix]->GetType(checker));
        if (ix < sigParams.size()) {
            checker->EmplaceSubstituted(&constraintsSubstitution, sigParams[ix]->AsETSTypeParameter(), instArgs[ix]);
        }
    }
    for (size_t ix = instArgs.size(); ix < sigParams.size(); ++ix) {
        auto typeParam = sigParams[ix]->AsETSTypeParameter();
        auto *dflt = typeParam->GetDefaultType();
        if (dflt == nullptr) {
            break;
        }

        dflt = dflt->Substitute(checker->Relation(), &constraintsSubstitution);
        instArgs.push_back(dflt);
        checker->EmplaceSubstituted(&constraintsSubstitution, typeParam, instArgs[ix]);
    }
    if (sigParams.size() != instArgs.size()) {
        checker->LogError(diagnostic::RTYPE_PARAM_COUNT_MISMATCH, {sigParams.size(), instArgs.size()}, pos);
        return std::nullopt;
    }

    for (size_t ix = 0; ix < sigParams.size(); ix++) {
        if (!IsCompatibleTypeArgument(checker, sigParams[ix]->AsETSTypeParameter(), instArgs[ix],
                                      &constraintsSubstitution)) {
            auto *constraintType = sigParams[ix]->AsETSTypeParameter()->GetConstraintType()->Substitute(
                checker->Relation(), &constraintsSubstitution);
            checker->LogError(diagnostic::TYPEARG_TYPEPARAM_SUBTYPING, {instArgs[ix], constraintType}, pos);

            return std::nullopt;
        }
        checker->EmplaceSubstituted(&substitution, sigParams[ix]->AsETSTypeParameter(), instArgs[ix]);
    }
    return substitution;
}

static Signature *MaybeSubstituteTypeParameters(
    ETSChecker *checker, std::tuple<Signature *, const ir::TSTypeParameterInstantiation *, TypeRelationFlag, bool> info,
    const ArenaVector<ir::Expression *> &arguments, const lexer::SourcePosition &pos)
{
    auto [signature, typeArguments, flags, _] = info;
    if (typeArguments == nullptr && signature->GetSignatureInfo()->typeParams.empty()) {
        return signature;
    }

    const std::optional<Substitution> substitution =
        (typeArguments != nullptr)
            ? BuildExplicitSubstitutionForArguments(checker, signature, typeArguments->Params(), pos)
            : BuildImplicitSubstitutionForArguments(checker, signature, arguments, pos);

    return (!substitution.has_value()) ? nullptr : signature->Substitute(checker->Relation(), &substitution.value());
}

static varbinder::Scope *NodeScope(ir::AstNode *ast)
{
    if (ast->IsBlockStatement()) {
        return ast->AsBlockStatement()->Scope();
    }
    if (ast->IsBlockExpression()) {
        return ast->AsBlockExpression()->Scope();
    }
    if (ast->IsDoWhileStatement()) {
        return ast->AsDoWhileStatement()->Scope();
    }
    if (ast->IsForInStatement()) {
        return ast->AsForInStatement()->Scope();
    }
    if (ast->IsForOfStatement()) {
        return ast->AsForOfStatement()->Scope();
    }
    if (ast->IsForUpdateStatement()) {
        return ast->AsForUpdateStatement()->Scope();
    }
    if (ast->IsSwitchStatement()) {
        return ast->AsSwitchStatement()->Scope();
    }
    if (ast->IsWhileStatement()) {
        return ast->AsWhileStatement()->Scope();
    }
    if (ast->IsCatchClause()) {
        return ast->AsCatchClause()->Scope();
    }
    if (ast->IsClassDefinition()) {
        return ast->AsClassDefinition()->Scope();
    }
    if (ast->IsScriptFunction()) {
        return ast->AsScriptFunction()->Scope()->ParamScope();
    }
    return nullptr;
}

// NOTE: #14993 merge with InstantiationContext::ValidateTypeArg
static bool IsCompatibleTypeArgument(ETSChecker *checker, ETSTypeParameter *typeParam, Type *typeArgument,
                                     const Substitution *substitution)
{
    if (typeArgument->IsTypeError()) {
        return true;
    }
    ES2PANDA_ASSERT(ETSChecker::IsReferenceType(typeArgument));
    auto constraint = typeParam->GetConstraintType()->Substitute(checker->Relation(), substitution);
    return checker->Relation()->IsSupertypeOf(constraint, typeArgument->Substitute(checker->Relation(), substitution));
}

static bool EnhanceSubstitutionForType(ETSChecker *checker, const ArenaVector<Type *> &typeParams, Type *paramType,
                                       Type *argumentType, Substitution *substitution);

static bool EnhanceSubstitutionForReadonly(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                           ETSReadonlyType *paramType, Type *argumentType, Substitution *substitution)
{
    return EnhanceSubstitutionForType(checker, typeParams, paramType->GetUnderlying(),
                                      checker->GetReadonlyType(argumentType), substitution);
}

static bool ValidateTypeSubstitution(ETSChecker *checker, const ArenaVector<Type *> &typeParams, Type *ctype,
                                     Type *argumentType, Substitution *substitution)
{
    if (!EnhanceSubstitutionForType(checker, typeParams, ctype, argumentType, substitution)) {
        return false;
    }
    return !ctype->IsETSTypeParameter() ||
           (substitution->count(ctype->AsETSTypeParameter()) > 0 &&
            checker->Relation()->IsAssignableTo(argumentType, substitution->at(ctype->AsETSTypeParameter())));
}

static bool EnhanceSubstitutionForUnion(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                        ETSUnionType *paramUn, Type *argumentType, Substitution *substitution)
{
    if (!argumentType->IsETSUnionType()) {
        bool foundValid = false;
        for (Type *ctype : paramUn->ConstituentTypes()) {
            foundValid |= ValidateTypeSubstitution(checker, typeParams, ctype, argumentType, substitution);
        }
        return foundValid;
    }
    auto *const argUn = argumentType->AsETSUnionType();

    std::vector<Type *> paramWlist;
    std::vector<Type *> argWlist;

    bool isIdenticalUpToTypeParams = false;
    for (auto *pc : paramUn->ConstituentTypes()) {
        for (auto *ac : argUn->ConstituentTypes()) {
            {
                // Type parameters are inferred separately, here we check the equality of the base classes
                SavedTypeRelationFlagsContext savedFlagsCtx(checker->Relation(),
                                                            TypeRelationFlag::IGNORE_TYPE_PARAMETERS);
                isIdenticalUpToTypeParams = checker->Relation()->IsIdenticalTo(pc, ac);
            }

            if (!isIdenticalUpToTypeParams) {
                paramWlist.push_back(pc);
                argWlist.push_back(ac);
                continue;
            }

            if (!EnhanceSubstitutionForType(checker, typeParams, pc, ac, substitution)) {
                return false;
            }
        }
    }
    auto *const newArg = checker->CreateETSUnionType(std::move(argWlist));

    for (auto *pc : paramWlist) {
        if (!EnhanceSubstitutionForType(checker, typeParams, pc, newArg, substitution)) {
            return false;
        }
    }
    return true;
}

static bool ProcessUntypedParameter(ETSChecker *checker, size_t paramIndex, Signature *paramSig, Signature *argSig,
                                    Substitution *substitution)
{
    auto declNode = argSig->Params()[paramIndex]->Declaration()->Node();
    if (!declNode->IsETSParameterExpression() || !checker->HasStatus(CheckerStatus::IN_TYPE_INFER)) {
        return false;
    }

    auto *paramExpr = declNode->AsETSParameterExpression();
    if (paramExpr->Ident()->TypeAnnotation() != nullptr) {
        return false;
    }

    Type *paramType = paramSig->Params()[paramIndex]->TsType();
    Type *inferredType = paramType->Substitute(checker->Relation(), substitution);

    varbinder::Variable *argParam = argSig->Params()[paramIndex];
    argParam->SetTsType(inferredType);
    paramExpr->Ident()->SetTsType(inferredType);
    paramExpr->Ident()->Variable()->SetTsType(inferredType);

    return true;
}

static void RemoveInvalidTypeMarkers(ir::AstNode *node) noexcept
{
    std::function<void(ir::AstNode *)> doNode = [&](ir::AstNode *nn) {
        if (nn->IsTyped() && !(nn->IsExpression() && nn->AsExpression()->IsTypeNode()) &&
            nn->AsTyped()->TsType() != nullptr && nn->AsTyped()->TsType()->IsTypeError()) {
            nn->AsTyped()->SetTsType(nullptr);
        }
        if (nn->IsIdentifier() && nn->AsIdentifier()->TsType() != nullptr &&
            nn->AsIdentifier()->TsType()->IsTypeError()) {
            nn->AsIdentifier()->SetVariable(nullptr);
        }
        if (!nn->IsETSTypeReference()) {
            nn->Iterate([&](ir::AstNode *child) { doNode(child); });
        }
    };

    doNode(node);
}

static void ResetInferredTypeInArrowBody(ir::AstNode *body, ETSChecker *checker,
                                         std::unordered_set<varbinder::Variable *> &inferredVarSet)
{
    checker::ScopeContext scopeCtx(checker, body->Parent()->Scope());
    std::function<void(ir::AstNode *)> doNode = [&](ir::AstNode *node) {
        if (node->IsIdentifier()) {
            auto *id = node->AsIdentifier();
            if (inferredVarSet.count(id->Variable()) == 0U) {
                return;
            }

            ir::AstNode *checkNode = id;
            while (checkNode->Parent()->IsTyped() && checkNode->Parent()->AsTyped()->TsType() == nullptr &&
                   checkNode->Parent() != body) {
                checkNode = checkNode->Parent();
            }
            checkNode->Check(checker);
        }
        if (node->IsVariableDeclarator()) {
            auto *id = node->AsVariableDeclarator()->Id();
            inferredVarSet.emplace(id->Variable());
            node->Check(checker);
        }
    };
    body->IterateRecursively(doNode);
}

static void ResetInferredNode(ETSChecker *checker, std::unordered_set<varbinder::Variable *> &inferredVarSet)
{
    auto relation = checker->Relation();
    auto resetFuncState = [](ir::ArrowFunctionExpression *expr) {
        auto *func = expr->Function();
        func->SetSignature(nullptr);
        func->ClearReturnStatements();
        expr->SetTsType(nullptr);
    };

    const bool hasValidNode = relation->GetNode() != nullptr && relation->GetNode()->IsArrowFunctionExpression();
    if (!checker->HasStatus(CheckerStatus::IN_TYPE_INFER) || !hasValidNode) {
        return;
    }

    auto *arrowFunc = relation->GetNode()->AsArrowFunctionExpression();
    relation->SetNode(nullptr);

    RemoveInvalidTypeMarkers(arrowFunc);
    ResetInferredTypeInArrowBody(arrowFunc->Function()->Body(), checker, inferredVarSet);
    resetFuncState(arrowFunc);
    arrowFunc->Check(checker);
}

static bool EnhanceSubstitutionForNonNullish(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                             ETSNonNullishType *paramType, Type *argumentType,
                                             Substitution *substitution)
{
    if (argumentType->IsETSNonNullishType()) {
        ES2PANDA_ASSERT(argumentType->AsETSNonNullishType()->GetUnderlying() != nullptr);
        return EnhanceSubstitutionForType(checker, typeParams, paramType->GetUnderlying(),
                                          argumentType->AsETSNonNullishType()->GetUnderlying(), substitution);
    }
    return EnhanceSubstitutionForType(checker, typeParams, paramType->GetUnderlying(), argumentType, substitution);
}

static bool EnhanceSubstitutionTypeParameter(ETSChecker *checker, ETSTypeParameter *paramType, Type *argumentType,
                                             Substitution *substitution)
{
    auto *const originalTparam = paramType->GetOriginal();
    if (argumentType->IsETSTypeParameter() && argumentType->AsETSTypeParameter()->GetOriginal() == originalTparam) {
        return false;
    }
    if (!ETSChecker::IsReferenceType(argumentType)) {
        checker->LogError(diagnostic::INFERENCE_TYPE_INCOMPAT, {paramType, argumentType},
                          paramType->GetDeclNode()->Start());
        return false;
    }

    // #23068 substitution happens before the constraint check, should be restored
    auto trialSubstitution = *substitution;
    checker->EmplaceSubstituted(&trialSubstitution, originalTparam, argumentType);
    if (!IsCompatibleTypeArgument(checker, paramType, argumentType, &trialSubstitution)) {
        return false;
    }
    checker->EmplaceSubstituted(substitution, originalTparam, argumentType);
    return true;
}

static bool EnhanceSubstitutionForFunction(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                           ETSFunctionType *paramType, Type *argumentType, Substitution *substitution)
{
    auto const enhance = [checker, typeParams, substitution](Type *ptype, Type *atype) {
        return EnhanceSubstitutionForType(checker, typeParams, ptype, atype, substitution);
    };

    if (!argumentType->IsETSFunctionType()) {
        return true;
    }

    auto *paramSig = paramType->ArrowSignature();
    auto *argSig = argumentType->AsETSFunctionType()->ArrowSignature();

    if (paramSig->MinArgCount() < argSig->MinArgCount()) {
        return false;
    }

    bool res = true;
    const size_t commonArity = std::min(argSig->ArgCount(), paramSig->ArgCount());

    std::unordered_set<varbinder::Variable *> inferredVarSet;
    for (size_t idx = 0; idx < commonArity; idx++) {
        auto *declNode = argSig->Params()[idx]->Declaration()->Node();
        if (ProcessUntypedParameter(checker, idx, paramSig, argSig, substitution)) {
            inferredVarSet.emplace(declNode->AsETSParameterExpression()->Ident()->Variable());
            continue;
        }
        res &= enhance(paramSig->Params()[idx]->TsType(), argSig->Params()[idx]->TsType());
    }

    ResetInferredNode(checker, inferredVarSet);

    if (argSig->HasRestParameter() && paramSig->HasRestParameter()) {
        res &= enhance(paramSig->RestVar()->TsType(), argSig->RestVar()->TsType());
    }
    res &= enhance(paramSig->ReturnType(), argSig->ReturnType());

    return res;
}

static bool EnhanceSubstitutionForAwaited(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                          ETSAwaitedType *paramType, Type *argumentType, Substitution *substitution)
{
    auto *argumentAwaitedType =
        argumentType->IsETSAwaitedType() ? argumentType->AsETSAwaitedType()->GetUnderlying() : argumentType;
    auto *paramAwaitedType = paramType->GetUnderlying();
    return EnhanceSubstitutionForType(checker, typeParams, paramAwaitedType, argumentAwaitedType, substitution);
}

static bool EnhanceSubstitutionForPartialTypeParam(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                                   ETSPartialTypeParameter *paramType, Type *argumentType,
                                                   Substitution *substitution)
{
    if (!argumentType->IsETSObjectType() || !argumentType->AsETSObjectType()->IsPartial()) {
        return false;
    }
    ES2PANDA_ASSERT(argumentType->AsETSObjectType()->GetBaseType() != nullptr);
    return EnhanceSubstitutionForType(checker, typeParams, paramType->GetUnderlying(),
                                      argumentType->AsETSObjectType()->GetBaseType(), substitution);
}

// Try to find the base type somewhere in object subtypes. Incomplete, yet safe
static ETSObjectType *FindEnhanceTargetInSupertypes(ETSObjectType *object, ETSObjectType *base)
{
    ES2PANDA_ASSERT(base == base->GetOriginalBaseType());
    if (object->GetConstOriginalBaseType() == base) {
        return object;
    }
    auto const traverse = [base](ETSObjectType *v) { return FindEnhanceTargetInSupertypes(v, base); };

    for (auto itf : object->Interfaces()) {
        auto res = traverse(itf);
        if (res != nullptr) {
            return res;
        }
    }

    if (object->SuperType() != nullptr) {
        auto res = traverse(object->SuperType());
        if (res != nullptr) {
            return res;
        }
    }
    return nullptr;
}

static bool EnhanceSubstitutionForObject(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                         ETSObjectType *paramType, Type *argumentType, Substitution *substitution)
{
    auto const enhance = [checker, typeParams, substitution](Type *ptype, Type *atype) {
        return EnhanceSubstitutionForType(checker, typeParams, ptype, atype, substitution);
    };

    if (!argumentType->IsETSObjectType()) {
        return true;
    }
    auto enhanceType = FindEnhanceTargetInSupertypes(argumentType->AsETSObjectType(), paramType->GetOriginalBaseType());
    if (enhanceType == nullptr) {
        return true;
    }
    ES2PANDA_ASSERT(enhanceType->GetOriginalBaseType() == paramType->GetOriginalBaseType());
    bool res = true;
    for (size_t i = 0; i < enhanceType->TypeArguments().size(); i++) {
        res &= enhance(paramType->TypeArguments()[i], enhanceType->TypeArguments()[i]);
    }
    return res;
}

static bool EnhanceSubstitutionForArray(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                        ETSArrayType *const paramType, Type *const argumentType,
                                        Substitution *const substitution)
{
    auto *const elementType =
        argumentType->IsETSArrayType() ? argumentType->AsETSArrayType()->ElementType() : argumentType;

    return EnhanceSubstitutionForType(checker, typeParams, paramType->ElementType(), elementType, substitution);
}

static bool EnhanceSubstitutionForResizableArray(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                                 ETSResizableArrayType *const paramType, Type *const argumentType,
                                                 Substitution *const substitution)
{
    auto *const elementType =
        argumentType->IsETSResizableArrayType() ? argumentType->AsETSResizableArrayType()->ElementType() : argumentType;

    return EnhanceSubstitutionForType(checker, typeParams, paramType->ElementType(), elementType, substitution);
}

static bool EnhanceSubstitutionForReturnTypeUtilityType(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                                        const ETSReturnTypeUtilityType *paramType, Type *argumentType,
                                                        Substitution *substitution)
{
    // If the argument is not a ReturnType<T> type, then the best infer about T, is that it is a function type.
    // (It can be a union of them, never type etc..., but these can be achieved with explicit instantiation)
    auto *argumentReturnTypeType = argumentType->IsETSReturnTypeUtilityType()
                                       ? argumentType->AsETSReturnTypeUtilityType()->GetUnderlying()
                                       : static_cast<Type *>(checker->GlobalBuiltinFunctionType());
    Type *paramReturnTypeType = paramType->IsETSReturnTypeUtilityType()
                                    ? paramType->GetUnderlying()
                                    : static_cast<Type *>(checker->GlobalBuiltinFunctionType());
    return EnhanceSubstitutionForType(checker, typeParams, paramReturnTypeType, argumentReturnTypeType, substitution);
}

static bool EnhanceSubstitutionForUtilityType(ETSChecker *checker, const ArenaVector<Type *> &typeParams,
                                              Type *paramType, Type *argumentType, Substitution *substitution)
{
    if (paramType->IsETSReadonlyType()) {
        return EnhanceSubstitutionForReadonly(checker, typeParams, paramType->AsETSReadonlyType(), argumentType,
                                              substitution);
    }
    if (paramType->IsETSPartialTypeParameter()) {
        return EnhanceSubstitutionForPartialTypeParam(checker, typeParams, paramType->AsETSPartialTypeParameter(),
                                                      argumentType, substitution);
    }
    if (paramType->IsETSAwaitedType()) {
        return EnhanceSubstitutionForAwaited(checker, typeParams, paramType->AsETSAwaitedType(), argumentType,
                                             substitution);
    }
    if (paramType->IsETSReturnTypeUtilityType()) {
        return EnhanceSubstitutionForReturnTypeUtilityType(checker, typeParams, paramType->AsETSReturnTypeUtilityType(),
                                                           argumentType, substitution);
    }
    if (paramType->IsETSNonNullishType()) {
        return EnhanceSubstitutionForNonNullish(checker, typeParams, paramType->AsETSNonNullishType(), argumentType,
                                                substitution);
    }

    return true;
}

/* A very rough and imprecise partial type inference */
// CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic
static bool EnhanceSubstitutionForType(ETSChecker *checker, const ArenaVector<Type *> &typeParams, Type *paramType,
                                       Type *argumentType, Substitution *substitution)
{
    ES2PANDA_ASSERT(argumentType != nullptr);
    if (paramType->IsETSTypeParameter()) {
        auto *const originalTparam = paramType->AsETSTypeParameter()->GetOriginal();
        if (std::find(typeParams.begin(), typeParams.end(), originalTparam) != typeParams.end() &&
            substitution->count(originalTparam) == 0) {
            return EnhanceSubstitutionTypeParameter(checker, paramType->AsETSTypeParameter(), argumentType,
                                                    substitution);
        }
    }
    if (paramType->IsETSFunctionType()) {
        return EnhanceSubstitutionForFunction(checker, typeParams, paramType->AsETSFunctionType(), argumentType,
                                              substitution);
    }
    if (paramType->IsETSUnionType()) {
        return EnhanceSubstitutionForUnion(checker, typeParams, paramType->AsETSUnionType(), argumentType,
                                           substitution);
    }
    if (paramType->IsETSResizableArrayType()) {
        return EnhanceSubstitutionForResizableArray(checker, typeParams, paramType->AsETSResizableArrayType(),
                                                    argumentType, substitution);
    }
    if (paramType->IsETSObjectType()) {
        return EnhanceSubstitutionForObject(checker, typeParams, paramType->AsETSObjectType(), argumentType,
                                            substitution);
    }
    if (paramType->IsETSArrayType()) {
        return EnhanceSubstitutionForArray(checker, typeParams, paramType->AsETSArrayType(), argumentType,
                                           substitution);
    }

    return EnhanceSubstitutionForUtilityType(checker, typeParams, paramType, argumentType, substitution);
}

bool ETSChecker::EnhanceSubstitutionForType(const ArenaVector<Type *> &typeParams, Type *paramType, Type *argumentType,
                                            Substitution *substitution)
{
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    return checker::EnhanceSubstitutionForType(this, typeParams, paramType, argumentType, substitution);
}

// #22952: optional arrow leftovers
static bool CheckLambdaAssignable(ETSChecker *checker, ir::Expression *expr, Type *paramType,
                                  ir::ScriptFunction *lambda);

static bool CheckLambdaAssignableUnion(ETSChecker *checker, ir::Expression *expr, ETSUnionType *paramType,
                                       ir::ScriptFunction *lambda)
{
    for (auto *type : paramType->ConstituentTypes()) {
        if (CheckLambdaAssignable(checker, expr, type, lambda)) {
            return true;
        }
    }
    return false;
}

static bool CheckLambdaTypeParameter(ETSChecker *checker, ir::ScriptFunction *lambda)
{
    if (lambda->Params().empty()) {
        return true;
    }
    for (auto param : lambda->Params()) {
        if (param->IsETSParameterExpression() &&
            param->AsETSParameterExpression()->Ident()->TypeAnnotation() == nullptr &&
            param->AsETSParameterExpression()->Ident()->TsType()->IsTypeError()) {
            checker->LogError(diagnostic::INFER_FAILURE_FUNC_PARAM,
                              {param->AsETSParameterExpression()->Ident()->Name()}, param->Start());
            return false;
        }
    }
    return true;
}

// #22952: optional arrow leftovers
static bool CheckLambdaAssignable(ETSChecker *checker, ir::Expression *expr, Type *paramType,
                                  ir::ScriptFunction *lambda)
{
    if (paramType->IsETSTypeAliasType()) {
        paramType = paramType->AsETSTypeAliasType()->GetTargetType();
    }

    if (!paramType->IsETSArrowType()) {
        // the surrounding function is made so we can *bypass* the typecheck in the "inference" context,
        // however the body of the function has to be checked in any case
        if (paramType->IsETSUnionType()) {
            return CheckLambdaAssignableUnion(checker, expr, paramType->AsETSUnionType(), lambda);
        }

        if (checker->Relation()->IsSupertypeOf(paramType, checker->GlobalBuiltinFunctionType())) {
            ES2PANDA_ASSERT(lambda->Parent()->IsArrowFunctionExpression());
            lambda->Parent()->AsArrowFunctionExpression()->SetPreferredType(nullptr);
            lambda->Parent()->Check(checker);
            return true;
        }
        return false;
    }

    // Workaround for lambda with type param, should be removed after preferred type regularization
    ir::AstNode *typeAnn = expr->AsETSParameterExpression()->Ident()->TypeAnnotation();
    if (typeAnn == nullptr) {
        return false;
    }
    if (typeAnn->IsETSTypeReference() && !typeAnn->AsETSTypeReference()->TsType()->IsETSArrayType()) {
        typeAnn = util::Helpers::DerefETSTypeReference(typeAnn);
    }
    if (typeAnn->IsTSTypeParameter() && !CheckLambdaTypeParameter(checker, lambda)) {
        return false;
    }

    auto *calleeType = paramType->AsETSFunctionType();
    const size_t restVarCount = calleeType->ArrowSignature()->RestVar() != nullptr ? 1 : 0;
    return lambda->Params().size() <= calleeType->ArrowSignature()->Params().size() + restVarCount;
}

// #22952: remove optional arrow leftovers
static bool CheckOptionalLambdaFunction(ETSChecker *checker, ir::Expression *argument, Signature *substitutedSig,
                                        size_t index)
{
    auto param = substitutedSig->Params()[index]->Declaration()->Node()->AsETSParameterExpression();
    if (argument->IsArrowFunctionExpression()) {
        auto *const arrowFuncExpr = argument->AsArrowFunctionExpression();
        ir::ScriptFunction *const lambda = arrowFuncExpr->Function();

        return CheckLambdaAssignable(checker, param, substitutedSig->Params()[index]->TsType(), lambda);
    }

    return true;
}

static bool IsInvalidArgumentAsIdentifier(varbinder::Scope *scope, const ir::Identifier *identifier)
{
    auto result = scope->Find(identifier->Name());
    return result.variable != nullptr &&
           (result.variable->HasFlag(varbinder::VariableFlags::CLASS_OR_INTERFACE_OR_ENUM |
                                     varbinder::VariableFlags::TYPE_ALIAS));
}

static bool CheckArrowFunctionParamIfNeeded(ETSChecker *checker, Signature *substitutedSig,
                                            const ArenaVector<ir::Expression *> &arguments, TypeRelationFlag flags)
{
    if ((flags & TypeRelationFlag::NO_CHECK_TRAILING_LAMBDA) != 0 && arguments.back()->IsArrowFunctionExpression()) {
        ir::ScriptFunction *const lambda = arguments.back()->AsArrowFunctionExpression()->Function();
        auto targetParm = substitutedSig->GetSignatureInfo()->params.back()->Declaration()->Node();
        auto targetParmType = substitutedSig->GetSignatureInfo()->params.back()->TsType();
        if (!CheckLambdaAssignable(checker, targetParm->AsETSParameterExpression(), targetParmType, lambda)) {
            return false;
        }
    }
    return true;
}

// Note: (Issue27688) if lambda is trailing lambda transferred, it must be in recheck.
// in signature matching, foo(()=>void) should be the same with foo() {}
static bool HasTransferredTrailingLambda(const ArenaVector<ir::Expression *> &arguments)
{
    return !arguments.empty() && arguments.back()->IsArrowFunctionExpression() &&
           arguments.back()->AsArrowFunctionExpression()->Function()->IsTrailingLambda();
}

bool ValidateRestParameter(ETSChecker *checker, Signature *signature, const ArenaVector<ir::Expression *> &arguments,
                           const lexer::SourcePosition &pos, TypeRelationFlag flags)
{
    size_t const argCount = arguments.size();
    size_t compareCount = argCount;
    auto const hasRestParameter = signature->HasRestParameter();
    if ((flags & TypeRelationFlag::NO_CHECK_TRAILING_LAMBDA) != 0 && !signature->Params().empty() &&
        signature->Params().back()->Declaration()->Node()->AsETSParameterExpression()->IsOptional()) {
        compareCount = compareCount - 1;
    }

    if (!hasRestParameter && argCount > 0 && arguments[argCount - 1]->IsSpreadElement()) {
        checker->LogError(diagnostic::ERROR_ARKTS_SPREAD_ONLY_WITH_REST, {}, pos);
        return false;
    }
    if (compareCount < signature->MinArgCount() || (argCount > signature->ArgCount() && !hasRestParameter)) {
        checker->LogError(diagnostic::PARAM_COUNT_MISMATCH, {signature->MinArgCount(), argCount}, pos);
        return false;
    }
    if (hasRestParameter &&
        (((flags & TypeRelationFlag::NO_CHECK_TRAILING_LAMBDA) != 0) || HasTransferredTrailingLambda(arguments))) {
        return false;
    }
    return !(argCount > signature->ArgCount() && hasRestParameter &&
             (flags & TypeRelationFlag::IGNORE_REST_PARAM) != 0);
}

// NOTE(dkofanov): Mimics type inferrence for integer literals. Also relies on the implicit widening which occurs
// later in checker and 'CheckCastLiteral' during 'ConstantExpressionLowering'.
static void InferTypeForNumberLiteral(ETSChecker *checker, ir::NumberLiteral *argumentLiteral, Type *paramType)
{
    if (argumentLiteral->IsFolded() || argumentLiteral->IsNarrowingBlocked()) {
        return;
    }
    argumentLiteral->SetTsType(nullptr);
    argumentLiteral->SetPreferredType(paramType);
    auto &number = argumentLiteral->AsNumberLiteral()->Number();

    auto *typeRel = checker->Relation();
    if (typeRel->IsSupertypeOf(checker->GlobalLongBuiltinType(), paramType)) {
        number.TryNarrowTo<int64_t>();
    } else if (typeRel->IsSupertypeOf(checker->GlobalIntBuiltinType(), paramType)) {
        number.TryNarrowTo<int32_t>();
    } else if (typeRel->IsSupertypeOf(checker->GlobalShortBuiltinType(), paramType)) {
        number.TryNarrowTo<int16_t>();
    } else if (typeRel->IsSupertypeOf(checker->GlobalByteBuiltinType(), paramType)) {
        number.TryNarrowTo<int8_t>();
    }
}

static bool SetPreferredTypeForArrayArgument(ETSChecker *checker, ir::ArrayExpression *arrayExpr,
                                             Signature *substitutedSig);

static bool IsValidRestArgument(ETSChecker *checker, ir::Expression *const argument, Signature *const substitutedSig,
                                const TypeRelationFlag flags, const std::size_t index)
{
    auto *restParamType = substitutedSig->RestVar()->TsType();
    if (restParamType->IsETSTupleType()) {
        return false;
    }
    if (argument->IsObjectExpression()) {
        argument->SetPreferredType(checker->GetElementTypeOfArray(restParamType));
        // Object literals should be checked separately afterwards after call resolution
        return true;
    }

    // Set preferred type for array expressions before checking, similar to spread elements
    if (argument->IsArrayExpression()) {
        if (!SetPreferredTypeForArrayArgument(checker, argument->AsArrayExpression(), substitutedSig)) {
            return false;
        }
    }

    const auto argumentType = argument->Check(checker);

    auto targetType = checker->GetElementTypeOfArray(restParamType);
    auto const invocationCtx = checker::InvocationContext(
        checker->Relation(), argument, argumentType, targetType, argument->Start(),
        {{diagnostic::REST_PARAM_INCOMPAT_AT, {argumentType, targetType, index + 1}}}, flags);

    bool result = invocationCtx.IsInvocable();
    // Clear preferred type if invocation fails, similar to spread elements
    if (!result && argument->IsArrayExpression()) {
        checker->ModifyPreferredType(argument->AsArrayExpression(), nullptr);
    }

    return result;
}

static bool SetPreferredTypeForArrayArgument(ETSChecker *checker, ir::ArrayExpression *arrayExpr,
                                             Signature *substitutedSig)
{
    auto *const restVarType = substitutedSig->RestVar()->TsType();
    if (!restVarType->IsETSArrayType() && !restVarType->IsETSResizableArrayType()) {
        return true;
    }
    auto targetType = checker->GetElementTypeOfArray(restVarType);
    // Validate tuple size before setting preferred type
    if (targetType->IsETSTupleType()) {
        auto *tupleType = targetType->AsETSTupleType();
        if (tupleType->GetTupleSize() != arrayExpr->Elements().size()) {
            // Size mismatch - don't set preferred type, this will cause a type error
            return false;
        }
    }
    arrayExpr->SetPreferredType(targetType);
    return true;
}

static void PrepareComparisonTypes(ETSChecker *checker, Type **argumentType, Type **targetType)
{
    if (!util::Helpers::IsArrayType(*argumentType) && !(*argumentType)->IsETSTupleType()) {
        *argumentType = checker->GetElementTypeOfSpreadType(*argumentType);
        if (util::Helpers::IsArrayType(*targetType)) {
            *targetType = checker->GetElementTypeOfArray(*targetType);
        }
    }

    while (util::Helpers::IsArrayType(*targetType) &&
           (util::Helpers::IsArrayType(*argumentType) || (*argumentType)->IsETSTupleType())) {
        if ((*argumentType)->IsETSTupleType()) {
            *argumentType =
                util::Helpers::CreateUnionOfTupleConstituentTypes(checker, (*argumentType)->AsETSTupleType());
        } else {
            *argumentType = checker->GetElementTypeOfArray(*argumentType);
        }
        *targetType = checker->GetElementTypeOfArray(*targetType);
    }
}

static bool ValidateSpreadRestArgument(ETSChecker *checker, ir::Expression *argument, Signature *substitutedSig,
                                       TypeRelationFlag flags, [[maybe_unused]] size_t index)
{
    auto *const restArgument = argument->AsSpreadElement()->Argument();
    Type *targetType = substitutedSig->RestVar()->TsType();

    argument->SetPreferredType(targetType);
    Type *argumentType = argument->Check(checker);

    // Types comparison here should be performed without error generation on fail
    // because it may be a search of valid signature in the set of overloaded methods
    PrepareComparisonTypes(checker, &argumentType, &targetType);
    auto const invocationCtx = checker::InvocationContext(checker->Relation(), restArgument, argumentType, targetType,
                                                          argument->Start(), {}, flags);
    if (!invocationCtx.IsInvocable()) {
        if (restArgument->IsArrayExpression()) {
            checker->ModifyPreferredType(restArgument->AsArrayExpression(), nullptr);
            argument->SetTsType(nullptr);
        }
        return false;
    }
    return true;
}

static bool SetPreferredTypeBeforeValidate(ETSChecker *checker, ir::Expression *argument, Type *paramType,
                                           TypeRelationFlag flags, bool isRestParam = false);

static bool ValidateSignatureRestParams(ETSChecker *checker, Signature *substitutedSig,
                                        const ArenaVector<ir::Expression *> &arguments, TypeRelationFlag flags)
{
    size_t const argumentCount = arguments.size();
    auto const commonArity = std::min(substitutedSig->ArgCount(), argumentCount);
    auto restParamType = substitutedSig->RestVar()->TsType();
    if (argumentCount == commonArity && restParamType->IsETSTupleType()) {
        return false;
    }

    for (size_t index = commonArity; index < argumentCount; ++index) {
        auto &argument = arguments[index];
        auto preferredType =
            restParamType->IsETSTupleType() ? restParamType : checker->GetElementTypeOfArray(restParamType);
        if (!SetPreferredTypeBeforeValidate(checker, argument, preferredType, flags, true)) {
            return false;
        }

        if (!argument->IsSpreadElement()) {
            if (!IsValidRestArgument(checker, argument, substitutedSig, flags, index)) {
                return false;
            }
            continue;
        }

        if (!ValidateSpreadRestArgument(checker, argument, substitutedSig, flags, index)) {
            return false;
        }
    }

    return true;
}

bool IsSignatureAccessible(Signature *sig, ETSObjectType *containingClass, TypeRelation *relation)
{
    // NOTE(vivienvoros): this check can be removed if signature is implicitly declared as public according to the
    // spec.
    if (!sig->HasSignatureFlag(SignatureFlags::PUBLIC | SignatureFlags::PROTECTED | SignatureFlags::PRIVATE)) {
        return true;
    }

    if (sig->HasSignatureFlag(SignatureFlags::PUBLIC) || sig->Owner() == containingClass ||
        (sig->HasSignatureFlag(SignatureFlags::PROTECTED) && relation->IsSupertypeOf(sig->Owner(), containingClass))) {
        return true;
    }

    return false;
}

static std::vector<bool> FindTypeInferenceArguments(const ArenaVector<ir::Expression *> &arguments)
{
    std::vector<bool> argTypeInferenceRequired(arguments.size());
    size_t index = 0;
    auto isNeedInterLambda = [](ir::AstNode *arg) {
        if (arg->IsArrowFunctionExpression()) {
            ir::ScriptFunction *const lambda = arg->AsArrowFunctionExpression()->Function();
            return ETSChecker::NeedTypeInference(lambda);
        }
        return false;
    };
    for (ir::Expression *arg : arguments) {
        if (arg->IsArrayExpression()) {
            if (arg->IsAnyChild(isNeedInterLambda)) {
                argTypeInferenceRequired[index] = true;
            }
        } else {
            argTypeInferenceRequired[index] = isNeedInterLambda(arg);
        }
        ++index;
    }
    return argTypeInferenceRequired;
}

static bool CheckLambdaInfer(ETSChecker *checker, ir::AstNode *typeAnnotation,
                             ir::ArrowFunctionExpression *const arrowFuncExpr, Type *const subParameterType)
{
    if (typeAnnotation->IsETSTypeReference()) {
        typeAnnotation = util::Helpers::DerefETSTypeReference(typeAnnotation);
    }

    if (typeAnnotation->IsTSTypeParameter()) {
        return true;
    }

    if (!typeAnnotation->IsETSFunctionType()) {
        return false;
    }

    ir::ScriptFunction *const lambda = arrowFuncExpr->Function();
    auto calleeType = typeAnnotation->AsETSFunctionType();
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    checker->InferTypesForLambda(lambda, calleeType, subParameterType->AsETSFunctionType()->ArrowSignature());

    return true;
}

// CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic
static bool CheckLambdaTypeAnnotation(ETSChecker *checker, ir::ETSParameterExpression *param,
                                      ir::ArrowFunctionExpression *const arrowFuncExpr, Type *const parameterType,
                                      TypeRelationFlag flags)
{
    ir::AstNode *typeAnnotation = param->Ident()->TypeAnnotation();
    if (typeAnnotation->IsETSTypeReference()) {
        typeAnnotation = util::Helpers::DerefETSTypeReference(typeAnnotation);
    }
    auto checkInvocable = [&arrowFuncExpr, &parameterType, checker](TypeRelationFlag functionFlags) {
        arrowFuncExpr->SetPreferredType(parameterType);
        Type *const argumentType = arrowFuncExpr->Check(checker);

        checker::InvocationContext invocationCtx(checker->Relation(), arrowFuncExpr, argumentType, parameterType,
                                                 arrowFuncExpr->Start(), std::nullopt, functionFlags);
        return invocationCtx.IsInvocable();
    };

    //  process `single` type as usual.
    if (!typeAnnotation->IsETSUnionType()) {
        // #22952: infer optional parameter heuristics
        auto nonNullishParam = param->IsOptional() ? checker->GetNonNullishType(parameterType) : parameterType;
        ES2PANDA_ASSERT(nonNullishParam != nullptr);
        if (!nonNullishParam->IsETSFunctionType()) {
            arrowFuncExpr->Check(checker);
            return true;
        }
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        return CheckLambdaInfer(checker, typeAnnotation, arrowFuncExpr, nonNullishParam) && checkInvocable(flags);
    }

    // Preserve actual lambda types
    ir::ScriptFunction *const lambda = arrowFuncExpr->Function();
    std::vector<ir::TypeNode *> lambdaParamTypes {};
    for (auto *const lambdaParam : lambda->Params()) {
        lambdaParamTypes.emplace_back(lambdaParam->AsETSParameterExpression()->Ident()->TypeAnnotation());
    }
    auto *const lambdaReturnTypeAnnotation = lambda->ReturnTypeAnnotation();

    if (!parameterType->IsETSUnionType() || parameterType->AsETSUnionType()->ConstituentTypes().size() !=
                                                typeAnnotation->AsETSUnionType()->Types().size()) {
        Type *const argumentType = arrowFuncExpr->Check(checker);
        return checker->Relation()->IsSupertypeOf(parameterType, argumentType);
    }

    const auto typeAnnsOfUnion = typeAnnotation->AsETSUnionType()->Types();
    const auto typeParamOfUnion = parameterType->AsETSUnionType()->ConstituentTypes();
    for (size_t ix = 0; ix < typeAnnsOfUnion.size(); ++ix) {
        auto *typeNode = typeAnnsOfUnion[ix];
        auto *paramNode = typeParamOfUnion[ix];
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        if (CheckLambdaInfer(checker, typeNode, arrowFuncExpr, paramNode) && checkInvocable(flags)) {
            return true;
        }

        //  Restore inferring lambda types:
        for (std::size_t i = 0U; i < lambda->Params().size(); ++i) {
            if (lambdaParamTypes[i] == nullptr) {
                lambda->Params()[i]->AsETSParameterExpression()->Ident()->SetTsTypeAnnotation(nullptr);
            }
        }
        if (lambdaReturnTypeAnnotation == nullptr) {
            lambda->SetReturnTypeAnnotation(nullptr);
        }
    }

    return false;
}

static bool ResolveLambdaArgumentType(ETSChecker *checker, Signature *signature, size_t paramPosition,
                                      std::pair<ir::Expression *, size_t> argumentInfo,
                                      TypeRelationFlag resolutionFlags)
{
    auto [argument, argumentPosition] = argumentInfo;
    if (!argument->IsArrowFunctionExpression()) {
        return true;
    }

    auto arrowFuncExpr = argument->AsArrowFunctionExpression();
    bool typeValid = true;
    ir::ScriptFunction *const lambda = arrowFuncExpr->Function();
    // Note: (Issue27688) if lambda is trailing lambda transferred, it must be in recheck.
    // its type was cleared before the check, so here we need recheck it.
    if (!checker->NeedTypeInference(lambda) && !lambda->IsTrailingLambda()) {
        return typeValid;
    }

    arrowFuncExpr->SetTsType(nullptr);
    arrowFuncExpr->SetPreferredType(nullptr);
    auto *const param =
        signature->GetSignatureInfo()->params[paramPosition]->Declaration()->Node()->AsETSParameterExpression();
    Type *const parameterType = signature->Params()[paramPosition]->TsType();

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    bool rc = CheckLambdaTypeAnnotation(checker, param, arrowFuncExpr, parameterType, resolutionFlags);
    if (!rc) {
        Type *const argumentType = arrowFuncExpr->Check(checker);
        checker->LogError(diagnostic::TYPE_MISMATCH_AT_IDX, {argumentType, parameterType, argumentPosition + 1},
                          arrowFuncExpr->Start());
        rc = false;
    } else if ((lambda->Signature() != nullptr) && !lambda->HasReturnStatement() && !lambda->HasThrowStatement()) {
        //  Need to check void return type here if there are no return statement(s) in the body.
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        if (!AssignmentContext(
                 // CC-OFFNXT(G.FMT.06-CPP) project code style
                 checker->Relation(), checker->ProgramAllocNode<ir::Identifier>(checker->ProgramAllocator()),
                 checker->GlobalETSUndefinedType(), lambda->Signature()->ReturnType(), lambda->Start(), std::nullopt,
                 checker::TypeRelationFlag::DIRECT_RETURN)
                 .IsAssignable()) {  // CC-OFF(G.FMT.02-CPP) project code style
            checker->LogError(diagnostic::ARROW_TYPE_MISMATCH,
                              {checker->GlobalETSUndefinedType(), lambda->Signature()->ReturnType()},
                              lambda->Body()->Start());
            rc = false;
        }
    }

    typeValid &= rc;

    return typeValid;
}

static bool TypeInference(ETSChecker *checker, Signature *signature, const ArenaVector<ir::Expression *> &arguments,
                          TypeRelationFlag inferenceFlags)
{
    bool typeConsistent = true;
    auto const argumentCount = arguments.size();
    auto const minArity = std::min(signature->ArgCount(), argumentCount);

    for (size_t idx = 0U; idx < minArity; ++idx) {
        auto const &argument = arguments[idx];

        if (idx == argumentCount - 1 && (inferenceFlags & TypeRelationFlag::NO_CHECK_TRAILING_LAMBDA) != 0) {
            continue;
        }
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        const bool valid = ResolveLambdaArgumentType(checker, signature, idx, {argument, idx}, inferenceFlags);
        typeConsistent &= valid;
    }

    return typeConsistent;
}

void ETSChecker::LogSignatureMismatch(ArenaVector<Signature *> const &signatures,
                                      const ArenaVector<ir::Expression *> &arguments, const lexer::SourcePosition &pos,
                                      std::string_view signatureKind)
{
    if (!arguments.empty() && !signatures.empty()) {
        std::string msg {};
        auto someSignature = signatures[0];

        if (someSignature->HasFunction()) {
            if (someSignature->Function()->IsConstructor()) {
                msg += util::Helpers::GetClassDefinition(someSignature->Function())->InternalName().Utf8();
            } else {
                msg += someSignature->Function()->Id()->Name().Utf8();
            }
        }

        msg += '(';

        for (std::size_t index = 0U; index < arguments.size(); ++index) {
            auto const &argument = arguments[index];
            Type const *const argumentType = argument->Check(this);
            if (argumentType != nullptr && !ContainsTypeError(argumentType)) {
                msg += argumentType->ToString();
            } else {
                //  NOTE (DZ): extra cases for some specific nodes can be added here (as for
                //  'ArrowFunctionExpression')
                msg += argument->ToString();
            }

            if (index == arguments.size() - 1U) {
                msg += ')';
                LogError(diagnostic::NO_MATCHING_SIG, {signatureKind, msg.c_str()}, pos);
                return;
            }

            msg += ", ";
        }
    }

    LogError(diagnostic::NO_MATCHING_SIG_2, {signatureKind}, pos);
}

static bool IsLastParameterLambdaWithReceiver(Signature const *sig)
{
    auto const &params = sig->Function()->Params();

    return !params.empty() && (params.back()->AsETSParameterExpression()->TypeAnnotation() != nullptr) &&
           params.back()->AsETSParameterExpression()->TypeAnnotation()->IsETSFunctionType() &&
           params.back()->AsETSParameterExpression()->TypeAnnotation()->AsETSFunctionType()->IsExtensionFunction();
}

static bool TrailingLambdaTypeInference(ETSChecker *checker, Signature *signature,
                                        const ArenaVector<ir::Expression *> &arguments)
{
    if (arguments.empty() || signature->GetSignatureInfo()->params.empty()) {
        return false;
    }
    ES2PANDA_ASSERT(arguments.back()->IsArrowFunctionExpression());
    const size_t lastParamPos = signature->GetSignatureInfo()->params.size() - 1;
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    return ResolveLambdaArgumentType(checker, signature, lastParamPos, {arguments.back(), arguments.size() - 1},
                                     TypeRelationFlag::NONE);
}

static ArenaVector<ir::Expression *> ExtendArgumentsWithFakeLamda(ETSChecker *checker, ir::CallExpression *callExpr,
                                                                  const ArenaVector<ir::Expression *> &arguments);
static void TransformTraillingLambda(ETSChecker *checker, ir::CallExpression *callExpr, Signature *sig,
                                     const ArenaVector<ir::Expression *> &arguments);
static void EnsureValidCurlyBrace(ETSChecker *checker, ir::CallExpression *callExpr);

Signature *ETSChecker::ResolveConstructExpression(ETSObjectType *type, ir::ETSNewClassInstanceExpression *expr)
{
    const ArenaVector<ir::Expression *> &arguments = expr->GetArguments();
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *var = type->GetProperty(compiler::Signatures::CONSTRUCTOR_NAME, PropertySearchFlags::SEARCH_STATIC_METHOD);
    Signature *sig = nullptr;
    if (var != nullptr && var->TsType()->IsETSFunctionType()) {
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        sig = MatchOrderSignatures(var->TsType()->AsETSFunctionType()->CallSignatures(), arguments, expr,
                                   TypeRelationFlag::NONE, "construct");
    } else {
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        sig = MatchOrderSignatures(type->ConstructSignatures(), arguments, expr, TypeRelationFlag::NONE, "construct");
    }
    return sig;
}

// Note: this function is extracted to reduce the size of `BuildMethodSignature`
static bool CollectOverload(checker::ETSChecker *checker, ir::MethodDefinition *method, ETSFunctionType *funcType)
{
    std::vector<ETSFunctionType *> overloads {};

    for (ir::MethodDefinition *const currentFunc : method->Overloads()) {
        currentFunc->Function()->Id()->SetVariable(currentFunc->Id()->Variable());
        checker->BuildFunctionSignature(currentFunc->Function(), method->IsConstructor());
        if (currentFunc->Function()->Signature() == nullptr) {
            auto *methodId = method->Id();
            ES2PANDA_ASSERT(methodId != nullptr);
            methodId->Variable()->SetTsType(checker->GlobalTypeError());
            return false;
        }

        auto *const overloadType = checker->BuildMethodType(currentFunc->Function());
        checker->CheckIdenticalOverloads(funcType, overloadType, currentFunc);

        if (currentFunc->TsType() == nullptr) {
            currentFunc->SetTsType(overloadType);
        }

        auto overloadSig = currentFunc->Function()->Signature();
        funcType->AddCallSignature(overloadSig);
        overloads.push_back(overloadType);
    }
    return true;
}

checker::Type *ETSChecker::BuildMethodSignature(ir::MethodDefinition *method)
{
    if (method->TsType() != nullptr) {
        return method->TsType();
    }
    auto *methodId = method->Id();
    method->Function()->Id()->SetVariable(methodId->Variable());
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    BuildFunctionSignature(method->Function(), method->IsConstructor());
    if (method->Function()->Signature() == nullptr) {
        return methodId->Variable()->SetTsType(GlobalTypeError());
    }
    auto *funcType = BuildMethodType(method->Function());
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    if (!CollectOverload(this, method, funcType)) {
        return GlobalTypeError();
    }

    return methodId->Variable()->SetTsType(funcType);
}

static bool CheckRestParamOverload(Signature *funcSig, Signature *overloadSig, TypeRelation *relation)
{
    if (std::abs(static_cast<int32_t>(funcSig->ArgCount() - overloadSig->ArgCount())) != 1) {
        return true;
    }
    if (!relation->NoReturnTypeCheck() && !relation->IsIdenticalTo(funcSig->ReturnType(), overloadSig->ReturnType())) {
        return true;
    }

    for (size_t idx = 0; idx < std::min(funcSig->ArgCount(), overloadSig->ArgCount()); ++idx) {
        if (!relation->IsIdenticalTo(funcSig->Params()[idx]->TsType(), overloadSig->Params()[idx]->TsType())) {
            return true;
        }
    }

    auto isLastParamIdentical = [relation](Signature *withRest, Signature *withArray) {
        if (withArray->Params().empty()) {
            return false;
        }
        auto *lastParamType = withArray->Params().back()->TsType();
        bool isArray = lastParamType->IsETSArrayType() || lastParamType->IsETSResizableArrayType();
        return isArray && relation->IsIdenticalTo(lastParamType, withRest->RestVar()->TsType());
    };
    if ((funcSig->HasRestParameter() && isLastParamIdentical(funcSig, overloadSig)) ||
        (overloadSig->HasRestParameter() && isLastParamIdentical(overloadSig, funcSig))) {
        return false;  // NOLINT(readability-simplify-boolean-expr)
    }
    return true;
}

bool ETSChecker::CheckIdenticalOverloads(ETSFunctionType *func, ETSFunctionType *overload,
                                         const ir::MethodDefinition *const currentFunc, bool omitSameAsm,
                                         TypeRelationFlag relationFlags)
{
    // Don't necessary to check overload for invalid functions
    if (func->Name().Is(ERROR_LITERAL)) {
        ES2PANDA_ASSERT(IsAnyError());
        return false;
    }

    SavedTypeRelationFlagsContext savedFlagsCtx(Relation(), relationFlags);

    auto *funcSig = func->CallSignatures()[0];
    auto *overloadSig = overload->CallSignatures()[0];
    Relation()->SignatureIsIdenticalTo(funcSig, overloadSig);
    if (Relation()->IsTrue() && funcSig->GetSignatureInfo()->restVar == overloadSig->GetSignatureInfo()->restVar) {
        LogError(diagnostic::FUNCTION_REDECL_BY_TYPE_SIG, {overload->Name().Utf8()}, currentFunc->Start());
        return false;
    }

    if (funcSig->HasRestParameter() != overloadSig->HasRestParameter() &&
        !CheckRestParamOverload(funcSig, overloadSig, Relation())) {
        LogError(diagnostic::FUNCTION_REDECL_BY_TYPE_SIG, {overload->Name().Utf8()}, currentFunc->Start());
        return false;
    }

    bool const returnTypeCheck = (relationFlags & TypeRelationFlag::NO_RETURN_TYPE_CHECK) ==
                                 static_cast<std::underlying_type_t<TypeRelationFlag>>(0U);
    if (!HasSameAssemblySignatures(func, overload, returnTypeCheck)) {
        return false;
    }

    if (!omitSameAsm) {
        LogError(diagnostic::FUNCTION_REDECL_BY_ASM_SIG, {func->Name().Utf8()}, currentFunc->Start());
        return false;
    }

    return true;
}

Signature *ETSChecker::ComposeSignature(ir::ScriptFunction *func, SignatureInfo *signatureInfo, Type *returnType,
                                        varbinder::Variable *nameVar)
{
    auto *signature = CreateSignature(signatureInfo, returnType, func);
    if (signature == nullptr) {  // #23134
        ES2PANDA_ASSERT(IsAnyError());
        return nullptr;
    }
    signature->SetOwner(Context().ContainingClass());
    signature->SetOwnerVar(nameVar);

    const auto *returnTypeAnnotation = func->ReturnTypeAnnotation();
    if (returnTypeAnnotation == nullptr && func->HasReturnStatement()) {
        signature->AddSignatureFlag(SignatureFlags::NEED_RETURN_TYPE);
    }

    if (returnTypeAnnotation != nullptr && returnTypeAnnotation->IsTSThisType()) {
        // #22951: the original signature retains the arbitrary this type
        // (sometimes ETSGLOBAL). should be resolved woth proper `this` functions support
        signature->AddSignatureFlag(SignatureFlags::THIS_RETURN_TYPE);
    }

    if (signature->Owner() != nullptr && signature->Owner()->GetDeclNode()->IsFinal()) {
        signature->AddSignatureFlag(SignatureFlags::FINAL);
    }
    return signature;
}

Type *ETSChecker::ComposeReturnType(ir::TypeNode *typeAnnotation, bool isAsync)
{
    if (typeAnnotation != nullptr) {
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        return GetTypeFromTypeAnnotation(typeAnnotation);
    }
    return isAsync ? CreatePromiseOf(GlobalETSUndefinedType()) : GlobalETSUndefinedType();
}

static varbinder::LocalVariable *SetupSignatureParameter(ir::ETSParameterExpression *param, Type *type)
{
    auto *const variable = param->Ident()->Variable();  // #23134
    if (variable == nullptr) {
        return nullptr;
    }
    param->Ident()->SetTsType(type);
    variable->SetTsType(type);
    return variable->AsLocalVariable();
}

// Should be moved to original ComposeSignatureInfo after AST fix
static bool AppendSignatureInfoParam(ETSChecker *checker, SignatureInfo *sigInfo, ir::ETSParameterExpression *param)
{
    auto variable = SetupSignatureParameter(param, [checker, param]() {
        if (param->TypeAnnotation() != nullptr) {
            auto type = checker->GetTypeFromTypeAnnotation(param->TypeAnnotation());
            return param->IsOptional() ? checker->CreateETSUnionType({type, checker->GlobalETSUndefinedType()}) : type;
        }
        if (param->Ident()->TsType() != nullptr) {
            return param->Ident()->TsType();
        }

        if (!param->Ident()->IsErrorPlaceHolder() && !checker->HasStatus(checker::CheckerStatus::IN_TYPE_INFER)) {
            checker->LogError(diagnostic::INFER_FAILURE_FUNC_PARAM, {param->Ident()->Name()}, param->Start());
        }

        return checker->GlobalTypeError();
    }());
    if (variable == nullptr) {  // #23134
        return false;
    }
    if (param->IsRestParameter()) {
        return true;
    }

    sigInfo->params.push_back(variable);
    if (!param->IsOptional()) {
        ++sigInfo->minArgCount;
    }
    ERROR_SANITY_CHECK(
        checker,
        !param->IsOptional() || param->Ident()->TsType()->IsTypeError() ||
            checker->Relation()->IsSupertypeOf(param->Ident()->TsType(), checker->GlobalETSUndefinedType()),
        return false);
    return true;
}

static void InitializeSignatureTypeParameters(ETSChecker *checker, SignatureInfo *signatureInfo,
                                              ir::TSTypeParameterDeclaration *typeParams)
{
    if (typeParams == nullptr) {
        return;
    }

    auto [typeParamTypes, ok] = checker->CreateUnconstrainedTypeParameters(typeParams);
    ES2PANDA_ASSERT(signatureInfo != nullptr);
    signatureInfo->typeParams = std::move(typeParamTypes);
    if (ok) {
        ok = checker->PrecheckTypeParameterConstraintCycles(typeParams);
    }
    if (ok) {
        checker->AssignTypeParameterConstraints(typeParams);
    }
}

static bool AppendSignatureInfoParams(ETSChecker *checker, SignatureInfo *signatureInfo,
                                      ArenaVector<ir::Expression *> const &params)
{
    for (auto *const paramExpr : params) {
        if (!paramExpr->IsETSParameterExpression()) {
            ES2PANDA_ASSERT(checker->IsAnyError());
            return false;
        }

        auto *const param = paramExpr->AsETSParameterExpression();
        checker->CheckAnnotations(param);
        if (!AppendSignatureInfoParam(checker, signatureInfo, param)) {  // #23134
            ES2PANDA_ASSERT(checker->IsAnyError());
            return false;
        }
    }

    return true;
}

static Type *ResolveRestParameterType(ETSChecker *checker, ir::ETSParameterExpression *param)
{
    Type *paramType = nullptr;
    if (param->TypeAnnotation() != nullptr) {
        paramType = checker->GetTypeFromTypeAnnotation(param->RestParameter()->TypeAnnotation());
    } else if (param->Ident()->TsType() != nullptr) {
        paramType = param->Ident()->TsType();
    }

    if (paramType == nullptr) {
        ES2PANDA_ASSERT(checker->IsAnyError());  // #23134
    } else if (paramType->IsETSTypeParameter()) {
        paramType = paramType->AsETSTypeParameter()->GetConstraintType();
    }

    return paramType;
}

static bool FinalizeRestParameter(ETSChecker *checker, SignatureInfo *signatureInfo,
                                  ArenaVector<ir::Expression *> const &params)
{
    auto *const lastParam = params.empty() ? nullptr : params.back()->AsETSParameterExpression();
    if (lastParam == nullptr || !lastParam->IsRestParameter()) {
        return true;
    }

    Type *restParamType = ResolveRestParameterType(checker, lastParam);
    if (restParamType == nullptr) {
        return false;
    }

    ES2PANDA_ASSERT(restParamType != nullptr);
    if (!restParamType->IsAnyETSArrayOrTupleType()) {
        checker->LogError(diagnostic::ONLY_ARRAY_OR_TUPLE_FOR_REST, {}, lastParam->Start());
        restParamType = checker->GlobalTypeError();
    }

    signatureInfo->restVar = SetupSignatureParameter(lastParam, restParamType);
    ES2PANDA_ASSERT(signatureInfo->restVar != nullptr);

    size_t nOpt =
        std::count_if(signatureInfo->params.begin(), signatureInfo->params.end(), [](varbinder::LocalVariable *var) {
            return var->Declaration()->Node()->AsETSParameterExpression()->IsOptional();
        });
    if (signatureInfo->restVar->TsType()->IsETSTupleType()) {
        signatureInfo->minArgCount += nOpt;
    }

    return true;
}

SignatureInfo *ETSChecker::ComposeSignatureInfo(ir::TSTypeParameterDeclaration *typeParams,
                                                ArenaVector<ir::Expression *> const &params)
{
    auto *const signatureInfo = CreateSignatureInfo();
    InitializeSignatureTypeParameters(this, signatureInfo, typeParams);

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    if (!AppendSignatureInfoParams(this, signatureInfo, params)) {
        return nullptr;
    }

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    if (!FinalizeRestParameter(this, signatureInfo, params)) {
        return nullptr;
    }

    return signatureInfo;
}

static void ValidateMainSignature(ETSChecker *checker, ir::ScriptFunction *func)
{
    if (func->Params().size() >= 2U) {
        checker->LogError(diagnostic::MAIN_INVALID_ARG_COUNT, {}, func->Start());
        return;
    }

    if (func->Params().size() == 1) {
        auto const *const param = func->Params()[0]->AsETSParameterExpression();

        if (param->IsRestParameter()) {
            checker->LogError(diagnostic::MAIN_WITH_REST, {}, param->Start());
        }

        const auto paramType = param->Variable()->TsType();
        if (!paramType->IsETSArrayType() || !paramType->AsETSArrayType()->ElementType()->IsETSStringType()) {
            checker->LogError(diagnostic::MAIN_PARAM_NOT_ARR_OF_STRING, {}, param->Start());
        }
    }
}

void ETSChecker::BuildFunctionSignature(ir::ScriptFunction *func, bool isConstructSig)
{
    ES2PANDA_ASSERT(func != nullptr);
    bool isArrow = func->IsArrow();
    // note(Ekko): For extenal function overload, need to not change ast tree, for arrow type, need perferred type.
    if (func->Signature() != nullptr && !isArrow) {
        return;
    }
    auto *nameVar = isArrow ? nullptr : func->Id()->Variable();
    auto funcName = nameVar == nullptr ? util::StringView() : nameVar->Name();

    if (func->IsConstructor() && func->IsStatic()) {
        LogError(diagnostic::INVALID_DECORATOR_CONSTRUCTOR, {}, func->Start());
        return;
    }

    if ((func->IsConstructor() || !func->IsStatic()) && !func->IsArrow()) {
        if (!func->Scope()->ParamScope()->Params().empty()) {
            func->Scope()->ParamScope()->Params().front()->SetTsType(Context().ContainingClass());
        }
    }
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *signatureInfo = ComposeSignatureInfo(func->TypeParams(), func->Params());

    Type *returnType = func->GetPreferredReturnType() != nullptr
                           ? func->GetPreferredReturnType()
                           // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
                           : ComposeReturnType(func->ReturnTypeAnnotation(), func->IsAsyncFunc());

    auto *signature = ComposeSignature(func, signatureInfo, returnType, nameVar);
    if (signature == nullptr) {  // #23134
        ES2PANDA_ASSERT(IsAnyError());
        return;
    }

    func->SetSignature(signature);

    if (isConstructSig) {
        signature->AddSignatureFlag(SignatureFlags::CONSTRUCT);
    } else {
        signature->AddSignatureFlag(SignatureFlags::CALL);
    }

    if (funcName.Is(compiler::Signatures::MAIN) &&
        func->Scope()->Name().Utf8().find(compiler::Signatures::ETS_GLOBAL) != std::string::npos) {
        func->AddFlag(ir::ScriptFunctionFlags::ENTRY_POINT);
    }
    if (func->IsEntryPoint()) {
        ValidateMainSignature(this, func);
    }

    VarBinder()->AsETSBinder()->BuildFunctionName(func);
}

checker::ETSFunctionType *ETSChecker::BuildMethodType(ir::ScriptFunction *func)
{
    ES2PANDA_ASSERT(!func->IsArrow());
    ES2PANDA_ASSERT(func != nullptr);
    auto *ident = func->Id();
    ETSFunctionType *funcType =
        CreateETSMethodType(ident->Name(), {{func->Signature()}, ProgramAllocator()->Adapter()});
    funcType->SetVariable(ident->Variable());
    return funcType;
}

static bool IsOverridableIn(Signature *signature)
{
    if (signature->HasSignatureFlag(SignatureFlags::PRIVATE)) {
        return false;
    }

    if (signature->HasSignatureFlag(SignatureFlags::PUBLIC)) {
        return true;
    }

    return signature->HasSignatureFlag(SignatureFlags::PROTECTED);
}

static bool IsMethodOverridesOther(ETSChecker *checker, Signature *base, Signature *derived)
{
    if (derived->Function()->IsConstructor()) {
        return false;
    }

    if (base == derived) {
        return true;
    }

    if (derived->HasSignatureFlag(SignatureFlags::STATIC) != base->HasSignatureFlag(SignatureFlags::STATIC)) {
        return false;
    }

    if (IsOverridableIn(base)) {
        SavedTypeRelationFlagsContext savedFlagsCtx(checker->Relation(), TypeRelationFlag::NO_RETURN_TYPE_CHECK |
                                                                             TypeRelationFlag::OVERRIDING_CONTEXT);
        if (checker->Relation()->SignatureIsSupertypeOf(base, derived)) {
            if (derived->HasSignatureFlag(SignatureFlags::STATIC)) {
                return false;
            }

            derived->Function()->SetOverride();
            return true;
        }
    }

    return false;
}

enum class OverrideErrorCode {
    NO_ERROR,
    OVERRIDDEN_FINAL,
    INCOMPATIBLE_RETURN,
    INCOMPATIBLE_TYPEPARAM,
    OVERRIDDEN_WEAKER,
};

static OverrideErrorCode CheckOverride(ETSChecker *checker, Signature *signature, Signature *other)
{
    if (other->HasSignatureFlag(SignatureFlags::STATIC)) {
        ES2PANDA_ASSERT(signature->HasSignatureFlag(SignatureFlags::STATIC));
        return OverrideErrorCode::NO_ERROR;
    }

    if (other->IsFinal()) {
        return OverrideErrorCode::OVERRIDDEN_FINAL;
    }
    if (signature->ProtectionFlag() > other->ProtectionFlag()) {
        return OverrideErrorCode::OVERRIDDEN_WEAKER;
    }

    // Return type substitutability is checked via the type relation system.
    // Any is handled correctly: it is only a subtype of itself.
    if (!checker->IsReturnTypeSubstitutable(signature, other)) {
        return OverrideErrorCode::INCOMPATIBLE_RETURN;
    }

    return OverrideErrorCode::NO_ERROR;
}

Signature *ETSChecker::AdjustForTypeParameters(Signature *source, Signature *target)
{
    return AdjustForTypeParameters(source, target, Relation());
}

Signature *ETSChecker::AdjustForTypeParameters(Signature *source, Signature *target, TypeRelation *relation)
{
    auto &sourceTypeParams = source->GetSignatureInfo()->typeParams;
    auto &targetTypeParams = target->GetSignatureInfo()->typeParams;
    if (sourceTypeParams.size() != targetTypeParams.size()) {
        return nullptr;
    }
    if (sourceTypeParams.empty()) {
        return target;
    }
    auto substitution = Substitution {};
    for (size_t ix = 0; ix < sourceTypeParams.size(); ix++) {
        if (!targetTypeParams[ix]->IsETSTypeParameter()) {
            continue;
        }
        EmplaceSubstituted(&substitution, targetTypeParams[ix]->AsETSTypeParameter(), sourceTypeParams[ix]);
    }
    return target->Substitute(relation, &substitution);
}

static void ReportOverrideError(ETSChecker *checker, Signature *signature, Signature *overriddenSignature,
                                const OverrideErrorCode &errorCode)
{
    switch (errorCode) {
        case OverrideErrorCode::OVERRIDDEN_FINAL: {
            checker->LogError(diagnostic::CANNOT_OVERRIDE_OVERRIDDEN_FINAL,
                              {signature->Function()->Id()->Name(), signature->ReturnType(), signature->Owner(),
                               overriddenSignature->Function()->Id()->Name(), overriddenSignature->ReturnType(),
                               overriddenSignature->Owner()},
                              signature->Function()->Start());
            return;
        }
        case OverrideErrorCode::INCOMPATIBLE_RETURN: {
            if (overriddenSignature->Owner()->HasObjectFlag(ETSObjectFlags::INTERFACE)) {
                checker->LogError(diagnostic::CANNOT_IMPLEMENT_INCOMPATIBLE_RETURN,
                                  {signature->Function()->Id()->Name(), signature->ReturnType(), signature->Owner(),
                                   overriddenSignature->Function()->Id()->Name(), overriddenSignature->ReturnType(),
                                   overriddenSignature->Owner()},
                                  signature->Function()->Start());
                return;
            }
            checker->LogError(diagnostic::CANNOT_OVERRIDE_INCOMPATIBLE_RETURN,
                              {signature->Function()->Id()->Name(), signature->ReturnType(), signature->Owner(),
                               overriddenSignature->Function()->Id()->Name(), overriddenSignature->ReturnType(),
                               overriddenSignature->Owner()},
                              signature->Function()->Start());
            return;
        }
        case OverrideErrorCode::OVERRIDDEN_WEAKER: {
            checker->LogError(diagnostic::CANNOT_OVERRIDE_OVERRIDDEN_WEAKER,
                              {signature->Function()->Id()->Name(), signature->ReturnType(), signature->Owner(),
                               overriddenSignature->Function()->Id()->Name(), overriddenSignature->ReturnType(),
                               overriddenSignature->Owner()},
                              signature->Function()->Start());
            return;
        }
        case OverrideErrorCode::INCOMPATIBLE_TYPEPARAM: {
            checker->LogError(diagnostic::CANNOT_OVERRIDE_INCOMPATIBLE_TYPEPARAM,
                              {signature->Function()->Id()->Name(), signature->ReturnType(), signature->Owner(),
                               overriddenSignature->Function()->Id()->Name(), overriddenSignature->ReturnType(),
                               overriddenSignature->Owner()},
                              signature->Function()->Start());
            return;
        }
        default: {
            ES2PANDA_UNREACHABLE();
        }
    }
}

static bool CheckOverride(ETSChecker *checker, Signature *signature, ETSObjectType *site)
{
    PropertySearchFlags flags =
        PropertySearchFlags::SEARCH_METHOD | PropertySearchFlags::DISALLOW_SYNTHETIC_METHOD_CREATION;
    auto *target = site->GetProperty(signature->Function()->Id()->Name(), flags);
    bool isOverridingAnySignature = false;

    if (target == nullptr || target->TsType() == nullptr || target->TsType()->IsTypeError()) {
        return isOverridingAnySignature;
    }

    for (auto *it : target->TsType()->AsETSFunctionType()->CallSignatures()) {
        bool typeParamError = false;
        if (!checker->Relation()->CheckTypeParameterConstraints(signature->TypeParams(), it->TypeParams())) {
            typeParamError = true;
        }

        auto *itSubst = checker->AdjustForTypeParameters(signature, it);

        if (itSubst == nullptr) {
            continue;
        }

        if (itSubst->HasSignatureFlag(SignatureFlags::ABSTRACT) || site->HasObjectFlag(ETSObjectFlags::INTERFACE)) {
            if ((itSubst->Function()->IsSetter() && !signature->Function()->IsSetter()) ||
                (itSubst->Function()->IsGetter() && !signature->Function()->IsGetter())) {
                continue;
            }
        }

        if (!IsMethodOverridesOther(checker, itSubst, signature)) {
            continue;
        }

        if (typeParamError) {
            ReportOverrideError(checker, signature, it, OverrideErrorCode::INCOMPATIBLE_TYPEPARAM);
            return false;
        }

        if (auto err = CheckOverride(checker, signature, itSubst); err != OverrideErrorCode::NO_ERROR) {
            ReportOverrideError(checker, signature, it, err);
            return false;
        }

        isOverridingAnySignature = true;
    }

    return isOverridingAnySignature;
}

static bool CheckInterfaceOverride(ETSChecker *const checker, ETSObjectType *const interface,
                                   Signature *const signature)
{
    bool isOverriding = CheckOverride(checker, signature, interface);

    for (auto *const superInterface : interface->Interfaces()) {
        isOverriding |= CheckInterfaceOverride(checker, superInterface, signature);
    }

    return isOverriding;
}

static bool HasMultipleOverridesOfSameSuperInterfaceMethod(ETSChecker *const checker, ETSObjectType *const interface,
                                                           Signature *const first, Signature *const second,
                                                           Signature **const conflictingSuperMethod)
{
    PropertySearchFlags const flags =
        PropertySearchFlags::SEARCH_METHOD | PropertySearchFlags::DISALLOW_SYNTHETIC_METHOD_CREATION;
    auto *const target = interface->GetProperty(first->Function()->Id()->Name(), flags);
    if (target == nullptr || target->TsType() == nullptr || target->TsType()->IsTypeError()) {
        for (auto *const superInterface : interface->Interfaces()) {
            if (HasMultipleOverridesOfSameSuperInterfaceMethod(checker, superInterface, first, second,
                                                               conflictingSuperMethod)) {
                return true;
            }
        }
        return false;
    }

    for (auto *const base : target->TsType()->AsETSFunctionType()->CallSignatures()) {
        auto *const firstSubst = checker->AdjustForTypeParameters(first, base);
        auto *const secondSubst = checker->AdjustForTypeParameters(second, base);
        if (firstSubst == nullptr || secondSubst == nullptr) {
            continue;
        }

        if (IsMethodOverridesOther(checker, firstSubst, first) &&
            IsMethodOverridesOther(checker, secondSubst, second)) {
            if (conflictingSuperMethod != nullptr) {
                *conflictingSuperMethod = base;
            }
            return true;
        }
    }

    for (auto *const superInterface : interface->Interfaces()) {
        if (HasMultipleOverridesOfSameSuperInterfaceMethod(checker, superInterface, first, second,
                                                           conflictingSuperMethod)) {
            return true;
        }
    }
    return false;
}

static bool ShouldSkipOverrideConflictCandidate(Signature *const current, Signature *const other)
{
    auto const currentPos = current->Function()->Start();
    auto const otherPos = other->Function()->Start();
    auto const sameProgram = currentPos.Program() == otherPos.Program();
    auto const hasPositionInfo = currentPos.line != 0 && otherPos.line != 0;
    if (sameProgram && hasPositionInfo) {
        return (currentPos.line < otherPos.line) ||
               (currentPos.line == otherPos.line && currentPos.index <= otherPos.index);
    }
    return other < current;
}

static Signature *FindConflictingSuperInterfaceMethod(ETSChecker *const checker, ETSObjectType *const owner,
                                                      Signature *const signature, Signature *const other)
{
    Signature *conflictingSuperMethod = nullptr;
    for (auto *const superInterface : owner->Interfaces()) {
        if (HasMultipleOverridesOfSameSuperInterfaceMethod(checker, superInterface, signature, other,
                                                           &conflictingSuperMethod)) {
            return conflictingSuperMethod != nullptr ? conflictingSuperMethod : other;
        }
    }
    return nullptr;
}

static void ReportMultipleInterfaceOverrideConflict(ETSChecker *const checker, Signature *const signature,
                                                    Signature *const other, Signature *const conflictingSuperMethod)
{
    checker->LogError(diagnostic::CONFLICTING_INTERFACE_OVERRIDE_OF_SAME_SUPER_METHOD,
                      {signature->Function()->Id()->Name(), signature, signature->Owner(), signature->Owner(),
                       other->Function()->Id()->Name(), other, other->Owner(),
                       conflictingSuperMethod->Function()->Id()->Name(), conflictingSuperMethod,
                       conflictingSuperMethod->Owner()},
                      signature->Function()->Start());
}

static bool CheckMultipleInterfaceOverrides(ETSChecker *const checker, Signature *const signature)
{
    auto *const owner = signature->Owner();
    ES2PANDA_ASSERT(owner != nullptr);
    if (!owner->HasObjectFlag(ETSObjectFlags::INTERFACE) || owner->Interfaces().empty()) {
        return false;
    }

    PropertySearchFlags const flags =
        PropertySearchFlags::SEARCH_METHOD | PropertySearchFlags::DISALLOW_SYNTHETIC_METHOD_CREATION;
    auto *const target = owner->GetProperty(signature->Function()->Id()->Name(), flags);
    if (target == nullptr || target->TsType() == nullptr || target->TsType()->IsTypeError()) {
        return false;
    }

    for (auto *const other : target->TsType()->AsETSFunctionType()->CallSignatures()) {
        if (other == signature || other->Function()->IsStatic() != signature->Function()->IsStatic()) {
            continue;
        }
        // Report only on the second declaration.
        if (ShouldSkipOverrideConflictCandidate(signature, other)) {
            continue;
        }

        auto *const conflictingSuperMethod = FindConflictingSuperInterfaceMethod(checker, owner, signature, other);
        if (conflictingSuperMethod != nullptr) {
            ReportMultipleInterfaceOverrideConflict(checker, signature, other, conflictingSuperMethod);
            return true;
        }
    }

    return false;
}

void ETSChecker::CheckOverride(Signature *signature)
{
    ES2PANDA_ASSERT(signature != nullptr);
    auto *owner = signature->Owner();
    ES2PANDA_ASSERT(owner != nullptr);
    bool isOverriding = false;

    if (!owner->HasObjectFlag(ETSObjectFlags::CLASS | ETSObjectFlags::INTERFACE)) {
        return;
    }

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    if (CheckMultipleInterfaceOverrides(this, signature)) {
        return;
    }

    for (auto *const interface : owner->Interfaces()) {
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        isOverriding |= CheckInterfaceOverride(this, interface, signature);
    }

    ETSObjectType *iter = owner->SuperType();
    while (iter != nullptr) {
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        isOverriding |= checker::CheckOverride(this, signature, iter);

        for (auto *const interface : iter->Interfaces()) {
            // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
            isOverriding |= CheckInterfaceOverride(this, interface, signature);
        }

        iter = iter->SuperType();
    }
    lexer::SourcePosition ownerPos = signature->Owner()->GetDeclNode()->Start();
    lexer::SourcePosition signaturePos = signature->Function()->Start();
    lexer::SourcePosition pos = signaturePos.line == 0 && signaturePos.index == 0 ? ownerPos : signaturePos;
    if (!isOverriding && signature->Function()->IsOverride()) {
        LogError(diagnostic::OVERRIDE_DOESNT_OVERRIDE,
                 {signature->Function()->Id()->Name(), signature, signature->Owner()}, pos);
    }
}

Signature *ETSChecker::GetSignatureFromMethodDefinition(const ir::MethodDefinition *methodDef)
{
    if (methodDef->TsType()->IsTypeError()) {
        return nullptr;
    }
    ES2PANDA_ASSERT_POS(methodDef->TsType() && methodDef->TsType()->IsETSFunctionType(), methodDef->Start());
    for (auto *it : methodDef->TsType()->AsETSFunctionType()->CallSignatures()) {
        if (it->Function() == methodDef->Function()) {
            return it;
        }
    }

    return nullptr;
}

static bool NeedToVerifySignatureVisibility(ETSChecker *checker, Signature *signature, const lexer::SourcePosition &pos)
{
    if (signature == nullptr) {
        checker->LogError(diagnostic::SIG_UNAVAILABLE, {}, pos);
        return false;
    }

    return (checker->Context().Status() & CheckerStatus::IGNORE_VISIBILITY) == 0U &&
           (signature->HasSignatureFlag(SignatureFlags::PRIVATE) ||
            signature->HasSignatureFlag(SignatureFlags::PROTECTED));
}

void ETSChecker::ValidateSignatureAccessibility(ETSObjectType *callee, Signature *signature,
                                                const lexer::SourcePosition &pos,
                                                const MaybeDiagnosticInfo &maybeErrorInfo)
{
    if (!NeedToVerifySignatureVisibility(this, signature, pos)) {
        return;
    }
    const auto *declNode = callee->GetDeclNode();
    auto *containingClass = Context().ContainingClass();
    bool isContainingSignatureInherited = containingClass->IsSignatureInherited(signature);
    ES2PANDA_ASSERT(declNode && (declNode->IsClassDefinition() || declNode->IsTSInterfaceDeclaration()));

    if (declNode->IsTSInterfaceDeclaration()) {
        if (containingClass == declNode->AsTSInterfaceDeclaration()->TsType() && isContainingSignatureInherited) {
            return;
        }
    }
    if (containingClass == declNode->AsClassDefinition()->TsType() && isContainingSignatureInherited) {
        return;
    }

    bool isSignatureInherited = callee->IsSignatureInherited(signature);
    const auto *currentOutermost = containingClass->OutermostClass();
    if (!signature->HasSignatureFlag(SignatureFlags::PRIVATE) &&
        ((signature->HasSignatureFlag(SignatureFlags::PROTECTED) &&
          // CC-OFFNXT(G.FMT.02-CPP) project code style
          (containingClass->IsDescendantOf(callee) || callee->IsDescendantOf(containingClass))) ||
         // CC-OFFNXT(G.FMT.02-CPP) project code style
         (currentOutermost != nullptr && currentOutermost == callee->OutermostClass())) &&
        isSignatureInherited) {
        return;
    }

    // Allow static methods to access private members through derived class instances
    if (signature->HasSignatureFlag(SignatureFlags::PRIVATE)) {
        auto *signatureOwner = signature->Owner();
        if (signatureOwner == containingClass && callee->IsDescendantOf(containingClass)) {
            auto *containingSignature = Context().ContainingSignature();
            if (containingSignature != nullptr && containingSignature->Function()->IsStatic()) {
                return;
            }
        }
    }

    if (!maybeErrorInfo.has_value()) {
        LogError(diagnostic::SIG_INVISIBLE, {signature->Function()->Id()->Name(), signature}, pos);
        return;
    }
    const auto [diagnostic, diagnosticParams] = *maybeErrorInfo;
    LogError(diagnostic, diagnosticParams, pos);
}

bool ETSChecker::IsReturnTypeSubstitutable(Signature *const s1, Signature *const s2)
{
    if (s2->HasSignatureFlag(checker::SignatureFlags::NEED_RETURN_TYPE)) {
        s2->Function()->Parent()->Parent()->Check(this);
    }
    auto *const r1 = s1->ReturnType();
    auto *const r2 = s2->ReturnType();

    // A method declaration d1 with return type R1 is return-type-substitutable for another method d2 with return
    // type R2 if any of the following is true:

    // - If R1 is a primitive type then R2 is identical to R1.
    if (r1->IsETSPrimitiveType() || r2->IsETSPrimitiveType()) {
        return Relation()->IsIdenticalTo(r2, r1);
    }

    auto const hasThisReturnType = [](Signature *s) {
        auto *retAnn = s->Function()->ReturnTypeAnnotation();
        return retAnn != nullptr && retAnn->IsTSThisType();
    };
    // - If S2 is a 'this' type(polymorphic) and S1 must be also 'this'
    // If the overridden method (s2) has a 'this' return type, then the overriding method (s1) must also have it.
    bool s1HasThisType = hasThisReturnType(s1);
    bool s2HasThisType = hasThisReturnType(s2);
    if (!s1HasThisType && s2HasThisType) {
        return false;
    }

    // - If R1 is a reference type then R1, adapted to the type parameters of d2 (link to generic methods), is a
    //   subtype of R2.
    ES2PANDA_ASSERT(IsReferenceType(r1));
    return Relation()->IsSupertypeOf(r2, r1);
}

std::string ETSChecker::GetAsyncImplName(const util::StringView &name)
{
    std::string newName =
        util::NameMangler::GetInstance()->CreateMangledNameByTypeAndName(util::NameMangler::ASYNC, name);
    return newName;
}

std::string ETSChecker::GetAsyncImplName(ir::MethodDefinition *asyncMethod)
{
    ir::ScriptFunction *scriptFunc = asyncMethod->Function();
    CHECK_NOT_NULL(scriptFunc);
    ir::Identifier *asyncName = scriptFunc->Id();
    ES2PANDA_ASSERT_POS(asyncName != nullptr, asyncMethod->Start());
    return GetAsyncImplName(asyncName->Name());
}

ir::MethodDefinition *ETSChecker::CreateMethod(const util::StringView &name, ir::ModifierFlags modifiers,
                                               ir::ScriptFunctionFlags flags, ArenaVector<ir::Expression *> &&params,
                                               varbinder::FunctionParamScope *paramScope, ir::TypeNode *returnType,
                                               ir::AstNode *body)
{
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *nameId = ProgramAllocNode<ir::Identifier>(name, ProgramAllocator());
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *scope = ProgramAllocator()->New<varbinder::FunctionScope>(ProgramAllocator(), paramScope);
    // clang-format off
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *const func = ProgramAllocNode<ir::ScriptFunction>(
        ProgramAllocator(), ir::ScriptFunction::ScriptFunctionData {
            // CC-OFFNXT(G.FMT.05-CPP) project codestyle clang format off
            body, ir::FunctionSignature(nullptr, std::move(params), returnType), flags, modifiers});
    // clang-format on
    ES2PANDA_ASSERT(func != nullptr);
    func->SetScope(scope);
    func->SetIdent(nameId);
    if (body != nullptr && body->IsBlockStatement()) {
        body->AsBlockStatement()->SetScope(scope);
    }
    ES2PANDA_ASSERT(scope != nullptr);
    scope->BindNode(func);
    paramScope->BindNode(func);
    scope->BindParamScope(paramScope);
    paramScope->BindFunctionScope(scope);

    if (!func->IsStatic()) {
        auto classDef = VarBinder()->GetScope()->AsClassScope()->Node()->AsClassDefinition();
        VarBinder()->AsETSBinder()->AddFunctionThisParam(func);
        func->Scope()->Find(varbinder::VarBinder::MANDATORY_PARAM_THIS).variable->SetTsType(classDef->TsType());
    }

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *funcExpr = ProgramAllocNode<ir::FunctionExpression>(func);
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *nameClone = nameId->Clone(ProgramAllocator(), nullptr);
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *method = util::NodeAllocator::ForceSetParent<ir::MethodDefinition>(
        ProgramAllocator(), ir::MethodDefinitionKind::METHOD, nameClone, funcExpr, modifiers, ProgramAllocator(),
        false);

    return method;
}

varbinder::FunctionParamScope *ETSChecker::CopyParams(
    const ArenaVector<ir::Expression *> &params, ArenaVector<ir::Expression *> &outParams,
    ArenaUnorderedMap<varbinder::Variable *, varbinder::Variable *> *paramVarMap)
{
    auto paramCtx = varbinder::LexicalScope<varbinder::FunctionParamScope>(VarBinder());

    for (auto *const it : params) {
        auto *const paramOld = it->AsETSParameterExpression();
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        auto *typeOld = paramOld->Clone(ProgramAllocator(), paramOld->Parent());
        ES2PANDA_ASSERT(typeOld != nullptr);
        auto *const paramNew = typeOld->AsETSParameterExpression();

        varbinder::Variable *var = VarBinder()->AddParamDecl(paramNew);
        Type *paramType = paramOld->Variable()->TsType();
        var->SetTsType(paramType);
        var->SetScope(paramCtx.GetScope());

        paramNew->SetVariable(var);
        paramNew->SetTsType(paramType);

        if (auto *newTypeAnno = paramNew->TypeAnnotation(); newTypeAnno != nullptr) {
            newTypeAnno->SetTsType(paramOld->TypeAnnotation()->TsType());
            compiler::InitScopesPhaseETS::RunExternalNode(newTypeAnno, VarBinder()->AsETSBinder());
        }

        if (paramVarMap != nullptr) {
            paramVarMap->insert({paramOld->Ident()->Variable(), var});
        }
        outParams.emplace_back(paramNew);
    }

    return paramCtx.GetScope();
}

void ETSChecker::ReplaceScope(ir::AstNode *root, ir::AstNode *oldNode, varbinder::Scope *newScope)
{
    if (root == nullptr) {
        return;
    }

    root->Iterate([this, oldNode, newScope](ir::AstNode *child) {
        auto *scope = NodeScope(child);
        if (scope != nullptr) {
            while (scope->Parent() != nullptr && scope->Parent()->Node() != oldNode) {
                scope = scope->Parent();
            }
            scope->SetParent(newScope);
        } else {
            ReplaceScope(child, oldNode, newScope);
        }
    });
}

static void MoveTrailingBlockToEnclosingBlockStatement(ir::CallExpression *callExpr)
{
    if (callExpr == nullptr) {
        return;
    }

    ir::AstNode *parent = callExpr->Parent();
    ir::AstNode *current = callExpr;
    while (parent != nullptr) {
        if (!parent->IsBlockStatement()) {
            current = parent;
            parent = parent->Parent();
        } else {
            // Collect trailing block, insert it only when block statements traversal ends to avoid order mismatch.
            parent->AsBlockStatement()->AddTrailingBlock(current, callExpr->TrailingBlock());
            callExpr->TrailingBlock()->SetParent(parent);
            callExpr->SetTrailingBlock(nullptr);
            break;
        }
    }
}

static ir::ScriptFunction *CreateLambdaFunction(ETSChecker *checker, ir::BlockStatement *trailingBlock, Signature *sig)
{
    auto *funcParamScope = varbinder::LexicalScope<varbinder::FunctionParamScope>(checker->VarBinder()).GetScope();
    auto paramCtx =
        varbinder::LexicalScope<varbinder::FunctionParamScope>::Enter(checker->VarBinder(), funcParamScope, false);

    auto funcCtx = varbinder::LexicalScope<varbinder::FunctionScope>(checker->VarBinder());
    auto *funcScope = funcCtx.GetScope();
    funcScope->BindParamScope(funcParamScope);
    funcParamScope->BindFunctionScope(funcScope);
    funcParamScope->SetParent(trailingBlock->Scope()->Parent());

    for (auto [_, var] : trailingBlock->Scope()->Bindings()) {
        (void)_;
        if (var->GetScope() == trailingBlock->Scope()) {
            var->SetScope(funcScope);
            funcScope->InsertBinding(var->Name(), var);
        }
    }

    ArenaVector<ir::Expression *> params(checker->ProgramAllocator()->Adapter());
    ir::ScriptFunctionFlags flags = ir::ScriptFunctionFlags::ARROW;
    bool trailingLambdaHasReceiver = false;
    if (IsLastParameterLambdaWithReceiver(sig)) {
        auto *actualLambdaType =
            sig->Function()->Params().back()->AsETSParameterExpression()->TypeAnnotation()->AsETSFunctionType();
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        auto *receiverOfTrailingBlock =
            actualLambdaType->Params()[0]->Clone(checker->ProgramAllocator(), nullptr)->AsExpression();
        auto *receiverVar = receiverOfTrailingBlock->AsETSParameterExpression()->Ident()->Variable();
        auto *receiverVarClone = checker->ProgramAllocator()->New<varbinder::LocalVariable>(receiverVar->Declaration(),
                                                                                            receiverVar->Flags());
        receiverVarClone->SetTsType(receiverVar->TsType());
        receiverVarClone->SetScope(funcParamScope);
        funcScope->InsertBinding(receiverVarClone->Name(), receiverVarClone);
        receiverOfTrailingBlock->AsETSParameterExpression()->Ident()->SetVariable(receiverVarClone);
        params.emplace_back(receiverOfTrailingBlock);
        trailingLambdaHasReceiver = true;
    }
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *funcNode = checker->ProgramAllocNode<ir::ScriptFunction>(
        checker->ProgramAllocator(),
        ir::ScriptFunction::ScriptFunctionData {
            trailingBlock, ir::FunctionSignature(nullptr, std::move(params), nullptr, trailingLambdaHasReceiver),
            flags});
    funcNode->SetScope(funcScope);
    funcScope->BindNode(funcNode);
    funcParamScope->BindNode(funcNode);

    trailingBlock->SetScope(funcScope);

    return funcNode;
}

static void TransformTraillingLambda(ETSChecker *checker, ir::CallExpression *callExpr, Signature *sig,
                                     const ArenaVector<ir::Expression *> &arguments)
{
    auto *trailingBlock = callExpr->TrailingBlock();
    ES2PANDA_ASSERT(trailingBlock != nullptr);

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *funcNode = CreateLambdaFunction(checker, trailingBlock, sig);
    funcNode->AddFlag(ir::ScriptFunctionFlags::TRAILING_LAMBDA);
    checker->ReplaceScope(funcNode->Body(), trailingBlock, funcNode->Scope());
    callExpr->SetTrailingBlock(nullptr);

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *arrowFuncNode = checker->ProgramAllocNode<ir::ArrowFunctionExpression>(funcNode, checker->ProgramAllocator());
    arrowFuncNode->SetRange(trailingBlock->Range());
    arrowFuncNode->SetParent(callExpr);
    // Add the undefined parameter before the trailinglambda parameter if need.
    auto missingArgs = sig->Params().size() - arguments.size();
    for (size_t i = 0; i < missingArgs - 1; ++i) {
        auto undefArg = checker->ProgramAllocator()->New<ir::UndefinedLiteral>();
        ES2PANDA_ASSERT(undefArg != nullptr);
        undefArg->SetTsType(checker->GlobalETSUndefinedType());
        callExpr->Arguments().push_back(undefArg);
        undefArg->SetParent(callExpr);
    }
    callExpr->Arguments().push_back(arrowFuncNode);
}

static ArenaVector<ir::Expression *> ExtendArgumentsWithFakeLamda(ETSChecker *checker, ir::CallExpression *callExpr,
                                                                  const ArenaVector<ir::Expression *> &arguments)
{
    auto funcCtx = varbinder::LexicalScope<varbinder::FunctionScope>(checker->VarBinder());
    auto *funcScope = funcCtx.GetScope();
    ArenaVector<ir::Expression *> params(checker->ProgramAllocator()->Adapter());

    ArenaVector<ir::Statement *> statements(checker->ProgramAllocator()->Adapter());
    auto *body = checker->ProgramAllocNode<ir::BlockStatement>(checker->ProgramAllocator(), std::move(statements));
    ES2PANDA_ASSERT(body != nullptr);
    body->SetScope(funcScope);

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *funcNode = checker->ProgramAllocNode<ir::ScriptFunction>(
        checker->ProgramAllocator(),
        ir::ScriptFunction::ScriptFunctionData {body, ir::FunctionSignature(nullptr, std::move(params), nullptr),
                                                ir::ScriptFunctionFlags::ARROW});
    ES2PANDA_ASSERT(funcNode != nullptr);
    funcNode->SetScope(funcScope);
    funcScope->BindNode(funcNode);
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *arrowFuncNode = checker->ProgramAllocNode<ir::ArrowFunctionExpression>(funcNode, checker->ProgramAllocator());
    ES2PANDA_ASSERT(arrowFuncNode != nullptr);
    arrowFuncNode->SetParent(callExpr);

    ArenaVector<ir::Expression *> fakeArguments = arguments;
    fakeArguments.push_back(arrowFuncNode);
    return fakeArguments;
}

static void EnsureValidCurlyBrace(ETSChecker *checker, ir::CallExpression *callExpr)
{
    if (callExpr->TrailingBlock() == nullptr) {
        return;
    }

    if (callExpr->IsTrailingBlockInNewLine()) {
        MoveTrailingBlockToEnclosingBlockStatement(callExpr);
        return;
    }

    checker->LogError(diagnostic::NO_SUCH_SIG_WITH_TRAILING_LAMBDA, {}, callExpr->Start());
}

ETSObjectType *ETSChecker::GetCachedFunctionalInterface(ir::ETSFunctionType *type)
{
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto hash = GetHashFromFunctionType(type);
    auto it = functionalInterfaceCache_.find(hash);
    if (it == functionalInterfaceCache_.cend()) {
        return nullptr;
    }
    return it->second;
}

void ETSChecker::CacheFunctionalInterface(ir::ETSFunctionType *type, ETSObjectType *ifaceType)
{
    auto hash = GetHashFromFunctionType(type);
    ES2PANDA_ASSERT(functionalInterfaceCache_.find(hash) == functionalInterfaceCache_.cend());
    functionalInterfaceCache_.emplace(hash, ifaceType);
}

void ETSChecker::CollectReturnStatements(ir::AstNode *parent)  // NOTE: remove with #28178
{
    parent->Iterate([this](ir::AstNode *childNode) -> void {
        if (childNode->IsScriptFunction()) {
            return;
        }

        auto scope = Scope();
        if (childNode->IsBlockStatement()) {
            scope = childNode->AsBlockStatement()->Scope();
        }
        checker::ScopeContext scopeCtx(this, scope);

        if (childNode->IsReturnStatement()) {
            ir::ReturnStatement *returnStmt = childNode->AsReturnStatement();
            returnStmt->Check(this);
        }

        CollectReturnStatements(childNode);
    });
}

std::vector<ConstraintCheckRecord> &ETSChecker::PendingConstraintCheckRecords()
{
    return pendingConstraintCheckRecords_;
}

size_t &ETSChecker::ConstraintCheckScopesCount()
{
    return constraintCheckScopesCount_;
}

bool ETSChecker::HasSameAssemblySignature(Signature const *const sig1, Signature const *const sig2,
                                          bool returnTypeCheck) noexcept
{
    if (returnTypeCheck) {
        if (sig1->ReturnType()->ToAssemblerTypeWithRank() != sig2->ReturnType()->ToAssemblerTypeWithRank()) {
            return false;
        }
    }

    if (sig1->ArgCount() != sig2->ArgCount()) {
        return false;
    }

    for (size_t ix = 0U; ix < sig1->Params().size(); ++ix) {
        if (sig1->Params()[ix]->TsType()->ToAssemblerTypeWithRank() !=
            sig2->Params()[ix]->TsType()->ToAssemblerTypeWithRank()) {
            return false;
        }
    }

    auto *rv1 = sig1->RestVar();
    auto *rv2 = sig2->RestVar();
    if (rv1 == nullptr && rv2 == nullptr) {
        return true;
    }
    if (rv1 == nullptr || rv2 == nullptr) {  // exactly one of them is null
        return false;
    }

    return (rv1->TsType()->ToAssemblerTypeWithRank() == rv2->TsType()->ToAssemblerTypeWithRank());
}

bool ETSChecker::HasSameAssemblySignatures(ETSFunctionType const *const func1, ETSFunctionType const *const func2,
                                           bool returnTypeCheck) noexcept
{
    for (auto const *sig1 : func1->CallSignatures()) {
        for (auto const *sig2 : func2->CallSignatures()) {
            if (HasSameAssemblySignature(sig1, sig2, returnTypeCheck)) {
                return true;
            }
        }
    }
    return false;
}

static Signature *ResolveTrailingLambda(ETSChecker *checker, ArenaVector<Signature *> &signatures,
                                        ir::CallExpression *callExpr, const ArenaVector<ir::Expression *> &arguments,
                                        bool reportError);

checker::Signature *FirstMatchSignatures(ETSChecker *checker, ArenaVector<Signature *> &signatures,
                                         ir::CallExpression *expr)
{
    return FirstMatchSignaturesWithArguments(checker, signatures, expr->Arguments(), expr);
}

checker::Signature *FirstMatchSignaturesWithArguments(ETSChecker *checker, ArenaVector<Signature *> &signatures,
                                                      const ArenaVector<ir::Expression *> &arguments,
                                                      ir::CallExpression *expr, bool reportError)
{
    if (expr->TrailingBlock() != nullptr) {
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        return ResolveTrailingLambda(checker, signatures, expr, arguments, reportError);
    }

    auto typeRelationFlag =
        checker->IsOverloadDeclaration(expr->Callee()) ? TypeRelationFlag::OVERLOADING_CONTEXT : TypeRelationFlag::NONE;

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto *signature =
        checker->MatchOrderSignatures(signatures, arguments, expr, typeRelationFlag, reportError ? "call" : "");
    signature = CheckDerivedStaticMethodCall(checker, expr, signature);
    if (signature == nullptr) {
        return nullptr;
    }

    UpdateDeclarationFromSignature(checker, expr, signature);
    return signature;
}

static void CleanArgumentsInformation(const ArenaVector<ir::Expression *> &arguments)
{
    if (arguments.empty()) {
        return;
    }
    for (auto *argument : arguments) {
        argument->CleanCheckInformation();
    }
}

static void LogOverloadMismatch(ETSChecker *checker, util::StringView callName,
                                const ArenaVector<ir::Expression *> &arguments, const lexer::SourcePosition &pos,
                                std::string_view signatureKind);

static Signature *ValidateOrderSignature(
    ETSChecker *checker, std::tuple<Signature *, const ir::TSTypeParameterInstantiation *, TypeRelationFlag, bool> info,
    const ArenaVector<ir::Expression *> &arguments, const lexer::SourceRange &range,
    const std::vector<bool> &argTypeInferenceRequired);

static util::StringView GetInvocationTargetName(const ir::Expression *expr)
{
    if (expr->IsCallExpression() && expr->AsCallExpression()->Callee()->TsType() == nullptr) {
        auto callee = expr->AsCallExpression()->Callee();
        if (callee->IsMemberExpression()) {
            auto prop = callee->AsMemberExpression()->Property();
            ES2PANDA_ASSERT(prop->IsIdentifier());
            return prop->AsIdentifier()->Name();
        }
        return util::StringView("");
    }
    if (expr->IsCallExpression() && expr->AsCallExpression()->Callee()->TsType()->IsETSFunctionType() &&
        !expr->AsCallExpression()->Callee()->TsType()->IsETSArrowType()) {
        return expr->AsCallExpression()->Callee()->TsType()->AsETSFunctionType()->Name();
    }
    if (expr->IsETSNewClassInstanceExpression() &&
        expr->AsETSNewClassInstanceExpression()->TsType()->IsETSObjectType()) {
        return expr->AsETSNewClassInstanceExpression()->TsType()->AsETSObjectType()->AssemblerName();
    }
    return util::StringView("");
}

struct MatchOrderFlags {
    TypeRelationFlag validateFlags = TypeRelationFlag::NONE;
    bool reportErrorsWhileChecking = false;
};

static SignatureMatchResult MatchOrderSignaturesImpl(ETSChecker *checker, ArenaVector<Signature *> &signatures,
                                                     const ArenaVector<ir::Expression *> &arguments,
                                                     const ir::Expression *expr, MatchOrderFlags flags)
{
    SignatureMatchResult matchResult {};

    const ir::TSTypeParameterInstantiation *typeArguments =
        expr->IsCallExpression() ? expr->AsCallExpression()->TypeParams() : nullptr;

    auto const signaturesNumber = signatures.size();

    for (std::size_t i = 0U; i < signaturesNumber;) {
        auto *sig = signatures[i++];
        bool const reportErrorsWhileChecking = flags.reportErrorsWhileChecking && signaturesNumber == i;

        std::vector<bool> argTypeInferenceRequired = FindTypeInferenceArguments(arguments);
        if (matchResult.notVisibleSignature != nullptr &&
            !IsSignatureAccessible(sig, checker->Context().ContainingClass(), checker->Relation())) {
            continue;
        }
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        auto *concreteSig = ValidateOrderSignature(
            checker, std::make_tuple(sig, typeArguments, flags.validateFlags, reportErrorsWhileChecking), arguments,
            expr->Range(), argTypeInferenceRequired);
        if (concreteSig == nullptr) {
            if (!reportErrorsWhileChecking) {
                CleanArgumentsInformation(arguments);
            }
            continue;
        }
        if (matchResult.notVisibleSignature == nullptr &&
            !IsSignatureAccessible(sig, checker->Context().ContainingClass(), checker->Relation())) {
            if (!reportErrorsWhileChecking) {
                CleanArgumentsInformation(arguments);
            }
            matchResult.notVisibleSignature = concreteSig;
        } else {
            matchResult.matchedSignature = concreteSig;
            return matchResult;
        }
    };

    return matchResult;
}

SignatureMatchResult ETSChecker::MatchOrderSignaturesWithResult(ArenaVector<Signature *> &signatures,
                                                                const ArenaVector<ir::Expression *> &arguments,
                                                                const ir::Expression *expr,
                                                                TypeRelationFlag validateFlags)
{
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    return MatchOrderSignaturesImpl(this, signatures, arguments, expr, {validateFlags, false});
}

Signature *ETSChecker::MatchOrderSignatures(ArenaVector<Signature *> &signatures,
                                            const ArenaVector<ir::Expression *> &arguments, const ir::Expression *expr,
                                            TypeRelationFlag validateFlags, std::string_view signatureKind)
{
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto result = MatchOrderSignaturesImpl(this, signatures, arguments, expr, {validateFlags, !signatureKind.empty()});
    if (result.matchedSignature != nullptr) {
        return result.matchedSignature;
    }

    const lexer::SourcePosition &pos = expr->Start();

    if (!signatureKind.empty() && result.notVisibleSignature != nullptr) {
        LogError(diagnostic::SIG_INVISIBLE,
                 {result.notVisibleSignature->Function()->Id()->Name(), result.notVisibleSignature}, pos);
    }

    if (!signatureKind.empty()) {
        util::StringView name = GetInvocationTargetName(expr);
        if (!name.Empty()) {
            LogOverloadMismatch(this, name, arguments, pos, signatureKind);
        } else {
            LogSignatureMismatch(signatures, arguments, pos, signatureKind);
        }
    }

    return nullptr;
}

static bool ValidateOrderSignatureRequiredParams(ETSChecker *checker, Signature *substitutedSig,
                                                 const ArenaVector<ir::Expression *> &arguments, TypeRelationFlag flags,
                                                 const std::vector<bool> &argTypeInferenceRequired);

static Signature *ValidateOrderSignature(
    ETSChecker *checker, std::tuple<Signature *, const ir::TSTypeParameterInstantiation *, TypeRelationFlag, bool> info,
    const ArenaVector<ir::Expression *> &arguments, const lexer::SourceRange &range,
    const std::vector<bool> &argTypeInferenceRequired)
{
    auto [baseSignature, typeArguments, flags, reportErrors] = info;

    // When considering overload variants, if the current signature is not matched, do not log TypeError
    InferMatchContext signatureMatchContext(checker, util::DiagnosticType::SEMANTIC, range, reportErrors);

    Signature *const signature = MaybeSubstituteTypeParameters(checker, info, arguments, range.start);
    if (signature == nullptr) {
        return nullptr;
    }

    size_t const argCount = arguments.size();
    auto const hasRestParameter = signature->RestVar() != nullptr;

    if (!ValidateRestParameter(checker, signature, arguments, range.start, flags)) {
        return nullptr;
    }

    auto count = std::min(signature->ArgCount(), argCount);
    // Check all required formal parameter(s) first
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    if (!ValidateOrderSignatureRequiredParams(checker, signature, arguments, flags, argTypeInferenceRequired) ||
        !signatureMatchContext.ValidMatchStatus()) {
        return nullptr;
    }

    // Check rest parameter(s) if any exists
    if (!hasRestParameter || (count >= argCount && !signature->RestVar()->TsType()->IsETSTupleType())) {
        return signature;
    }
    if (!ValidateSignatureRestParams(checker, signature, arguments, flags) ||
        !signatureMatchContext.ValidMatchStatus()) {
        return nullptr;
    }

    return signature;
}

static bool SetPreferredTypeBeforeValidate(ETSChecker *checker, ir::Expression *argument, Type *paramType,
                                           TypeRelationFlag flags, bool isRestParam)
{
    if (argument->IsObjectExpression()) {
        argument->AsObjectExpression()->SetPreferredType(paramType);
    }

    if (argument->IsMemberExpression()) {
        checker->SetArrayPreferredTypeForNestedMemberExpressions(argument->AsMemberExpression(), paramType);
    } else if (argument->IsSpreadElement() && !isRestParam) {
        checker->LogError(diagnostic::SPREAD_ONTO_SINGLE_PARAM, {}, argument->Start());
        return false;
    } else if (argument->IsNumberLiteral()) {
        InferTypeForNumberLiteral(checker, argument->AsNumberLiteral(), paramType);
    }

    if (argument->IsArrayExpression()) {
        argument->AsArrayExpression()->SetPreferredTypeBasedOnFuncParam(checker, paramType, flags);
    }

    if (argument->IsETSNewArrayInstanceExpression()) {
        argument->AsETSNewArrayInstanceExpression()->SetPreferredTypeBasedOnFuncParam(checker, paramType, flags);
    }

    if (argument->IsETSNewMultiDimArrayInstanceExpression()) {
        argument->AsETSNewMultiDimArrayInstanceExpression()->SetPreferredTypeBasedOnFuncParam(checker, paramType,
                                                                                              flags);
    }

    return true;
}

static bool ValidateOrderSignatureInvocationContext(ETSChecker *checker, Signature *substitutedSig,
                                                    ir::Expression *argument, std::size_t index,
                                                    TypeRelationFlag flags);

static bool ValidateTypeInferenceRequiredArgument(ETSChecker *checker, Signature *substitutedSig,
                                                  const ArenaVector<ir::Expression *> &arguments,
                                                  const std::tuple<ir::Expression *, Type *, ir::AstNode *> &argInfo,
                                                  TypeRelationFlag flags)
{
    auto [argument, paramType, targetParm] = argInfo;

    if (argument->IsArrowFunctionExpression()) {
        // Note: If the signatures are from lambdas, then they have no `Function`.
        ir::ScriptFunction *const lambda = argument->AsArrowFunctionExpression()->Function();
        ERROR_SANITY_CHECK(checker, targetParm->IsETSParameterExpression(), return false);
        return CheckLambdaAssignable(checker, targetParm->AsETSParameterExpression(), paramType, lambda) &&
               TypeInference(checker, substitutedSig, arguments, flags);
    }

    if (!argument->IsArrayExpression() || !ContainsTypeError(paramType)) {
        return true;
    }

    checker->LogError(diagnostic::INFER_FAILURE_FUNC_PARAM, {targetParm->AsETSParameterExpression()->Ident()->Name()},
                      argument->Start());
    return false;
}

static bool ValidateOrderSignatureRequiredParams(ETSChecker *checker, Signature *substitutedSig,
                                                 const ArenaVector<ir::Expression *> &arguments, TypeRelationFlag flags,
                                                 const std::vector<bool> &argTypeInferenceRequired)
{
    auto commonArity = std::min(arguments.size(), substitutedSig->ArgCount());
    if ((flags & TypeRelationFlag::NO_CHECK_TRAILING_LAMBDA) != 0) {
        if (commonArity == 0) {
            ES2PANDA_ASSERT(substitutedSig->GetSignatureInfo()->params.empty());
            return false;
        }
        commonArity = commonArity - 1;
    }
    for (size_t index = 0; index < commonArity; ++index) {
        auto &argument = arguments[index];
        auto const paramType = checker->GetNonNullishType(substitutedSig->Params()[index]->TsType());
        if (!SetPreferredTypeBeforeValidate(checker, argument, paramType, flags)) {
            return false;
        }

        auto *targetParm = substitutedSig->GetSignatureInfo()->params[index]->Declaration()->Node();
        if (argTypeInferenceRequired[index] &&
            !ValidateTypeInferenceRequiredArgument(checker, substitutedSig, arguments,
                                                   std::make_tuple(argument, paramType, targetParm), flags)) {
            return false;
        }

        if (argument->IsIdentifier() && IsInvalidArgumentAsIdentifier(checker->Scope(), argument->AsIdentifier())) {
            checker->LogError(diagnostic::ARG_IS_CLASS_ID, {}, argument->Start());
            return false;
        }

        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        if (!ValidateOrderSignatureInvocationContext(checker, substitutedSig, argument, index, flags)) {
            return false;
        }
    }

    return CheckArrowFunctionParamIfNeeded(checker, substitutedSig, arguments, flags);
}

static bool ValidateOrderSignatureInvocationContext(ETSChecker *checker, Signature *substitutedSig,
                                                    ir::Expression *argument, std::size_t index, TypeRelationFlag flags)
{
    Type *targetType = substitutedSig->Params()[index]->TsType();
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    Type *argumentType = argument->Check(checker);

    if ((flags & TypeRelationFlag::OVERLOADING_CONTEXT) == 0) {
        InferMatchContext sctx {checker, util::DiagnosticType::SEMANTIC, argument->Range(), false};
        if (!CheckOptionalLambdaFunction(checker, argument, substitutedSig, index)) {
            return false;
        }
    }

    auto const invocationCtx =
        checker::InvocationContext(checker->Relation(), argument, argumentType, targetType, argument->Start(),
                                   {{diagnostic::TYPE_MISMATCH_AT_IDX, {argumentType, targetType, index + 1}}}, flags);

    return invocationCtx.IsInvocable();
}

static Signature *ResolvePotentialTrailingLambda(ETSChecker *checker, ir::CallExpression *callExpr,
                                                 ArenaVector<Signature *> const &signatures,
                                                 ArenaVector<ir::Expression *> &arguments);

static Signature *ResolveTrailingLambda(ETSChecker *checker, ArenaVector<Signature *> &signatures,
                                        ir::CallExpression *callExpr,
                                        const ArenaVector<ir::Expression *> &argumentsWithoutTrailingLambda,
                                        bool reportError)
{
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto arguments = ExtendArgumentsWithFakeLamda(checker, callExpr, argumentsWithoutTrailingLambda);
    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto sig = ResolvePotentialTrailingLambda(checker, callExpr, signatures, arguments);
    sig = CheckDerivedStaticMethodCall(checker, callExpr, sig);
    if (sig != nullptr) {
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        TransformTraillingLambda(checker, callExpr, sig, argumentsWithoutTrailingLambda);
        // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
        TrailingLambdaTypeInference(checker, sig, callExpr->Arguments());
        UpdateDeclarationFromSignature(checker, callExpr, sig);
        callExpr->SetIsTrailingCall(true);
        return sig;
    }

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    sig = checker->MatchOrderSignatures(signatures, argumentsWithoutTrailingLambda, callExpr, TypeRelationFlag::NONE,
                                        reportError ? "call" : "");
    sig = CheckDerivedStaticMethodCall(checker, callExpr, sig);
    if (sig != nullptr) {
        EnsureValidCurlyBrace(checker, callExpr);
    }

    UpdateDeclarationFromSignature(checker, callExpr, sig);
    return sig;
}

static bool SkipIfMismatchOnTrailingLambdaType(Signature *sig)
{
    if (sig->Params().empty()) {
        return false;
    }
    auto *lambdaType = sig->Params().back()->TsType();
    if (lambdaType == nullptr || !lambdaType->IsETSArrowType()) {
        return false;
    }
    auto *arrowSig = lambdaType->AsETSFunctionType()->ArrowSignature();
    auto requiredCallerParams = arrowSig->MinArgCount();
    if (arrowSig->IsExtensionFunction() && requiredCallerParams > 0) {
        requiredCallerParams--;
    }
    return requiredCallerParams > 0;
}

static Signature *ResolvePotentialTrailingLambda(ETSChecker *checker, ir::CallExpression *callExpr,
                                                 ArenaVector<Signature *> const &signatures,
                                                 ArenaVector<ir::Expression *> &arguments)
{
    auto *trailingLambda = arguments.back()->AsArrowFunctionExpression();
    ArenaVector<Signature *> normalSig(checker->ProgramAllocator()->Adapter());
    ArenaVector<Signature *> sigContainLambdaWithReceiverAsParam(checker->ProgramAllocator()->Adapter());
    for (auto sig : signatures) {
        if (!sig->HasFunction()) {
            continue;
        }
        if (SkipIfMismatchOnTrailingLambdaType(sig)) {
            continue;
        }
        if (!IsLastParameterLambdaWithReceiver(sig)) {
            normalSig.emplace_back(sig);
            continue;
        }

        auto *candidateFunctionType =
            sig->Function()->Params().back()->AsETSParameterExpression()->TypeAnnotation()->AsETSFunctionType();
        auto *currentReceiver = candidateFunctionType->Params()[0];
        trailingLambda->Function()->EmplaceParams(currentReceiver);
        sigContainLambdaWithReceiverAsParam.emplace_back(sig);

        InferMatchContext sctx {checker, util::DiagnosticType::SEMANTIC, callExpr->Range(), false};

        auto signature =
            // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
            checker->MatchOrderSignatures(sigContainLambdaWithReceiverAsParam, arguments, callExpr,
                                          TypeRelationFlag::NO_CHECK_TRAILING_LAMBDA);
        if (signature != nullptr && sctx.ValidMatchStatus()) {
            return signature;
        }
        sigContainLambdaWithReceiverAsParam.clear();
        trailingLambda->Function()->ClearParams();
    }

    InferMatchContext sctx {checker, util::DiagnosticType::SEMANTIC, callExpr->Range(), false};

    // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
    auto signature =
        checker->MatchOrderSignatures(normalSig, arguments, callExpr, TypeRelationFlag::NO_CHECK_TRAILING_LAMBDA);
    if (!sctx.ValidMatchStatus()) {
        return nullptr;
    }
    return signature;
}

std::optional<Substitution> ETSChecker::CheckTypeParamsAndBuildSubstitutionIfValid(
    Signature *signature, const ArenaVector<ir::TypeNode *> &params, const lexer::SourcePosition &pos)
{
    return BuildExplicitSubstitutionForArguments(this, signature, params, pos);
}

static void LogOverloadMismatch(ETSChecker *checker, util::StringView callName,
                                const ArenaVector<ir::Expression *> &arguments, const lexer::SourcePosition &pos,
                                std::string_view signatureKind)
{
    std::string msg = callName.Mutf8();
    msg += '(';

    for (std::size_t index = 0U; index < arguments.size(); ++index) {
        auto const &argument = arguments[index];
        Type const *const argumentType = argument->Check(checker);
        if (argumentType != nullptr && !ContainsTypeError(argumentType)) {
            msg += argumentType->ToString();
        } else {
            //  NOTE (DZ): extra cases for some specific nodes can be added here (as for 'ArrowFunctionExpression')
            msg += argument->ToString();
        }

        if (index != arguments.size() - 1U) {
            msg += ", ";
        }
    }
    msg += ')';
    checker->LogError(diagnostic::NO_MATCHING_SIG, {signatureKind, msg.c_str()}, pos);
}

}  // namespace ark::es2panda::checker