/**
 * Do mangling for C++ linkage.
 *
 * This is the POSIX side of the implementation.
 * It exports two functions to C++, `toCppMangleItanium` and `cppTypeInfoMangleItanium`.
 *
 * Copyright: Copyright (C) 1999-2025 by The D Language Foundation, All Rights Reserved
 * Authors: Walter Bright, https://www.digitalmars.com
 * 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/mangle/cpp.d, _cppmangle.d)
 * Documentation:  https://dlang.org/phobos/dmd_cppmangle.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/mangle/cpp.d
 *
 * References:
 *  Follows Itanium C++ ABI 1.86 section 5.1
 *  http://refspecs.linux-foundation.org/cxxabi-1.86.html#mangling
 *  which is where the grammar comments come from.
 */

module dmd.mangle.cpp;

import core.stdc.stdio;

import dmd.arraytypes;
import dmd.astenums;
import dmd.attrib;
import dmd.declaration;
import dmd.dsymbol;
import dmd.dsymbolsem : isGNUABITag;
import dmd.dtemplate;
import dmd.errors;
import dmd.expression;
import dmd.func;
import dmd.globals;
import dmd.id;
import dmd.identifier;
import dmd.location;
import dmd.mtype;
import dmd.nspace;
import dmd.root.array;
import dmd.common.outbuffer;
import dmd.rootobject;
import dmd.root.string;
import dmd.target;
import dmd.typesem;
import dmd.visitor;


// C++ operators
enum CppOperator { Unknown, Cast, Assign, Eq, Index, Call, Unary, Binary, OpAssign }

/**************
 * Check if id is a C++ operator
 * Params:
 *      id = identifier to be checked
 * Returns:
 *      CppOperator, or Unknown if not a C++ operator
 */
package CppOperator isCppOperator(const scope Identifier id)
{
    with (Id) with (CppOperator)
    {
        return (id == opCast)     ? Cast     :
               (id == opAssign)   ? Assign   :
               (id == opEquals)   ? Eq       :
               (id == opIndex)    ? Index    :
               (id == opCall)     ? Call     :
               (id == opUnary)    ? Unary    :
               (id == opBinary)   ? Binary   :
               (id == opOpAssign) ? OpAssign :
                                    Unknown  ;
    }
}

///
const(char)* toCppMangleItanium(Dsymbol s)
{
    //printf("toCppMangleItanium(%s)\n", s.toChars());
    OutBuffer buf;
    scope CppMangleVisitor v = new CppMangleVisitor(&buf, s.loc);
    v.mangleOf(s);
    if (v.errors)
        fatal();
    return buf.extractChars();
}

///
const(char)* cppTypeInfoMangleItanium(Dsymbol s)
{
    //printf("cppTypeInfoMangle(%s)\n", s.toChars());
    OutBuffer buf;
    buf.writestring("_ZTI");    // "TI" means typeinfo structure
    scope CppMangleVisitor v = new CppMangleVisitor(&buf, s.loc);
    v.cpp_mangle_name(s, false);
    if (v.errors)
        fatal();
    return buf.extractChars();
}

///
const(char)* cppThunkMangleItanium(FuncDeclaration fd, int offset)
{
    //printf("cppThunkMangleItanium(%s)\n", fd.toChars());
    OutBuffer buf;
    buf.printf("_ZThn%u_", offset);  // "Th" means thunk, "n%u" is the call offset
    scope CppMangleVisitor v = new CppMangleVisitor(&buf, fd.loc);
    v.mangle_function_encoding(fd);
    if (v.errors)
        fatal();
    return buf.extractChars();
}

/******************************
 * Determine if sym is a full aggregate destructor.
 * Params:
 *      sym = Dsymbol
 * Returns:
 *      true if sym is an aggregate destructor
 */
bool isAggregateDtor(const Dsymbol sym)
{
    const dtor = sym.isDtorDeclaration();
    if (!dtor)
        return false;
    const ad = dtor.isMember();
    assert(ad);
    return dtor == ad.aggrDtor;
}

/// Context used when processing pre-semantic AST
private struct Context
{
    /// Template instance of the function being mangled
    TemplateInstance ti;
    /// Function declaration we're mangling
    FuncDeclaration fd;
    /// Current type / expression being processed (semantically analyzed)
    RootObject res;

    @disable ref Context opAssign(ref Context other);
    @disable ref Context opAssign(Context other);

    /**
     * Helper function to track `res`
     *
     * Params:
     *   next = Value to set `this.res` to.
     *          If `this.res` is `null`, the expression is not evalutated.
     *          This allow this code to be used even when no context is needed.
     *
     * Returns:
     *   The previous state of this `Context` object
     */
    private Context push(lazy RootObject next) @safe
    {
        auto r = this.res;
        if (r !is null)
            this.res = next;
        return Context(this.ti, this.fd, r);
    }

    /**
     * Reset the context to a previous one, making any adjustment necessary
     */
    private void pop(ref Context prev) @safe
    {
        this.res = prev.res;
    }
}

private final class CppMangleVisitor : Visitor
{
    /// Context used when processing pre-semantic AST
    private Context context;

    ABITagContainer abiTags;    /// Container for already-written ABI tags
    Objects components;         /// array of components available for substitution
    OutBuffer* buf;             /// append the mangling to buf[]
    Loc loc;                    /// location for use in error messages
    bool errors;                /// failed to mangle properly

    /**
     * Constructor
     *
     * Params:
     *   buf = `OutBuffer` to write the mangling to
     *   loc = `Loc` of the symbol being mangled
     */
    this(OutBuffer* buf, Loc loc) scope
    {
        this.buf = buf;
        this.loc = loc;
    }

    /*****
     * Entry point. Append mangling to buf[]
     * Params:
     *  s = symbol to mangle
     */
    void mangleOf(Dsymbol s)
    {
        if (VarDeclaration vd = s.isVarDeclaration())
        {
            mangle_variable(vd, vd.cppnamespace !is null);
        }
        else if (FuncDeclaration fd = s.isFuncDeclaration())
        {
            mangle_function(fd);
        }
        else
        {
            assert(0);
        }
    }

    /**
     * Mangle the return type of a function
     *
     * This is called on a templated function type.
     * Context is set to the `FuncDeclaration`.
     *
     * Params:
     *   preSemantic = the `FuncDeclaration`'s `originalType`
     */
    void mangleReturnType(TypeFunction preSemantic)
    {
        auto tf = this.context.res.asFuncDecl().type.isTypeFunction();
        Type rt = preSemantic.nextOf();
        // https://issues.dlang.org/show_bug.cgi?id=22739
        // auto return type means that rt is null.
        // if so, just pick up the type from the instance
        if (!rt)
            rt = tf.nextOf();
        if (tf.isRef)
            rt = rt.referenceTo();
        auto prev = this.context.push(tf.nextOf());
        scope (exit) this.context.pop(prev);
        this.headOfType(rt);
    }

    /**
     * Write a seq-id from an index number, excluding the terminating '_'
     *
     * Params:
     *   idx = the index in a substitution list.
     *         Note that index 0 has no value, and `S0_` would be the
     *         substitution at index 1 in the list.
     *
     * See-Also:
     *  https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.seq-id
     */
    private void writeSequenceFromIndex(size_t idx) @safe
    {
        if (idx)
        {
            void write_seq_id(size_t i)
            {
                if (i >= 36)
                {
                    write_seq_id(i / 36);
                    i %= 36;
                }
                i += (i < 10) ? '0' : 'A' - 10;
                buf.writeByte(cast(char)i);
            }

            write_seq_id(idx - 1);
        }
    }

    /**
     * Attempt to perform substitution on `p`
     *
     * If `p` already appeared in the mangling, it is stored as
     * a 'part', and short references in the form of `SX_` can be used.
     * Note that `p` can be anything: template declaration, struct declaration,
     * class declaration, namespace...
     *
     * Params:
     *   p = The object to attempt to substitute
     *   nested = Whether or not `p` is to be considered nested.
     *            When `true`, `N` will be prepended before the substitution.
     *
     * Returns:
     *   Whether `p` already appeared in the mangling,
     *   and substitution has been written to `this.buf`.
     */
    bool substitute(RootObject p, bool nested = false)
    {
        //printf("substitute %s\n", p ? p.toChars() : null);
        auto i = find(p);
        if (i < 0)
            return false;

        //printf("\tmatch\n");
        /* Sequence is S_, S0_, .., S9_, SA_, ..., SZ_, S10_, ...
         */
        if (nested)
            buf.writeByte('N');
        buf.writeByte('S');
        writeSequenceFromIndex(i);
        buf.writeByte('_');
        return true;
    }

    /******
     * See if `p` exists in components[]
     *
     * Note that components can contain `null` entries,
     * as the index used in mangling is based on the index in the array.
     *
     * If called with an object whose dynamic type is `Nspace`,
     * calls the `find(Nspace)` overload.
     *
     * Returns:
     *  index if found, -1 if not
     */
    int find(RootObject p)
    {
        //printf("find %p %d %s\n", p, p.dyncast(), p ? p.toChars() : null);
        scope v = new ComponentVisitor(p);
        foreach (i, component; components)
        {
            if (component)
                component.visitObject(v);
            if (v.result)
                return cast(int)i;
        }
        return -1;
    }

    /*********************
     * Append p to components[]
     */
    void append(RootObject p)
    {
        //printf("append %p %d %s\n", p, p.dyncast(), p ? p.toChars() : "null");
        components.push(p);
    }

