/**
 * Compiler implementation of the
 * $(LINK2 http://www.dlang.org, D programming language).
 *
 * Copyright:   Copyright (c) 1999-2017 by Digital Mars, All Rights Reserved
 * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/ddmd/opover.d, _opover.d)
 */

module ddmd.opover;

// Online documentation: https://dlang.org/phobos/ddmd_opover.html

import core.stdc.stdio;
import ddmd.aggregate;
import ddmd.aliasthis;
import ddmd.arraytypes;
import ddmd.dclass;
import ddmd.declaration;
import ddmd.dscope;
import ddmd.dstruct;
import ddmd.dsymbol;
import ddmd.dtemplate;
import ddmd.errors;
import ddmd.expression;
import ddmd.expressionsem;
import ddmd.func;
import ddmd.globals;
import ddmd.id;
import ddmd.identifier;
import ddmd.mtype;
import ddmd.statement;
import ddmd.tokens;
import ddmd.visitor;

/***********************************
 * Determine if operands of binary op can be reversed
 * to fit operator overload.
 */
extern (C++) bool isCommutative(TOK op)
{
    switch (op)
    {
    case TOKadd:
    case TOKmul:
    case TOKand:
    case TOKor:
    case TOKxor:
    // EqualExp
    case TOKequal:
    case TOKnotequal:
    // CmpExp
    case TOKlt:
    case TOKle:
    case TOKgt:
    case TOKge:
        return true;
    default:
        break;
    }
    return false;
}

/***********************************
 * Get Identifier for operator overload.
 */
extern (C++) static Identifier opId(Expression e)
{
    extern (C++) final class OpIdVisitor : Visitor
    {
        alias visit = super.visit;
    public:
        Identifier id;

        override void visit(Expression e)
        {
            assert(0);
        }

        override void visit(UAddExp e)
        {
            id = Id.uadd;
        }

        override void visit(NegExp e)
        {
            id = Id.neg;
        }

        override void visit(ComExp e)
        {
            id = Id.com;
        }

        override void visit(CastExp e)
        {
            id = Id._cast;
        }

        override void visit(InExp e)
        {
            id = Id.opIn;
        }

        override void visit(PostExp e)
        {
            id = (e.op == TOKplusplus) ? Id.postinc : Id.postdec;
        }

        override void visit(AddExp e)
        {
            id = Id.add;
        }

        override void visit(MinExp e)
        {
            id = Id.sub;
        }

        override void visit(MulExp e)
        {
            id = Id.mul;
        }

        override void visit(DivExp e)
        {
            id = Id.div;
        }

        override void visit(ModExp e)
        {
            id = Id.mod;
        }

        override void visit(PowExp e)
        {
            id = Id.pow;
        }

        override void visit(ShlExp e)
        {
            id = Id.shl;
        }

        override void visit(ShrExp e)
        {
            id = Id.shr;
        }

        override void visit(UshrExp e)
        {
            id = Id.ushr;
        }

        override void visit(AndExp e)
        {
            id = Id.iand;
        }

        override void visit(OrExp e)
        {
            id = Id.ior;
        }

        override void visit(XorExp e)
        {
            id = Id.ixor;
        }

        override void visit(CatExp e)
        {
            id = Id.cat;
        }

        override void visit(AssignExp e)
        {
            id = Id.assign;
        }

        override void visit(AddAssignExp e)
        {
            id = Id.addass;
        }

        override void visit(MinAssignExp e)
        {
            id = Id.subass;
        }

        override void visit(MulAssignExp e)
        {
            id = Id.mulass;
        }

        override void visit(DivAssignExp e)
        {
            id = Id.divass;
        }

        override void visit(ModAssignExp e)
        {
            id = Id.modass;
        }

        override void visit(AndAssignExp e)
        {
            id = Id.andass;
        }

        override void visit(OrAssignExp e)
        {
            id = Id.orass;
        }

        override void visit(XorAssignExp e)
        {
            id = Id.xorass;
        }

        override void visit(ShlAssignExp e)
        {
            id = Id.shlass;
        }

        override void visit(ShrAssignExp e)
        {
            id = Id.shrass;
        }

        override void visit(UshrAssignExp e)
        {
            id = Id.ushrass;
        }

        override void visit(CatAssignExp e)
        {
            id = Id.catass;
        }

        override void visit(PowAssignExp e)
        {
            id = Id.powass;
        }

        override void visit(EqualExp e)
        {
            id = Id.eq;
        }

        override void visit(CmpExp e)
        {
            id = Id.cmp;
        }

        override void visit(ArrayExp e)
        {
            id = Id.index;
        }

        override void visit(PtrExp e)
        {
            id = Id.opStar;
        }
    }

    scope OpIdVisitor v = new OpIdVisitor();
    e.accept(v);
    return v.id;
}

/***********************************
 * Get Identifier for reverse operator overload,
 * NULL if not supported for this operator.
 */
extern (C++) static Identifier opId_r(Expression e)
{
    extern (C++) final class OpIdRVisitor : Visitor
    {
        alias visit = super.visit;
    public:
        Identifier id;

        override void visit(Expression e)
        {
            id = null;
        }

        override void visit(InExp e)
        {
            id = Id.opIn_r;
        }

        override void visit(AddExp e)
        {
            id = Id.add_r;
        }

        override void visit(MinExp e)
        {
            id = Id.sub_r;
        }

        override void visit(MulExp e)
        {
            id = Id.mul_r;
        }

        override void visit(DivExp e)
        {
            id = Id.div_r;
        }

        override void visit(ModExp e)
        {
            id = Id.mod_r;
        }

        override void visit(PowExp e)
        {
            id = Id.pow_r;
        }

        override void visit(ShlExp e)
        {
            id = Id.shl_r;
        }

        override void visit(ShrExp e)
        {
            id = Id.shr_r;
        }

        override void visit(UshrExp e)
        {
            id = Id.ushr_r;
        }

        override void visit(AndExp e)
        {
            id = Id.iand_r;
        }

        override void visit(OrExp e)
        {
            id = Id.ior_r;
        }

        override void visit(XorExp e)
        {
            id = Id.ixor_r;
        }

        override void visit(CatExp e)
        {
            id = Id.cat_r;
        }
    }

    scope OpIdRVisitor v = new OpIdRVisitor();
    e.accept(v);
    return v.id;
}

/************************************
 * If type is a class or struct, return the symbol for it,
 * else NULL
 */
extern (C++) AggregateDeclaration isAggregate(Type t)
{
    t = t.toBasetype();
    if (t.ty == Tclass)
    {
        return (cast(TypeClass)t).sym;
    }
    else if (t.ty == Tstruct)
    {
        return (cast(TypeStruct)t).sym;
    }
    return null;
}

/*******************************************
 * Helper function to turn operator into template argument list
 */
extern (C++) Objects* opToArg(Scope* sc, TOK op)
{
    /* Remove the = from op=
     */
    switch (op)
    {
    case TOKaddass:
        op = TOKadd;
        break;
    case TOKminass:
        op = TOKmin;
        break;
    case TOKmulass:
        op = TOKmul;
        break;
    case TOKdivass:
        op = TOKdiv;
        break;
    case TOKmodass:
        op = TOKmod;
        break;
    case TOKandass:
        op = TOKand;
        break;
    case TOKorass:
        op = TOKor;
        break;
    case TOKxorass:
        op = TOKxor;
        break;
    case TOKshlass:
        op = TOKshl;
        break;
    case TOKshrass:
        op = TOKshr;
        break;
    case TOKushrass:
        op = TOKushr;
        break;
    case TOKcatass:
        op = TOKcat;
        break;
    case TOKpowass:
        op = TOKpow;
        break;
    default:
        break;
    }
    Expression e = new StringExp(Loc(), cast(char*)Token.toChars(op));
    e = e.semantic(sc);
    auto tiargs = new Objects();
    tiargs.push(e);
    return tiargs;
}

