// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QV4CODEGEN_P_H
#define QV4CODEGEN_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <private/qqmljsastvisitor_p.h>
#include <private/qqmljsengine_p.h>
#include <private/qqmljsast_p.h>
#include <private/qqmljsdiagnosticmessage_p.h>
#include <private/qv4compiler_p.h>
#include <private/qv4compilercontext_p.h>
#include <private/qv4util_p.h>
#include <private/qv4bytecodegenerator_p.h>
#include <private/qv4calldata_p.h>

#include <QtCore/qsharedpointer.h>
#include <QtCore/qxpfunctional.h>

#include <stack>

QT_BEGIN_NAMESPACE

namespace QV4 {

namespace Moth {
struct Instruction;
}

namespace CompiledData {
struct CompilationUnit;
}

namespace Compiler {

struct ControlFlow;
struct ControlFlowCatch;
struct ControlFlowFinally;

class Q_QML_COMPILER_EXPORT CodegenWarningInterface
{
public:
    virtual void reportVarUsedBeforeDeclaration(const QString &name, const QString &fileName,
                                                QQmlJS::SourceLocation declarationLocation,
                                                QQmlJS::SourceLocation accessLocation);
    virtual void reportFunctionUsedBeforeDeclaration(const QString &name, const QString &fileName,
                                                     QQmlJS::SourceLocation declarationLocation,
                                                     QQmlJS::SourceLocation accessLocation);

    virtual QQmlJS::AST::Visitor *unreachableVisitor() { return nullptr; }
    virtual ~CodegenWarningInterface() = default;
};

inline CodegenWarningInterface *defaultCodegenWarningInterface()
{
    static CodegenWarningInterface iface;
    return &iface;
}

class Q_QML_COMPILER_EXPORT Codegen: protected QQmlJS::AST::Visitor
{
protected:
    using BytecodeGenerator = QV4::Moth::BytecodeGenerator;
    using Instruction = QV4::Moth::Instruction;
public:
    Codegen(QV4::Compiler::JSUnitGenerator *jsUnitGenerator, bool strict,
            CodegenWarningInterface *iface = defaultCodegenWarningInterface(),
            bool storeSourceLocations = false);

    static bool isNameGlobal(QAnyStringView name);
    static void forEachGlobalName(qxp::function_ref<void(QLatin1StringView)> &&handler);

    void generateFromProgram(
            const QString &sourceCode, QQmlJS::AST::Program *ast, Module *module,
            ContextType contextType = ContextType::Global);

    void generateFromModule(const QString &sourceCode, QQmlJS::AST::ESModule *ast, Module *module);
    void generateFromModule(const Value &value, Module *module);

public:
    class VolatileMemoryLocationScanner;
    class VolatileMemoryLocations {
        friend VolatileMemoryLocationScanner;
        bool allVolatile = false;
        QList<QStringView> specificLocations;
    public:
        bool isVolatile(QStringView name) {
            if (allVolatile)
                return true;
            return specificLocations.contains(name);
        }

        void add(QStringView name) { if (!allVolatile) specificLocations.append(name); }
        void setAllVolatile() { allVolatile = true; }
    };
    class RValue {
        Codegen *codegen;
        enum Type {
            Invalid,
            Accumulator,
            StackSlot,
            Const
        } type;
        union {
            Moth::StackSlot theStackSlot;
            QV4::ReturnedValue constant;
        };

    public:
        static RValue fromStackSlot(Codegen *codegen, Moth::StackSlot stackSlot) {
            RValue r;
            r.codegen = codegen;
            r.type = StackSlot;
            r.theStackSlot = stackSlot;
            return r;
        }
        static RValue fromAccumulator(Codegen *codegen) {
            RValue r;
            r.codegen = codegen;
            r.type = Accumulator;
            return r;
        }
        static RValue fromConst(Codegen *codegen, QV4::ReturnedValue value) {
            RValue r;
            r.codegen = codegen;
            r.type = Const;
            r.constant = value;
            return r;
        }

        bool operator==(const RValue &other) const;