    /**
     * Write an identifier preceded by its length
     *
     * Params:
     *   ident = `Identifier` to write to `this.buf`
     */
    void writeIdentifier(const ref Identifier ident)
    {
        const name = ident.toString();
        this.buf.print(name.length);
        this.buf.writestring(name);
    }

    /**
     * Insert the leftover ABI tags to the buffer
     *
     * This inset ABI tags that hasn't already been written
     * after the mangled name of the function.
     * For more details, see the `abiTags` variable.
     *
     * Params:
     *   off  = Offset to insert at
     *   tf   = Type of the function to mangle the return type of
     */
    void writeRemainingTags(size_t off, TypeFunction tf)
    {
        Array!StringExp toWrite;
        leftOver(tf, &this.abiTags.written, &toWrite);
        OutBuffer b2;
        foreach (se; toWrite)
        {
            auto tag = se.peekString();
            // We can only insert a slice, and each insert is a memmove,
            // so use a temporary buffer to keep it efficient.
            b2.reset();
            b2.writestring("B");
            b2.print(tag.length);
            b2.writestring(tag);
            this.buf.insert(off, b2[]);
            off += b2.length;
        }
    }

    /************************
     * Determine if symbol is indeed the global ::std namespace.
     * Params:
     *  s = symbol to check
     * Returns:
     *  true if it is ::std
     */
    static bool isStd(Dsymbol s)
    {
        if (!s)
            return false;

        if (auto cnd = s.isCPPNamespaceDeclaration())
            return isStd(cnd);

        return (s.ident == Id.std &&    // the right name
                s.isNspace() &&         // g++ disallows global "std" for other than a namespace
                !getQualifier(s));      // at global level
    }

    /// Ditto
    static bool isStd(CPPNamespaceDeclaration s)
    {
        return s && s.cppnamespace is null && s.ident == Id.std;
    }

    /************************
     * Determine if type is a C++ fundamental type.
     * Params:
     *  t = type to check
     * Returns:
     *  true if it is a fundamental type
     */
    static bool isFundamentalType(Type t)
    {
        // First check the target whether some specific ABI is being followed.
        bool isFundamental = void;
        if (target.cpp.fundamentalType(t, isFundamental))
            return isFundamental;

        if (auto te = t.isTypeEnum())
        {
            // Peel off enum type from special types.
            if (te.sym.isSpecial())
                t = te.memType();
        }

        // Fundamental arithmetic types:
        // 1. integral types: bool, char, int, ...
        // 2. floating point types: float, double, real
        // 3. void
        // 4. null pointer: std::nullptr_t (since C++11)
        if (t.ty == Tvoid || t.ty == Tbool)
            return true;
        if (t.ty == Tnull && global.params.cplusplus >= CppStdRevision.cpp11)
            return true;

        return t.isTypeBasic() && (t.isIntegral() || t.isReal());
    }

    /******************************
     * Write the mangled representation of a template argument.
     * Params:
     *  ti  = the template instance
     *  arg = the template argument index
     */
    void template_arg(TemplateInstance ti, size_t arg)
    {
        TemplateDeclaration td = ti.tempdecl.isTemplateDeclaration();
        assert(td);
        TemplateParameter tp = (*td.parameters)[arg];
        RootObject o = (*ti.tiargs)[arg];

        auto prev = this.context.push({
                TemplateInstance parentti;
                if (this.context.res.dyncast() == DYNCAST.dsymbol)
                    parentti = this.context.res.asFuncDecl().parent.isTemplateInstance();
                else
                {
                    auto parent = this.context.res.asType().toDsymbol(null).parent;
                    parentti = parent.isTemplateInstance();
                    // https://issues.dlang.org/show_bug.cgi?id=22760
                    // The template instance may sometimes have the form
                    // S1!int.S1, therefore the above instruction might yield null
                    if (parentti is null && parent.parent)
                        parentti = parent.parent.isTemplateInstance();
                }
                return (*parentti.tiargs)[arg];
            }());
        scope (exit) this.context.pop(prev);

        if (tp.isTemplateTypeParameter())
        {
            Type t = isType(o);
            assert(t);
            t.accept(this);
        }
        else if (TemplateValueParameter tv = tp.isTemplateValueParameter())
        {
            // <expr-primary> ::= L <type> <value number> E  # integer literal
            if (tv.valType.isIntegral())
            {
                Expression e = isExpression(o);
                assert(e);
                buf.writeByte('L');
                tv.valType.accept(this);
                auto val = e.toUInteger();
                if (!tv.valType.isUnsigned() && cast(sinteger_t)val < 0)
                {
                    val = -val;
                    buf.writeByte('n');
                }
                buf.print(val);
                buf.writeByte('E');
            }
            else
            {
                .error(ti.loc, "%s `%s` internal compiler error: C++ `%s` template value parameter is not supported", ti.kind, ti.toPrettyChars, tv.valType.toChars());
                errors = true;
                return;
            }
        }
        else if (tp.isTemplateAliasParameter())
        {
            // Passing a function as alias parameter is the same as passing
            // `&function`
            Dsymbol d = isDsymbol(o);
            Expression e = isExpression(o);
            if (d && d.isFuncDeclaration())
            {
                // X .. E => template parameter is an expression
                // 'ad'   => unary operator ('&')
                // L .. E => is a <expr-primary>
                buf.writestring("XadL");
                mangle_function(d.isFuncDeclaration());
                buf.writestring("EE");
            }
            else if (e && e.isVarExp() && e.isVarExp().var.isVarDeclaration())
            {
                VarDeclaration vd = e.isVarExp().var.isVarDeclaration();
                buf.writeByte('L');
                mangle_variable(vd, true);
                buf.writeByte('E');
            }
            else if (d && d.isTemplateDeclaration() && d.isTemplateDeclaration().onemember)
            {
                if (!substitute(d))
                {
                    cpp_mangle_name(d, false);
                }
            }
            else
            {
                .error(ti.loc, "%s `%s` internal compiler error: C++ `%s` template alias parameter is not supported", ti.kind, ti.toPrettyChars, o.toChars());
                errors = true;
            }
        }
        else if (tp.isTemplateThisParameter())
        {
            .error(ti.loc, "%s `%s` internal compiler error: C++ `%s` template this parameter is not supported", ti.kind, ti.toPrettyChars, o.toChars());
            errors = true;
        }
        else
        {
            assert(0);
        }
    }

    /******************************
     * Write the mangled representation of the template arguments.
     * Params:
     *  ti = the template instance
     *  firstArg = index of the first template argument to mangle
     *             (used for operator overloading)
     * Returns:
     *  true if any arguments were written
     */
    bool template_args(TemplateInstance ti, int firstArg = 0)
    {
        /* <template-args> ::= I <template-arg>+ E
         */
        if (!ti || ti.tiargs.length <= firstArg)   // could happen if std::basic_string is not a template
            return false;
        buf.writeByte('I');
        foreach (i; firstArg .. ti.tiargs.length)
        {
            TemplateDeclaration td = ti.tempdecl.isTemplateDeclaration();
            assert(td);
            TemplateParameter tp = (*td.parameters)[i];

            /*
             * <template-arg> ::= <type>               # type or template
             *                ::= X <expression> E     # expression
             *                ::= <expr-primary>       # simple expressions
             *                ::= J <template-arg>* E  # argument pack
             *
             * Reference: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.template-arg
             */
            if (TemplateTupleParameter tt = tp.isTemplateTupleParameter())
            {
                buf.writeByte('J');     // argument pack

                // mangle the rest of the arguments as types
                foreach (j; i .. (*ti.tiargs).length)
                {
                    Type t = isType((*ti.tiargs)[j]);
                    if (t is null)
                    {
                        .error(ti.loc, "%s `%s` internal compiler error: C++ `%s` template value parameter is not supported", ti.kind, ti.toPrettyChars, (*ti.tiargs)[j].toChars());
                        errors = true;
                        return false;
                    }
                    t.accept(this);
                }

                buf.writeByte('E');
                break;
            }

            template_arg(ti, i);
        }
        buf.writeByte('E');
        return true;
    }

    /**
     * Write the symbol `p` if not null, then execute the delegate
     *
     * Params:
     *   p = Symbol to write
     *   dg = Delegate to execute
     */
    void writeChained(Dsymbol p, scope void delegate() dg)
    {
        if (p && !p.isModule())
        {
            buf.writestring("N");
            source_name(p, true);
            dg();
            buf.writestring("E");
        }
        else
            dg();
    }