/************************************
 * Operator overload.
 * Check for operator overload, if so, replace
 * with function call.
 * Return NULL if not an operator overload.
 */
extern (C++) Expression op_overload(Expression e, Scope* sc)
{
    extern (C++) final class OpOverload : Visitor
    {
        alias visit = super.visit;
    public:
        Scope* sc;
        Expression result;

        extern (D) this(Scope* sc)
        {
            this.sc = sc;
        }

        override void visit(Expression e)
        {
            assert(0);
        }

        override void visit(UnaExp e)
        {
            //printf("UnaExp::op_overload() (%s)\n", e.toChars());
            if (e.e1.op == TOKarray)
            {
                ArrayExp ae = cast(ArrayExp)e.e1;
                ae.e1 = ae.e1.semantic(sc);
                ae.e1 = resolveProperties(sc, ae.e1);
                Expression ae1old = ae.e1;
                const(bool) maybeSlice = (ae.arguments.dim == 0 || ae.arguments.dim == 1 && (*ae.arguments)[0].op == TOKinterval);
                IntervalExp ie = null;
                if (maybeSlice && ae.arguments.dim)
                {
                    assert((*ae.arguments)[0].op == TOKinterval);
                    ie = cast(IntervalExp)(*ae.arguments)[0];
                }
                while (true)
                {
                    if (ae.e1.op == TOKerror)
                    {
                        result = ae.e1;
                        return;
                    }
                    Expression e0 = null;
                    Expression ae1save = ae.e1;
                    ae.lengthVar = null;
                    Type t1b = ae.e1.type.toBasetype();
                    AggregateDeclaration ad = isAggregate(t1b);
                    if (!ad)
                        break;
                    if (search_function(ad, Id.opIndexUnary))
                    {
                        // Deal with $
                        result = resolveOpDollar(sc, ae, &e0);
                        if (!result) // op(a[i..j]) might be: a.opSliceUnary!(op)(i, j)
                            goto Lfallback;
                        if (result.op == TOKerror)
                            return;
                        /* Rewrite op(a[arguments]) as:
                         *      a.opIndexUnary!(op)(arguments)
                         */
                        Expressions* a = ae.arguments.copy();
                        Objects* tiargs = opToArg(sc, e.op);
                        result = new DotTemplateInstanceExp(e.loc, ae.e1, Id.opIndexUnary, tiargs);
                        result = new CallExp(e.loc, result, a);
                        if (maybeSlice) // op(a[]) might be: a.opSliceUnary!(op)()
                            result = result.trySemantic(sc);
                        else
                            result = result.semantic(sc);
                        if (result)
                        {
                            result = Expression.combine(e0, result);
                            return;
                        }
                    }
                Lfallback:
                    if (maybeSlice && search_function(ad, Id.opSliceUnary))
                    {
                        // Deal with $
                        result = resolveOpDollar(sc, ae, ie, &e0);
                        if (result.op == TOKerror)
                            return;
                        /* Rewrite op(a[i..j]) as:
                         *      a.opSliceUnary!(op)(i, j)
                         */
                        auto a = new Expressions();
                        if (ie)
                        {
                            a.push(ie.lwr);
                            a.push(ie.upr);
                        }
                        Objects* tiargs = opToArg(sc, e.op);
                        result = new DotTemplateInstanceExp(e.loc, ae.e1, Id.opSliceUnary, tiargs);
                        result = new CallExp(e.loc, result, a);
                        result = result.semantic(sc);
                        result = Expression.combine(e0, result);
                        return;
                    }
                    // Didn't find it. Forward to aliasthis
                    if (ad.aliasthis && t1b != ae.att1)
                    {
                        if (!ae.att1 && t1b.checkAliasThisRec())
                            ae.att1 = t1b;
                        /* Rewrite op(a[arguments]) as:
                         *      op(a.aliasthis[arguments])
                         */
                        ae.e1 = resolveAliasThis(sc, ae1save, true);
                        if (ae.e1)
                            continue;
                    }
                    break;
                }
                ae.e1 = ae1old; // recovery
                ae.lengthVar = null;
            }
            e.e1 = e.e1.semantic(sc);
            e.e1 = resolveProperties(sc, e.e1);
            if (e.e1.op == TOKerror)
            {
                result = e.e1;
                return;
            }
            AggregateDeclaration ad = isAggregate(e.e1.type);
            if (ad)
            {
                Dsymbol fd = null;
                version (all)
                {
                    // Old way, kept for compatibility with D1
                    if (e.op != TOKpreplusplus && e.op != TOKpreminusminus)
                    {
                        fd = search_function(ad, opId(e));
                        if (fd)
                        {
                            // Rewrite +e1 as e1.add()
                            result = build_overload(e.loc, sc, e.e1, null, fd);
                            return;
                        }
                    }
                }
                /* Rewrite as:
                 *      e1.opUnary!(op)()
                 */
                fd = search_function(ad, Id.opUnary);
                if (fd)
                {
                    Objects* tiargs = opToArg(sc, e.op);
                    result = new DotTemplateInstanceExp(e.loc, e.e1, fd.ident, tiargs);
                    result = new CallExp(e.loc, result);
                    result = result.semantic(sc);
                    return;
                }
                // Didn't find it. Forward to aliasthis
                if (ad.aliasthis && e.e1.type != e.att1)
                {
                    /* Rewrite op(e1) as:
                     *      op(e1.aliasthis)
                     */
                    //printf("att una %s e1 = %s\n", Token::toChars(op), this.e1.type.toChars());
                    Expression e1 = new DotIdExp(e.loc, e.e1, ad.aliasthis.ident);
                    UnaExp ue = cast(UnaExp)e.copy();
                    if (!ue.att1 && e.e1.type.checkAliasThisRec())
                        ue.att1 = e.e1.type;
                    ue.e1 = e1;
                    result = ue.trySemantic(sc);
                    return;
                }
            }
        }

        override void visit(ArrayExp ae)
        {
            //printf("ArrayExp::op_overload() (%s)\n", ae.toChars());
            ae.e1 = ae.e1.semantic(sc);
            ae.e1 = resolveProperties(sc, ae.e1);
            Expression ae1old = ae.e1;
            const(bool) maybeSlice = (ae.arguments.dim == 0 || ae.arguments.dim == 1 && (*ae.arguments)[0].op == TOKinterval);
            IntervalExp ie = null;
            if (maybeSlice && ae.arguments.dim)
            {
                assert((*ae.arguments)[0].op == TOKinterval);
                ie = cast(IntervalExp)(*ae.arguments)[0];
            }
            while (true)
            {
                if (ae.e1.op == TOKerror)
                {
                    result = ae.e1;
                    return;
                }
                Expression e0 = null;
                Expression ae1save = ae.e1;
                ae.lengthVar = null;
                Type t1b = ae.e1.type.toBasetype();
                AggregateDeclaration ad = isAggregate(t1b);
                if (!ad)
                {
                    // If the non-aggregate expression ae.e1 is indexable or sliceable,
                    // convert it to the corresponding concrete expression.
                    if (t1b.ty == Tpointer || t1b.ty == Tsarray || t1b.ty == Tarray || t1b.ty == Taarray ||
                        t1b.ty == Ttuple || t1b.ty == Tvector || ae.e1.op == TOKtype)
                    {
                        // Convert to SliceExp
                        if (maybeSlice)
                        {
                            result = new SliceExp(ae.loc, ae.e1, ie);
                            result = result.semantic(sc);
                            return;
                        }
                        // Convert to IndexExp
                        if (ae.arguments.dim == 1)
                        {
                            result = new IndexExp(ae.loc, ae.e1, (*ae.arguments)[0]);
                            result = result.semantic(sc);
                            return;
                        }
                    }
                    break;
                }
                if (search_function(ad, Id.index))
                {
                    // Deal with $
                    result = resolveOpDollar(sc, ae, &e0);
                    if (!result) // a[i..j] might be: a.opSlice(i, j)
                        goto Lfallback;
                    if (result.op == TOKerror)
                        return;
                    /* Rewrite e1[arguments] as:
                     *      e1.opIndex(arguments)
                     */
                    Expressions* a = ae.arguments.copy();
                    result = new DotIdExp(ae.loc, ae.e1, Id.index);
                    result = new CallExp(ae.loc, result, a);
                    if (maybeSlice) // a[] might be: a.opSlice()
                        result = result.trySemantic(sc);
                    else
                        result = result.semantic(sc);
                    if (result)
                    {
                        result = Expression.combine(e0, result);
                        return;
                    }
                }
            Lfallback:
                if (maybeSlice && ae.e1.op == TOKtype)
                {
                    result = new SliceExp(ae.loc, ae.e1, ie);
                    result = result.semantic(sc);
                    result = Expression.combine(e0, result);
                    return;
                }
                if (maybeSlice && search_function(ad, Id.slice))
                {
                    // Deal with $
                    result = resolveOpDollar(sc, ae, ie, &e0);
                    if (result.op == TOKerror)
                        return;
                    /* Rewrite a[i..j] as:
                     *      a.opSlice(i, j)
                     */
                    auto a = new Expressions();
                    if (ie)
                    {
                        a.push(ie.lwr);
                        a.push(ie.upr);
                    }
                    result = new DotIdExp(ae.loc, ae.e1, Id.slice);
                    result = new CallExp(ae.loc, result, a);
                    result = result.semantic(sc);
                    result = Expression.combine(e0, result);
                    return;
                }
                // Didn't find it. Forward to aliasthis
                if (ad.aliasthis && t1b != ae.att1)
                {
                    if (!ae.att1 && t1b.checkAliasThisRec())
                        ae.att1 = t1b;
                    //printf("att arr e1 = %s\n", this.e1.type.toChars());
                    /* Rewrite op(a[arguments]) as:
                     *      op(a.aliasthis[arguments])
                     */
                    ae.e1 = resolveAliasThis(sc, ae1save, true);
                    if (ae.e1)
                        continue;
                }
                break;
            }
            ae.e1 = ae1old; // recovery
            ae.lengthVar = null;
        }

        /***********************************************
         * This is mostly the same as UnaryExp::op_overload(), but has
         * a different rewrite.
         */
        override void visit(CastExp e)
        {
            //printf("CastExp::op_overload() (%s)\n", e.toChars());
            AggregateDeclaration ad = isAggregate(e.e1.type);
            if (ad)
            {
                Dsymbol fd = null;
                /* Rewrite as:
                 *      e1.opCast!(T)()
                 */
                fd = search_function(ad, Id._cast);
                if (fd)
                {
                    version (all)
                    {
                        // Backwards compatibility with D1 if opCast is a function, not a template
                        if (fd.isFuncDeclaration())
                        {
                            // Rewrite as:  e1.opCast()
                            result = build_overload(e.loc, sc, e.e1, null, fd);
                            return;
                        }
                    }
                    auto tiargs = new Objects();
                    tiargs.push(e.to);
                    result = new DotTemplateInstanceExp(e.loc, e.e1, fd.ident, tiargs);
                    result = new CallExp(e.loc, result);
                    result = result.semantic(sc);
                    return;
                }
                // Didn't find it. Forward to aliasthis
                if (ad.aliasthis)
                {
                    /* Rewrite op(e1) as:
                     *      op(e1.aliasthis)
                     */
                    Expression e1 = new DotIdExp(e.loc, e.e1, ad.aliasthis.ident);
                    result = e.copy();
                    (cast(UnaExp)result).e1 = e1;
                    result = result.trySemantic(sc);
                    return;
                }
            }
        }

        override void visit(BinExp e)
        {
            //printf("BinExp::op_overload() (%s)\n", e.toChars());
            Identifier id = opId(e);
            Identifier id_r = opId_r(e);
            Expressions args1;
            Expressions args2;
            int argsset = 0;
            AggregateDeclaration ad1 = isAggregate(e.e1.type);
            AggregateDeclaration ad2 = isAggregate(e.e2.type);
            if (e.op == TOKassign && ad1 == ad2)
            {
                StructDeclaration sd = ad1.isStructDeclaration();
                if (sd && !sd.hasIdentityAssign)
                {
                    /* This is bitwise struct assignment. */
                    return;
                }
            }
            Dsymbol s = null;
            Dsymbol s_r = null;
            version (all)
            {
                // the old D1 scheme
                if (ad1 && id)
                {
                    s = search_function(ad1, id);
                }
                if (ad2 && id_r)
                {
                    s_r = search_function(ad2, id_r);
                    // https://issues.dlang.org/show_bug.cgi?id=12778
                    // If both x.opBinary(y) and y.opBinaryRight(x) found,
                    // and they are exactly same symbol, x.opBinary(y) should be preferred.
                    if (s_r && s_r == s)
                        s_r = null;
                }
            }
            Objects* tiargs = null;
            if (e.op == TOKplusplus || e.op == TOKminusminus)
            {
                // Bug4099 fix
                if (ad1 && search_function(ad1, Id.opUnary))
                    return;
            }
            if (!s && !s_r && e.op != TOKequal && e.op != TOKnotequal && e.op != TOKassign && e.op != TOKplusplus && e.op != TOKminusminus)
            {
                /* Try the new D2 scheme, opBinary and opBinaryRight
                 */
                if (ad1)
                {
                    s = search_function(ad1, Id.opBinary);
                    if (s && !s.isTemplateDeclaration())
                    {
                        e.e1.error("%s.opBinary isn't a template", e.e1.toChars());
                        result = new ErrorExp();
                        return;
                    }
                }
                if (ad2)
                {
                    s_r = search_function(ad2, Id.opBinaryRight);
                    if (s_r && !s_r.isTemplateDeclaration())
                    {
                        e.e2.error("%s.opBinaryRight isn't a template", e.e2.toChars());
                        result = new ErrorExp();
                        return;
                    }
                    if (s_r && s_r == s) // https://issues.dlang.org/show_bug.cgi?id=12778
                        s_r = null;
                }
                // Set tiargs, the template argument list, which will be the operator string
                if (s || s_r)
                {
                    id = Id.opBinary;
                    id_r = Id.opBinaryRight;
                    tiargs = opToArg(sc, e.op);
                }
            }
            if (s || s_r)
            {
                /* Try:
                 *      a.opfunc(b)
                 *      b.opfunc_r(a)
                 * and see which is better.
                 */
                args1.setDim(1);
                args1[0] = e.e1;
                expandTuples(&args1);
                args2.setDim(1);
                args2[0] = e.e2;
                expandTuples(&args2);
                argsset = 1;
                Match m;
                m.last = MATCHnomatch;
                if (s)
                {
                    functionResolve(&m, s, e.loc, sc, tiargs, e.e1.type, &args2);
                    if (m.lastf && (m.lastf.errors || m.lastf.semantic3Errors))
                    {
                        result = new ErrorExp();
                        return;
                    }
                }
                FuncDeclaration lastf = m.lastf;
                if (s_r)
                {
                    functionResolve(&m, s_r, e.loc, sc, tiargs, e.e2.type, &args1);
                    if (m.lastf && (m.lastf.errors || m.lastf.semantic3Errors))
                    {
                        result = new ErrorExp();
                        return;
                    }
                }
                if (m.count > 1)
                {
                    // Error, ambiguous
                    e.error("overloads %s and %s both match argument list for %s", m.lastf.type.toChars(), m.nextf.type.toChars(), m.lastf.toChars());
                }
                else if (m.last <= MATCHnomatch)
                {
                    m.lastf = m.anyf;
                    if (tiargs)
                        goto L1;
                }
                if (e.op == TOKplusplus || e.op == TOKminusminus)
                {
                    // Kludge because operator overloading regards e++ and e--
                    // as unary, but it's implemented as a binary.
                    // Rewrite (e1 ++ e2) as e1.postinc()
                    // Rewrite (e1 -- e2) as e1.postdec()
                    result = build_overload(e.loc, sc, e.e1, null, m.lastf ? m.lastf : s);
                }
                else if (lastf && m.lastf == lastf || !s_r && m.last <= MATCHnomatch)
                {
                    // Rewrite (e1 op e2) as e1.opfunc(e2)
                    result = build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s);
                }
                else
                {
                    // Rewrite (e1 op e2) as e2.opfunc_r(e1)
                    result = build_overload(e.loc, sc, e.e2, e.e1, m.lastf ? m.lastf : s_r);
                }
                return;
            }
        L1:
            version (all)
            {
                // Retained for D1 compatibility
                if (isCommutative(e.op) && !tiargs)
                {
                    s = null;
                    s_r = null;
                    if (ad1 && id_r)
                    {
                        s_r = search_function(ad1, id_r);
                    }
                    if (ad2 && id)
                    {
                        s = search_function(ad2, id);
                        if (s && s == s_r) // https://issues.dlang.org/show_bug.cgi?id=12778
                            s = null;
                    }
                    if (s || s_r)
                    {
                        /* Try:
                         *  a.opfunc_r(b)
                         *  b.opfunc(a)
                         * and see which is better.
                         */
                        if (!argsset)
                        {
                            args1.setDim(1);
                            args1[0] = e.e1;
                            expandTuples(&args1);
                            args2.setDim(1);
                            args2[0] = e.e2;
                            expandTuples(&args2);
                        }
                        Match m;
                        m.last = MATCHnomatch;
                        if (s_r)
                        {
                            functionResolve(&m, s_r, e.loc, sc, tiargs, e.e1.type, &args2);
                            if (m.lastf && (m.lastf.errors || m.lastf.semantic3Errors))
                            {
                                result = new ErrorExp();
                                return;
                            }
                        }
                        FuncDeclaration lastf = m.lastf;
                        if (s)
                        {
                            functionResolve(&m, s, e.loc, sc, tiargs, e.e2.type, &args1);
                            if (m.lastf && (m.lastf.errors || m.lastf.semantic3Errors))
                            {
                                result = new ErrorExp();
                                return;
                            }
                        }
                        if (m.count > 1)
                        {
                            // Error, ambiguous
                            e.error("overloads %s and %s both match argument list for %s", m.lastf.type.toChars(), m.nextf.type.toChars(), m.lastf.toChars());
                        }
                        else if (m.last <= MATCHnomatch)
                        {
                            m.lastf = m.anyf;
                        }
                        if (lastf && m.lastf == lastf || !s && m.last <= MATCHnomatch)
                        {
                            // Rewrite (e1 op e2) as e1.opfunc_r(e2)
                            result = build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s_r);
                        }
                        else
                        {
                            // Rewrite (e1 op e2) as e2.opfunc(e1)
                            result = build_overload(e.loc, sc, e.e2, e.e1, m.lastf ? m.lastf : s);
                        }
                        // When reversing operands of comparison operators,
                        // need to reverse the sense of the op
                        switch (e.op)
                        {
                        case TOKlt:
                            e.op = TOKgt;
                            break;
                        case TOKgt:
                            e.op = TOKlt;
                            break;
                        case TOKle:
                            e.op = TOKge;
                            break;
                        case TOKge:
                            e.op = TOKle;
                            break;
                        default:
                            break;
                        }
                        return;
                    }
                }
            }
            // Try alias this on first operand
            if (ad1 && ad1.aliasthis && !(e.op == TOKassign && ad2 && ad1 == ad2)) // https://issues.dlang.org/show_bug.cgi?id=2943
            {
                /* Rewrite (e1 op e2) as:
                 *      (e1.aliasthis op e2)
                 */
                if (e.att1 && e.e1.type == e.att1)
                    return;
                //printf("att bin e1 = %s\n", this.e1.type.toChars());
                Expression e1 = new DotIdExp(e.loc, e.e1, ad1.aliasthis.ident);
                BinExp be = cast(BinExp)e.copy();
                if (!be.att1 && e.e1.type.checkAliasThisRec())
                    be.att1 = e.e1.type;
                be.e1 = e1;
                result = be.trySemantic(sc);
                return;
            }
            // Try alias this on second operand
            /* https://issues.dlang.org/show_bug.cgi?id=2943
             * make sure that when we're copying the struct, we don't
             * just copy the alias this member
             */
            if (ad2 && ad2.aliasthis && !(e.op == TOKassign && ad1 && ad1 == ad2))
            {
                /* Rewrite (e1 op e2) as:
                 *      (e1 op e2.aliasthis)
                 */
                if (e.att2 && e.e2.type == e.att2)
                    return;
                //printf("att bin e2 = %s\n", e.e2.type.toChars());
                Expression e2 = new DotIdExp(e.loc, e.e2, ad2.aliasthis.ident);
                BinExp be = cast(BinExp)e.copy();
                if (!be.att2 && e.e2.type.checkAliasThisRec())
                    be.att2 = e.e2.type;
                be.e2 = e2;
                result = be.trySemantic(sc);
                return;
            }
            return;
        }

        override void visit(EqualExp e)
        {
            //printf("EqualExp::op_overload() (%s)\n", e.toChars());
            Type t1 = e.e1.type.toBasetype();
            Type t2 = e.e2.type.toBasetype();

            /* Check for array equality.
             */
            if ((t1.ty == Tarray || t1.ty == Tsarray) &&
                (t2.ty == Tarray || t2.ty == Tsarray))
            {
                bool needsDirectEq()
                {
                    Type t1n = t1.nextOf().toBasetype();
                    Type t2n = t2.nextOf().toBasetype();
                    if (((t1n.ty == Tchar || t1n.ty == Twchar || t1n.ty == Tdchar) &&
                         (t2n.ty == Tchar || t2n.ty == Twchar || t2n.ty == Tdchar)) ||
                        (t1n.ty == Tvoid || t2n.ty == Tvoid))
                    {
                        return false;
                    }
                    if (t1n.constOf() != t2n.constOf())
                        return true;

                    Type t = t1n;
                    while (t.toBasetype().nextOf())
                        t = t.nextOf().toBasetype();
                    if (t.ty != Tstruct)
                        return false;

                    semanticTypeInfo(sc, t);
                    return (cast(TypeStruct)t).sym.hasIdentityEquals;
                }

                if (needsDirectEq())
                {
                    /* Rewrite as:
                     *      _ArrayEq(e1, e2)
                     */
                    Expression eeq = new IdentifierExp(e.loc, Id._ArrayEq);
                    result = new CallExp(e.loc, eeq, e.e1, e.e2);
                    if (e.op == TOKnotequal)
                        result = new NotExp(e.loc, result);
                    result = result.trySemantic(sc); // for better error message
                    if (!result)
                    {
                        e.error("cannot compare %s and %s", t1.toChars(), t2.toChars());
                        result = new ErrorExp();
                    }
                    return;
                }
            }

            /* Check for class equality with null literal or typeof(null).
             */
            if (t1.ty == Tclass && e.e2.op == TOKnull ||
                t2.ty == Tclass && e.e1.op == TOKnull)
            {
                e.error("use '%s' instead of '%s' when comparing with null",
                    Token.toChars(e.op == TOKequal ? TOKidentity : TOKnotidentity),
                    Token.toChars(e.op));
                result = new ErrorExp();
                return;
            }
            if (t1.ty == Tclass && t2.ty == Tnull ||
                t1.ty == Tnull && t2.ty == Tclass)
            {
                // Comparing a class with typeof(null) should not call opEquals
                return;
            }

            /* Check for class equality.
             */
            if (t1.ty == Tclass && t2.ty == Tclass)
            {
                ClassDeclaration cd1 = t1.isClassHandle();
                ClassDeclaration cd2 = t2.isClassHandle();
                if (!(cd1.cpp || cd2.cpp))
                {
                    /* Rewrite as:
                     *      .object.opEquals(e1, e2)
                     */
                    Expression e1x = e.e1;
                    Expression e2x = e.e2;

                    /* The explicit cast is necessary for interfaces
                     * https://issues.dlang.org/show_bug.cgi?id=4088
                     */
                    Type to = ClassDeclaration.object.getType();
                    if (cd1.isInterfaceDeclaration())
                        e1x = new CastExp(e.loc, e.e1, t1.isMutable() ? to : to.constOf());
                    if (cd2.isInterfaceDeclaration())
                        e2x = new CastExp(e.loc, e.e2, t2.isMutable() ? to : to.constOf());

                    result = new IdentifierExp(e.loc, Id.empty);
                    result = new DotIdExp(e.loc, result, Id.object);
                    result = new DotIdExp(e.loc, result, Id.eq);
                    result = new CallExp(e.loc, result, e1x, e2x);
                    if (e.op == TOKnotequal)
                        result = new NotExp(e.loc, result);
                    result = result.semantic(sc);
                    return;
                }
            }

            result = compare_overload(e, sc, Id.eq);
            if (result)
            {
                if (result.op == TOKcall && e.op == TOKnotequal)
                {
                    result = new NotExp(result.loc, result);
                    result = result.semantic(sc);
                }
                return;
            }

            /* Check for pointer equality.
             */
            if (t1.ty == Tpointer || t2.ty == Tpointer)
            {
                /* Rewrite:
                 *      ptr1 == ptr2
                 * as:
                 *      ptr1 is ptr2
                 *
                 * This is just a rewriting for deterministic AST representation
                 * as the backend input.
                 */
                auto op2 = e.op == TOKequal ? TOKidentity : TOKnotidentity;
                result = new IdentityExp(op2, e.loc, e.e1, e.e2);
                result = result.semantic(sc);
                return;
            }

            /* Check for struct equality without opEquals.
             */
            if (t1.ty == Tstruct && t2.ty == Tstruct)
            {
                auto sd = (cast(TypeStruct)t1).sym;
                if (sd != (cast(TypeStruct)t2).sym)
                    return;

                import ddmd.clone : needOpEquals;
                if (!needOpEquals(sd))
                {
                    // Use bitwise equality.
                    auto op2 = e.op == TOKequal ? TOKidentity : TOKnotidentity;
                    result = new IdentityExp(op2, e.loc, e.e1, e.e2);
                    result = result.semantic(sc);
                    return;
                }

                /* Do memberwise equality.
                 * Rewrite:
                 *      e1 == e2
                 * as:
                 *      e1.tupleof == e2.tupleof
                 *
                 * If sd is a nested struct, and if it's nested in a class, it will
                 * also compare the parent class's equality. Otherwise, compares
                 * the identity of parent context through void*.
                 */
                if (e.att1 && t1 == e.att1) return;
                if (e.att2 && t2 == e.att2) return;

                e = cast(EqualExp)e.copy();
                if (!e.att1) e.att1 = t1;
                if (!e.att2) e.att2 = t2;
                e.e1 = new DotIdExp(e.loc, e.e1, Id._tupleof);
                e.e2 = new DotIdExp(e.loc, e.e2, Id._tupleof);
                result = e.semantic(sc);

                /* https://issues.dlang.org/show_bug.cgi?id=15292
                 * if the rewrite result is same with the original,
                 * the equality is unresolvable because it has recursive definition.
                 */
                if (result.op == e.op &&
                    (cast(EqualExp)result).e1.type.toBasetype() == t1)
                {
                    e.error("cannot compare %s because its auto generated member-wise equality has recursive definition",
                        t1.toChars());
                    result = new ErrorExp();
                }
                return;
            }

            /* Check for tuple equality.
             */
            if (e.e1.op == TOKtuple && e.e2.op == TOKtuple)
            {
                auto tup1 = cast(TupleExp)e.e1;
                auto tup2 = cast(TupleExp)e.e2;
                size_t dim = tup1.exps.dim;
                if (dim != tup2.exps.dim)
                {
                    e.error("mismatched tuple lengths, %d and %d",
                        cast(int)dim, cast(int)tup2.exps.dim);
                    result = new ErrorExp();
                    return;
                }

                if (dim == 0)
                {
                    // zero-length tuple comparison should always return true or false.
                    result = new IntegerExp(e.loc, (e.op == TOKequal), Type.tbool);
                }
                else
                {
                    for (size_t i = 0; i < dim; i++)
                    {
                        auto ex1 = (*tup1.exps)[i];
                        auto ex2 = (*tup2.exps)[i];
                        auto eeq = new EqualExp(e.op, e.loc, ex1, ex2);
                        eeq.att1 = e.att1;
                        eeq.att2 = e.att2;

                        if (!result)
                            result = eeq;
                        else if (e.op == TOKequal)
                            result = new AndAndExp(e.loc, result, eeq);
                        else
                            result = new OrOrExp(e.loc, result, eeq);
                    }
                    assert(result);
                }
                result = Expression.combine(Expression.combine(tup1.e0, tup2.e0), result);
                result = result.semantic(sc);
                return;
            }
        }

        override void visit(CmpExp e)
        {
            //printf("CmpExp::op_overload() (%s)\n", e.toChars());
            result = compare_overload(e, sc, Id.cmp);
        }

        /*********************************
         * Operator overloading for op=
         */
        override void visit(BinAssignExp e)
        {
            //printf("BinAssignExp::op_overload() (%s)\n", e.toChars());
            if (e.e1.op == TOKarray)
            {
                ArrayExp ae = cast(ArrayExp)e.e1;
                ae.e1 = ae.e1.semantic(sc);
                ae.e1 = resolveProperties(sc, ae.e1);
                Expression ae1old = ae.e1;
                const(bool) maybeSlice = (ae.arguments.dim == 0 || ae.arguments.dim == 1 && (*ae.arguments)[0].op == TOKinterval);
                IntervalExp ie = null;
                if (maybeSlice && ae.arguments.dim)
                {
                    assert((*ae.arguments)[0].op == TOKinterval);
                    ie = cast(IntervalExp)(*ae.arguments)[0];
                }
                while (true)
                {
                    if (ae.e1.op == TOKerror)
                    {
                        result = ae.e1;
                        return;
                    }
                    Expression e0 = null;
                    Expression ae1save = ae.e1;
                    ae.lengthVar = null;
                    Type t1b = ae.e1.type.toBasetype();
                    AggregateDeclaration ad = isAggregate(t1b);
                    if (!ad)
                        break;
                    if (search_function(ad, Id.opIndexOpAssign))
                    {
                        // Deal with $
                        result = resolveOpDollar(sc, ae, &e0);
                        if (!result) // (a[i..j] op= e2) might be: a.opSliceOpAssign!(op)(e2, i, j)
                            goto Lfallback;
                        if (result.op == TOKerror)
                            return;
                        result = e.e2.semantic(sc);
                        if (result.op == TOKerror)
                            return;
                        e.e2 = result;
                        /* Rewrite a[arguments] op= e2 as:
                         *      a.opIndexOpAssign!(op)(e2, arguments)
                         */
                        Expressions* a = ae.arguments.copy();
                        a.insert(0, e.e2);
                        Objects* tiargs = opToArg(sc, e.op);
                        result = new DotTemplateInstanceExp(e.loc, ae.e1, Id.opIndexOpAssign, tiargs);
                        result = new CallExp(e.loc, result, a);
                        if (maybeSlice) // (a[] op= e2) might be: a.opSliceOpAssign!(op)(e2)
                            result = result.trySemantic(sc);
                        else
                            result = result.semantic(sc);
                        if (result)
                        {
                            result = Expression.combine(e0, result);
                            return;
                        }
                    }
                Lfallback:
                    if (maybeSlice && search_function(ad, Id.opSliceOpAssign))
                    {
                        // Deal with $
                        result = resolveOpDollar(sc, ae, ie, &e0);
                        if (result.op == TOKerror)
                            return;
                        result = e.e2.semantic(sc);
                        if (result.op == TOKerror)
                            return;
                        e.e2 = result;
                        /* Rewrite (a[i..j] op= e2) as:
                         *      a.opSliceOpAssign!(op)(e2, i, j)
                         */
                        auto a = new Expressions();
                        a.push(e.e2);
                        if (ie)
                        {
                            a.push(ie.lwr);
                            a.push(ie.upr);
                        }
                        Objects* tiargs = opToArg(sc, e.op);
                        result = new DotTemplateInstanceExp(e.loc, ae.e1, Id.opSliceOpAssign, tiargs);
                        result = new CallExp(e.loc, result, a);
                        result = result.semantic(sc);
                        result = Expression.combine(e0, result);
                        return;
                    }
                    // Didn't find it. Forward to aliasthis
                    if (ad.aliasthis && t1b != ae.att1)
                    {
                        if (!ae.att1 && t1b.checkAliasThisRec())
                            ae.att1 = t1b;
                        /* Rewrite (a[arguments] op= e2) as:
                         *      a.aliasthis[arguments] op= e2
                         */
                        ae.e1 = resolveAliasThis(sc, ae1save, true);
                        if (ae.e1)
                            continue;
                    }
                    break;
                }
                ae.e1 = ae1old; // recovery
                ae.lengthVar = null;
            }
            result = e.binSemanticProp(sc);
            if (result)
                return;
            // Don't attempt 'alias this' if an error occurred
            if (e.e1.type.ty == Terror || e.e2.type.ty == Terror)
            {
                result = new ErrorExp();
                return;
            }
            Identifier id = opId(e);
            Expressions args2;
            AggregateDeclaration ad1 = isAggregate(e.e1.type);
            Dsymbol s = null;
            version (all)
            {
                // the old D1 scheme
                if (ad1 && id)
                {
                    s = search_function(ad1, id);
                }
            }
            Objects* tiargs = null;
            if (!s)
            {
                /* Try the new D2 scheme, opOpAssign
                 */
                if (ad1)
                {
                    s = search_function(ad1, Id.opOpAssign);
                    if (s && !s.isTemplateDeclaration())
                    {
                        e.error("%s.opOpAssign isn't a template", e.e1.toChars());
                        result = new ErrorExp();
                        return;
                    }
                }
                // Set tiargs, the template argument list, which will be the operator string
                if (s)
                {
                    id = Id.opOpAssign;
                    tiargs = opToArg(sc, e.op);
                }
            }
            if (s)
            {
                /* Try:
                 *      a.opOpAssign(b)
                 */
                args2.setDim(1);
                args2[0] = e.e2;
                expandTuples(&args2);
                Match m;
                m.last = MATCHnomatch;
                if (s)
                {
                    functionResolve(&m, s, e.loc, sc, tiargs, e.e1.type, &args2);
                    if (m.lastf && (m.lastf.errors || m.lastf.semantic3Errors))
                    {
                        result = new ErrorExp();
                        return;
                    }
                }
                if (m.count > 1)
                {
                    // Error, ambiguous
                    e.error("overloads %s and %s both match argument list for %s", m.lastf.type.toChars(), m.nextf.type.toChars(), m.lastf.toChars());
                }
                else if (m.last <= MATCHnomatch)
                {
                    m.lastf = m.anyf;
                    if (tiargs)
                        goto L1;
                }
                // Rewrite (e1 op e2) as e1.opOpAssign(e2)
                result = build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s);
                return;
            }
        L1:
            // Try alias this on first operand
            if (ad1 && ad1.aliasthis)
            {
                /* Rewrite (e1 op e2) as:
                 *      (e1.aliasthis op e2)
                 */
                if (e.att1 && e.e1.type == e.att1)
                    return;
                //printf("att %s e1 = %s\n", Token::toChars(e.op), e.e1.type.toChars());
                Expression e1 = new DotIdExp(e.loc, e.e1, ad1.aliasthis.ident);
                BinExp be = cast(BinExp)e.copy();
                if (!be.att1 && e.e1.type.checkAliasThisRec())
                    be.att1 = e.e1.type;
                be.e1 = e1;
                result = be.trySemantic(sc);
                return;
            }
            // Try alias this on second operand
            AggregateDeclaration ad2 = isAggregate(e.e2.type);
            if (ad2 && ad2.aliasthis)
            {
                /* Rewrite (e1 op e2) as:
                 *      (e1 op e2.aliasthis)
                 */
                if (e.att2 && e.e2.type == e.att2)
                    return;
                //printf("att %s e2 = %s\n", Token::toChars(e.op), e.e2.type.toChars());
                Expression e2 = new DotIdExp(e.loc, e.e2, ad2.aliasthis.ident);
                BinExp be = cast(BinExp)e.copy();
                if (!be.att2 && e.e2.type.checkAliasThisRec())
                    be.att2 = e.e2.type;
                be.e2 = e2;
                result = be.trySemantic(sc);
                return;
            }
        }
    }

    scope OpOverload v = new OpOverload(sc);
    e.accept(v);
    return v.result;
}