        bool isValid() const { return type != Invalid; }
        bool isAccumulator() const { return type == Accumulator; }
        bool isStackSlot() const { return type == StackSlot; }
        bool isConst() const { return type == Const; }

        Moth::StackSlot stackSlot() const {
            Q_ASSERT(isStackSlot());
            return theStackSlot;
        }

        QV4::ReturnedValue constantValue() const {
            Q_ASSERT(isConst());
            return constant;
        }

        Q_REQUIRED_RESULT RValue storeOnStack() const;
        void loadInAccumulator() const;
    };
    struct Reference {
        enum Type {
            Invalid,
            Accumulator,
            Super,
            SuperProperty,
            StackSlot,
            ScopedLocal,
            Name,
            Member,
            Subscript,
            Import,
            LastLValue = Import,
            Const
        } type = Invalid;

        bool isLValue() const { return !isReadonly && type > Accumulator; }

        Reference(Codegen *cg, Type t = Invalid) : Reference()
        {
            type = t;
            codegen = cg;
        }

        Reference(const QString &name = QString()) :
            constant(0),
            name(name),
            isArgOrEval(false),
            isReadonly(false),
            isReferenceToConst(false),
            requiresTDZCheck(false),
            subscriptRequiresTDZCheck(false),
            stackSlotIsLocalOrArgument(false),
            isVolatile(false),
            global(false),
            qmlGlobal(false),
            throwsReferenceError(false),
            subscriptLoadedForCall(false),
            isOptional(false),
            hasSavedCallBaseSlot(false)
        {}

        Reference(const Reference &) = default;
        Reference(Reference &&) = default;
        Reference &operator =(const Reference &) = default;
        Reference &operator =(Reference &&) = default;

        bool operator==(const Reference &other) const;
        bool operator!=(const Reference &other) const
        { return !(*this == other); }

        bool isValid() const { return type != Invalid; }
        bool loadTriggersSideEffect() const {
            switch (type) {
            case Name:
            case Member:
            case Subscript:
            case SuperProperty:
                return true;
            default:
                return requiresTDZCheck;
            }
        }
        bool isConstant() const { return type == Const; }
        bool isAccumulator() const { return type == Accumulator; }
        bool isSuper() const { return type == Super; }
        bool isSuperProperty() const { return type == SuperProperty; }
        bool isStackSlot() const { return type == StackSlot; }
        bool isRegister() const {
            return isStackSlot();
        }

        static Reference fromAccumulator(Codegen *cg) {
            return Reference(cg, Accumulator);
        }
        static Reference fromSuper(Codegen *cg) {
            return Reference(cg, Super);
        }
        static Reference fromStackSlot(Codegen *cg, int tempIndex = -1, bool isLocal = false) {
            Reference r(cg, StackSlot);
            if (tempIndex == -1)
                tempIndex = cg->bytecodeGenerator->newRegister();
            r.theStackSlot = Moth::StackSlot::createRegister(tempIndex);
            r.stackSlotIsLocalOrArgument = isLocal;
            return r;
        }
        static Reference fromScopedLocal(Codegen *cg, int index, int scope) {
            Reference r(cg, ScopedLocal);
            r.index = index;
            r.scope = scope;
            return r;
        }
        static Reference fromImport(Codegen *cg, int index) {
            Reference r(cg, Import);
            r.index = index;
            return r;
        }
        static Reference fromName(Codegen *cg, const QString &name) {
            Reference r(cg, Name);
            r.name = name;
            return r;
        }
        static Reference
        fromMember(const Reference &baseRef, const QString &name,
                   QQmlJS::SourceLocation sourceLocation = QQmlJS::SourceLocation(),
                   bool isOptional = false,
                   std::vector<Moth::BytecodeGenerator::Jump> *optionalChainJumpsToPatch = nullptr)
        {
            Q_ASSERT(baseRef.isValid());
            Reference r(baseRef.codegen, Member);
            r.propertyBase = baseRef.asRValue();
            r.propertyNameIndex = r.codegen->registerString(name);
            r.requiresTDZCheck = baseRef.requiresTDZCheck;
            r.sourceLocation = sourceLocation;
            r.optionalChainJumpsToPatch = optionalChainJumpsToPatch;
            r.isOptional = isOptional;
            return r;
        }
        static Reference fromSuperProperty(const Reference &property) {
            Q_ASSERT(property.isStackSlot());
            Reference r(property.codegen, SuperProperty);
            r.property = property.stackSlot();
            r.subscriptRequiresTDZCheck = property.requiresTDZCheck;
            return r;
        }
        static Reference fromSubscript(const Reference &baseRef, const Reference &subscript) {
            Q_ASSERT(baseRef.isStackSlot());
            Reference r(baseRef.codegen, Subscript);
            r.elementBase = baseRef.stackSlot();
            r.elementSubscript = subscript.asRValue();
            r.requiresTDZCheck = baseRef.requiresTDZCheck;
            r.subscriptRequiresTDZCheck = subscript.requiresTDZCheck;
            return r;
        }
        static Reference fromConst(Codegen *cg, QV4::ReturnedValue constant) {
            Reference r(cg, Const);
            r.constant = constant;
            r.isReadonly = true;
            return r;
        }
        static Reference fromThis(Codegen *cg) {
            Reference r = fromStackSlot(cg, CallData::This);
            r.isReadonly = true;
            // ### Optimize this. Functions that are not derived constructors or arrow functions can't have an
            // empty this object
            r.requiresTDZCheck = true;
            return r;
        }