    /**
     * Write the name of `s` to the buffer
     *
     * Params:
     *   s = Symbol to write the name of
     *   haveNE = Whether `N..E` is already part of the mangling
     *            Because `Nspace` and `CPPNamespaceAttribute` can be
     *            mixed, this is a mandatory hack.
     */
    void source_name(Dsymbol s, bool haveNE = false)
    {
        version (none)
        {
            printf("source_name(%s)\n", s.toChars());
            auto sl = this.buf.peekSlice();
            assert(sl.length == 0 || haveNE || s.cppnamespace is null || sl != "_ZN");
        }
        auto ti = s.isTemplateInstance();

        if (!ti)
        {
            auto ag = s.isAggregateDeclaration();
            const ident = (ag && ag.pMangleOverride) ? ag.pMangleOverride.id : s.ident;
            this.writeNamespace(s.cppnamespace, () {
                this.writeIdentifier(ident);
                this.abiTags.writeSymbol(s, this);
                },
                haveNE);
            return;
        }

        bool needsTa = false;

        // https://issues.dlang.org/show_bug.cgi?id=20413
        // N..E is not needed when substituting members of the std namespace.
        // This is observed in the GCC and Clang implementations.
        // The Itanium specification is not clear enough on this specific case.
        // References:
        //   https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.name
        //   https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-compression
        Dsymbol q = getQualifier(ti.tempdecl);
        Dsymbol ns = ti.tempdecl.cppnamespace;
        const inStd = ns && isStd(ns) || q && isStd(q);
        const isNested = !inStd && (ns || q);

        if (substitute(ti.tempdecl, !haveNE && isNested))
        {
            template_args(ti);
            if (!haveNE && isNested)
                buf.writeByte('E');
            return;
        }
        else if (this.writeStdSubstitution(ti, needsTa))
        {
            this.abiTags.writeSymbol(ti, this);
            if (needsTa)
                template_args(ti);
            return;
        }

        auto ag = ti.aliasdecl ? ti.aliasdecl.isAggregateDeclaration() : null;
        if (ag && ag.pMangleOverride)
        {
            this.writeNamespace(
                ti.toAlias().cppnamespace, () {
                    this.writeIdentifier(ag.pMangleOverride.id);
                    if (ag.pMangleOverride.agg && ag.pMangleOverride.agg.isInstantiated())
                    {
                        auto to = ag.pMangleOverride.agg.isInstantiated();
                        append(to);
                        this.abiTags.writeSymbol(to.tempdecl, this);
                        template_args(to);
                    }
              }, haveNE);
        }
        else
        {
            this.writeNamespace(
                s.cppnamespace, () {
                    this.writeIdentifier(ti.tempdecl.toAlias().ident);
                    append(ti.tempdecl);
                    this.abiTags.writeSymbol(ti.tempdecl, this);
                    template_args(ti);
                }, haveNE);
        }
    }

    /********
     * See if s is actually an instance of a template
     * Params:
     *  s = symbol
     * Returns:
     *  if s is instance of a template, return the instance, otherwise return s
     */
    static Dsymbol getInstance(Dsymbol s)
    {
        if (Dsymbol p = s.toParent())
        {
            if (TemplateInstance ti = p.isTemplateInstance())
                return ti;
        }
        return s;
    }

    /// Get the namespace of a template instance
    CPPNamespaceDeclaration getTiNamespace(TemplateInstance ti)
    {
        // If we receive a pre-semantic `TemplateInstance`,
        // `cppnamespace` is always `null`
        return ti.tempdecl ? ti.cppnamespace
            : this.context.res.asType().toDsymbol(null).cppnamespace;
    }

    /********
     * Get qualifier for `s`, meaning the symbol
     * that s is in the symbol table of.
     * The module does not count as a qualifier, because C++
     * does not have modules.
     * Params:
     *  s = symbol that may have a qualifier
     *      s is rewritten to be TemplateInstance if s is one
     * Returns:
     *  qualifier, null if none
     */
    static Dsymbol getQualifier(Dsymbol s)
    {
        Dsymbol p = s.toParent();
        return (p && !p.isModule()) ? p : null;
    }

    // Detect type char
    static bool isChar(RootObject o)
    {
        Type t = isType(o);
        return (t && t.equals(Type.tchar));
    }

    // Detect type ::std::char_traits<char>
    bool isChar_traits_char(RootObject o)
    {
        return isIdent_char(Id.char_traits, o);
    }

    // Detect type ::std::allocator<char>
    bool isAllocator_char(RootObject o)
    {
        return isIdent_char(Id.allocator, o);
    }

    // Detect type ::std::ident<char>
    bool isIdent_char(Identifier ident, RootObject o)
    {
        Type t = isType(o);
        if (!t || !t.isTypeStruct())
            return false;
        Dsymbol s = t.toDsymbol(null);
        if (s.ident != ident)
            return false;
        Dsymbol p = s.toParent();
        if (!p)
            return false;
        TemplateInstance ti = p.isTemplateInstance();
        if (!ti)
            return false;
        Dsymbol q = getQualifier(ti);
        const bool inStd = isStd(q) || isStd(this.getTiNamespace(ti));
        return inStd && ti.tiargs.length == 1 && isChar((*ti.tiargs)[0]);
    }

    /***
     * Detect template args <char, ::std::char_traits<char>>
     * and write st if found.
     * Returns:
     *  true if found
     */
    bool char_std_char_traits_char(TemplateInstance ti, string st)
    {
        if (ti.tiargs.length == 2 &&
            isChar((*ti.tiargs)[0]) &&
            isChar_traits_char((*ti.tiargs)[1]))
        {
            buf.writestring(st.ptr);
            return true;
        }
        return false;
    }


    void prefix_name(Dsymbol s)
    {
        //printf("prefix_name(%s)\n", s.toChars());
        if (substitute(s))
            return;
        if (isStd(s))
            return buf.writestring("St");

        auto si = getInstance(s);
        if (Dsymbol p = getQualifier(si))
        {
            if (isStd(p))
            {
                bool needsTa;
                auto ti = si.isTemplateInstance();
                if (this.writeStdSubstitution(ti, needsTa))
                {
                    this.abiTags.writeSymbol(ti, this);
                    if (needsTa)
                    {
                        template_args(ti);
                        append(ti);
                    }
                    return;
                }
                buf.writestring("St");
            }
            else
                prefix_name(p);
        }
        source_name(si, true);
        if (!isStd(si))
            /* Do this after the source_name() call to keep components[]
             * in the right order.
             * https://issues.dlang.org/show_bug.cgi?id=17947
             */
            append(si);
    }

    /**
     * Write common substitution for standard types, such as std::allocator
     *
     * This function assumes that the symbol `ti` is in the namespace `std`.
     *
     * Params:
     *   ti = Template instance to consider
     *   needsTa = If this function returns `true`, this value indicates
     *             if additional template argument mangling is needed
     *
     * Returns:
     *   `true` if a special std symbol was found
     */
    bool writeStdSubstitution(TemplateInstance ti, out bool needsTa)
    {
        if (!ti)
            return false;
        if (!isStd(this.getTiNamespace(ti)) && !isStd(getQualifier(ti)))
            return false;

        if (ti.name == Id.allocator)
        {
            buf.writestring("Sa");
            needsTa = true;
            return true;
        }
        if (ti.name == Id.basic_string)
        {
            // ::std::basic_string<char, ::std::char_traits<char>, ::std::allocator<char>>
            if (ti.tiargs.length == 3 &&
                isChar((*ti.tiargs)[0]) &&
                isChar_traits_char((*ti.tiargs)[1]) &&
                isAllocator_char((*ti.tiargs)[2]))

            {
                buf.writestring("Ss");
                return true;
            }
            buf.writestring("Sb");      // ::std::basic_string
            needsTa = true;
            return true;
        }

        // ::std::basic_istream<char, ::std::char_traits<char>>
        if (ti.name == Id.basic_istream &&
            char_std_char_traits_char(ti, "Si"))
            return true;

        // ::std::basic_ostream<char, ::std::char_traits<char>>
        if (ti.name == Id.basic_ostream &&
            char_std_char_traits_char(ti, "So"))
            return true;

        // ::std::basic_iostream<char, ::std::char_traits<char>>
        if (ti.name == Id.basic_iostream &&
            char_std_char_traits_char(ti, "Sd"))
            return true;

        return false;
    }

    void cpp_mangle_name(Dsymbol s, bool qualified)
    {
        //printf("cpp_mangle_name(%s, %d)\n", s.toChars(), qualified);
        Dsymbol p = s.toParent();
        Dsymbol se = s;
        bool write_prefix = true;
        if (p && p.isTemplateInstance())
        {
            se = p;
            if (find(p.isTemplateInstance().tempdecl) >= 0)
                write_prefix = false;
            p = p.toParent();
        }
        if (!p || p.isModule())
        {
            source_name(se, false);
            append(s);
            return;
        }

        if (!isStd(p) || qualified)
        {
            buf.writeByte('N');
            if (write_prefix)
            {
                if (isStd(p))
                    buf.writestring("St");
                else
                    prefix_name(p);
            }
            source_name(se, true);
            buf.writeByte('E');
            append(s);
            return;
        }
        /* The N..E is not required if:
         * 1. the parent is 'std'
         * 2. 'std' is the initial qualifier
         * 3. there is no CV-qualifier or a ref-qualifier for a member function
         * ABI 5.1.8
         */
        TemplateInstance ti = se.isTemplateInstance();
        if (s.ident == Id.allocator)
        {
            buf.writestring("Sa"); // "Sa" is short for ::std::allocator
            template_args(ti);
        }
        else if (s.ident == Id.basic_string)
        {
            // ::std::basic_string<char, ::std::char_traits<char>, ::std::allocator<char>>
            if (ti.tiargs.length == 3 &&
                isChar((*ti.tiargs)[0]) &&
                isChar_traits_char((*ti.tiargs)[1]) &&
                isAllocator_char((*ti.tiargs)[2]))
            {
                buf.writestring("Ss");
                return;
            }
            buf.writestring("Sb");      // ::std::basic_string
            template_args(ti);
        }
        else
        {
            // ::std::basic_istream<char, ::std::char_traits<char>>
            if (s.ident == Id.basic_istream)
            {
                if (char_std_char_traits_char(ti, "Si"))
                    return;
            }
            else if (s.ident == Id.basic_ostream)
            {
                if (char_std_char_traits_char(ti, "So"))
                    return;
            }
            else if (s.ident == Id.basic_iostream)
            {
                if (char_std_char_traits_char(ti, "Sd"))
                    return;
            }
            buf.writestring("St");
            source_name(se, true);
        }
        append(s);
    }