/******************************************
 * Common code for overloading of EqualExp and CmpExp
 */
extern (C++) Expression compare_overload(BinExp e, Scope* sc, Identifier id)
{
    //printf("BinExp::compare_overload(id = %s) %s\n", id.toChars(), e.toChars());
    AggregateDeclaration ad1 = isAggregate(e.e1.type);
    AggregateDeclaration ad2 = isAggregate(e.e2.type);
    Dsymbol s = null;
    Dsymbol s_r = null;
    if (ad1)
    {
        s = search_function(ad1, id);
    }
    if (ad2)
    {
        s_r = search_function(ad2, id);
        if (s == s_r)
            s_r = null;
    }
    Objects* tiargs = null;
    if (s || s_r)
    {
        /* Try:
         *      a.opEquals(b)
         *      b.opEquals(a)
         * and see which is better.
         */
        Expressions args1;
        Expressions args2;
        args1.setDim(1);
        args1[0] = e.e1;
        expandTuples(&args1);
        args2.setDim(1);
        args2[0] = e.e2;
        expandTuples(&args2);
        Match m;
        m.last = MATCHnomatch;
        if (0 && s && s_r)
        {
            printf("s  : %s\n", s.toPrettyChars());
            printf("s_r: %s\n", s_r.toPrettyChars());
        }
        if (s)
        {
            functionResolve(&m, s, e.loc, sc, tiargs, e.e1.type, &args2);
            if (m.lastf && (m.lastf.errors || m.lastf.semantic3Errors))
                return new ErrorExp();
        }
        FuncDeclaration lastf = m.lastf;
        int count = m.count;
        if (s_r)
        {
            functionResolve(&m, s_r, e.loc, sc, tiargs, e.e2.type, &args1);
            if (m.lastf && (m.lastf.errors || m.lastf.semantic3Errors))
                return new ErrorExp();
        }
        if (m.count > 1)
        {
            /* The following if says "not ambiguous" if there's one match
             * from s and one from s_r, in which case we pick s.
             * This doesn't follow the spec, but is a workaround for the case
             * where opEquals was generated from templates and we cannot figure
             * out if both s and s_r came from the same declaration or not.
             * The test case is:
             *   import std.typecons;
             *   void main() {
             *    assert(tuple("has a", 2u) == tuple("has a", 1));
             *   }
             */
            if (!(m.lastf == lastf && m.count == 2 && count == 1))
            {
                // Error, ambiguous
                e.error("overloads %s and %s both match argument list for %s", m.lastf.type.toChars(), m.nextf.type.toChars(), m.lastf.toChars());
            }
        }
        else if (m.last <= MATCHnomatch)
        {
            m.lastf = m.anyf;
        }
        Expression result;
        if (lastf && m.lastf == lastf || !s_r && m.last <= MATCHnomatch)
        {
            // Rewrite (e1 op e2) as e1.opfunc(e2)
            result = build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s);
        }
        else
        {
            // Rewrite (e1 op e2) as e2.opfunc_r(e1)
            result = build_overload(e.loc, sc, e.e2, e.e1, m.lastf ? m.lastf : s_r);
            // When reversing operands of comparison operators,
            // need to reverse the sense of the op
            switch (e.op)
            {
            case TOKlt:
                e.op = TOKgt;
                break;
            case TOKgt:
                e.op = TOKlt;
                break;
            case TOKle:
                e.op = TOKge;
                break;
            case TOKge:
                e.op = TOKle;
                break;
            default:
                break;
            }
        }
        return result;
    }
    // Try alias this on first operand
    if (ad1 && ad1.aliasthis)
    {
        /* Rewrite (e1 op e2) as:
         *      (e1.aliasthis op e2)
         */
        if (e.att1 && e.e1.type == e.att1)
            return null;
        //printf("att cmp_bin e1 = %s\n", e.e1.type.toChars());
        Expression e1 = new DotIdExp(e.loc, e.e1, ad1.aliasthis.ident);
        BinExp be = cast(BinExp)e.copy();
        if (!be.att1 && e.e1.type.checkAliasThisRec())
            be.att1 = e.e1.type;
        be.e1 = e1;
        return be.trySemantic(sc);
    }
    // Try alias this on second operand
    if (ad2 && ad2.aliasthis)
    {
        /* Rewrite (e1 op e2) as:
         *      (e1 op e2.aliasthis)
         */
        if (e.att2 && e.e2.type == e.att2)
            return null;
        //printf("att cmp_bin e2 = %s\n", e.e2.type.toChars());
        Expression e2 = new DotIdExp(e.loc, e.e2, ad2.aliasthis.ident);
        BinExp be = cast(BinExp)e.copy();
        if (!be.att2 && e.e2.type.checkAliasThisRec())
            be.att2 = e.e2.type;
        be.e2 = e2;
        return be.trySemantic(sc);
    }
    return null;
}