        RValue asRValue() const;
        Reference asLValue() const;

        Q_REQUIRED_RESULT static Reference storeConstOnStack(Codegen *cg, QV4::ReturnedValue constant)
        { return Reference::fromConst(cg, constant).storeOnStack(); }

        static void storeConstOnStack(Codegen *cg, QV4::ReturnedValue constant, int stackSlot)
        { Reference::fromConst(cg, constant).storeOnStack(stackSlot); }

        Q_REQUIRED_RESULT Reference storeOnStack() const;
        void storeOnStack(int tempIndex) const;
        Q_REQUIRED_RESULT Reference storeRetainAccumulator() const;
        Reference storeConsumeAccumulator() const;

        Q_REQUIRED_RESULT Reference baseObject() const;

        bool storeWipesAccumulator() const;
        void loadInAccumulator() const;

        int nameAsIndex() const {
            Q_ASSERT(type == Name);
            return codegen->registerString(name);
        }

        Moth::StackSlot stackSlot() const {
            if (Q_UNLIKELY(!isStackSlot()))
                Q_UNREACHABLE();
            return theStackSlot;
        }

        void tdzCheck() const
        {
            if (isAccumulator())
                tdzCheck(requiresTDZCheck, throwsReferenceError);
            else if (isStackSlot())
                tdzCheckStackSlot(stackSlot(), requiresTDZCheck, throwsReferenceError);
        }

        union {
            Moth::StackSlot theStackSlot;
            QV4::ReturnedValue constant;
            struct { // Scoped arguments/Local
                int index;
                int scope;
            };
            struct {
                RValue propertyBase;
                int propertyNameIndex;
            };
            struct {
                Moth::StackSlot elementBase;
                union {
                    RValue elementSubscript;
                    Moth::StackSlot element;
                };
            };
            Moth::StackSlot property; // super property
        };
        QString name;
        Codegen *codegen = nullptr;

        quint32 isArgOrEval:1;
        quint32 isReadonly:1;
        quint32 isReferenceToConst:1;
        quint32 requiresTDZCheck:1;
        quint32 subscriptRequiresTDZCheck:1;
        quint32 stackSlotIsLocalOrArgument:1;
        quint32 isVolatile:1;
        quint32 global:1;
        quint32 qmlGlobal:1;
        quint32 throwsReferenceError:1;
        quint32 subscriptLoadedForCall:1;
        quint32 isOptional: 1;
        quint32 hasSavedCallBaseSlot: 1;
        QQmlJS::SourceLocation sourceLocation = QQmlJS::SourceLocation();
        std::vector<Moth::BytecodeGenerator::Jump> *optionalChainJumpsToPatch = nullptr;
        int savedCallBaseSlot = -1;
        int savedCallPropertyNameIndex = -1;