    /**
     * Write CV-qualifiers to the buffer
     *
     * CV-qualifiers are 'r': restrict (unused in D), 'V': volatile, 'K': const
     *
     * See_Also:
     *   https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.CV-qualifiers
     */
    void CV_qualifiers(const Type t)
    {
        if (t.isConst())
            buf.writeByte('K');
    }

    /**
     * Mangles a variable
     *
     * Params:
     *   d = Variable declaration to mangle
     *   isNested = Whether this variable is nested, e.g. a template parameter
     *              or within a namespace
     */
    void mangle_variable(VarDeclaration d, bool isNested)
    {
        // fake mangling for fields to fix https://issues.dlang.org/show_bug.cgi?id=16525
        if (!(d.storage_class & (STC.extern_ | STC.field | STC.gshared)))
        {
            .error(d.loc, "%s `%s` internal compiler error: C++ static non-`__gshared` non-`extern` variables not supported", d.kind, d.toPrettyChars);
            errors = true;
            return;
        }
        Dsymbol p = d.toParent();
        if (p && !p.isModule()) //for example: char Namespace1::beta[6] should be mangled as "_ZN10Namespace14betaE"
        {
            buf.writestring("_ZN");
            prefix_name(p);
            source_name(d, true);
            buf.writeByte('E');
        }
        else if (isNested)
        {
            buf.writestring("_Z");
            source_name(d, false);
        }
        else
        {
            if (auto varTags = ABITagContainer.forSymbol(d))
            {
                buf.writestring("_Z");
                source_name(d, false);
                return;
            }
            if (auto typeTags = ABITagContainer.forSymbol(d.type.toDsymbol(null)))
            {
                buf.writestring("_Z");
                source_name(d, false);
                this.abiTags.write(*this.buf, typeTags);
                return;
            }
            //char beta[6] should mangle as "beta"
            buf.writestring(d.ident.toString());
        }
    }

    void mangle_function(FuncDeclaration d)
    {
        //printf("mangle_function(%s)\n", d.toChars());
        /*
         * <mangled-name> ::= _Z <encoding>
         */
        buf.writestring("_Z");
        this.mangle_function_encoding(d);
    }

    void mangle_function_encoding(FuncDeclaration d)
    {
        //printf("mangle_function_encoding(%s)\n", d.toChars());
        /*
         * <encoding> ::= <function name> <bare-function-type>
         *            ::= <data name>
         *            ::= <special-name>
         */
        TypeFunction tf = d.type.isTypeFunction();

        if (TemplateDeclaration ftd = getFuncTemplateDecl(d))
        {
            /* It's an instance of a function template
             */
            TemplateInstance ti = d.parent.isTemplateInstance();
            assert(ti);
            this.mangleTemplatedFunction(d, tf, ftd, ti);
            return;
        }

        Dsymbol p = d.toParent();
        if (p && !p.isModule() && tf.linkage == LINK.cpp)
        {
            this.mangleNestedFuncPrefix(tf, p);

            if (auto ctor = d.isCtorDeclaration())
                buf.writestring(ctor.isCpCtor ? "C2" : "C1");
            else if (d.isAggregateDtor())
                buf.writestring("D1");
            else if (d.ident && d.ident == Id.opAssign)
                buf.writestring("aS");
            else if (d.ident && d.ident == Id.opEquals)
                buf.writestring("eq");
            else if (d.ident && d.ident == Id.opIndex)
                buf.writestring("ix");
            else if (d.ident && d.ident == Id.opCall)
                buf.writestring("cl");
            else
                source_name(d, true);
            buf.writeByte('E');
        }
        else
        {
            source_name(d, false);
        }

        // Save offset for potentially writing tags
        const size_t off = this.buf.length();

        // Template args accept extern "C" symbols with special mangling
        if (tf.linkage == LINK.cpp)
            mangleFunctionParameters(tf.parameterList);

        if (!tf.next.isTypeBasic())
            this.writeRemainingTags(off, tf);
    }

    /**
     * Recursively mangles a non-scoped namespace
     *
     * Parameters:
     *   ns = Namespace to mangle
     *   dg = A delegate to write the identifier in this namespace
     *   haveNE = When `false` (the default), surround the namespace / dg
     *            call with nested name qualifier (`N..E`).
     *            Otherwise, they are already present (e.g. `Nspace` was used).
     */
    void writeNamespace(CPPNamespaceDeclaration ns, scope void delegate() dg,
                        bool haveNE = false)
    {
        void runDg () { if (dg !is null) dg(); }

        if (ns is null || ns.ident is null)
            return runDg();

        if (isStd(ns))
        {
            if (!substitute(ns))
                buf.writestring("St");
            runDg();
        }
        else if (dg !is null)
        {
            if (!haveNE)
                buf.writestring("N");
            if (!substitute(ns))
            {
                this.writeNamespace(ns.cppnamespace, null);
                this.writeIdentifier(ns.ident);
                append(ns);
            }
            dg();
            if (!haveNE)
                buf.writestring("E");
        }
        else if (!substitute(ns))
        {
            this.writeNamespace(ns.cppnamespace, null);
            this.writeIdentifier(ns.ident);
            append(ns);
        }
    }

    /**
     * Mangles a function template to C++
     *
     * Params:
     *   d = Function declaration
     *   tf = Function type (casted d.type)
     *   ftd = Template declaration (ti.templdecl)
     *   ti = Template instance (d.parent)
     */
    void mangleTemplatedFunction(FuncDeclaration d, TypeFunction tf,
                                 TemplateDeclaration ftd, TemplateInstance ti)
    {
        Dsymbol p = ti.toParent();
        // Check if this function is *not* nested
        if (!p || p.isModule() || tf.linkage != LINK.cpp)
        {
            this.context.ti = ti;
            this.context.fd = d;
            this.context.res = d;
            TypeFunction preSemantic = d.originalType.isTypeFunction();
            auto nspace = ti.toParent();
            if (nspace && nspace.isNspace())
                this.writeChained(ti.toParent(), () => source_name(ti, true));
            else
                source_name(ti, false);
            this.mangleReturnType(preSemantic);
            this.mangleFunctionParameters(ParameterList(preSemantic.parameterList.parameters, tf.parameterList.varargs));
            return;
        }

        // It's a nested function (e.g. a member of an aggregate)
        this.mangleNestedFuncPrefix(tf, p);

        if (d.isCtorDeclaration())
        {
            buf.writestring("C1");
            mangleFunctionParameters(tf.parameterList);
            return;
        }
        else if (d.isAggregateDtor())
        {
            buf.writestring("D1");
            mangleFunctionParameters(tf.parameterList);
            return;
        }

        int firstTemplateArg = 0;
        bool appendReturnType = true;
        bool isConvertFunc = false;
        string symName;

        // test for special symbols
        CppOperator whichOp = isCppOperator(ti.name);
        final switch (whichOp)
        {
        case CppOperator.Unknown:
            break;
        case CppOperator.Cast:
            symName = "cv";
            firstTemplateArg = 1;
            isConvertFunc = true;
            appendReturnType = false;
            break;
        case CppOperator.Assign:
            symName = "aS";
            break;
        case CppOperator.Eq:
            symName = "eq";
            break;
        case CppOperator.Index:
            symName = "ix";
            break;
        case CppOperator.Call:
            symName = "cl";
            break;
        case CppOperator.Unary:
        case CppOperator.Binary:
        case CppOperator.OpAssign:
            TemplateDeclaration td = ti.tempdecl.isTemplateDeclaration();
            assert(td);
            assert(ti.tiargs.length >= 1);
            TemplateParameter tp = (*td.parameters)[0];
            TemplateValueParameter tv = tp.isTemplateValueParameter();
            if (!tv || !tv.valType.isString())
                break; // expecting a string argument to operators!
            Expression exp = (*ti.tiargs)[0].isExpression();
            StringExp str = exp.toStringExp();
            switch (whichOp)
            {
            case CppOperator.Unary:
                switch (str.peekString())
                {
                case "*":   symName = "de"; goto continue_template;
                case "++":  symName = "pp"; goto continue_template;
                case "--":  symName = "mm"; goto continue_template;
                case "-":   symName = "ng"; goto continue_template;
                case "+":   symName = "ps"; goto continue_template;
                case "~":   symName = "co"; goto continue_template;
                default:    break;
                }
                break;
            case CppOperator.Binary:
                switch (str.peekString())
                {
                case ">>":  symName = "rs"; goto continue_template;
                case "<<":  symName = "ls"; goto continue_template;
                case "*":   symName = "ml"; goto continue_template;
                case "-":   symName = "mi"; goto continue_template;
                case "+":   symName = "pl"; goto continue_template;
                case "&":   symName = "an"; goto continue_template;
                case "/":   symName = "dv"; goto continue_template;
                case "%":   symName = "rm"; goto continue_template;
                case "^":   symName = "eo"; goto continue_template;
                case "|":   symName = "or"; goto continue_template;
                default:    break;
                }
                break;
            case CppOperator.OpAssign:
                switch (str.peekString())
                {
                case "*":   symName = "mL"; goto continue_template;
                case "+":   symName = "pL"; goto continue_template;
                case "-":   symName = "mI"; goto continue_template;
                case "/":   symName = "dV"; goto continue_template;
                case "%":   symName = "rM"; goto continue_template;
                case ">>":  symName = "rS"; goto continue_template;
                case "<<":  symName = "lS"; goto continue_template;
                case "&":   symName = "aN"; goto continue_template;
                case "|":   symName = "oR"; goto continue_template;
                case "^":   symName = "eO"; goto continue_template;
                default:    break;
                }
                break;
            default:
                assert(0);
            continue_template:
                firstTemplateArg = 1;
                break;
            }
            break;
        }
        if (symName.length == 0)
            source_name(ti, true);
        else
        {
            buf.writestring(symName);
            if (isConvertFunc)
                template_arg(ti, 0);
            appendReturnType = template_args(ti, firstTemplateArg) && appendReturnType;
        }
        buf.writeByte('E');
        if (appendReturnType)
            headOfType(tf.nextOf());  // mangle return type
        mangleFunctionParameters(tf.parameterList);
    }

