/**
 * Defines a function declaration.
 *
 * Includes:
 * - function/delegate literals
 * - function aliases
 * - (static/shared) constructors/destructors/post-blits
 * - `invariant`
 * - `unittest`
 *
 * Copyright:   Copyright (C) 1999-2026 by The D Language Foundation, All Rights Reserved
 * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/func.d, _func.d)
 * Documentation:  https://dlang.org/phobos/dmd_func.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/func.d
 */

module dmd.func;

import core.stdc.stdio;
import core.stdc.string;
import dmd.aggregate;
import dmd.arraytypes;
import dmd.astenums;
import dmd.dclass;
import dmd.declaration;
import dmd.dsymbol;
import dmd.dtemplate;
import dmd.hdrgen;
import dmd.id;
import dmd.identifier;
import dmd.location;
import dmd.mtype;
import dmd.objc;
import dmd.common.outbuffer;
import dmd.rootobject;
import dmd.statement;
import dmd.targetcompiler;
import dmd.tokens;
import dmd.visitor;

/// Inline Status
enum ILS : ubyte
{
    uninitialized,       /// not computed yet
    no,                  /// cannot inline
    yes,                 /// can inline
}

enum BUILTIN : ubyte
{
    unknown = 255,   /// not known if this is a builtin
    unimp = 0,       /// this is not a builtin
    gcc,             /// this is a GCC builtin
    llvm,            /// this is an LLVM builtin
    sin,
    cos,
    tan,
    sqrt,
    fabs,
    ldexp,
    log,
    log2,
    log10,
    exp,
    expm1,
    exp2,
    round,
    floor,
    ceil,
    trunc,
    copysign,
    pow,
    fmin,
    fmax,
    fma,
    isnan,
    isinfinity,
    isfinite,
    bsf,
    bsr,
    bswap,
    popcnt,
    yl2x,
    yl2xp1,
    toPrecFloat,
    toPrecDouble,
    toPrecReal,
    ctfeWrite,
}

private struct FUNCFLAG
{
    bool purityInprocess;    /// working on determining purity
    bool safetyInprocess;    /// working on determining safety
    bool nothrowInprocess;   /// working on determining nothrow
    bool nogcInprocess;      /// working on determining @nogc
    bool saferD;             /// do -preview=safer checks if this function has default safety
    bool scopeInprocess;     /// infer `return` and `scope` for parameters
    bool inlineScanned;      /// function has been scanned for inline possibilities
    bool hasCatches;         /// function has try-catch statements
    bool skipCodegen;        /// do not generate code for this function.
    bool printf;             /// is a printf-like function

    bool scanf;              /// is a scanf-like function
    bool noreturn;           /// the function does not return
    bool isNRVO = true;      /// Support for named return value optimization
    bool isNaked;            /// The function is 'naked' (see inline ASM)
    bool isGenerated;        /// The function is compiler generated (e.g. `opCmp`)
    bool isIntroducing;      /// If this function introduces the overload set
    bool hasSemantic3Errors; /// If errors in semantic3 this function's frame ptr
    bool hasNoEH;            /// No exception unwinding is needed
    bool inferRetType;       /// Return type is to be inferred
    bool hasDualContext;     /// has a dual-context 'this' parameter

    bool hasAlwaysInlines;   /// Contains references to functions that must be inlined
    bool isCrtCtor;          /// Has attribute pragma(crt_constructor)
    bool isCrtDtor;          /// Has attribute pragma(crt_destructor)
    bool hasEscapingSiblings;/// Has sibling functions that escape
    bool computedEscapingSiblings; /// `hasEscapingSiblings` has been computed
    bool dllImport;          /// __declspec(dllimport)
    bool dllExport;          /// __declspec(dllexport)

    bool hasReturnExp;         /// Has return exp; statement
    bool hasInlineAsm;         /// Has asm{} statement
    bool hasMultipleReturnExp; /// Has multiple return exp; statements
}

/***********************************************************
 * Tuple of result identifier (possibly null) and statement.
 * This is used to store out contracts: out(id){ ensure }
 */
extern (C++) struct Ensure
{
    Identifier id;
    Statement ensure;

    Ensure syntaxCopy()
    {
        return Ensure(id, ensure.syntaxCopy());
    }

    /*****************************************
     * Do syntax copy of an array of Ensure's.
     */
    static Ensures* arraySyntaxCopy(Ensures* a)
    {
        if (!a)
            return null;

        Ensures* b = a.copy();
        foreach (i, e; *a)
            (*b)[i] = e.syntaxCopy();
        return b;
    }

}

/***********************************************************
 * Most functions don't have contracts, so save memory by grouping
 * this information into a separate struct
 */