    private:
        void storeAccumulator() const;
        Reference doStoreOnStack(int tempIndex) const;
        void tdzCheck(bool requiresCheck, bool throwsReferenceError) const;
        void tdzCheckStackSlot(
                Moth::StackSlot slot, bool requiresCheck, bool throwsReferenceError) const;
    };

    struct RegisterScope {
        RegisterScope(Codegen *cg)
            : generator(cg->bytecodeGenerator),
              regCountForScope(generator->currentReg) {}
        ~RegisterScope() {
            generator->currentReg = regCountForScope;
        }
        BytecodeGenerator *generator;
        int regCountForScope;
    };

    struct ObjectPropertyValue {
        ObjectPropertyValue() {}

        Reference rvalue;
        int getter = -1; // index in _module->functions or -1 if not set
        int setter = -1;
        uint keyAsIndex = UINT_MAX;

        bool hasGetter() const { return getter >= 0; }
        bool hasSetter() const { return setter >= 0; }
    };
protected:

    enum Format { ex, cx, nx };
    class Result {
        Reference _result;

        const BytecodeGenerator::Label *_iftrue = nullptr;
        const BytecodeGenerator::Label *_iffalse = nullptr;
        Format _format = ex;
        Format _requested;
        bool _trueBlockFollowsCondition = false;

    public:
        explicit Result(const QString &name)
            : _result(name)
            , _requested(ex)
        {}

        explicit Result(const Reference &lrvalue)
            : _result(lrvalue)
            , _requested(ex)
        {}

        explicit Result(Format requested = ex)
            : _requested(requested) {}

        explicit Result(const BytecodeGenerator::Label *iftrue,
                        const BytecodeGenerator::Label *iffalse,
                        bool trueBlockFollowsCondition)
            : _iftrue(iftrue)
            , _iffalse(iffalse)
            , _requested(cx)
            , _trueBlockFollowsCondition(trueBlockFollowsCondition)
        {
            Q_ASSERT(iftrue);
            Q_ASSERT(iffalse);
        }

        const BytecodeGenerator::Label *iftrue() const {
            Q_ASSERT(_requested == cx);
            return _iftrue;
        }

        const BytecodeGenerator::Label *iffalse() const {
            Q_ASSERT(_requested == cx);
            return _iffalse;
        }

        Format format() const {
            return _format;
        }

        bool accept(Format f)
        {
            if (_requested == f) {
                _format = f;
                return true;
            }
            return false;
        }

        bool trueBlockFollowsCondition() const {
            return _trueBlockFollowsCondition;
        }

        const Reference &result() const {
            return _result;
        }

        void setResult(const Reference &result) {
            _result = result;
        }

        void setResult(Reference &&result) {
            _result = std::move(result);
        }

        void clearResultName() {
            _result.name.clear();
        }
    };

    void enterContext(QQmlJS::AST::Node *node);
    int leaveContext();
public:
    Context *enterBlock(QQmlJS::AST::Node *node);
    int leaveBlock() { return leaveContext(); }
protected:
    void leaveLoop();

    enum UnaryOperation {
        UPlus,
        UMinus,
        PreIncrement,
        PreDecrement,
        PostIncrement,
        PostDecrement,
        Not,
        Compl
    };

    Reference unop(UnaryOperation op, const Reference &expr);

    void addCJump();

public:
    int registerString(const QString &name) {
        return jsUnitGenerator->registerString(name);
    }
    int registerConstant(QV4::ReturnedValue v)
    {
        return jsUnitGenerator->registerConstant(v);
    }
    int registerGetterLookup(int nameIndex, JSUnitGenerator::LookupMode mode)
    {
        return jsUnitGenerator->registerGetterLookup(nameIndex, mode);
    }
    int registerSetterLookup(int nameIndex)
    {
        return jsUnitGenerator->registerSetterLookup(nameIndex);
    }
    int registerGlobalGetterLookup(int nameIndex, JSUnitGenerator::LookupMode mode)
    {
        return jsUnitGenerator->registerGlobalGetterLookup(nameIndex, mode);
    }
    int registerQmlContextPropertyGetterLookup(int nameIndex, JSUnitGenerator::LookupMode mode)
    {
        return jsUnitGenerator->registerQmlContextPropertyGetterLookup(nameIndex, mode);
    }