    /**
     * Mangle the parameters of a function
     *
     * For templated functions, `context.res` is set to the `FuncDeclaration`
     *
     * Params:
     *   parameters = Array of `Parameter` to mangle
     *   varargs = if != 0, this function has varargs parameters
     */
    void mangleFunctionParameters(ParameterList parameterList)
    {
        int numparams = 0;

        foreach (n, fparam; parameterList)
        {
            Type t = fparam.type.merge2();
            if (fparam.isReference())
                t = t.referenceTo();
            else if (fparam.isLazy())
            {
                // Mangle as delegate
                auto tf = new TypeFunction(ParameterList(), t, LINK.d);
                auto td = new TypeDelegate(tf);
                t = td.merge();
            }
            else if (Type cpptype = target.cpp.parameterType(t))
                t = cpptype;
            if (t.ty == Tsarray)
            {
                // Static arrays in D are passed by value; no counterpart in C++
                .error(loc, "internal compiler error: unable to pass static array `%s` to extern(C++) function, use pointer instead",
                    t.toChars());
                errors = true;
                return;
            }
            auto prev = this.context.push({
                    TypeFunction tf;
                    if (isDsymbol(this.context.res))
                        tf = this.context.res.asFuncDecl().type.isTypeFunction();
                    else
                        tf = this.context.res.asType().isTypeFunction();
                    assert(tf);
                    return (*tf.parameterList.parameters)[n].type;
                }());
            scope (exit) this.context.pop(prev);

            if (this.context.ti && global.params.cplusplus >= CppStdRevision.cpp11)
                handleParamPack(t, this.context.ti.tempdecl.isTemplateDeclaration().parameters);

            headOfType(t);
            ++numparams;
        }

        if (parameterList.varargs == VarArg.variadic)
            buf.writeByte('z');
        else if (!numparams)
            buf.writeByte('v'); // encode (void) parameters
    }

    /****** The rest is type mangling ************/

    void error(Type t)
    {
        const(char)* p;
        if (t.isImmutable())
            p = "`immutable` ";
        else if (t.isShared())
            p = "`shared` ";
        else
            p = "";
        .error(loc, "internal compiler error: %stype `%s` cannot be mapped to C++\n", p, t.toChars());
        errors = true; //Fatal, because this error should be handled in frontend
    }

    /****************************
     * Mangle a type,
     * treating it as a Head followed by a Tail.
     * Params:
     *  t = Head of a type
     */
    void headOfType(Type t)
    {
        if (auto tc = t.isTypeClass())
        {
            mangleTypeClass(tc, true);
        }
        else
        {
            // For value types, strip const/immutable/shared from the head of the type
            auto prev = this.context.push(this.context.res.asType().mutableOf().unSharedOf());
            scope (exit) this.context.pop(prev);
            t.mutableOf().unSharedOf().accept(this);
        }
    }

    /******
     * Write out 1 or 2 character basic type mangling.
     * Handle const and substitutions.
     * Params:
     *  t = type to mangle
     *  p = if not 0, then character prefix
     *  c = mangling character
     */
    void writeBasicType(Type t, char p, char c)
    {
        // Only do substitutions for non-fundamental types.
        if (!isFundamentalType(t) || t.isConst())
        {
            if (substitute(t))
                return;
            else
                append(t);
        }
        CV_qualifiers(t);
        if (p)
            buf.writeByte(p);
        buf.writeByte(c);
    }


    /****************
     * Write structs and enums.
     * Params:
     *  t = TypeStruct or TypeEnum
     */
    void doSymbol(Type t)
    {
        if (substitute(t))
            return;
        CV_qualifiers(t);

        // Handle any target-specific struct types.
        if (auto tm = target.cpp.typeMangle(t))
        {
            buf.writestring(tm);
        }
        else
        {
            Dsymbol s = t.toDsymbol(null);
            Dsymbol p = s.toParent();
            if (p && p.isTemplateInstance())
            {
                 /* https://issues.dlang.org/show_bug.cgi?id=17947
                  * Substitute the template instance symbol, not the struct/enum symbol
                  */
                if (substitute(p))
                    return;
            }
            if (!substitute(s))
                cpp_mangle_name(s, false);
        }
        if (t.isConst())
            append(t);
    }



    /************************
     * Mangle a class type.
     * If it's the head, treat the initial pointer as a value type.
     * Params:
     *  t = class type
     *  head = true for head of a type
     */
    void mangleTypeClass(TypeClass t, bool head)
    {
        if (t.isImmutable() || t.isShared())
            return error(t);

        /* Mangle as a <pointer to><struct>
         */
        if (substitute(t))
            return;
        if (!head)
            CV_qualifiers(t);
        buf.writeByte('P');

        CV_qualifiers(t);

        {
            Dsymbol s = t.toDsymbol(null);
            Dsymbol p = s.toParent();
            if (p && p.isTemplateInstance())
            {
                 /* https://issues.dlang.org/show_bug.cgi?id=17947
                  * Substitute the template instance symbol, not the class symbol
                  */
                if (substitute(p))
                    return;
            }
        }

        if (!substitute(t.sym))
        {
            cpp_mangle_name(t.sym, false);
        }
        if (t.isConst())
            append(null);  // C++ would have an extra type here
        append(t);
    }

    /**
     * Mangle the prefix of a nested (e.g. member) function
     *
     * Params:
     *   tf = Type of the nested function
     *   parent = Parent in which the function is nested
     */
    void mangleNestedFuncPrefix(TypeFunction tf, Dsymbol parent)
    {
        /* <nested-name> ::= N [<CV-qualifiers>] <prefix> <unqualified-name> E
         *               ::= N [<CV-qualifiers>] <template-prefix> <template-args> E
         */
        buf.writeByte('N');
        CV_qualifiers(tf);

        /* <prefix> ::= <prefix> <unqualified-name>
         *          ::= <template-prefix> <template-args>
         *          ::= <template-param>
         *          ::= # empty
         *          ::= <substitution>
         *          ::= <prefix> <data-member-prefix>
         */
        prefix_name(parent);
    }

    /**
     * Write `Dp` (C++11 function parameter pack prefix) if 't' is a TemplateSequenceParameter (T...).
     *
     * Params:
     *   t      = Parameter type
     *   params = Template parameters of the function
     */
    private void handleParamPack(Type t, TemplateParameters* params)
    {
        if (t.isTypeReference())
            t = t.nextOf();
        auto ti = t.isTypeIdentifier();
        if (!ti)
            return;

        auto idx = templateParamIndex(ti.ident, params);
        if (idx < params.length && (*params)[idx].isTemplateTupleParameter())
            buf.writestring("Dp");
    }

    /**
     * Helper function to write a `T..._` template index.
     *
     * Params:
     *   idx   = Index of `param` in the template argument list
     *   param = Template parameter to mangle
     */
    private void writeTemplateArgIndex(size_t idx, TemplateParameter param)
    {
        // expressions are mangled in <X..E>
        if (param.isTemplateValueParameter())
            buf.writeByte('X');
        buf.writeByte('T');
        writeSequenceFromIndex(idx);
        buf.writeByte('_');
        if (param.isTemplateValueParameter())
            buf.writeByte('E');
    }

    /**
     * Given an array of template parameters and an identifier,
     * returns the index of the identifier in that array.
     *
     * Params:
     *   ident = Identifier for which substitution is attempted
     *           (e.g. `void func(T)(T param)` => `T` from `T param`)
     *   params = `TemplateParameters` of the enclosing symbol
     *           (in the previous example, `func`'s template parameters)
     *
     * Returns:
     *   The index of the identifier match in `params`,
     *   or `params.length` if there wasn't any match.
     */
    private static size_t templateParamIndex(
        const ref Identifier ident, TemplateParameters* params) @safe
    {
        foreach (idx, param; *params)
            if (param.ident == ident)
                return idx;
        return params.length;
    }

    /**
     * Given a template instance `t`, write its qualified name
     * without the template parameter list
     *
     * Params:
     *   t = Post-parsing `TemplateInstance` pointing to the symbol
     *       to mangle (one level deep)
     *   dg = Delegate to execute after writing the qualified symbol
     *
     */
    private void writeQualified(TemplateInstance t, scope void delegate() dg)
    {
        auto type = isType(this.context.res);
        if (!type)
        {
            this.writeIdentifier(t.name);
            return dg();
        }
        auto sym1 = type.toDsymbol(null);
        if (!sym1)
        {
            this.writeIdentifier(t.name);
            return dg();
        }
        // Get the template instance
        auto sym = getQualifier(sym1);
        auto sym2 = getQualifier(sym);
        if (sym2 && isStd(sym2)) // Nspace path
        {
            bool unused;
            assert(sym.isTemplateInstance());
            if (this.writeStdSubstitution(sym.isTemplateInstance(), unused))
                return dg();
            // std names don't require `N..E`
            buf.writestring("St");
            this.writeIdentifier(t.name);
            this.append(t);
            return dg();
        }
        else if (sym2)
        {
            buf.writestring("N");
            if (!this.substitute(sym2))
                sym2.accept(this);
        }
        this.writeNamespace(
            sym1.cppnamespace, () {
                this.writeIdentifier(t.name);
                this.append(t);
                dg();
            });
        if (sym2)
            buf.writestring("E");
    }

extern(C++):