private struct ContractInfo
{
    Statements* frequires;              /// in contracts
    Ensures* fensures;                  /// out contracts
    Statement frequire;                 /// lowered in contract
    Statement fensure;                  /// lowered out contract
    FuncDeclaration fdrequire;          /// function that does the in contract
    FuncDeclaration fdensure;           /// function that does the out contract
    Expressions* fdrequireParams;       /// argument list for __require
    Expressions* fdensureParams;        /// argument list for __ensure
}

/// Information for data flow analysis regarding parameters
extern(D) struct ParametersDFAInfo
{
    ParameterDFAInfo thisPointer;
    ParameterDFAInfo returnValue;
    ParameterDFAInfo[] parameters;
}

/// Information for data flow analysis per parameter
extern(D) struct ParameterDFAInfo
{
    /// Parameter id: -1 for into unknown, -2 this, -3 return, otherwise it is FuncDeclaration.parameters index
    int parameterId;

    /// User supplied attributes
    Inferrable userSupplied;
    /// Inferred attributes
    Inferrable inferred;

    /// Is parameter by-ref, so will be unpredictable after call from callee's perspective
    bool isByRef;

    /// Given a property, has it been specificed and is it guaranteed?
    enum Fact : ubyte
    {
        ///
        Unspecified,
        ///
        NotGuaranteed,
        ///
        Guaranteed
    }

    ///
    enum EscapedRelationship
    {
        ///
        Unknown = 0b00,
        ///
        ByValue = 0b01,
        ///
        PointerTo = 0b10,
        ///
        Borrows = 0b11,
    }

    ///
    struct Inferrable
    {
        /// Is the parameter non-null upon input, only applies to pointers.
        Fact notNullIn;
        /// Is the parameter non-null, applies only for by-ref parameters that are pointers.
        Fact notNullOut;

        /// If we escape into nothing, and still modellable (scope)
        bool escapeIntoNothing;

        /**
        return, this, into-the-unknown, (29x) parameters...

        00 = unknown
        01 = by-value
        10 = pointer-to
        11 = borrows
        */
        ulong escapesInto;

        /// Will this parameter escape into the following parameter id, at what relationship strength?
        EscapedRelationship willEscape(int id)
        {
            if (id >= 29)
                return EscapedRelationship.Unknown;

            if (id == -3)
                return cast(EscapedRelationship)(this.escapesInto & 0x3);
            else if (id == -2)
                return cast(EscapedRelationship)((this.escapesInto >> 2) & 0x3);
            else if (id == -1)
                return cast(EscapedRelationship)((this.escapesInto >> 4) & 0x3);

            this.escapesInto >>= 6 + (id * 2);
            return cast(EscapedRelationship)(this.escapesInto & 0x3);
        }

        /// Set this parameter's relationship strength to the following parameter id.
        void willEscape(int id, EscapedRelationship as)
        {
            if (id >= 29)
                return;

            int shift;

            if (id == -3)
                shift = 0;
            else if (id == -2)
                shift = 2;
            else if (id == -1)
                shift = 4;
            else
                shift = 6 + (id * 2);

            const ulong mask = 3UL << shift;
            this.escapesInto = (this.escapesInto & ~mask) | ((cast(ulong) as & 0x3) << shift);
        }
    }

    ///
    Fact notNullIn()
    {
        return inferred.notNullIn > userSupplied.notNullIn ? inferred.notNullIn : userSupplied.notNullIn;
    }

    ///
    Fact notNullOut()
    {
        return inferred.notNullOut > inferred.notNullOut ? inferred.notNullOut : userSupplied.notNullOut;
    }

    bool escapeIntoNothing()
    {
        return userSupplied.escapeIntoNothing || inferred.escapeIntoNothing;
    }
}

/***********************************************************
 */