    // Returns index in _module->functions
    int defineFunction(const QString &name, QQmlJS::AST::Node *ast,
                               QQmlJS::AST::FormalParameterList *formals,
                               QQmlJS::AST::StatementList *body);

protected:
    void statement(QQmlJS::AST::Statement *ast);
    void statement(QQmlJS::AST::ExpressionNode *ast);
    void condition(QQmlJS::AST::ExpressionNode *ast, const BytecodeGenerator::Label *iftrue,
                   const BytecodeGenerator::Label *iffalse,
                   bool trueBlockFollowsCondition);

    inline Reference expression(QQmlJS::AST::ExpressionNode *ast, const QString &name = QString())
    {
        if (!ast || hasError())
            return Reference();

        pushExpr(name);
        ast->accept(this);
        return popResult();
    }

    inline void accept(QQmlJS::AST::Node *node)
    {
        if (!hasError() && node)
            node->accept(this);
    }

    void program(QQmlJS::AST::Program *ast);
    void statementList(QQmlJS::AST::StatementList *ast);
    void variableDeclaration(QQmlJS::AST::PatternElement *ast);
    void variableDeclarationList(QQmlJS::AST::VariableDeclarationList *ast);

    Reference targetForPatternElement(QQmlJS::AST::PatternElement *p);
    void initializeAndDestructureBindingElement(QQmlJS::AST::PatternElement *e, const Reference &baseRef = Reference(), bool isDefinition = false);
    void destructurePropertyList(const Reference &object, QQmlJS::AST::PatternPropertyList *bindingList, bool isDefinition = false);
    void destructureElementList(const Reference &array, QQmlJS::AST::PatternElementList *bindingList, bool isDefinition = false);
    void destructurePattern(QQmlJS::AST::Pattern *p, const Reference &rhs);

    Reference referenceForPropertyName(const Codegen::Reference &object, QQmlJS::AST::PropertyName *name);

    void emitReturn(const Reference &expr);

    // nodes
    bool visit(QQmlJS::AST::ArgumentList *ast) override;
    bool visit(QQmlJS::AST::CaseBlock *ast) override;
    bool visit(QQmlJS::AST::CaseClause *ast) override;
    bool visit(QQmlJS::AST::CaseClauses *ast) override;
    bool visit(QQmlJS::AST::Catch *ast) override;
    bool visit(QQmlJS::AST::DefaultClause *ast) override;
    bool visit(QQmlJS::AST::Elision *ast) override;
    bool visit(QQmlJS::AST::Finally *ast) override;
    bool visit(QQmlJS::AST::FormalParameterList *ast) override;
    bool visit(QQmlJS::AST::Program *ast) override;
    bool visit(QQmlJS::AST::StatementList *ast) override;
    bool visit(QQmlJS::AST::UiArrayMemberList *ast) override;
    bool visit(QQmlJS::AST::UiImport *ast) override;
    bool visit(QQmlJS::AST::UiHeaderItemList *ast) override;
    bool visit(QQmlJS::AST::UiPragmaValueList *ast) override;
    bool visit(QQmlJS::AST::UiPragma *ast) override;
    bool visit(QQmlJS::AST::UiObjectInitializer *ast) override;
    bool visit(QQmlJS::AST::UiObjectMemberList *ast) override;
    bool visit(QQmlJS::AST::UiParameterList *ast) override;
    bool visit(QQmlJS::AST::UiProgram *ast) override;
    bool visit(QQmlJS::AST::UiQualifiedId *ast) override;
    bool visit(QQmlJS::AST::VariableDeclarationList *ast) override;

    bool visit(QQmlJS::AST::PatternElement *ast) override;
    bool visit(QQmlJS::AST::PatternElementList *ast) override;
    bool visit(QQmlJS::AST::PatternProperty *ast) override;
    bool visit(QQmlJS::AST::PatternPropertyList *ast) override;