/***********************************
 * Utility to build a function call out of this reference and argument.
 */
extern (C++) Expression build_overload(Loc loc, Scope* sc, Expression ethis, Expression earg, Dsymbol d)
{
    assert(d);
    Expression e;
    //printf("build_overload(id = '%s')\n", id.toChars());
    //earg.print();
    //earg.type.print();
    Declaration decl = d.isDeclaration();
    if (decl)
        e = new DotVarExp(loc, ethis, decl, false);
    else
        e = new DotIdExp(loc, ethis, d.ident);
    e = new CallExp(loc, e, earg);
    e = e.semantic(sc);
    return e;
}

/***************************************
 * Search for function funcid in aggregate ad.
 */
extern (C++) Dsymbol search_function(ScopeDsymbol ad, Identifier funcid)
{
    Dsymbol s = ad.search(Loc(), funcid);
    if (s)
    {
        //printf("search_function: s = '%s'\n", s.kind());
        Dsymbol s2 = s.toAlias();
        //printf("search_function: s2 = '%s'\n", s2.kind());
        FuncDeclaration fd = s2.isFuncDeclaration();
        if (fd && fd.type.ty == Tfunction)
            return fd;
        TemplateDeclaration td = s2.isTemplateDeclaration();
        if (td)
            return td;
    }
    return null;
}