    alias visit = Visitor.visit;

    override void visit(TypeNull t)
    {
        if (t.isImmutable() || t.isShared())
            return error(t);

        writeBasicType(t, 'D', 'n');
    }

    override void visit(TypeNoreturn t)
    {
        if (t.isImmutable() || t.isShared())
            return error(t);

        writeBasicType(t, 0, 'v');      // mangle like `void`
    }

    override void visit(TypeBasic t)
    {
        if (t.isImmutable() || t.isShared())
            return error(t);

        // Handle any target-specific basic types.
        if (auto tm = target.cpp.typeMangle(t))
        {
            // Only do substitutions for non-fundamental types.
            if (!isFundamentalType(t) || t.isConst())
            {
                if (substitute(t))
                    return;
                else
                    append(t);
            }
            CV_qualifiers(t);
            buf.writestring(tm);
            return;
        }

        /* <builtin-type>:
         * v        void
         * w        wchar_t
         * b        bool
         * c        char
         * a        signed char
         * h        unsigned char
         * s        short
         * t        unsigned short
         * i        int
         * j        unsigned int
         * l        long
         * m        unsigned long
         * x        long long, __int64
         * y        unsigned long long, __int64
         * n        __int128
         * o        unsigned __int128
         * f        float
         * d        double
         * e        long double, __float80
         * g        __float128
         * z        ellipsis
         * Dd       64 bit IEEE 754r decimal floating point
         * De       128 bit IEEE 754r decimal floating point
         * Df       32 bit IEEE 754r decimal floating point
         * Dh       16 bit IEEE 754r half-precision floating point
         * Di       char32_t
         * Ds       char16_t
         * u <source-name>  # vendor extended type
         */
        if (t.isImaginary() || t.isComplex())
        {
            // https://issues.dlang.org/show_bug.cgi?id=22806
            // Complex and imaginary types are represented in the same way as
            // arrays or vectors in C++.  First substitute the outer type, then
            // write out the mangle string of the underlying type.
            if (substitute(t))
                return;
            append(t);
            CV_qualifiers(t);

            if (t.isImaginary())
                buf.writeByte('G'); // 'G' means imaginary
            else
                buf.writeByte('C'); // 'C' means complex

            switch (t.ty)
            {
                case Timaginary32:
                case Tcomplex32:
                    return Type.tfloat32.accept(this);
                case Timaginary64:
                case Tcomplex64:
                    return Type.tfloat64.accept(this);
                case Timaginary80:
                case Tcomplex80:
                    return Type.tfloat80.accept(this);
                default:
                    assert(0);
            }
        }

        char c;
        char p = 0;
        switch (t.ty)
        {
            case Tvoid:                 c = 'v';        break;
            case Tint8:                 c = 'a';        break;
            case Tuns8:                 c = 'h';        break;
            case Tint16:                c = 's';        break;
            case Tuns16:                c = 't';        break;
            case Tint32:                c = 'i';        break;
            case Tuns32:                c = 'j';        break;
            case Tfloat32:              c = 'f';        break;
            case Tint64:
                c = target.c.longsize == 8 ? 'l' : 'x';
                break;
            case Tuns64:
                c = target.c.longsize == 8 ? 'm' : 'y';
                break;
            case Tint128:                c = 'n';       break;
            case Tuns128:                c = 'o';       break;
            case Tfloat64:               c = 'd';       break;
            case Tfloat80:               c = 'e';       break;
            case Tbool:                  c = 'b';       break;
            case Tchar:                  c = 'c';       break;
            case Twchar:        p = 'D'; c = 's';       break;  // since C++11
            case Tdchar:        p = 'D'; c = 'i';       break;  // since C++11

            default:
                return error(t);
        }
        writeBasicType(t, p, c);
    }

    override void visit(TypeVector t)
    {
        if (t.isImmutable() || t.isShared())
            return error(t);

        if (substitute(t))
            return;
        append(t);
        CV_qualifiers(t);

        // Handle any target-specific vector types.
        if (auto tm = target.cpp.typeMangle(t))
        {
            buf.writestring(tm);
        }
        else
        {
            assert(t.basetype && t.basetype.ty == Tsarray);
            auto tsa = t.basetype.isTypeSArray();
            assert(tsa.dim);
            buf.writestring("Dv");          // -- Gnu ABI v.4
            buf.print(tsa.dim.toInteger());
            buf.writeByte('_');
            t.basetype.nextOf().accept(this);
        }
    }

    override void visit(TypeSArray t)
    {
        if (t.isImmutable() || t.isShared())
            return error(t);

        if (!substitute(t))
            append(t);
        CV_qualifiers(t);
        buf.writeByte('A');
        buf.print(t.dim ? t.dim.toInteger() : 0);
        buf.writeByte('_');
        t.next.accept(this);
    }

    override void visit(TypePointer t)
    {
        if (t.isImmutable() || t.isShared())
            return error(t);

        // Check for const - Since we cannot represent C++'s `char* const`,
        // and `const char* const` (a.k.a `const(char*)` in D) is mangled
        // the same as `const char*` (`const(char)*` in D), we need to add
        // an extra `K` if `nextOf()` is `const`, before substitution
        CV_qualifiers(t);
        if (substitute(t))
            return;
        buf.writeByte('P');
        auto prev = this.context.push(this.context.res.asType().nextOf());
        scope (exit) this.context.pop(prev);
        t.next.accept(this);
        append(t);
    }

    override void visit(TypeReference t)
    {
        if (substitute(t))
            return;
        buf.writeByte('R');
        CV_qualifiers(t.nextOf());
        headOfType(t.nextOf());
        if (t.nextOf().isConst())
            append(t.nextOf());
        append(t);
    }

    override void visit(TypeFunction t)
    {
        /*
         *  <function-type> ::= F [Y] <bare-function-type> E
         *  <bare-function-type> ::= <signature type>+
         *  # types are possible return type, then parameter types
         */
        /* ABI says:
            "The type of a non-static member function is considered to be different,
            for the purposes of substitution, from the type of a namespace-scope or
            static member function whose type appears similar. The types of two
            non-static member functions are considered to be different, for the
            purposes of substitution, if the functions are members of different
            classes. In other words, for the purposes of substitution, the class of
            which the function is a member is considered part of the type of
            function."

            BUG: Right now, types of functions are never merged, so our simplistic
            component matcher always finds them to be different.
            We should use Type.equals on these, and use different
            TypeFunctions for non-static member functions, and non-static
            member functions of different classes.
         */
        if (substitute(t))
            return;
        buf.writeByte('F');
        if (t.linkage == LINK.c)
            buf.writeByte('Y');
        Type tn = t.next;
        if (t.isRef)
            tn = tn.referenceTo();
        tn.accept(this);
        mangleFunctionParameters(t.parameterList);
        buf.writeByte('E');
        append(t);
    }

    override void visit(TypeStruct t)
    {
        if (t.isImmutable() || t.isShared())
            return error(t);
        //printf("TypeStruct %s\n", t.toChars());
        doSymbol(t);
    }

    override void visit(TypeEnum t)
    {
        if (t.isImmutable() || t.isShared())
            return error(t);

        /* __c_(u)long(long) and others get special mangling
         */
        const id = t.sym.ident;
        //printf("enum id = '%s'\n", id.toChars());
        if (id == Id.__c_long)
            return writeBasicType(t, 0, 'l');
        if (id == Id.__c_ulong)
            return writeBasicType(t, 0, 'm');
        if (id == Id.__c_char)
            return writeBasicType(t, 0, 'c');
        if (id == Id.__c_wchar_t)
            return writeBasicType(t, 0, 'w');
        if (id == Id.__c_longlong)
            return writeBasicType(t, 0, 'x');
        if (id == Id.__c_ulonglong)
            return writeBasicType(t, 0, 'y');
        if (id == Id.__c_complex_float)
            return Type.tcomplex32.accept(this);
        if (id == Id.__c_complex_double)
            return Type.tcomplex64.accept(this);
        if (id == Id.__c_complex_real)
            return Type.tcomplex80.accept(this);

        doSymbol(t);
    }

    override void visit(TypeClass t)
    {
        mangleTypeClass(t, false);
    }

    /**
     * Performs template parameter substitution
     *
     * Mangling is performed on a copy of the post-parsing AST before
     * any semantic pass is run.
     * There is no easy way to link a type to the template parameters
     * once semantic has run, because:
     * - the `TemplateInstance` installs aliases in its scope to its params
     * - `AliasDeclaration`s are resolved in many places
     * - semantic passes are destructive, so the `TypeIdentifier` gets lost
     *
     * As a result, the best approach with the current architecture is to:
     * - Run the visitor on the `originalType` of the function,
     *   looking up any `TypeIdentifier` at the template scope when found.
     * - Fallback to the post-semantic `TypeFunction` when the identifier is
     *   not a template parameter.
     */
    override void visit(TypeIdentifier t)
    {
        auto decl = this.context.ti.tempdecl.isTemplateDeclaration();
        assert(decl.parameters !is null);
        auto idx = templateParamIndex(t.ident, decl.parameters);
        // If not found, default to the post-semantic type
        if (idx >= decl.parameters.length)
            return this.context.res.visitObject(this);

        auto param = (*decl.parameters)[idx];
        if (auto type = this.context.res.isType())
            CV_qualifiers(type);
        // Otherwise, attempt substitution (`S_` takes precedence on `T_`)
        if (this.substitute(param))
            return;

        // If substitution failed, write `TX_` where `X` is the index
        this.writeTemplateArgIndex(idx, param);
        this.append(param);
        // Write the ABI tags, if any
        if (auto sym = this.context.res.isDsymbol())
            this.abiTags.writeSymbol(sym, this);
    }