    bool visit(QQmlJS::AST::ExportDeclaration *ast) override;

    bool visit(QQmlJS::AST::TypeAnnotation *ast) override;

    // expressions
    bool visit(QQmlJS::AST::ArrayPattern *ast) override;
    bool visit(QQmlJS::AST::ArrayMemberExpression *ast) override;
    bool visit(QQmlJS::AST::BinaryExpression *ast) override;
    bool visit(QQmlJS::AST::CallExpression *ast) override;
    void endVisit(QQmlJS::AST::CallExpression *ast) override;
    bool visit(QQmlJS::AST::CommaExpression *ast) override;
    bool visit(QQmlJS::AST::ConditionalExpression *ast) override;
    bool visit(QQmlJS::AST::DeleteExpression *ast) override;
    void endVisit(QQmlJS::AST::DeleteExpression *ast) override;
    bool visit(QQmlJS::AST::FalseLiteral *ast) override;
    bool visit(QQmlJS::AST::SuperLiteral *ast) override;
    bool visit(QQmlJS::AST::FieldMemberExpression *ast) override;
    void endVisit(QQmlJS::AST::FieldMemberExpression *ast) override;
    bool visit(QQmlJS::AST::TaggedTemplate *ast) override;
    bool visit(QQmlJS::AST::FunctionExpression *ast) override;
    bool visit(QQmlJS::AST::IdentifierExpression *ast) override;
    bool visit(QQmlJS::AST::NestedExpression *ast) override;
    bool visit(QQmlJS::AST::NewExpression *ast) override;
    bool visit(QQmlJS::AST::NewMemberExpression *ast) override;
    bool visit(QQmlJS::AST::NotExpression *ast) override;
    bool visit(QQmlJS::AST::NullExpression *ast) override;
    bool visit(QQmlJS::AST::NumericLiteral *ast) override;
    bool visit(QQmlJS::AST::ObjectPattern *ast) override;
    bool visit(QQmlJS::AST::PostDecrementExpression *ast) override;
    bool visit(QQmlJS::AST::PostIncrementExpression *ast) override;
    bool visit(QQmlJS::AST::PreDecrementExpression *ast) override;
    bool visit(QQmlJS::AST::PreIncrementExpression *ast) override;
    bool visit(QQmlJS::AST::RegExpLiteral *ast) override;
    bool visit(QQmlJS::AST::StringLiteral *ast) override;
    bool visit(QQmlJS::AST::TemplateLiteral *ast) override;
    bool visit(QQmlJS::AST::ThisExpression *ast) override;
    bool visit(QQmlJS::AST::TildeExpression *ast) override;
    bool visit(QQmlJS::AST::TrueLiteral *ast) override;
    bool visit(QQmlJS::AST::TypeOfExpression *ast) override;
    bool visit(QQmlJS::AST::UnaryMinusExpression *ast) override;
    bool visit(QQmlJS::AST::UnaryPlusExpression *ast) override;
    bool visit(QQmlJS::AST::VoidExpression *ast) override;
    bool visit(QQmlJS::AST::FunctionDeclaration *ast) override;
    bool visit(QQmlJS::AST::YieldExpression *ast) override;
    bool visit(QQmlJS::AST::ClassExpression *ast) override;
    bool visit(QQmlJS::AST::ClassDeclaration *ast) override;