extern (C++) bool inferAggregate(ForeachStatement fes, Scope* sc, ref Dsymbol sapply)
{
    //printf("inferAggregate(%s)\n", fes.aggr.toChars());
    Identifier idapply = (fes.op == TOKforeach) ? Id.apply : Id.applyReverse;
    Identifier idfront = (fes.op == TOKforeach) ? Id.Ffront : Id.Fback;
    int sliced = 0;
    Type tab;
    Type att = null;
    Expression aggr = fes.aggr;
    AggregateDeclaration ad;
    while (1)
    {
        aggr = aggr.semantic(sc);
        aggr = resolveProperties(sc, aggr);
        aggr = aggr.optimize(WANTvalue);
        if (!aggr.type || aggr.op == TOKerror)
            goto Lerr;
        tab = aggr.type.toBasetype();
        switch (tab.ty)
        {
        case Tarray:
        case Tsarray:
        case Ttuple:
        case Taarray:
            break;
        case Tclass:
            ad = (cast(TypeClass)tab).sym;
            goto Laggr;
        case Tstruct:
            ad = (cast(TypeStruct)tab).sym;
            goto Laggr;
        Laggr:
            if (!sliced)
            {
                sapply = search_function(ad, idapply);
                if (sapply)
                {
                    // opApply aggregate
                    break;
                }
                if (fes.aggr.op != TOKtype)
                {
                    Expression rinit = new ArrayExp(aggr.loc, fes.aggr);
                    rinit = rinit.trySemantic(sc);
                    if (rinit) // if application of [] succeeded
                    {
                        aggr = rinit;
                        sliced = 1;
                        continue;
                    }
                }
            }
            if (ad.search(Loc(), idfront))
            {
                // range aggregate
                break;
            }
            if (ad.aliasthis)
            {
                if (att == tab)
                    goto Lerr;
                if (!att && tab.checkAliasThisRec())
                    att = tab;
                aggr = resolveAliasThis(sc, aggr);
                continue;
            }
            goto Lerr;
        case Tdelegate:
            if (aggr.op == TOKdelegate)
            {
                sapply = (cast(DelegateExp)aggr).func;
            }
            break;
        case Terror:
            break;
        default:
            goto Lerr;
        }
        break;
    }
    fes.aggr = aggr;
    return true;
Lerr:
    return false;
}