    /// Ditto
    override void visit(TypeInstance t)
    {
        assert(t.tempinst !is null);
        t.tempinst.accept(this);
    }

    /**
     * Mangles a `TemplateInstance`
     *
     * A `TemplateInstance` can be found either in the parameter,
     * or the return value.
     * Arguments to the template instance needs to be mangled but the template
     * can be partially substituted, so for example the following:
     * `Container!(T, Val) func16479_12 (alias Container, T, int Val) ()`
     * will mangle the return value part to "T_IT0_XT1_EE"
     */
    override void visit(TemplateInstance t)
    {
        // Template names are substituted, but args still need to be written
        void writeArgs ()
        {
            buf.writeByte('I');
            // When visiting the arguments, the context will be set to the
            // resolved type
            auto analyzed_ti = this.context.res.asType().toDsymbol(null).isInstantiated();
            auto prev = this.context;
            scope (exit) this.context.pop(prev);
            foreach (idx, RootObject o; *t.tiargs)
            {
                this.context.res = (*analyzed_ti.tiargs)[idx];
                o.visitObject(this);
            }
            if (analyzed_ti.tiargs.length > t.tiargs.length)
            {
                // If the resolved AST has more args than the parse one,
                // we have default arguments
                auto oparams = analyzed_ti.tempdecl.isTemplateDeclaration().origParameters;
                foreach (idx, arg; (*oparams)[t.tiargs.length .. $])
                {
                    this.context.res = (*analyzed_ti.tiargs)[idx + t.tiargs.length];

                    if (auto ttp = arg.isTemplateTypeParameter())
                        ttp.defaultType.accept(this);
                    else if (auto tvp = arg.isTemplateValueParameter())
                        tvp.defaultValue.accept(this);
                    else if (auto tvp = arg.isTemplateThisParameter())
                        tvp.defaultType.accept(this);
                    else if (auto tvp = arg.isTemplateAliasParameter())
                        tvp.defaultAlias.visitObject(this);
                    else
                        assert(0, arg.toString());
                }
            }
            buf.writeByte('E');
        }

        // `name` is used, not `ident`
        assert(t.name !is null);
        assert(t.tiargs !is null);

        bool needsTa;
        auto decl = this.context.ti.tempdecl.isTemplateDeclaration();
        // Attempt to substitute the template itself
        auto idx = templateParamIndex(t.name, decl.parameters);
        if (idx < decl.parameters.length)
        {
            auto param = (*decl.parameters)[idx];
            if (auto type = t.getType())
                CV_qualifiers(type);
            if (this.substitute(param))
                return;
            this.writeTemplateArgIndex(idx, param);
            this.append(param);
            writeArgs();
        }
        else if (this.writeStdSubstitution(t, needsTa))
        {
            if (needsTa)
                writeArgs();
        }
        else if (!this.substitute(t))
            this.writeQualified(t, &writeArgs);
    }

    /// Ditto
    override void visit(IntegerExp t)
    {
        this.buf.writeByte('L');
        t.type.accept(this);
        this.buf.print(t.getInteger());
        this.buf.writeByte('E');
    }

    override void visit(Nspace t)
    {
        if (auto p = getQualifier(t))
            p.accept(this);

        if (isStd(t))
            buf.writestring("St");
        else
        {
            this.writeIdentifier(t.ident);
            this.append(t);
        }
    }

    override void visit(Type t)
    {
        error(t);
    }

    void visit(Tuple t)
    {
        assert(0);
    }
}

/// Helper code to visit `RootObject`, as it doesn't define `accept`,
/// only its direct subtypes do.
private void visitObject(V : Visitor)(RootObject o, V this_)
{
    assert(o !is null);
    if (Type ta = isType(o))
        ta.accept(this_);
    else if (Expression ea = isExpression(o))
        ea.accept(this_);
    else if (Dsymbol sa = isDsymbol(o))
        sa.accept(this_);
    else if (TemplateParameter t = isTemplateParameter(o))
        t.accept(this_);
    else if (Tuple t = isTuple(o))
        // `Tuple` inherits `RootObject` and does not define accept
        // For this reason, this uses static dispatch on the visitor
        this_.visit(t);
    else
        assert(0, o.toString());
}

/// Helper function to safely get a type out of a `RootObject`
private Type asType(RootObject o) @safe
{
    if (Type ta = isType(o))
        return ta;

    // When called with context.res as argument, it can be `FuncDeclaration`
    if (auto fd = o.asFuncDecl())
        return fd.type;
    assert(0);
}

/// Helper function to safely get a `FuncDeclaration` out of a `RootObject`
private FuncDeclaration asFuncDecl(RootObject o) @safe
{
    Dsymbol d = isDsymbol(o);
    assert(d !is null);
    auto fd = d.isFuncDeclaration();
    assert(fd !is null);
    return fd;
}

/// Helper class to compare entries in components
private extern(C++) final class ComponentVisitor : Visitor
{
    /// Only one of the following is not `null`, it's always
    /// the most specialized type, set from the ctor
    private Nspace namespace;

    /// Ditto
    private CPPNamespaceDeclaration namespace2;

    /// Ditto
    private TypePointer tpointer;

    /// Ditto
    private TypeReference tref;

    /// Ditto
    private TypeIdentifier tident;

    /// Least specialized type
    private RootObject object;

    /// Set to the result of the comparison
    private bool result;

    public this(RootObject base) @trusted
    {
        switch (base.dyncast())
        {
        case DYNCAST.dsymbol:
            if (auto ns = (cast(Dsymbol)base).isNspace())
                this.namespace = ns;
            else if (auto ns = (cast(Dsymbol)base).isCPPNamespaceDeclaration())
                this.namespace2 = ns;
            else
                goto default;
            break;

        case DYNCAST.type:
            auto t = cast(Type)base;
            if (auto tp = t.isTypePointer())
                this.tpointer = tp;
            else if (auto tr = t.isTypeReference())
                this.tref = tr;
            else if (auto ti = t.isTypeIdentifier())
                this.tident = ti;
            else
                goto default;
            break;

        // Note: ABI tags are also handled here (they are TupleExp of StringExp)
        default:
            this.object = base;
        }
    }

    /// Introduce base class overloads
    alias visit = Visitor.visit;

    /// Least specialized overload of each direct child of `RootObject`
    public override void visit(Dsymbol o)
    {
        this.result = this.object && this.object == o;
    }

    /// Ditto
    public override void visit(Expression o)
    {
        this.result = this.object && this.object == o;
    }

    /// Ditto
    public void visit(Tuple o)
    {
        this.result = this.object && this.object == o;
    }

    /// Ditto
    public override void visit(Type o)
    {
        this.result = this.object && this.object == o;
    }

    /// Ditto
    public override void visit(TemplateParameter o)
    {
        this.result = this.object && this.object == o;
    }

    /**
     * This overload handles composed types including template parameters
     *
     * Components for substitutions include "next" type.
     * For example, if `ref T` is present, `ref T` and `T` will be present
     * in the substitution array.
     * But since we don't have the final/merged type, we cannot rely on
     * object comparison, and need to recurse instead.
     */
    public override void visit(TypeReference o)
    {
        if (!this.tref)
            return;
        if (this.tref == o)
            this.result = true;
        else
        {
            // It might be a reference to a template parameter that we already
            // saw, so we need to recurse
            scope v = new ComponentVisitor(this.tref.next);
            o.next.visitObject(v);
            this.result = v.result;
        }
    }

    /// Ditto
    public override void visit(TypePointer o)
    {
        if (!this.tpointer)
            return;
        if (this.tpointer == o)
            this.result = true;
        else
        {
            // It might be a pointer to a template parameter that we already
            // saw, so we need to recurse
            scope v = new ComponentVisitor(this.tpointer.next);
            o.next.visitObject(v);
            this.result = v.result;
        }
    }

    /// Ditto
    public override void visit(TypeIdentifier o)
    {
        /// Since we know they are at the same level, scope resolution will
        /// give us the same symbol, thus we can just compare ident.
        this.result = (this.tident && (this.tident.ident == o.ident));
    }

    /**
     * Overload which accepts a Namespace
     *
     * It is very common for large C++ projects to have multiple files sharing
     * the same `namespace`. If any D project adopts the same approach
     * (e.g. separating data structures from functions), it will lead to two
     * `Nspace` objects being instantiated, with different addresses.
     * At the same time, we cannot compare just any Dsymbol via identifier,
     * because it messes with templates.
     *
     * See_Also:
     *  https://issues.dlang.org/show_bug.cgi?id=18922
     *
     * Params:
     *   ns = C++ namespace to do substitution for
     */
    public override void visit(Nspace ns)
    {
        this.result = isNamespaceEqual(this.namespace, ns)
            || isNamespaceEqual(this.namespace2, ns);
    }

    /// Ditto
    public override void visit(CPPNamespaceDeclaration ns)
    {
        this.result = isNamespaceEqual(this.namespace, ns)
            || isNamespaceEqual(this.namespace2, ns);
    }
}

/// Transitional functions for `CPPNamespaceDeclaration` / `Nspace`
/// Remove when `Nspace` is removed.
private bool isNamespaceEqual (Nspace a, Nspace b)
{
    if (a is null || b is null)
        return false;
    return a.equals(b);
}