    // statements
    bool visit(QQmlJS::AST::Block *ast) override;
    bool visit(QQmlJS::AST::BreakStatement *ast) override;
    bool visit(QQmlJS::AST::ContinueStatement *ast) override;
    bool visit(QQmlJS::AST::DebuggerStatement *ast) override;
    bool visit(QQmlJS::AST::DoWhileStatement *ast) override;
    bool visit(QQmlJS::AST::EmptyStatement *ast) override;
    bool visit(QQmlJS::AST::ExpressionStatement *ast) override;
    bool visit(QQmlJS::AST::ForEachStatement *ast) override;
    bool visit(QQmlJS::AST::ForStatement *ast) override;
    bool visit(QQmlJS::AST::IfStatement *ast) override;
    bool visit(QQmlJS::AST::LabelledStatement *ast) override;
    bool visit(QQmlJS::AST::ReturnStatement *ast) override;
    bool visit(QQmlJS::AST::SwitchStatement *ast) override;
    bool visit(QQmlJS::AST::ThrowStatement *ast) override;
    bool visit(QQmlJS::AST::TryStatement *ast) override;
    bool visit(QQmlJS::AST::VariableStatement *ast) override;
    bool visit(QQmlJS::AST::WhileStatement *ast) override;
    bool visit(QQmlJS::AST::WithStatement *ast) override;

    // ui object members
    bool visit(QQmlJS::AST::UiArrayBinding *ast) override;
    bool visit(QQmlJS::AST::UiObjectBinding *ast) override;
    bool visit(QQmlJS::AST::UiObjectDefinition *ast) override;
    bool visit(QQmlJS::AST::UiPublicMember *ast) override;
    bool visit(QQmlJS::AST::UiScriptBinding *ast) override;
    bool visit(QQmlJS::AST::UiSourceElement *ast) override;

    bool throwSyntaxErrorOnEvalOrArgumentsInStrictMode(const Reference &r,
                                                       const QQmlJS::SourceLocation &loc);
    virtual void throwSyntaxError(const QQmlJS::SourceLocation &loc, const QString &detail);
    virtual void throwReferenceError(const QQmlJS::SourceLocation &loc, const QString &detail);
    void throwRecursionDepthError() override
    {
        throwSyntaxError(QQmlJS::SourceLocation(),
                         QStringLiteral("Maximum statement or expression depth exceeded"));
    }

public:
    enum ErrorType {
        NoError,
        SyntaxError,
        ReferenceError
    };

    ErrorType errorType() const { return _errorType; }
    bool hasError() const { return _errorType != NoError; }
    QQmlJS::DiagnosticMessage error() const;
    QUrl url() const;

    Reference binopHelper(QQmlJS::AST::BinaryExpression *ast, QSOperator::Op oper, Reference &left,
                          Reference &right);
    Reference jumpBinop(QSOperator::Op oper, Reference &left, Reference &right);
    struct Arguments { int argc; int argv; bool hasSpread; };
    Arguments pushArgs(QQmlJS::AST::ArgumentList *args);
    void handleCall(Reference &base, Arguments calldata, int slotForFunction, int slotForThisObject, bool optional = false);

    Arguments pushTemplateArgs(QQmlJS::AST::TemplateLiteral *args);
    bool handleTaggedTemplate(Reference base, QQmlJS::AST::TaggedTemplate *ast);
    void createTemplateObject(QQmlJS::AST::TemplateLiteral *t);

    void setUseFastLookups(bool b) { useFastLookups = b; }

    void handleTryCatch(QQmlJS::AST::TryStatement *ast);
    void handleTryFinally(QQmlJS::AST::TryStatement *ast);


    Reference referenceForName(
            const QString &name, bool lhs,
            const QQmlJS::SourceLocation &accessLocation = QQmlJS::SourceLocation());

    QQmlRefPointer<QV4::CompiledData::CompilationUnit> generateCompilationUnit(
            bool generateUnitData = true);
    static QQmlRefPointer<QV4::CompiledData::CompilationUnit> compileModule(
            bool debugMode, const QString &url, const QString &sourceCode,
            const QDateTime &sourceTimeStamp, QList<QQmlJS::DiagnosticMessage> *diagnostics);

    static const QV4::CompiledData::Unit *generateNativeModuleUnitData(
            bool debugMode, const QString &url, const QV4::Value &value);

    Context *currentContext() const { return _context; }
    BytecodeGenerator *generator() const { return bytecodeGenerator; }

    void loadClosure(int index);

    Module *module() const { return _module; }