/*****************************************
 * Given array of parameters and an aggregate type,
 * if any of the parameter types are missing, attempt to infer
 * them from the aggregate type.
 */
extern (C++) bool inferApplyArgTypes(ForeachStatement fes, Scope* sc, ref Dsymbol sapply)
{
    if (!fes.parameters || !fes.parameters.dim)
        return false;
    if (sapply) // prefer opApply
    {
        for (size_t u = 0; u < fes.parameters.dim; u++)
        {
            Parameter p = (*fes.parameters)[u];
            if (p.type)
            {
                p.type = p.type.semantic(fes.loc, sc);
                p.type = p.type.addStorageClass(p.storageClass);
            }
        }
        Expression ethis;
        Type tab = fes.aggr.type.toBasetype();
        if (tab.ty == Tclass || tab.ty == Tstruct)
            ethis = fes.aggr;
        else
        {
            assert(tab.ty == Tdelegate && fes.aggr.op == TOKdelegate);
            ethis = (cast(DelegateExp)fes.aggr).e1;
        }
        /* Look for like an
         *  int opApply(int delegate(ref Type [, ...]) dg);
         * overload
         */
        FuncDeclaration fd = sapply.isFuncDeclaration();
        if (fd)
        {
            sapply = inferApplyArgTypesX(ethis, fd, fes.parameters);
        }
        return sapply !is null;
    }
    /* Return if no parameters need types.
     */
    for (size_t u = 0; u < fes.parameters.dim; u++)
    {
        Parameter p = (*fes.parameters)[u];
        if (!p.type)
            break;
    }
    AggregateDeclaration ad;
    Parameter p = (*fes.parameters)[0];
    Type taggr = fes.aggr.type;
    assert(taggr);
    Type tab = taggr.toBasetype();
    switch (tab.ty)
    {
    case Tarray:
    case Tsarray:
    case Ttuple:
        if (fes.parameters.dim == 2)
        {
            if (!p.type)
            {
                p.type = Type.tsize_t; // key type
                p.type = p.type.addStorageClass(p.storageClass);
            }
            p = (*fes.parameters)[1];
        }
        if (!p.type && tab.ty != Ttuple)
        {
            p.type = tab.nextOf(); // value type
            p.type = p.type.addStorageClass(p.storageClass);
        }
        break;
    case Taarray:
        {
            TypeAArray taa = cast(TypeAArray)tab;
            if (fes.parameters.dim == 2)
            {
                if (!p.type)
                {
                    p.type = taa.index; // key type
                    p.type = p.type.addStorageClass(p.storageClass);
                    if (p.storageClass & STCref) // key must not be mutated via ref
                        p.type = p.type.addMod(MODconst);
                }
                p = (*fes.parameters)[1];
            }
            if (!p.type)
            {
                p.type = taa.next; // value type
                p.type = p.type.addStorageClass(p.storageClass);
            }
            break;
        }
    case Tclass:
        ad = (cast(TypeClass)tab).sym;
        goto Laggr;
    case Tstruct:
        ad = (cast(TypeStruct)tab).sym;
        goto Laggr;
    Laggr:
        if (fes.parameters.dim == 1)
        {
            if (!p.type)
            {
                /* Look for a front() or back() overload
                 */
                Identifier id = (fes.op == TOKforeach) ? Id.Ffront : Id.Fback;
                Dsymbol s = ad.search(Loc(), id);
                FuncDeclaration fd = s ? s.isFuncDeclaration() : null;
                if (fd)
                {
                    // Resolve inout qualifier of front type
                    p.type = fd.type.nextOf();
                    if (p.type)
                    {
                        p.type = p.type.substWildTo(tab.mod);
                        p.type = p.type.addStorageClass(p.storageClass);
                    }
                }
                else if (s && s.isTemplateDeclaration())
                {
                }
                else if (s && s.isDeclaration())
                    p.type = (cast(Declaration)s).type;
                else
                    break;
            }
            break;
        }
        break;
    case Tdelegate:
        {
            if (!inferApplyArgTypesY(cast(TypeFunction)tab.nextOf(), fes.parameters))
                return false;
            break;
        }
    default:
        break; // ignore error, caught later
    }
    return true;
}

