1/*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "ETSAnalyzerHelpers.h"
17#include "checker/types/ets/etsAsyncFuncReturnType.h"
18
19namespace ark::es2panda::checker {
20void CheckExtensionIsShadowedInCurrentClassOrInterface(checker::ETSChecker *checker, checker::ETSObjectType *objType,
21                                                       ir::ScriptFunction *extensionFunc, checker::Signature *signature)
22{
23    const auto methodName = extensionFunc->Id()->Name();
24    // Only check if there are class and interfaces' instance methods which would shadow instance extension method
25    auto *const variable = objType->GetOwnProperty<checker::PropertyType::INSTANCE_METHOD>(methodName);
26    if (variable == nullptr) {
27        return;
28    }
29
30    const auto *const funcType = variable->TsType()->AsETSFunctionType();
31    for (auto *funcSignature : funcType->CallSignatures()) {
32        signature->SetReturnType(funcSignature->ReturnType());
33        if (!checker->Relation()->IsCompatibleTo(signature, funcSignature)) {
34            continue;
35        }
36
37        checker->ReportWarning({"extension is shadowed by a instance member function '", funcType->Name(),
38                                funcSignature, "' in class ", objType->Name()},
39                               extensionFunc->Body()->Start());
40        return;
41    }
42}
43
44void CheckExtensionIsShadowedByMethod(checker::ETSChecker *checker, checker::ETSObjectType *objType,
45                                      ir::ScriptFunction *extensionFunc, checker::Signature *signature)
46{
47    if (objType == nullptr) {
48        return;
49    }
50
51    CheckExtensionIsShadowedInCurrentClassOrInterface(checker, objType, extensionFunc, signature);
52
53    for (auto *interface : objType->Interfaces()) {
54        CheckExtensionIsShadowedByMethod(checker, interface, extensionFunc, signature);
55    }
56
57    CheckExtensionIsShadowedByMethod(checker, objType->SuperType(), extensionFunc, signature);
58}
59
60static void ReplaceThisInExtensionMethod(checker::ETSChecker *checker, ir::ScriptFunction *extensionFunc)
61{
62    ASSERT(!extensionFunc->Params().empty());
63    ASSERT(extensionFunc->Params()[0]->AsETSParameterExpression()->Ident()->Name() ==
64           varbinder::TypedBinder::MANDATORY_PARAM_THIS);
65    auto thisVariable = extensionFunc->Params()[0]->Variable();
66    extensionFunc->Body()->TransformChildrenRecursively(
67        [=](ir::AstNode *ast) {
68            if (ast->IsThisExpression()) {
69                auto *thisParam = checker->Allocator()->New<ir::Identifier>(
70                    varbinder::TypedBinder::MANDATORY_PARAM_THIS, checker->Allocator());
71                thisParam->SetParent(ast->Parent());
72                thisParam->SetVariable(thisVariable);
73                return static_cast<ir::AstNode *>(thisParam);
74            }
75            return ast;
76        },
77        "replace-this-in-extension-method");
78}
79
80void CheckExtensionMethod(checker::ETSChecker *checker, ir::ScriptFunction *extensionFunc, ir::MethodDefinition *node)
81{
82    auto *const classType = checker->GetApparentType(extensionFunc->Signature()->Params()[0]->TsType());
83    if (!classType->IsETSObjectType() ||
84        (!classType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::CLASS) &&
85         !classType->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::INTERFACE))) {
86        checker->LogTypeError("Extension function can only defined for class and interface type.", node->Start());
87    }
88    if (classType->Variable()->Declaration()->Node()->IsClassDefinition() &&
89        !classType->Variable()->Declaration()->Node()->AsClassDefinition()->IsClassDefinitionChecked()) {
90        classType->Variable()->Declaration()->Node()->Check(checker);
91    }
92
93    // NOTE(gogabr): should be done in a lowering
94    ReplaceThisInExtensionMethod(checker, extensionFunc);
95
96    checker::SignatureInfo *originalExtensionSigInfo = checker->Allocator()->New<checker::SignatureInfo>(
97        extensionFunc->Signature()->GetSignatureInfo(), checker->Allocator());
98    originalExtensionSigInfo->minArgCount -= 1;
99    originalExtensionSigInfo->params.erase(originalExtensionSigInfo->params.begin());
100    checker::Signature *originalExtensionSigature =
101        checker->CreateSignature(originalExtensionSigInfo, extensionFunc->Signature()->ReturnType(), extensionFunc);
102
103    CheckExtensionIsShadowedByMethod(checker, classType->AsETSObjectType(), extensionFunc, originalExtensionSigature);
104}
105
106void DoBodyTypeChecking(ETSChecker *checker, ir::MethodDefinition *node, ir::ScriptFunction *scriptFunc)
107{
108    if (scriptFunc->HasBody() && (node->IsNative() || node->IsAbstract() || node->IsDeclare())) {
109        checker->LogTypeError("Native, Abstract and Declare methods cannot have body.", scriptFunc->Body()->Start());
110    }
111
112    if (!scriptFunc->IsAsyncFunc() && scriptFunc->HasBody() &&
113        (!scriptFunc->IsExternal() || scriptFunc->IsExternalOverload())) {
114        checker::ScopeContext scopeCtx(checker, scriptFunc->Scope());
115        checker::SavedCheckerContext savedContext(checker, checker->Context().Status(),
116                                                  checker->Context().ContainingClass());
117        checker->Context().SetContainingSignature(checker->GetSignatureFromMethodDefinition(node));
118
119        if (node->IsStatic() && !node->IsConstructor() &&
120            !checker->Context().ContainingClass()->HasObjectFlag(checker::ETSObjectFlags::GLOBAL)) {
121            checker->AddStatus(checker::CheckerStatus::IN_STATIC_CONTEXT);
122        }
123
124        if (node->IsConstructor()) {
125            checker->AddStatus(checker::CheckerStatus::IN_CONSTRUCTOR);
126        }
127
128        if (node->IsExtensionMethod()) {
129            CheckExtensionMethod(checker, scriptFunc, node);
130        }
131
132        scriptFunc->Body()->Check(checker);
133
134        if (scriptFunc->ReturnTypeAnnotation() == nullptr) {
135            if (scriptFunc->IsAsyncImplFunc()) {
136                ComposeAsyncImplFuncReturnType(checker, scriptFunc);
137            }
138
139            for (auto &returnStatement : scriptFunc->ReturnStatements()) {
140                returnStatement->SetReturnType(checker, scriptFunc->Signature()->ReturnType());
141            }
142        }
143
144        checker->Context().SetContainingSignature(nullptr);
145    }
146}
147
148void ComposeAsyncImplFuncReturnType(ETSChecker *checker, ir::ScriptFunction *scriptFunc)
149{
150    const auto &promiseGlobal = checker->GlobalBuiltinPromiseType()->AsETSObjectType();
151    auto promiseType =
152        promiseGlobal->Instantiate(checker->Allocator(), checker->Relation(), checker->GetGlobalTypesHolder())
153            ->AsETSObjectType();
154    promiseType->AddTypeFlag(checker::TypeFlag::GENERIC);
155    promiseType->TypeArguments().clear();
156    promiseType->TypeArguments().emplace_back(scriptFunc->Signature()->ReturnType());
157
158    auto *objectId =
159        checker->AllocNode<ir::Identifier>(compiler::Signatures::BUILTIN_OBJECT_CLASS, checker->Allocator());
160    objectId->SetReference();
161    checker->VarBinder()->AsETSBinder()->LookupTypeReference(objectId, false);
162    auto *returnType = checker->AllocNode<ir::ETSTypeReference>(
163        checker->AllocNode<ir::ETSTypeReferencePart>(objectId, nullptr, nullptr));
164    objectId->SetParent(returnType->Part());
165    returnType->Part()->SetParent(returnType);
166    returnType->SetTsType(
167        checker->Allocator()->New<ETSAsyncFuncReturnType>(checker->Allocator(), checker->Relation(), promiseType));
168    returnType->Check(checker);
169    scriptFunc->Signature()->SetReturnType(returnType->TsType());
170}
171
172void ComposeAsyncImplMethod(ETSChecker *checker, ir::MethodDefinition *node)
173{
174    auto *classDef = checker->FindAncestorGivenByType(node, ir::AstNodeType::CLASS_DEFINITION)->AsClassDefinition();
175    auto *scriptFunc = node->Function();
176    ir::MethodDefinition *implMethod = checker->CreateAsyncProxy(node, classDef);
177
178    implMethod->Check(checker);
179    node->SetAsyncPairMethod(implMethod);
180
181    if (scriptFunc->Signature()->HasSignatureFlag(SignatureFlags::NEED_RETURN_TYPE)) {
182        node->Function()->Signature()->SetReturnType(
183            implMethod->Function()->Signature()->ReturnType()->AsETSAsyncFuncReturnType()->PromiseType());
184        scriptFunc->Signature()->RemoveSignatureFlag(SignatureFlags::NEED_RETURN_TYPE);
185    }
186
187    if (node->Function()->IsOverload()) {
188        auto *baseOverloadImplMethod = node->BaseOverloadMethod()->AsyncPairMethod();
189        implMethod->Function()->Id()->SetVariable(baseOverloadImplMethod->Function()->Id()->Variable());
190        baseOverloadImplMethod->AddOverload(implMethod);
191    } else {
192        classDef->Body().push_back(implMethod);
193    }
194}
195
196void CheckPredefinedMethodReturnType(ETSChecker *checker, ir::ScriptFunction *scriptFunc)
197{
198    auto const &position = scriptFunc->Start();
199
200    if (scriptFunc->IsSetter() && (scriptFunc->Signature()->ReturnType() != checker->GlobalVoidType())) {
201        checker->LogTypeError("Setter must have void return type", position);
202    }
203
204    if (scriptFunc->IsGetter() && (scriptFunc->Signature()->ReturnType() == checker->GlobalVoidType())) {
205        checker->LogTypeError("Getter must return a value", position);
206    }
207
208    auto const name = scriptFunc->Id()->Name();
209    auto const methodName = std::string {ir::PREDEFINED_METHOD} + std::string {name.Utf8()};
210
211    if (name.Is(compiler::Signatures::GET_INDEX_METHOD)) {
212        if (scriptFunc->Signature()->ReturnType() == checker->GlobalVoidType()) {
213            checker->LogTypeError(methodName + "' shouldn't have void return type.", position);
214        }
215    } else if (name.Is(compiler::Signatures::SET_INDEX_METHOD)) {
216        if (scriptFunc->Signature()->ReturnType() != checker->GlobalVoidType()) {
217            checker->LogTypeError(methodName + "' should have void return type.", position);
218        }
219    } else if (name.Is(compiler::Signatures::ITERATOR_METHOD)) {
220        CheckIteratorMethodReturnType(checker, scriptFunc, position, methodName);
221    }
222}
223
224static bool HasIteratorInterface(ETSObjectType const *const objectType)
225{
226    auto const hasIteratorInterfaceImpl = [](ETSObjectType const *const checkType,
227                                             auto &&iteratorInterfaceImpl) -> bool {
228        if (checkType->Name().Is(ir::ITERATOR_INTERFACE_NAME)) {
229            return true;
230        }
231        for (const auto *const interface : checkType->Interfaces()) {
232            if (iteratorInterfaceImpl(interface, iteratorInterfaceImpl)) {
233                return true;
234            }
235        }
236        return false;
237    };
238
239    return hasIteratorInterfaceImpl(objectType, hasIteratorInterfaceImpl);
240}
241
242void CheckIteratorMethodReturnType(ETSChecker *checker, ir::ScriptFunction *scriptFunc,
243                                   const lexer::SourcePosition &position, const std::string &methodName)
244{
245    const auto *returnType = scriptFunc->Signature()->ReturnType();
246
247    if (returnType == nullptr) {
248        checker->LogTypeError(methodName + "' doesn't have return type.", position);
249    }
250
251    if (returnType->IsETSTypeParameter()) {
252        returnType = checker->GetApparentType(returnType->AsETSTypeParameter()->GetConstraintType());
253    }
254
255    if (returnType->IsETSObjectType() && HasIteratorInterface(returnType->AsETSObjectType())) {
256        return;
257    }
258
259    while (returnType->IsETSObjectType() && returnType->AsETSObjectType()->SuperType() != nullptr) {
260        returnType = returnType->AsETSObjectType()->SuperType();
261        if (returnType->IsETSObjectType() && HasIteratorInterface(returnType->AsETSObjectType())) {
262            return;
263        }
264    }
265
266    checker->LogTypeError(
267        {"The return type of '", scriptFunc->Id()->Name(), "' must be a type that implements Iterator interface."},
268        position);
269}
270
271checker::Type *InitAnonymousLambdaCallee(checker::ETSChecker *checker, ir::Expression *callee,
272                                         checker::Type *calleeType)
273{
274    auto *const arrowFunc = callee->AsArrowFunctionExpression()->Function();
275
276    ArenaVector<ir::Expression *> params {checker->Allocator()->Adapter()};
277    checker->CopyParams(arrowFunc->Params(), params);
278    checker::Type *funcReturnType = nullptr;
279
280    auto *typeAnnotation = arrowFunc->ReturnTypeAnnotation();
281    if (typeAnnotation != nullptr) {
282        typeAnnotation = typeAnnotation->Clone(checker->Allocator(), nullptr);
283        typeAnnotation->SetTsType(arrowFunc->ReturnTypeAnnotation()->TsType());
284    } else if (arrowFunc->Signature()->ReturnType() != nullptr) {
285        auto newTypeAnnotation = callee->AsArrowFunctionExpression()->CreateTypeAnnotation(checker);
286        typeAnnotation = arrowFunc->ReturnTypeAnnotation();
287        funcReturnType = newTypeAnnotation->GetType(checker);
288    }
289
290    auto signature = ir::FunctionSignature(nullptr, std::move(params), typeAnnotation);
291    auto *funcType = checker->AllocNode<ir::ETSFunctionType>(std::move(signature), ir::ScriptFunctionFlags::NONE);
292
293    funcType->SetScope(arrowFunc->Scope()->AsFunctionScope()->ParamScope());
294    auto *const funcIface = typeAnnotation != nullptr ? funcType->Check(checker) : funcReturnType;
295    checker->Relation()->SetNode(callee);
296    checker->Relation()->IsAssignableTo(calleeType, funcIface);
297    return funcIface;
298}
299
300checker::Signature *ResolveCallExtensionFunction(checker::ETSFunctionType *functionType, checker::ETSChecker *checker,
301                                                 ir::CallExpression *expr)
302{
303    auto *memberExpr = expr->Callee()->AsMemberExpression();
304    expr->Arguments().insert(expr->Arguments().begin(), memberExpr->Object());
305    auto *signature =
306        checker->ResolveCallExpressionAndTrailingLambda(functionType->CallSignatures(), expr, expr->Start());
307    if (signature == nullptr) {
308        return nullptr;
309    }
310    if (!signature->Function()->IsExtensionMethod()) {
311        checker->LogTypeError({"Property '", memberExpr->Property()->AsIdentifier()->Name(),
312                               "' does not exist on type '", memberExpr->ObjType()->Name(), "'"},
313                              memberExpr->Property()->Start());
314    }
315    expr->SetSignature(signature);
316    expr->SetCallee(memberExpr->Property());
317    memberExpr->Property()->AsIdentifier()->SetParent(expr);
318    expr->Arguments()[0]->SetParent(expr);
319    checker->HandleUpdatedCallExpressionNode(expr);
320    // Set TsType for new Callee(original member expression's Object)
321    expr->Callee()->Check(checker);
322    return signature;
323}
324
325checker::Signature *ResolveCallForETSExtensionFuncHelperType(checker::ETSExtensionFuncHelperType *type,
326                                                             checker::ETSChecker *checker, ir::CallExpression *expr)
327{
328    checker::Signature *signature = checker->ResolveCallExpressionAndTrailingLambda(
329        type->ClassMethodType()->CallSignatures(), expr, expr->Start(), checker::TypeRelationFlag::NO_THROW);
330
331    if (signature != nullptr) {
332        if (expr->Callee()->IsMemberExpression()) {
333            auto memberExpr = expr->Callee()->AsMemberExpression();
334            auto var = type->ClassMethodType()->Variable();
335            memberExpr->Property()->AsIdentifier()->SetVariable(var);
336        }
337
338        return signature;
339    }
340
341    return ResolveCallExtensionFunction(type->ExtensionMethodType(), checker, expr);
342}
343
344ArenaVector<checker::Signature *> GetUnionTypeSignatures(ETSChecker *checker, checker::ETSUnionType *etsUnionType)
345{
346    ArenaVector<checker::Signature *> callSignatures(checker->Allocator()->Adapter());
347
348    for (auto *constituentType : etsUnionType->ConstituentTypes()) {
349        if (constituentType->IsETSObjectType()) {
350            ArenaVector<checker::Signature *> tmpCallSignatures(checker->Allocator()->Adapter());
351            tmpCallSignatures = constituentType->AsETSObjectType()
352                                    ->GetOwnProperty<checker::PropertyType::INSTANCE_METHOD>("invoke0")
353                                    ->TsType()
354                                    ->AsETSFunctionType()
355                                    ->CallSignatures();
356            callSignatures.insert(callSignatures.end(), tmpCallSignatures.begin(), tmpCallSignatures.end());
357        }
358        if (constituentType->IsETSFunctionType()) {
359            ArenaVector<checker::Signature *> tmpCallSignatures(checker->Allocator()->Adapter());
360            tmpCallSignatures = constituentType->AsETSFunctionType()->CallSignatures();
361            callSignatures.insert(callSignatures.end(), tmpCallSignatures.begin(), tmpCallSignatures.end());
362        }
363        if (constituentType->IsETSUnionType()) {
364            ArenaVector<checker::Signature *> tmpCallSignatures(checker->Allocator()->Adapter());
365            tmpCallSignatures = GetUnionTypeSignatures(checker, constituentType->AsETSUnionType());
366            callSignatures.insert(callSignatures.end(), tmpCallSignatures.begin(), tmpCallSignatures.end());
367        }
368    }
369
370    return callSignatures;
371}
372
373ArenaVector<checker::Signature *> &ChooseSignatures(ETSChecker *checker, checker::Type *calleeType,
374                                                    bool isConstructorCall, bool isFunctionalInterface,
375                                                    bool isUnionTypeWithFunctionalInterface)
376{
377    static ArenaVector<checker::Signature *> unionSignatures(checker->Allocator()->Adapter());
378    unionSignatures.clear();
379    if (isConstructorCall) {
380        return calleeType->AsETSObjectType()->ConstructSignatures();
381    }
382    if (isFunctionalInterface) {
383        return calleeType->AsETSObjectType()
384            ->GetOwnProperty<checker::PropertyType::INSTANCE_METHOD>(FUNCTIONAL_INTERFACE_INVOKE_METHOD_NAME)
385            ->TsType()
386            ->AsETSFunctionType()
387            ->CallSignatures();
388    }
389    if (isUnionTypeWithFunctionalInterface) {
390        unionSignatures = GetUnionTypeSignatures(checker, calleeType->AsETSUnionType());
391        return unionSignatures;
392    }
393    return calleeType->AsETSFunctionType()->CallSignatures();
394}
395
396checker::ETSObjectType *ChooseCalleeObj(ETSChecker *checker, ir::CallExpression *expr, checker::Type *calleeType,
397                                        bool isConstructorCall)
398{
399    if (isConstructorCall) {
400        return calleeType->AsETSObjectType();
401    }
402    if (expr->Callee()->IsIdentifier()) {
403        return checker->Context().ContainingClass();
404    }
405    ASSERT(expr->Callee()->IsMemberExpression());
406    return expr->Callee()->AsMemberExpression()->ObjType();
407}
408
409void ProcessExclamationMark(ETSChecker *checker, ir::UnaryExpression *expr, checker::Type *operandType)
410{
411    if (checker->IsNullLikeOrVoidExpression(expr->Argument())) {
412        auto tsType = checker->CreateETSBooleanType(true);
413        tsType->AddTypeFlag(checker::TypeFlag::CONSTANT);
414        expr->SetTsType(tsType);
415        return;
416    }
417
418    if (operandType == nullptr || !operandType->IsConditionalExprType()) {
419        checker->LogTypeError("Bad operand type, the type of the operand must be boolean type.",
420                              expr->Argument()->Start());
421        expr->SetTsType(checker->GlobalTypeError());
422        return;
423    }
424
425    auto exprRes = operandType->ResolveConditionExpr();
426    if (std::get<0>(exprRes)) {
427        auto tsType = checker->CreateETSBooleanType(!std::get<1>(exprRes));
428        tsType->AddTypeFlag(checker::TypeFlag::CONSTANT);
429        expr->SetTsType(tsType);
430        return;
431    }
432
433    expr->SetTsType(checker->GlobalETSBooleanType());
434}
435
436void SetTsTypeForUnaryExpression(ETSChecker *checker, ir::UnaryExpression *expr, checker::Type *operandType)
437{
438    switch (expr->OperatorType()) {
439        case lexer::TokenType::PUNCTUATOR_MINUS:
440        case lexer::TokenType::PUNCTUATOR_PLUS: {
441            if (operandType == nullptr || !operandType->HasTypeFlag(checker::TypeFlag::ETS_CONVERTIBLE_TO_NUMERIC)) {
442                checker->LogTypeError("Bad operand type, the type of the operand must be numeric type.",
443                                      expr->Argument()->Start());
444                expr->SetTsType(checker->GlobalTypeError());
445                break;
446            }
447
448            if (operandType->HasTypeFlag(checker::TypeFlag::CONSTANT) &&
449                expr->OperatorType() == lexer::TokenType::PUNCTUATOR_MINUS) {
450                expr->SetTsType(checker->NegateNumericType(operandType, expr));
451                break;
452            }
453
454            expr->SetTsType(operandType);
455            break;
456        }
457        case lexer::TokenType::PUNCTUATOR_TILDE: {
458            if (operandType == nullptr || !operandType->HasTypeFlag(checker::TypeFlag::ETS_CONVERTIBLE_TO_NUMERIC)) {
459                checker->LogTypeError("Bad operand type, the type of the operand must be numeric type.",
460                                      expr->Argument()->Start());
461                expr->SetTsType(checker->GlobalTypeError());
462                break;
463            }
464
465            if (operandType->HasTypeFlag(checker::TypeFlag::CONSTANT)) {
466                expr->SetTsType(checker->BitwiseNegateNumericType(operandType, expr));
467                break;
468            }
469
470            expr->SetTsType(checker->SelectGlobalIntegerTypeForNumeric(operandType));
471            break;
472        }
473        case lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK: {
474            ProcessExclamationMark(checker, expr, operandType);
475            break;
476        }
477        case lexer::TokenType::PUNCTUATOR_DOLLAR_DOLLAR: {
478            expr->SetTsType(expr->Argument()->TsType());
479            break;
480        }
481        default: {
482            UNREACHABLE();
483            break;
484        }
485    }
486}
487
488checker::ETSObjectType *CreateSyntheticType(ETSChecker *checker, util::StringView const &syntheticName,
489                                            checker::ETSObjectType *lastObjectType, ir::Identifier *id)
490{
491    auto *syntheticObjType = checker->Allocator()->New<checker::ETSObjectType>(
492        checker->Allocator(), syntheticName, syntheticName,
493        std::make_tuple(id, checker::ETSObjectFlags::NO_OPTS, checker->Relation()));
494
495    auto *classDecl = checker->Allocator()->New<varbinder::ClassDecl>(syntheticName);
496    varbinder::LocalVariable *var =
497        checker->Allocator()->New<varbinder::LocalVariable>(classDecl, varbinder::VariableFlags::CLASS);
498    var->SetTsType(syntheticObjType);
499    lastObjectType->AddProperty<checker::PropertyType::STATIC_FIELD>(var);
500    syntheticObjType->SetEnclosingType(lastObjectType);
501    return syntheticObjType;
502}
503
504// NOLINTBEGIN(modernize-avoid-c-arrays)
505static constexpr char const INVALID_CONST_ASSIGNMENT[] = "Cannot assign a value to a constant variable ";
506static constexpr char const INVALID_READONLY_ASSIGNMENT[] = "Cannot assign a value to a readonly variable ";
507static constexpr char const ITERATOR_TYPE_ABSENT[] = "Cannot obtain iterator type in 'for-of' statement.";
508// NOLINTEND(modernize-avoid-c-arrays)
509
510checker::Type *GetIteratorType(ETSChecker *checker, checker::Type *elemType, ir::AstNode *left)
511{
512    // Just to avoid extra nested level(s)
513    auto const getIterType = [checker, elemType](ir::VariableDeclarator *const declarator) -> checker::Type * {
514        if (declarator->TsType() == nullptr) {
515            if (auto *resolved = checker->FindVariableInFunctionScope(declarator->Id()->AsIdentifier()->Name(),
516                                                                      varbinder::ResolveBindingOptions::ALL_NON_TYPE);
517                resolved != nullptr) {
518                resolved->SetTsType(elemType);
519                return elemType;
520            }
521        } else {
522            return declarator->TsType();
523        }
524        return checker->GlobalTypeError();
525    };
526
527    checker::Type *iterType = nullptr;
528    if (left->IsIdentifier()) {
529        if (auto *const variable = left->AsIdentifier()->Variable(); variable != nullptr) {
530            auto *decl = variable->Declaration();
531            if (decl->IsConstDecl() || decl->IsReadonlyDecl()) {
532                std::string_view errorMsg =
533                    decl->IsConstDecl() ? INVALID_CONST_ASSIGNMENT : INVALID_READONLY_ASSIGNMENT;
534                checker->LogTypeError({errorMsg, variable->Name()}, decl->Node()->Start());
535            }
536        }
537        iterType = left->AsIdentifier()->TsType();
538    } else if (left->IsVariableDeclaration()) {
539        if (auto const &declarators = left->AsVariableDeclaration()->Declarators(); !declarators.empty()) {
540            iterType = getIterType(declarators.front());
541        }
542    }
543
544    if (iterType == nullptr) {
545        checker->LogTypeError(ITERATOR_TYPE_ABSENT, left->Start());
546        return checker->GlobalTypeError();
547    }
548    return iterType;
549}
550
551bool CheckArgumentVoidType(checker::Type *&funcReturnType, ETSChecker *checker, const std::string &name,
552                           ir::ReturnStatement *st)
553{
554    if (name.find(compiler::Signatures::ETS_MAIN_WITH_MANGLE_BEGIN) != std::string::npos) {
555        if (!funcReturnType->IsETSVoidType() && !funcReturnType->IsIntType()) {
556            checker->LogTypeError("Bad return type, main enable only void or int type.", st->Start());
557        }
558    }
559    return true;
560}
561
562bool CheckReturnType(ETSChecker *checker, checker::Type *funcReturnType, checker::Type *argumentType,
563                     ir::Expression *stArgument, bool isAsync)
564{
565    if (funcReturnType->IsETSVoidType() || funcReturnType == checker->GlobalVoidType()) {
566        if (argumentType != checker->GlobalVoidType()) {
567            checker->LogTypeError("Unexpected return value, enclosing method return type is void.",
568                                  stArgument->Start());
569            return false;
570        }
571        if (!checker::AssignmentContext(checker->Relation(), stArgument, argumentType, funcReturnType,
572                                        stArgument->Start(), {},
573                                        checker::TypeRelationFlag::DIRECT_RETURN | checker::TypeRelationFlag::NO_THROW)
574                 .IsAssignable()) {
575            checker->LogTypeError({"Return statement type is not compatible with the enclosing method's return type."},
576                                  stArgument->Start());
577            return false;
578        }
579        return true;
580    }
581
582    if (isAsync && funcReturnType->IsETSObjectType() &&
583        funcReturnType->AsETSObjectType()->GetOriginalBaseType() == checker->GlobalBuiltinPromiseType()) {
584        auto promiseArg = funcReturnType->AsETSObjectType()->TypeArguments()[0];
585        checker::AssignmentContext(checker->Relation(), stArgument, argumentType, promiseArg, stArgument->Start(), {},
586                                   checker::TypeRelationFlag::DIRECT_RETURN | checker::TypeRelationFlag::NO_THROW);
587        if (checker->Relation()->IsTrue()) {
588            return true;
589        }
590    }
591
592    const Type *targetType = checker->TryGettingFunctionTypeFromInvokeFunction(funcReturnType);
593    const Type *sourceType = checker->TryGettingFunctionTypeFromInvokeFunction(argumentType);
594    if (!checker::AssignmentContext(checker->Relation(), stArgument, argumentType, funcReturnType, stArgument->Start(),
595                                    {}, checker::TypeRelationFlag::DIRECT_RETURN | checker::TypeRelationFlag::NO_THROW)
596             .IsAssignable()) {
597        checker->LogTypeError(
598            {"Type '", sourceType, "' is not compatible with the enclosing method's return type '", targetType, "'"},
599            stArgument->Start());
600        return false;
601    }
602    return true;
603}
604
605void InferReturnType(ETSChecker *checker, ir::ScriptFunction *containingFunc, checker::Type *&funcReturnType,
606                     ir::Expression *stArgument)
607{
608    //  First (or single) return statement in the function:
609    funcReturnType =
610        stArgument == nullptr ? checker->GlobalVoidType() : checker->GetNonConstantType(stArgument->Check(checker));
611    /*
612    when st_argment is ArrowFunctionExpression, need infer type for st_argment
613    example code:
614    ```
615    return () => {}
616    ```
617    */
618    if (stArgument != nullptr && stArgument->IsArrowFunctionExpression()) {
619        auto arrowFunc = stArgument->AsArrowFunctionExpression();
620        auto typeAnnotation = arrowFunc->CreateTypeAnnotation(checker);
621
622        auto *argumentType = arrowFunc->TsType();
623        funcReturnType = typeAnnotation->GetType(checker);
624
625        const Type *sourceType = checker->TryGettingFunctionTypeFromInvokeFunction(argumentType);
626        const Type *targetType = checker->TryGettingFunctionTypeFromInvokeFunction(funcReturnType);
627
628        if (!checker::AssignmentContext(checker->Relation(), arrowFunc, argumentType, funcReturnType,
629                                        stArgument->Start(), {},
630                                        checker::TypeRelationFlag::DIRECT_RETURN | checker::TypeRelationFlag::NO_THROW)
631                 .IsAssignable()) {
632            checker->LogTypeError({"Type '", sourceType,
633                                   "' is not compatible with the enclosing method's return type '", targetType, "'"},
634                                  stArgument->Start());
635            funcReturnType = checker->GlobalTypeError();
636            return;
637        }
638    }
639
640    containingFunc->Signature()->SetReturnType(funcReturnType);
641    containingFunc->Signature()->RemoveSignatureFlag(checker::SignatureFlags::NEED_RETURN_TYPE);
642    containingFunc->Signature()->AddSignatureFlag(checker::SignatureFlags::INFERRED_RETURN_TYPE);
643    checker->VarBinder()->AsETSBinder()->BuildFunctionName(containingFunc);
644
645    if (stArgument != nullptr && stArgument->IsObjectExpression()) {
646        stArgument->AsObjectExpression()->SetPreferredType(funcReturnType);
647    }
648}
649
650void ProcessReturnStatements(ETSChecker *checker, ir::ScriptFunction *containingFunc, checker::Type *&funcReturnType,
651                             ir::ReturnStatement *st, ir::Expression *stArgument)
652{
653    funcReturnType = containingFunc->Signature()->ReturnType();
654
655    if (stArgument == nullptr) {
656        // previous return statement(s) have value
657        if (!funcReturnType->IsETSVoidType() && funcReturnType != checker->GlobalVoidType()) {
658            checker->LogTypeError("All return statements in the function should be empty or have a value.",
659                                  st->Start());
660            return;
661        }
662    } else {
663        if (stArgument->IsObjectExpression()) {
664            stArgument->AsObjectExpression()->SetPreferredType(funcReturnType);
665        }
666
667        if (stArgument->IsMemberExpression()) {
668            checker->SetArrayPreferredTypeForNestedMemberExpressions(stArgument->AsMemberExpression(), funcReturnType);
669        }
670
671        checker::Type *argumentType = checker->GetNonConstantType(stArgument->Check(checker));
672
673        //  previous return statement(s) don't have any value
674        if (funcReturnType->IsETSVoidType() && !argumentType->IsETSVoidType()) {
675            checker->LogTypeError("All return statements in the function should be empty or have a value.",
676                                  stArgument->Start());
677            return;
678        }
679
680        const auto name = containingFunc->Scope()->InternalName().Mutf8();
681        if (!CheckArgumentVoidType(funcReturnType, checker, name, st)) {
682            return;
683        }
684
685        auto *const relation = checker->Relation();
686        relation->SetNode(stArgument);
687
688        if (!relation->IsIdenticalTo(funcReturnType, argumentType)) {
689            checker->ResolveReturnStatement(funcReturnType, argumentType, containingFunc, st);
690        }
691
692        relation->SetNode(nullptr);
693        relation->SetFlags(checker::TypeRelationFlag::NONE);
694    }
695}
696
697ETSObjectType *CreateOptionalSignaturesForFunctionalType(ETSChecker *checker, ir::ETSFunctionType *node,
698                                                         ETSObjectType *genericInterfaceType,
699                                                         Substitution *substitution, size_t optionalParameterIndex)
700{
701    const auto &params = node->Params();
702    auto returnType = node->ReturnType()->GetType(checker);
703
704    for (size_t i = 0; i < optionalParameterIndex; i++) {
705        checker::ETSChecker::EmplaceSubstituted(
706            substitution, genericInterfaceType->TypeArguments()[i]->AsETSTypeParameter()->GetOriginal(),
707            InstantiateBoxedPrimitiveType(checker, params[i],
708                                          params[i]->AsETSParameterExpression()->TypeAnnotation()->GetType(checker)));
709    }
710
711    for (size_t i = optionalParameterIndex; i < params.size(); i++) {
712        checker::ETSChecker::EmplaceSubstituted(
713            substitution, genericInterfaceType->TypeArguments()[i]->AsETSTypeParameter()->GetOriginal(),
714            CreateParamTypeWithDefaultParam(checker, params[i]));
715    }
716
717    checker::ETSChecker::EmplaceSubstituted(
718        substitution,
719        genericInterfaceType->TypeArguments()[genericInterfaceType->TypeArguments().size() - 1]
720            ->AsETSTypeParameter()
721            ->GetOriginal(),
722        InstantiateBoxedPrimitiveType(checker, node->ReturnType(), returnType));
723
724    return genericInterfaceType->Substitute(checker->Relation(), substitution)->AsETSObjectType();
725}
726
727ETSObjectType *CreateInterfaceTypeForETSFunctionType(ETSChecker *checker, ir::ETSFunctionType *node,
728                                                     ETSObjectType *genericInterfaceType, Substitution *substitution)
729{
730    size_t i = 0;
731    if (auto const &params = node->Params(); params.size() < checker->GlobalBuiltinFunctionTypeVariadicThreshold()) {
732        for (; i < params.size(); i++) {
733            checker::ETSChecker::EmplaceSubstituted(
734                substitution, genericInterfaceType->TypeArguments()[i]->AsETSTypeParameter()->GetOriginal(),
735                InstantiateBoxedPrimitiveType(
736                    checker, params[i], params[i]->AsETSParameterExpression()->TypeAnnotation()->GetType(checker)));
737        }
738    }
739
740    checker::ETSChecker::EmplaceSubstituted(
741        substitution, genericInterfaceType->TypeArguments()[i]->AsETSTypeParameter()->GetOriginal(),
742        InstantiateBoxedPrimitiveType(checker, node->ReturnType(), node->ReturnType()->GetType(checker)));
743    return genericInterfaceType->Substitute(checker->Relation(), substitution)->AsETSObjectType();
744}
745
746Type *CreateParamTypeWithDefaultParam(ETSChecker *checker, ir::Expression *param)
747{
748    if (!param->AsETSParameterExpression()->IsDefault()) {
749        checker->LogTypeError({"Expected initializer for ", param->AsETSParameterExpression()->Ident()->Name()},
750                              param->Start());
751    }
752
753    ArenaVector<Type *> types(checker->Allocator()->Adapter());
754    types.push_back(InstantiateBoxedPrimitiveType(
755        checker, param, param->AsETSParameterExpression()->TypeAnnotation()->GetType(checker)));
756
757    if (param->AsETSParameterExpression()->Initializer()->IsUndefinedLiteral()) {
758        types.push_back(checker->GlobalETSUndefinedType());
759    }
760
761    return checker->CreateETSUnionType(Span<Type *const>(types));
762}
763
764Type *InstantiateBoxedPrimitiveType(ETSChecker *checker, ir::Expression *param, Type *paramType)
765{
766    if (paramType->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE)) {
767        auto node = checker->Relation()->GetNode();
768        checker->Relation()->SetNode(param);
769        auto *const boxedTypeArg = checker->PrimitiveTypeAsETSBuiltinType(paramType);
770        ASSERT(boxedTypeArg);
771        paramType =
772            boxedTypeArg->Instantiate(checker->Allocator(), checker->Relation(), checker->GetGlobalTypesHolder());
773        checker->Relation()->SetNode(node);
774    }
775
776    return paramType;
777}
778}  // namespace ark::es2panda::checker
779