    BytecodeGenerator::Label returnLabel() {
        if (!_returnLabel)
            _returnLabel = new BytecodeGenerator::Label(bytecodeGenerator->newLabel());
        return *_returnLabel;
    }

protected:
    friend class ScanFunctions;
    friend struct ControlFlow;
    friend struct ControlFlowCatch;
    friend struct ControlFlowFinally;

    inline void setExprResult(const Reference &result) { m_expressions.back().setResult(result); }
    inline void setExprResult(Reference &&result) { m_expressions.back().setResult(std::move(result)); }
    inline Reference exprResult() const { return m_expressions.back().result(); }
    inline void clearExprResultName() { m_expressions.back().clearResultName(); }

    inline bool exprAccept(Format f) { return m_expressions.back().accept(f); }

    inline const Result &currentExpr() const { return m_expressions.back(); }

    inline void pushExpr(Result &&expr) { m_expressions.push_back(std::move(expr)); }
    inline void pushExpr(const Result &expr) { m_expressions.push_back(expr); }
    inline void pushExpr(const QString &name = QString()) { m_expressions.emplace_back(name); }

    inline Result popExpr()
    {
        const Result result = m_expressions.back();
        m_expressions.pop_back();
        return result;
    }

    inline Reference popResult() {
        const Reference result = m_expressions.back().result();
        m_expressions.pop_back();
        return result;
    }

    std::vector<Result> m_expressions;
    VolatileMemoryLocations _volatileMemoryLocations;
    Module *_module;
    int _returnAddress;
    Context *_context;
    Context *_functionContext = nullptr;
    QQmlJS::AST::LabelledStatement *_labelledStatement;
    QV4::Compiler::JSUnitGenerator *jsUnitGenerator;
    BytecodeGenerator *bytecodeGenerator = nullptr;
    Moth::BytecodeGenerator::Label *_returnLabel = nullptr;
    bool _strictMode;
    bool useFastLookups = true;
    bool requiresReturnValue = false;
    bool insideSwitch = false;
    bool inFormalParameterList = false;
    bool functionEndsWithReturn = false;
    bool _tailCallsAreAllowed = true;
    bool storeSourceLocations = false;

    struct OptionalChainState
    {
        QQmlJS::AST::Node *tailNodeOfChain = nullptr;
        std::vector<Moth::BytecodeGenerator::Jump> jumpsToPatch;
        bool actuallyHasOptionals = false;
    };
    QSet<QQmlJS::AST::Node*> m_seenOptionalChainNodes;
    std::stack<OptionalChainState> m_optionalChainsStates;

    ControlFlow *controlFlow = nullptr;

    bool _fileNameIsUrl;
    ErrorType _errorType = NoError;
    QQmlJS::DiagnosticMessage _error;
    CodegenWarningInterface *_interface;

    class TailCallBlocker
    {
    public:
        TailCallBlocker(Codegen *cg, bool onoff = false)
            : _cg(cg)
            , _saved(_cg->_tailCallsAreAllowed)
            , _onoff(onoff)
        { _cg->_tailCallsAreAllowed = onoff; }

        ~TailCallBlocker()
        { _cg->_tailCallsAreAllowed = _saved; }

        void unblock() const
        { _cg->_tailCallsAreAllowed = _saved; }

        void reblock() const
        { _cg->_tailCallsAreAllowed = _onoff; }

    private:
        Codegen *_cg;
        bool _saved;
        bool _onoff;
    };

private:
    Q_DISABLE_COPY(Codegen)
    VolatileMemoryLocations scanVolatileMemoryLocations(QQmlJS::AST::Node *ast);
    void handleConstruct(const Reference &base, QQmlJS::AST::ArgumentList *args);
    void throwError(ErrorType errorType, const QQmlJS::SourceLocation &loc,
                    const QString &detail);
    bool traverseOptionalChain(QQmlJS::AST::Node *node);
    void optionalChainFinalizer(const Reference &expressionResult, bool tailOfChain,
                                bool isDeleteExpression = false);
    Reference loadSubscriptForCall(const Reference &base);
    void generateThrowException(const QString &type, const QString &text = QString());
};

}

}

QT_END_NAMESPACE

#endif // QV4CODEGEN_P_H