extern (C++) static Dsymbol inferApplyArgTypesX(Expression ethis, FuncDeclaration fstart, Parameters* parameters)
{
    MOD mod = ethis.type.mod;
    MATCH match = MATCHnomatch;
    FuncDeclaration fd_best;
    FuncDeclaration fd_ambig;
    overloadApply(fstart, (Dsymbol s)
    {
        auto f = s.isFuncDeclaration();
        if (!f)
            return 0;
        auto tf = cast(TypeFunction)f.type;
        MATCH m = MATCHexact;
        if (f.isThis())
        {
            if (!MODimplicitConv(mod, tf.mod))
                m = MATCHnomatch;
            else if (mod != tf.mod)
                m = MATCHconst;
        }
        if (!inferApplyArgTypesY(tf, parameters, 1))
            m = MATCHnomatch;
        if (m > match)
        {
            fd_best = f;
            fd_ambig = null;
            match = m;
        }
        else if (m == match)
            fd_ambig = f;
        return 0;
    });

    if (fd_best)
    {
        inferApplyArgTypesY(cast(TypeFunction)fd_best.type, parameters);
        if (fd_ambig)
        {
            .error(ethis.loc, "%s.%s matches more than one declaration:\n%s:     %s\nand:\n%s:     %s",
                ethis.toChars(), fstart.ident.toChars(),
                fd_best.loc.toChars(), fd_best.type.toChars(),
                fd_ambig.loc.toChars(), fd_ambig.type.toChars());
            fd_best = null;
        }
    }
    return fd_best;
}