extern (C++) class FuncDeclaration : Declaration
{
    Statement fbody;                    /// function body

    FuncDeclarations foverrides;        /// functions this function overrides

    private ContractInfo* contracts;    /// contract information

    const(char)* mangleString;          /// mangled symbol created from mangleExact()

    VarDeclaration vresult;             /// result variable for out contracts
    LabelDsymbol returnLabel;           /// where the return goes

    bool[size_t] isTypeIsolatedCache;   /// cache for the potentially very expensive isTypeIsolated check

    // used to prevent symbols in different
    // scopes from having the same name
    DsymbolTable localsymtab;
    VarDeclaration vthis;               /// 'this' parameter (member and nested)
    VarDeclaration v_arguments;         /// '_arguments' parameter

    VarDeclaration v_argptr;            /// '_argptr' variable
    VarDeclarations* parameters;        /// Array of VarDeclaration's for parameters
    DsymbolTable labtab;                /// statement label symbol table
    Dsymbol overnext;                   /// next in overload list
    FuncDeclaration overnext0;          /// next in overload list (only used during IFTI)
    Loc endloc;                         /// location of closing curly bracket
    int vtblIndex = -1;                 /// for member functions, index into vtbl[]

    ILS inlineStatusStmt = ILS.uninitialized;
    ILS inlineStatusExp = ILS.uninitialized;
    PINLINE inlining = PINLINE.default_;

    int inlineNest;                     /// !=0 if nested inline

    ForeachStatement fes;               /// if foreach body, this is the foreach
    BaseClass* interfaceVirtual;        /// if virtual, but only appears in base interface vtbl[]
    /** if !=NULL, then this is the type
    of the 'introducing' function
    this one is overriding
    */
    Type tintro;

    STC storage_class2;        /// storage class for template onemember's

    // Things that should really go into Scope

    VarDeclaration nrvo_var;            /// variable to replace with shidden
    void* shidden;                    /// hidden pointer passed to function

    ReturnStatements* returns;

    GotoStatements* gotos;              /// Gotos with forward references

    mixin FuncDeclarationExtra;

    /// set if this is a known, builtin function we can evaluate at compile time
    BUILTIN builtin = BUILTIN.unknown;

    /// set if someone took the address of this function
    int tookAddressOf;

    bool requiresClosure;               // this function needs a closure

    /** local variables in this function which are referenced by nested functions
     * (They'll get put into the "closure" for this function.)
     */
    VarDeclarations closureVars;

    /** Outer variables which are referenced by this nested function
     * (the inverse of closureVars)
     */
    VarDeclarations outerVars;

    // Most recent encountered `main` (`WinMain` or `DllMain`) function.
    // Track it to give error messages for multiple entrypoints
    __gshared FuncDeclaration lastMain;

    /// Sibling nested functions which called this one
    FuncDeclarations siblingCallers;

    FuncDeclarations* inlinedNestedCallees;

    /// In case of failed `@safe` inference, store the error that made the function `@system` for
    /// better diagnostics
    AttributeViolation* safetyViolation;
    AttributeViolation* nogcViolation;
    AttributeViolation* pureViolation;
    AttributeViolation* nothrowViolation;

    ParametersDFAInfo* parametersDFAInfo;

    /// See the `FUNCFLAG` struct
    import dmd.common.bitfields;
    mixin(generateBitFields!(FUNCFLAG, uint));

    /**
     * Data for a function declaration that is needed for the Objective-C
     * integration.
     */
    ObjcFuncDeclaration objc;

    extern (D) this(Loc loc, Loc endloc, Identifier ident, STC storage_class, Type type, bool noreturn = false)
    {
        super(DSYM.funcDeclaration, loc, ident);
        //.printf("FuncDeclaration(id = '%s', type = %s)\n", ident.toChars(), type.toChars());
        //.printf("storage_class = x%llx\n", storage_class);
        this.storage_class = storage_class;
        this.type = type;
        if (type)
        {
            // Normalize storage_class, because function-type related attributes
            // are already set in the 'type' in parsing phase.
            this.storage_class &= ~(STC.TYPECTOR | STC.FUNCATTR);
        }
        this.endloc = endloc;
        if (noreturn)
            this.noreturn = true;

        /* The type given for "infer the return type" is a TypeFunction with
         * NULL for the return type.
         */
        if (type)
        {
            if (auto tf = type.isTypeFunction())
                this.inferRetType = tf.next is null;
            else
                assert(0); // unreachable
        }
    }

    static FuncDeclaration create(Loc loc, Loc endloc, Identifier id, StorageClass storage_class, Type type, bool noreturn = false)
    {
        return new FuncDeclaration(loc, endloc, id, cast(STC) storage_class, type, noreturn);
    }

    final nothrow pure @safe
    {
        private ref ContractInfo getContracts()
        {
            if (!contracts)
                contracts = new ContractInfo();
            return *contracts;
        }

        // getters
        inout(Statements*) frequires() inout { return contracts ? contracts.frequires : null; }
        inout(Ensures*) fensures() inout { return contracts ? contracts.fensures : null; }
        inout(Statement) frequire() inout { return contracts ? contracts.frequire: null; }
        inout(Statement) fensure() inout { return contracts ? contracts.fensure : null; }
        inout(FuncDeclaration) fdrequire() inout { return contracts ? contracts.fdrequire : null; }
        inout(FuncDeclaration) fdensure() inout { return contracts ? contracts.fdensure: null; }
        inout(Expressions*) fdrequireParams() inout { return contracts ? contracts.fdrequireParams: null; }
        inout(Expressions*) fdensureParams() inout { return contracts ? contracts.fdensureParams: null; }

        extern (D) private static string generateContractSetter(string field, string type)
        {
            return type ~ " " ~ field ~ "(" ~ type ~ " param)" ~
                    "{
                        if (!param && !contracts) return null;
                        return getContracts()." ~ field ~ " = param;
                     }";
        }

        mixin(generateContractSetter("frequires", "Statements*"));
        mixin(generateContractSetter("fensures", "Ensures*"));
        mixin(generateContractSetter("frequire", "Statement"));
        mixin(generateContractSetter("fensure", "Statement"));
        mixin(generateContractSetter("fdrequire", "FuncDeclaration"));
        mixin(generateContractSetter("fdensure", "FuncDeclaration"));
        mixin(generateContractSetter("fdrequireParams", "Expressions*"));
        mixin(generateContractSetter("fdensureParams", "Expressions*"));
    }

    override FuncDeclaration syntaxCopy(Dsymbol s)
    {
        //printf("FuncDeclaration::syntaxCopy('%s')\n", toChars());
        FuncDeclaration f = s ? cast(FuncDeclaration)s
                              : new FuncDeclaration(loc, endloc, ident, storage_class, type.syntaxCopy(), this.noreturn != 0);
        f.frequires = frequires ? Statement.arraySyntaxCopy(frequires, null) : null;
        f.fensures = fensures ? Ensure.arraySyntaxCopy(fensures) : null;
        f.fbody = fbody ? fbody.syntaxCopy() : null;
        return f;
    }

    /********************************************
     * find function template root in overload list
     */
    extern (D) final TemplateDeclaration findTemplateDeclRoot()
    {
        FuncDeclaration f = this;
        while (f && f.overnext)
        {
            //printf("f.overnext = %p %s\n", f.overnext, f.overnext.toChars());
            if (TemplateDeclaration td = f.overnext.isTemplateDeclaration())
                return td;
            f = f.overnext.isFuncDeclaration();
        }
        return null;
    }

    /********************************************
     * Returns true if function was declared
     * directly or indirectly in a unittest block
     */
    final bool inUnittest()
    {
        Dsymbol f = this;
        do
        {
            if (f.isUnitTestDeclaration())
                return true;
            f = f.toParent();
        }
        while (f);
        return false;
    }

    /********************************
     * Searches for a label with the given identifier. This function will insert a new
     * `LabelDsymbol` into `labtab` if it does not contain a mapping for `ident`.
     *
     * Params:
     *   ident = identifier of the requested label
     *   loc   = location used when creating a new `LabelDsymbol`
     *
     * Returns: the `LabelDsymbol` for `ident`
     */
    final LabelDsymbol searchLabel(Identifier ident, Loc loc)
    {
        Dsymbol s;
        if (!labtab)
            labtab = new DsymbolTable(); // guess we need one

        s = labtab.lookup(ident);
        if (!s)
        {
            s = new LabelDsymbol(ident, loc);
            labtab.insert(s);
        }
        return cast(LabelDsymbol)s;
    }

    override const(char)* toPrettyChars(bool QualifyTypes = false, bool keepOneMember = false)
    {
        if (isDMain())
            return "D main";
        return Dsymbol.toPrettyChars(QualifyTypes, keepOneMember);
    }

    /** for diagnostics, e.g. 'int foo(int x, int y) pure' */
    final const(char)* toFullSignature()
    {
        OutBuffer buf;
        functionToBufferWithIdent(type.toTypeFunction(), buf, toChars(), isStatic);
        return buf.extractChars();
    }

    final bool isDMain() const
    {
        return ident == Id.main && resolvedLinkage() != LINK.c && !isMember() && !isNested();
    }

    final bool isCMain() const
    {
        return ident == Id.main && resolvedLinkage() == LINK.c && !isMember() && !isNested();
    }

    final bool isWinMain() const
    {
        //printf("FuncDeclaration::isWinMain() %s\n", toChars());
        version (none)
        {
            bool x = ident == Id.WinMain && resolvedLinkage() != LINK.c && !isMember();
            printf("%s\n", x ? "yes" : "no");
            return x;
        }
        else
        {
            return ident == Id.WinMain && resolvedLinkage() != LINK.c && !isMember();
        }
    }

    final bool isDllMain() const
    {
        return ident == Id.DllMain && resolvedLinkage() != LINK.c && !isMember();
    }

    final bool isRtInit() const
    {
        return ident == Id.rt_init && resolvedLinkage() == LINK.c && !isMember() && !isNested();
    }

    override final bool isExport() const
    {
        return visibility.kind == Visibility.Kind.export_ || dllExport;
    }

    override final bool isImportedSymbol() const
    {
        //printf("isImportedSymbol()\n");
        //printf("protection = %d\n", visibility);
        return (visibility.kind == Visibility.Kind.export_ || dllImport) && !fbody;
    }

    override final bool isCodeseg() const pure nothrow @nogc @safe
    {
        return true; // functions are always in the code segment
    }

    override final bool isOverloadable() const
    {
        return true; // functions can be overloaded
    }

    /***********************************
     * Override so it can work even if semantic() hasn't yet
     * been run.
     */
    override final bool isAbstract()
    {
        if (storage_class & STC.abstract_)
            return true;
        if (semanticRun >= PASS.semanticdone)
            return false;

        if (_scope)
        {
           if (_scope.stc & STC.abstract_)
                return true;
           parent = _scope.parent;
           Dsymbol parent = toParent();
           if (parent.isInterfaceDeclaration())
                return true;
        }
        return false;
    }

    extern (D) final uint saveFlags()
    {
        return bitFields;
    }

    extern (D) final uint restoreFlags(uint f)
    {
        bitFields = f;
        return bitFields;
    }


    /**************************************
     * The function is doing something that may throw an exception, register that in case nothrow is being inferred
     *
     * Params:
     *     loc = location of action
     *     format = format string for error message
     *     args = arguments to format string
     */
    extern (D) final void setThrow(Loc loc, const(char)* format, RootObject[] args...)
    {
        if (nothrowInprocess && !nothrowViolation)
        {
            nothrowViolation = new AttributeViolation(loc, format, args); // action that requires GC
        }
    }

    /**************************************
     * The function calls non-`nothrow` function f, register that in case nothrow is being inferred
     * Params:
     *     loc = location of call
     *     fd = function being called
     */
    extern (D) final void setThrowCall(Loc loc, FuncDeclaration fd)
    {
        if (nothrowInprocess && !nothrowViolation)
        {
            nothrowViolation = new AttributeViolation(loc, fd); // action that requires GC
        }
    }

    /****************************************
     * Determine if function needs a static frame pointer.
     * Returns:
     *  `true` if function is really nested within other function.
     * Contracts:
     *  If isNested() returns true, isThis() should return false,
     *  unless the function needs a dual-context pointer.
     */
    bool isNested() const
    {
        auto f = toAliasFunc();
        //printf("\ttoParent2() = '%s'\n", f.toParent2().toChars());
        return ((f.storage_class & STC.static_) == 0) &&
                (f._linkage == LINK.d) &&
                (f.toParent2().isFuncDeclaration() !is null ||
                 f.toParent2() !is f.toParentLocal());
    }

    /****************************************
     * Determine if function is a non-static member function
     * that has an implicit 'this' expression.
     * Returns:
     *  The aggregate it is a member of, or null.
     * Contracts:
     *  Both isThis() and isNested() should return true if function needs a dual-context pointer,
     *  otherwise if isThis() returns true, isNested() should return false.
     */
    override inout(AggregateDeclaration) isThis() inout
    {
        //printf("+FuncDeclaration::isThis() '%s'\n", toChars());
        auto ad = (storage_class & STC.static_) ? .objc.isThis(this) : isMemberLocal();
        //printf("-FuncDeclaration::isThis() %p\n", ad);
        return ad;
    }

    override final bool needThis()
    {
        //printf("FuncDeclaration::needThis() '%s'\n", toChars());
        return toAliasFunc().isThis() !is null;
    }

    final bool isFinalFunc() const
    {
        if (toAliasFunc() != this)
            return toAliasFunc().isFinalFunc();

        version (none)
        {{
            auto cd = toParent().isClassDeclaration();
            printf("FuncDeclaration::isFinalFunc(%s), %x\n", toChars(), Declaration.isFinal());
            printf("%p %d %d %d\n", isMember(), isStatic(), Declaration.isFinal(), ((cd = toParent().isClassDeclaration()) !is null && cd.storage_class & STC.final_));
            printf("result is %d\n", isMember() && (Declaration.isFinal() || (cd !is null && cd.storage_class & STC.final_)));
            if (cd)
                printf("\tmember of %s\n", cd.toChars());
        }}
        if (!isMember())
            return false;
        if (Declaration.isFinal())
            return true;
        auto cd = toParent().isClassDeclaration();
        return (cd !is null) && (cd.storage_class & STC.final_);
    }

    override const(char)* kind() const
    {
        return this.isGenerated ? "generated function" : "function";
    }

    /*********************************************
     * Returns: the function's parameter list, and whether
     * it is variadic or not.
     */
    final ParameterList getParameterList()
    {
        if (type)
        {
            if (TypeFunction fdtype = type.isTypeFunction()) // Could also be TypeError
                return fdtype.parameterList;
        }

        return ParameterList(null, VarArg.none);
    }

    inout(FuncDeclaration) toAliasFunc() inout @safe
    {
        return this;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/**
Checks for mismatching modifiers between `lhsMod` and `rhsMod` and prints the
mismatching modifiers to `buf`.

The modifiers of the `lhsMod` mismatching the ones with the `rhsMod` are printed, i.e.
lhs(shared) vs. rhs() prints "`shared`", wheras lhs() vs rhs(shared) prints "non-shared".

Params:
    buf = output buffer to write to
    lhsMod = modifier on the left-hand side
    lhsMod = modifier on the right-hand side

Returns:

A tuple with `isMutable` and `isNotShared` set
if the `lhsMod` is missing those modifiers (compared to rhs).
*/
auto MODMatchToBuffer(OutBuffer* buf, ubyte lhsMod, ubyte rhsMod)
{
    static struct Mismatches
    {
        bool isNotShared;
        bool isMutable;
    }

    Mismatches mismatches;

    bool bothMutable = ((lhsMod & rhsMod) == 0);
    bool sharedMismatch = ((lhsMod ^ rhsMod) & MODFlags.shared_) != 0;
    bool sharedMismatchOnly = ((lhsMod ^ rhsMod) == MODFlags.shared_);

    if (lhsMod & MODFlags.shared_)
        buf.writestring("`shared` ");
    else if (sharedMismatch && !(lhsMod & MODFlags.immutable_))
    {
        buf.writestring("non-shared ");
        mismatches.isNotShared = true;
    }

    if (bothMutable && sharedMismatchOnly)
    {
    }
    else if (lhsMod & MODFlags.immutable_)
        buf.writestring("`immutable` ");
    else if (lhsMod & MODFlags.const_)
        buf.writestring("`const` ");
    else if (lhsMod & MODFlags.wild)
        buf.writestring("`inout` ");
    else
    {
        buf.writestring("mutable ");
        mismatches.isMutable = true;
    }

    return mismatches;
}

///
unittest
{
    OutBuffer buf;
    auto mismatches = MODMatchToBuffer(&buf, MODFlags.shared_, 0);
    assert(buf[] == "`shared` ");
    assert(!mismatches.isNotShared);

    buf.setsize(0);
    mismatches = MODMatchToBuffer(&buf, 0, MODFlags.shared_);
    assert(buf[] == "non-shared ");
    assert(mismatches.isNotShared);

    buf.setsize(0);
    mismatches = MODMatchToBuffer(&buf, MODFlags.const_, 0);
    assert(buf[] == "`const` ");
    assert(!mismatches.isMutable);

    buf.setsize(0);
    mismatches = MODMatchToBuffer(&buf, 0, MODFlags.const_);
    assert(buf[] == "mutable ");
    assert(mismatches.isMutable);
}

/***********************************************************
 * Used as a way to import a set of functions from another scope into this one.
 */
extern (C++) final class FuncAliasDeclaration : FuncDeclaration
{
    FuncDeclaration funcalias;
    bool hasOverloads;

    extern (D) this(Identifier ident, FuncDeclaration funcalias, bool hasOverloads = true)
    {
        super(funcalias.loc, funcalias.endloc, ident, funcalias.storage_class, funcalias.type);
        assert(funcalias != this);
        this.funcalias = funcalias;
        this.dsym = DSYM.funcAliasDeclaration;

        this.hasOverloads = hasOverloads;
        if (hasOverloads)
        {
            if (FuncAliasDeclaration fad = funcalias.isFuncAliasDeclaration())
                this.hasOverloads = fad.hasOverloads;
        }
        else
        {
            // for internal use
            assert(!funcalias.isFuncAliasDeclaration());
            this.hasOverloads = false;
        }
        userAttribDecl = funcalias.userAttribDecl;
    }

    override const(char)* kind() const
    {
        return "function alias";
    }

    override inout(FuncDeclaration) toAliasFunc() inout
    {
        return funcalias.toAliasFunc();
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class FuncLiteralDeclaration : FuncDeclaration
{
    TOK tok;        // TOK.function_ or TOK.delegate_
    Type treq;      // target of return type inference

    // backend
    bool deferToObj;

    extern (D) this(Loc loc, Loc endloc, Type type, TOK tok, ForeachStatement fes, Identifier id = null, STC storage_class = STC.none)
    {
        super(loc, endloc, null, storage_class, type);
        this.dsym = DSYM.funcLiteralDeclaration;
        this.ident = id ? id : Id.empty;
        this.tok = tok;
        this.fes = fes;
        // Always infer scope for function literals
        // See https://issues.dlang.org/show_bug.cgi?id=20362
        this.scopeInprocess = true;
        //printf("FuncLiteralDeclaration() id = '%s', type = '%s'\n", this.ident.toChars(), type.toChars());
    }

    override FuncLiteralDeclaration syntaxCopy(Dsymbol s)
    {
        //printf("FuncLiteralDeclaration::syntaxCopy('%s')\n", toChars());
        assert(!s);
        auto f = new FuncLiteralDeclaration(loc, endloc, type.syntaxCopy(), tok, fes, ident, storage_class & STC.auto_);
        f.treq = treq; // don't need to copy
        FuncDeclaration.syntaxCopy(f);
        return f;
    }

    override bool isNested() const
    {
        //printf("FuncLiteralDeclaration::isNested() '%s'\n", toChars());
        return (tok != TOK.function_) && !isThis();
    }

    override inout(AggregateDeclaration) isThis() inout
    {
        return tok == TOK.delegate_ ? super.isThis() : null;
    }

    override const(char)* kind() const
    {
        // GCC requires the (char*) casts
        return (tok != TOK.function_) ? "delegate" : "function";
    }

    override const(char)* toPrettyChars(bool QualifyTypes = false, bool keepOneMember = false)
    {
        if (parent)
        {
            if (TemplateInstance ti = parent.isTemplateInstance())
                return ti.tempdecl.toPrettyChars(QualifyTypes, keepOneMember);
        }
        return Dsymbol.toPrettyChars(QualifyTypes, keepOneMember);
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class CtorDeclaration : FuncDeclaration
{
    bool isCpCtor;    // copy constructor
    bool isMoveCtor;  // move constructor (aka rvalue constructor)
    extern (D) this(Loc loc, Loc endloc, STC stc, Type type)
    {
        super(loc, endloc, Id.ctor, stc, type);
        this.dsym = DSYM.ctorDeclaration;
        //printf("CtorDeclaration(loc = %s) %s %p\n", loc.toChars(), toChars(), this);
    }

    override CtorDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto f = new CtorDeclaration(loc, endloc, storage_class, type.syntaxCopy());
        FuncDeclaration.syntaxCopy(f);
        return f;
    }

    override const(char)* kind() const
    {
        return isCpCtor ? "copy constructor" :
             isMoveCtor ? "move constructor" : "constructor";
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class PostBlitDeclaration : FuncDeclaration
{
    extern (D) this(Loc loc, Loc endloc, STC stc, Identifier id)
    {
        super(loc, endloc, id, stc, null);
        this.dsym = DSYM.postBlitDeclaration;
    }

    override PostBlitDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto dd = new PostBlitDeclaration(loc, endloc, storage_class, ident);
        FuncDeclaration.syntaxCopy(dd);
        return dd;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class DtorDeclaration : FuncDeclaration
{
    extern (D) this(Loc loc, Loc endloc)
    {
        super(loc, endloc, Id.dtor, STC.none, null);
        this.dsym = DSYM.dtorDeclaration;
    }

    extern (D) this(Loc loc, Loc endloc, STC stc, Identifier id)
    {
        super(loc, endloc, id, stc, null);
        this.dsym = DSYM.dtorDeclaration;
    }

    override DtorDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto dd = new DtorDeclaration(loc, endloc, storage_class, ident);
        FuncDeclaration.syntaxCopy(dd);
        return dd;
    }

    override const(char)* kind() const
    {
        return "destructor";
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) class StaticCtorDeclaration : FuncDeclaration
{
    extern (D) this(Loc loc, Loc endloc, STC stc)
    {
        super(loc, endloc, Identifier.generateIdWithLoc("_staticCtor", loc), STC.static_ | stc, null);
        this.dsym = DSYM.staticCtorDeclaration;
    }

    extern (D) this(Loc loc, Loc endloc, string name, STC stc)
    {
        super(loc, endloc, Identifier.generateIdWithLoc(name, loc), STC.static_ | stc, null);
        this.dsym = DSYM.staticCtorDeclaration;
    }

    override StaticCtorDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto scd = new StaticCtorDeclaration(loc, endloc, storage_class);
        FuncDeclaration.syntaxCopy(scd);
        return scd;
    }

    override final inout(AggregateDeclaration) isThis() inout @nogc nothrow pure @safe
    {
        return null;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class SharedStaticCtorDeclaration : StaticCtorDeclaration
{
    /// Exclude this constructor from cyclic dependency check
    bool standalone;

    extern (D) this(Loc loc, Loc endloc, STC stc)
    {
        super(loc, endloc, "_sharedStaticCtor", stc);
        this.dsym = DSYM.sharedStaticCtorDeclaration;
    }

    override SharedStaticCtorDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto scd = new SharedStaticCtorDeclaration(loc, endloc, storage_class);
        FuncDeclaration.syntaxCopy(scd);
        return scd;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) class StaticDtorDeclaration : FuncDeclaration
{
    VarDeclaration vgate; // 'gate' variable

    extern (D) this(Loc loc, Loc endloc, STC stc)
    {
        super(loc, endloc, Identifier.generateIdWithLoc("_staticDtor", loc), STC.static_ | stc, null);
        this.dsym = DSYM.staticDtorDeclaration;
    }

    extern (D) this(Loc loc, Loc endloc, string name, STC stc)
    {
        super(loc, endloc, Identifier.generateIdWithLoc(name, loc), STC.static_ | stc, null);
        this.dsym = DSYM.staticDtorDeclaration;
    }

    override StaticDtorDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto sdd = new StaticDtorDeclaration(loc, endloc, storage_class);
        FuncDeclaration.syntaxCopy(sdd);
        return sdd;
    }

    override final inout(AggregateDeclaration) isThis() inout
    {
        return null;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class SharedStaticDtorDeclaration : StaticDtorDeclaration
{
    extern (D) this(Loc loc, Loc endloc, STC stc)
    {
        super(loc, endloc, "_sharedStaticDtor", stc);
        this.dsym = DSYM.sharedStaticDtorDeclaration;
    }

    override SharedStaticDtorDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto sdd = new SharedStaticDtorDeclaration(loc, endloc, storage_class);
        FuncDeclaration.syntaxCopy(sdd);
        return sdd;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class InvariantDeclaration : FuncDeclaration
{
    extern (D) this(Loc loc, Loc endloc, STC stc, Identifier id, Statement fbody)
    {
        // Make a unique invariant for now; we'll fix it up as we add it to the aggregate invariant list.
        super(loc, endloc, id ? id : Identifier.generateId("__invariant"), stc, null);
        this.fbody = fbody;
        this.dsym = DSYM.invariantDeclaration;
    }

    override InvariantDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto id = new InvariantDeclaration(loc, endloc, storage_class, null, null);
        FuncDeclaration.syntaxCopy(id);
        return id;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}


/***********************************************************
 */
extern (C++) final class UnitTestDeclaration : FuncDeclaration
{
    char* codedoc;      // for documented unittest

    // toObjFile() these nested functions after this one
    FuncDeclarations deferredNested;

    extern (D) this(Loc loc, Loc endloc, STC stc, char* codedoc)
    {
        super(loc, endloc, Identifier.generateIdWithLoc("__unittest", loc), stc, null);
        this.codedoc = codedoc;
        this.dsym = DSYM.unitTestDeclaration;
    }

    override UnitTestDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto utd = new UnitTestDeclaration(loc, endloc, storage_class, codedoc);
        FuncDeclaration.syntaxCopy(utd);
        return utd;
    }

    override inout(AggregateDeclaration) isThis() inout
    {
        return null;
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 */
extern (C++) final class NewDeclaration : FuncDeclaration
{
    extern (D) this(Loc loc, STC stc)
    {
        super(loc, Loc.initial, Id.classNew, STC.static_ | stc, null);
        this.dsym = DSYM.newDeclaration;
    }

    override NewDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        auto f = new NewDeclaration(loc, storage_class);
        FuncDeclaration.syntaxCopy(f);
        return f;
    }

    override const(char)* kind() const
    {
        return "allocator";
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/// Stores a reason why a function failed to infer a function attribute like `@safe` or `pure`
///
/// Has two modes:
/// - a regular safety error, stored in `action`
/// - a call to a function without the attribute, which is a special case, because in that case,
///   that function might recursively also have a `AttributeViolation`. This way, in case
///   of a big call stack, the error can go down all the way to the root cause.
struct AttributeViolation
{
    Loc loc;               /// location of error

    FuncDeclaration fd;    /// function is the focus of the violation

    // -- OR --

    string action;   /// Action that made the attribute fail to get inferred

    VarDeclaration scopeVar;  /// For scope violations: the parameter whose scope status caused the issue

    this(Loc loc, FuncDeclaration fd) { this.loc = loc; this.fd = fd; }

    this(Loc loc, const(char)* fmt, RootObject[] args)
    {
        this.loc = loc;
        assert(args.length <= 4); // expand if necessary
        OutBuffer buf;
        buf.printf(fmt,
            args.length > 0 && args[0] ? args[0].toErrMsg() : "",
            args.length > 1 && args[1] ? args[1].toErrMsg() : "",
            args.length > 2 && args[2] ? args[2].toErrMsg() : "",
            args.length > 3 && args[3] ? args[3].toErrMsg() : "",
        );
        this.action = buf.extractSlice();
    }

    this(Loc loc, const(char)* fmt, VarDeclaration scopeVar, RootObject[] args)
    {
        this(loc, fmt, args);
        this.scopeVar = scopeVar;
    }
}