/// Ditto
private bool isNamespaceEqual (Nspace a, CPPNamespaceDeclaration b)
{
    return isNamespaceEqual(b, a);
}

/// Ditto
private bool isNamespaceEqual (CPPNamespaceDeclaration a, Nspace b, size_t idx = 0)
{
    if ((a is null) != (b is null))
        return false;
    if (!a.ident.equals(b.ident))
        return false;

    // We need to see if there's more ident enclosing
    if (auto pb = b.toParent().isNspace())
        return isNamespaceEqual(a.cppnamespace, pb);
    return a.cppnamespace is null;
}

/// Returns:
///   Whether  two `CPPNamespaceDeclaration` are equals
private bool isNamespaceEqual (CPPNamespaceDeclaration a, CPPNamespaceDeclaration b) @safe
{
    if (a is null || b is null)
        return false;

    if ((a.cppnamespace is null) != (b.cppnamespace is null))
        return false;
    if (a.ident != b.ident)
        return false;
    return a.cppnamespace is null ? true : isNamespaceEqual(a.cppnamespace, b.cppnamespace);
}

/**
 * A container for ABI tags
 *
 * At its hearth, there is a sorted array of ABI tags having been written
 * already. ABI tags can be present on parameters, template parameters,
 * return value, and varaible. ABI tags for a given type needs to be written
 * sorted. When a function returns a type that has ABI tags, only the tags that
 * haven't been printed as part of the mangling (e.g. arguments) are written
 * directly after the function name.
 *
 * This means that:
 * ---
 * /++ C++ type definitions:
 * struct [[gnu::abi_tag("tag1")]] Struct1 {};
 * struct [[gnu::abi_tag("tag2")]] Struct2 {};
 * // Can also be: "tag2", "tag1", since tags are sorted.
 * struct [[gnu::abi_tag("tag1", "tag2")]] Struct3 {};
 * +/
 * // Functions definitions:
 * Struct3 func1 (Struct1);
 * Struct3 func2 (Struct2);
 * Struct3 func3 (Struct2, Struct1);
 * ---
 * Will be respectively pseudo-mangled (part of interest between stars) as:
 * "_Z4 func1 *B4tag2* ParamsMangling" (ParamsMangling includes tag1),
 * "_Z4 func2 *B4tag1* ParamsMangling" (ParamsMangling includes tag2),
 * "_Z4 func2 *B4tag1* ParamsMangling" (ParamsMangling includes both).
 *
 * This is why why need to keep a list of tags that were written,
 * and insert the missing one after parameter mangling has been written.
 * Since there's a lot of operations that are not easily doable in DMD
 * (since we can't use Phobos), this special container is implemented.
 */
private struct ABITagContainer
{
    private Array!StringExp written;

    static ArrayLiteralExp forSymbol (Dsymbol s)
    {
        if (!s)
            return null;
        // If this is a template instance, we want the declaration,
        // as that's where the UDAs are
        if (auto ti = s.isTemplateInstance())
            s = ti.tempdecl;
        if (!s.userAttribDecl || !s.userAttribDecl.atts)
            return null;

        foreach (exp; *s.userAttribDecl.atts)
        {
            if (isGNUABITag(exp))
                return (*exp.isStructLiteralExp().elements)[0]
                    .isArrayLiteralExp();
        }
        return null;
    }

    void writeSymbol(Dsymbol s, CppMangleVisitor self)
    {
        auto tale = forSymbol(s);
        if (!tale) return;
        if (self.substitute(tale))
            return;
        this.write(*self.buf, tale);
    }

    /**
     * Write an ArrayLiteralExp (expected to be an ABI tag) to the buffer
     *
     * Params:
     *   buf = Buffer to write mangling to
     *   ale = GNU ABI tag array literal expression, semantically analyzed
     */
    void write (ref OutBuffer buf, ArrayLiteralExp ale, bool skipKnown = false)
    {
        void writeElem (StringExp exp)
        {
            const tag = exp.peekString();
            buf.writestring("B");
            buf.print(tag.length);
            buf.writestring(tag);
        }

        bool match;
        foreach (exp; *ale.elements)
        {
            auto elem = exp.toStringExp();
            auto idx = closestIndex(this.written[], elem, match);
            if (!match)
            {
                writeElem(elem);
                this.written.insert(idx, elem);
            }
            else if (!skipKnown)
                writeElem(elem);
        }
    }
}

/**
 * Returns the closest index to to `exp` in `slice`
 *
 * Performs a binary search on `slice` (assumes `slice` is sorted),
 * and returns either `exp`'s index in `slice` if `exact` is `true`,
 * or the index at which `exp` can be inserted in `slice` if `exact is `false`.
 * Inserting `exp` at the return value will keep the array sorted.
 *
 * Params:
 *   slice = The sorted slice to search into
 *   exp   = The string expression to search for
 *   exact = If `true` on return, `exp` was found in `slice`
 *
 * Returns:
 *   Either the index to insert `exp` at (if `exact == false`),
 *   or the index of `exp` in `slice`.
 */
private size_t closestIndex (const(StringExp)[] slice, StringExp exp, out bool exact)
{
    if (!slice.length) return 0;

    const StringExp* first = slice.ptr;
    while (true)
    {
        int res = dstrcmp(exp.peekString(), slice[$ / 2].peekString());
        if (res == 0)
        {
            exact = true;
            return (&slice[$/2] - first);
        }

        if (slice.length == 1)
            return (slice.ptr - first) + (res > 0);
        slice = slice[(res > 0 ? $ / 2 : 0) .. (res > 0 ? $ : $ / 2)];
    }
}

//
unittest
{
    bool match;
    auto s1 = new StringExp(Loc.initial, "Amande");
    auto s2 = new StringExp(Loc.initial, "Baguette");
    auto s3 = new StringExp(Loc.initial, "Croissant");
    auto s4 = new StringExp(Loc.initial, "Framboises");
    auto s5 = new StringExp(Loc.initial, "Proscuitto");

    // Found, odd size
    assert(closestIndex([s1, s2, s3, s4, s5], s1, match) == 0 && match);
    assert(closestIndex([s1, s2, s3, s4, s5], s2, match) == 1 && match);
    assert(closestIndex([s1, s2, s3, s4, s5], s3, match) == 2 && match);
    assert(closestIndex([s1, s2, s3, s4, s5], s4, match) == 3 && match);
    assert(closestIndex([s1, s2, s3, s4, s5], s5, match) == 4 && match);

    // Not found, even size
    assert(closestIndex([s2, s3, s4, s5], s1, match) == 0 && !match);
    assert(closestIndex([s1, s3, s4, s5], s2, match) == 1 && !match);
    assert(closestIndex([s1, s2, s4, s5], s3, match) == 2 && !match);
    assert(closestIndex([s1, s2, s3, s5], s4, match) == 3 && !match);
    assert(closestIndex([s1, s2, s3, s4], s5, match) == 4 && !match);

    // Found, even size
    assert(closestIndex([s1, s2, s3, s4], s1, match) == 0 && match);
    assert(closestIndex([s1, s2, s3, s4], s2, match) == 1 && match);
    assert(closestIndex([s1, s2, s3, s4], s3, match) == 2 && match);
    assert(closestIndex([s1, s2, s3, s4], s4, match) == 3 && match);
    assert(closestIndex([s1, s3, s4, s5], s5, match) == 3 && match);

    // Not found, odd size
    assert(closestIndex([s2, s4, s5], s1, match) == 0 && !match);
    assert(closestIndex([s1, s4, s5], s2, match) == 1 && !match);
    assert(closestIndex([s1, s2, s4], s3, match) == 2 && !match);
    assert(closestIndex([s1, s3, s5], s4, match) == 2 && !match);
    assert(closestIndex([s1, s2, s4], s5, match) == 3 && !match);
}

/***
 * Visits the return type of a function and writes leftover ABI tags
 * Params:
 *   tf = Type of the function to mangle the return type of
 *   previous = already written ones
 *   toWrite = where to put StringExp's to be written
 */
private
void leftOver(TypeFunction tf, const(Array!StringExp)* previous, Array!StringExp* toWrite)
{
    extern(C++) final class LeftoverVisitor : Visitor
    {
        /// List of tags to write
        private Array!StringExp* toWrite;
        /// List of tags to ignore
        private const(Array!StringExp)* ignore;

        ///
        public this(const(Array!StringExp)* previous, Array!StringExp* toWrite) @safe
        {
            this.ignore = previous;
            this.toWrite = toWrite;
        }

        /// Reintroduce base class overloads
        public alias visit = Visitor.visit;

        /// Least specialized overload of each direct child of `RootObject`
        public override void visit(Dsymbol o)
        {
            auto ale = ABITagContainer.forSymbol(o);
            if (!ale) return;

            bool match;
            foreach (elem; *ale.elements)
            {
                auto se = elem.toStringExp();
                closestIndex((*this.ignore)[], se, match);
                if (match) continue;
                auto idx = closestIndex((*this.toWrite)[], se, match);
                if (!match)
                    (*this.toWrite).insert(idx, se);
            }
        }

        /// Ditto
        public override void visit(Type o)
        {
            if (auto sym = o.toDsymbol(null))
                sym.accept(this);
        }

        /// Composite type
        public override void visit(TypePointer o)
        {
            o.next.accept(this);
        }

        public override void visit(TypeReference o)
        {
            o.next.accept(this);
        }
    }

    scope remainingVisitor = new LeftoverVisitor(previous, toWrite);
    tf.next.accept(remainingVisitor);
}