/******************************
 * Infer parameters from type of function.
 * Returns:
 *      1 match for this function
 *      0 no match for this function
 */
extern (C++) static int inferApplyArgTypesY(TypeFunction tf, Parameters* parameters, int flags = 0)
{
    size_t nparams;
    Parameter p;
    if (Parameter.dim(tf.parameters) != 1)
        goto Lnomatch;
    p = Parameter.getNth(tf.parameters, 0);
    if (p.type.ty != Tdelegate)
        goto Lnomatch;
    tf = cast(TypeFunction)p.type.nextOf();
    assert(tf.ty == Tfunction);
    /* We now have tf, the type of the delegate. Match it against
     * the parameters, filling in missing parameter types.
     */
    nparams = Parameter.dim(tf.parameters);
    if (nparams == 0 || tf.varargs)
        goto Lnomatch; // not enough parameters
    if (parameters.dim != nparams)
        goto Lnomatch; // not enough parameters
    for (size_t u = 0; u < nparams; u++)
    {
        p = (*parameters)[u];
        Parameter param = Parameter.getNth(tf.parameters, u);
        if (p.type)
        {
            if (!p.type.equals(param.type))
                goto Lnomatch;
        }
        else if (!flags)
        {
            p.type = param.type;
            p.type = p.type.addStorageClass(p.storageClass);
        }
    }
    return 1;
Lnomatch:
    return 0;
}
