/**
 * Handles operator overloading.
 *
 * Specification: $(LINK2 https://dlang.org/spec/operatoroverloading.html, Operator Overloading)
 *
 * Copyright:   Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
 * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/opover.d, _opover.d)
 * Documentation:  https://dlang.org/phobos/dmd_opover.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/opover.d
 */

module dmd.opover;

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

/***********************************
 * Determine if operands of binary op can be reversed
 * to fit operator overload.
 */
bool isCommutative(EXP op)
{
    switch (op)
    {
    case EXP.add:
    case EXP.mul:
    case EXP.and:
    case EXP.or:
    case EXP.xor:
    // EqualExp
    case EXP.equal:
    case EXP.notEqual:
    // CmpExp
    case EXP.lessThan:
    case EXP.lessOrEqual:
    case EXP.greaterThan:
    case EXP.greaterOrEqual:
        return true;
    default:
        break;
    }
    return false;
}

/***********************************
 * Get Identifier for operator overload.
 */
private Identifier opId(Expression e)
{
    switch (e.op)
    {
    case EXP.uadd:                      return Id.uadd;
    case EXP.negate:                    return Id.neg;
    case EXP.tilde:                     return Id.com;
    case EXP.cast_:                     return Id._cast;
    case EXP.in_:                       return Id.opIn;
    case EXP.plusPlus:                  return Id.postinc;
    case EXP.minusMinus:                return Id.postdec;
    case EXP.add:                       return Id.add;
    case EXP.min:                       return Id.sub;
    case EXP.mul:                       return Id.mul;
    case EXP.div:                       return Id.div;
    case EXP.mod:                       return Id.mod;
    case EXP.pow:                       return Id.pow;
    case EXP.leftShift:                 return Id.shl;
    case EXP.rightShift:                return Id.shr;
    case EXP.unsignedRightShift:        return Id.ushr;
    case EXP.and:                       return Id.iand;
    case EXP.or:                        return Id.ior;
    case EXP.xor:                       return Id.ixor;
    case EXP.concatenate:               return Id.cat;
    case EXP.assign:                    return Id.assign;
    case EXP.addAssign:                 return Id.addass;
    case EXP.minAssign:                 return Id.subass;
    case EXP.mulAssign:                 return Id.mulass;
    case EXP.divAssign:                 return Id.divass;
    case EXP.modAssign:                 return Id.modass;
    case EXP.powAssign:                 return Id.powass;
    case EXP.leftShiftAssign:           return Id.shlass;
    case EXP.rightShiftAssign:          return Id.shrass;
    case EXP.unsignedRightShiftAssign:  return Id.ushrass;
    case EXP.andAssign:                 return Id.andass;
    case EXP.orAssign:                  return Id.orass;
    case EXP.xorAssign:                 return Id.xorass;
    case EXP.concatenateAssign:         return Id.catass;
    case EXP.equal:                     return Id.eq;
    case EXP.lessThan:
    case EXP.lessOrEqual:
    case EXP.greaterThan:
    case EXP.greaterOrEqual:            return Id.cmp;
    case EXP.array:                     return Id.index;
    case EXP.star:                      return Id.opStar;
    default:                            assert(0);
    }
}

/***********************************
 * Get Identifier for reverse operator overload,
 * `null` if not supported for this operator.
 */
private Identifier opId_r(Expression e)
{
    switch (e.op)
    {
    case EXP.in_:               return Id.opIn_r;
    case EXP.add:               return Id.add_r;
    case EXP.min:               return Id.sub_r;
    case EXP.mul:               return Id.mul_r;
    case EXP.div:               return Id.div_r;
    case EXP.mod:               return Id.mod_r;
    case EXP.pow:               return Id.pow_r;
    case EXP.leftShift:         return Id.shl_r;
    case EXP.rightShift:        return Id.shr_r;
    case EXP.unsignedRightShift:return Id.ushr_r;
    case EXP.and:               return Id.iand_r;
    case EXP.or:                return Id.ior_r;
    case EXP.xor:               return Id.ixor_r;
    case EXP.concatenate:       return Id.cat_r;
    default:                    return null;
    }
}

/*******************************************
 * Helper function to turn operator into template argument list
 */
Objects* opToArg(Scope* sc, EXP op)
{
    /* Remove the = from op=
     */
    switch (op)
    {
    case EXP.addAssign:
        op = EXP.add;
        break;
    case EXP.minAssign:
        op = EXP.min;
        break;
    case EXP.mulAssign:
        op = EXP.mul;
        break;
    case EXP.divAssign:
        op = EXP.div;
        break;
    case EXP.modAssign:
        op = EXP.mod;
        break;
    case EXP.andAssign:
        op = EXP.and;
        break;
    case EXP.orAssign:
        op = EXP.or;
        break;
    case EXP.xorAssign:
        op = EXP.xor;
        break;
    case EXP.leftShiftAssign:
        op = EXP.leftShift;
        break;
    case EXP.rightShiftAssign:
        op = EXP.rightShift;
        break;
    case EXP.unsignedRightShiftAssign:
        op = EXP.unsignedRightShift;
        break;
    case EXP.concatenateAssign:
        op = EXP.concatenate;
        break;
    case EXP.powAssign:
        op = EXP.pow;
        break;
    default:
        break;
    }
    Expression e = new StringExp(Loc.initial, EXPtoString(op));
    e = e.expressionSemantic(sc);
    auto tiargs = new Objects();
    tiargs.push(e);
    return tiargs;
}

// Try alias this on first operand
private Expression checkAliasThisForLhs(AggregateDeclaration ad, Scope* sc, BinExp e)
{
    if (!ad || !ad.aliasthis)
        return null;

    /* Rewrite (e1 op e2) as:
     *      (e1.aliasthis op e2)
     */
    if (isRecursiveAliasThis(e.att1, e.e1.type))
        return null;
    //printf("att %s e1 = %s\n", Token.toChars(e.op), e.e1.type.toChars());
    BinExp be = cast(BinExp)e.copy();
    // Resolve 'alias this' but in case of assigment don't resolve properties yet
    // because 'e1 = e2' could mean 'e1(e2)' or 'e1() = e2'
    bool findOnly = (e.op == EXP.assign);
    be.e1 = resolveAliasThis(sc, e.e1, true, findOnly);
    if (!be.e1)
        return null;

    Expression result;
    if (be.op == EXP.concatenateAssign)
        result = be.op_overload(sc);
    else
        result = be.trySemantic(sc);

    return result;
}

// Try alias this on second operand
private Expression checkAliasThisForRhs(AggregateDeclaration ad, Scope* sc, BinExp e)
{
    if (!ad || !ad.aliasthis)
        return null;
    /* Rewrite (e1 op e2) as:
     *      (e1 op e2.aliasthis)
     */
    if (isRecursiveAliasThis(e.att2, e.e2.type))
        return null;
    //printf("att %s e2 = %s\n", Token.toChars(e.op), e.e2.type.toChars());
    BinExp be = cast(BinExp)e.copy();
    be.e2 = resolveAliasThis(sc, e.e2, true);
    if (!be.e2)
        return null;

    Expression result;
    if (be.op == EXP.concatenateAssign)
        result = be.op_overload(sc);
    else
        result = be.trySemantic(sc);

    return result;
}

/************************************
 * Operator overload.
 * Check for operator overload, if so, replace
 * with function call.
 * Params:
 *      e = expression with operator
 *      sc = context
 *      pop = if not null, is set to the operator that was actually overloaded,
 *            which may not be `e.op`. Happens when operands are reversed to
 *            match an overload
 * Returns:
 *      `null` if not an operator overload,
 *      otherwise the lowered expression
 */
Expression op_overload(Expression e, Scope* sc, EXP* pop = null)
{
        Expression visit(Expression e)
        {
            assert(0);
        }

        Expression visitUna(UnaExp e)
        {
            //printf("UnaExp::op_overload() (%s)\n", e.toChars());
            Expression result;
            if (auto ae = e.e1.isArrayExp())
            {
                ae.e1 = ae.e1.expressionSemantic(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 == EXP.interval);
                IntervalExp ie = null;
                if (maybeSlice && ae.arguments.dim)
                {
                    ie = (*ae.arguments)[0].isIntervalExp();
                }
                while (true)
                {
                    if (ae.e1.op == EXP.error)
                    {
                        return ae.e1;
                    }
                    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 == EXP.error)
                            return result;
                        /* 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.expressionSemantic(sc);
                        if (result)
                        {
                            return Expression.combine(e0, result);
                        }
                    }
                Lfallback:
                    if (maybeSlice && search_function(ad, Id.opSliceUnary))
                    {
                        // Deal with $
                        result = resolveOpDollar(sc, ae, ie, &e0);
                        if (result.op == EXP.error)
                            return result;
                        /* 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.expressionSemantic(sc);
                        result = Expression.combine(e0, result);
                        return result;
                    }
                    // Didn't find it. Forward to aliasthis
                    if (ad.aliasthis && !isRecursiveAliasThis(ae.att1, ae.e1.type))
                    {
                        /* 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.expressionSemantic(sc);
            e.e1 = resolveProperties(sc, e.e1);
            if (e.e1.op == EXP.error)
            {
                return e.e1;
            }
            AggregateDeclaration ad = isAggregate(e.e1.type);
            if (ad)
            {
                Dsymbol fd = null;
                /* 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.expressionSemantic(sc);
                    return result;
                }
                // D1-style operator overloads, deprecated
                if (e.op != EXP.prePlusPlus && e.op != EXP.preMinusMinus)
                {
                    auto id = opId(e);
                    fd = search_function(ad, id);
                    if (fd)
                    {
                        // @@@DEPRECATED_2.110@@@.
                        // Deprecated in 2.088, made an error in 2.100
                        e.error("`%s` is obsolete.  Use `opUnary(string op)() if (op == \"%s\")` instead.", id.toChars(), EXPtoString(e.op).ptr);
                        return ErrorExp.get();
                    }
                }
                // Didn't find it. Forward to aliasthis
                if (ad.aliasthis && !isRecursiveAliasThis(e.att1, e.e1.type))
                {
                    /* Rewrite op(e1) as:
                     *      op(e1.aliasthis)
                     */
                    //printf("att una %s e1 = %s\n", EXPtoString(op).ptr, this.e1.type.toChars());
                    Expression e1 = new DotIdExp(e.loc, e.e1, ad.aliasthis.ident);
                    UnaExp ue = cast(UnaExp)e.copy();
                    ue.e1 = e1;
                    result = ue.trySemantic(sc);
                    return result;
                }
            }
            return result;
        }

        Expression visitArray(ArrayExp ae)
        {
            //printf("ArrayExp::op_overload() (%s)\n", ae.toChars());
            ae.e1 = ae.e1.expressionSemantic(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 == EXP.interval);
            IntervalExp ie = null;
            if (maybeSlice && ae.arguments.dim)
            {
                ie = (*ae.arguments)[0].isIntervalExp();
            }
            Expression result;
            while (true)
            {
                if (ae.e1.op == EXP.error)
                {
                    return ae.e1;
                }
                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 (isIndexableNonAggregate(t1b) || ae.e1.op == EXP.type)
                    {
                        // Convert to SliceExp
                        if (maybeSlice)
                        {
                            result = new SliceExp(ae.loc, ae.e1, ie);
                            result = result.expressionSemantic(sc);
                            return result;
                        }
                        // Convert to IndexExp
                        if (ae.arguments.dim == 1)
                        {
                            result = new IndexExp(ae.loc, ae.e1, (*ae.arguments)[0]);
                            result = result.expressionSemantic(sc);
                            return result;
                        }
                    }
                    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 == EXP.error)
                        return result;
                    /* 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.expressionSemantic(sc);
                    if (result)
                    {
                        return Expression.combine(e0, result);
                    }
                }
            Lfallback:
                if (maybeSlice && ae.e1.op == EXP.type)
                {
                    result = new SliceExp(ae.loc, ae.e1, ie);
                    result = result.expressionSemantic(sc);
                    result = Expression.combine(e0, result);
                    return result;
                }
                if (maybeSlice && search_function(ad, Id.slice))
                {
                    // Deal with $
                    result = resolveOpDollar(sc, ae, ie, &e0);

                    if (result.op == EXP.error)
                    {
                        if (!e0 && !search_function(ad, Id.dollar)) {
                            ae.loc.errorSupplemental("Aggregate declaration '%s' does not define 'opDollar'", ae.e1.toChars());
                        }
                        return result;
                    }
                    /* 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.expressionSemantic(sc);
                    result = Expression.combine(e0, result);
                    return result;
                }
                // Didn't find it. Forward to aliasthis
                if (ad.aliasthis && !isRecursiveAliasThis(ae.att1, ae.e1.type))
                {
                    //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;
            return result;
        }

        /***********************************************
         * This is mostly the same as UnaryExp::op_overload(), but has
         * a different rewrite.
         */
        Expression visitCast(CastExp e)
        {
            //printf("CastExp::op_overload() (%s)\n", e.toChars());
            Expression result;
            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()
                            return build_overload(e.loc, sc, e.e1, null, fd);
                        }
                    }
                    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.expressionSemantic(sc);
                    return result;
                }
                // Didn't find it. Forward to aliasthis
                if (ad.aliasthis && !isRecursiveAliasThis(e.att1, e.e1.type))
                {
                    /* Rewrite op(e1) as:
                     *      op(e1.aliasthis)
                     */
                    if (auto e1 = resolveAliasThis(sc, e.e1, true))
                    {
                        result = e.copy();
                        (cast(UnaExp)result).e1 = e1;
                        result = result.op_overload(sc);
                        return result;
                    }
                }
            }
            return result;
        }

        Expression visitBin(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 == EXP.assign && ad1 == ad2)
            {
                StructDeclaration sd = ad1.isStructDeclaration();
                if (sd &&
                    (!sd.hasIdentityAssign ||
                     /* Do a blit if we can and the rvalue is something like .init,
                      * where a postblit is not necessary.
                      */
                     (sd.hasBlitAssign && !e.e2.isLvalue())))
                {
                    /* This is bitwise struct assignment. */
                    return null;
                }
            }
            Dsymbol s = null;
            Dsymbol s_r = null;
            Objects* tiargs = null;
            if (e.op == EXP.plusPlus || e.op == EXP.minusMinus)
            {
                // Bug4099 fix
                if (ad1 && search_function(ad1, Id.opUnary))
                    return null;
            }
            if (e.op != EXP.equal && e.op != EXP.notEqual && e.op != EXP.assign && e.op != EXP.plusPlus && e.op != EXP.minusMinus)
            {
                /* Try 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());
                        return ErrorExp.get();
                    }
                }
                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());
                        return ErrorExp.get();
                    }
                    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 the D1-style operators, deprecated
                if (ad1 && id)
                {
                    s = search_function(ad1, id);
                    if (s && id != Id.assign)
                    {
                        // @@@DEPRECATED_2.110@@@.
                        // Deprecated in 2.088, made an error in 2.100
                        if (id == Id.postinc || id == Id.postdec)
                            e.error("`%s` is obsolete.  Use `opUnary(string op)() if (op == \"%s\")` instead.", id.toChars(), EXPtoString(e.op).ptr);
                        else
                            e.error("`%s` is obsolete.  Use `opBinary(string op)(...) if (op == \"%s\")` instead.", id.toChars(), EXPtoString(e.op).ptr);
                        return ErrorExp.get();
                    }
                }
                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;
                    if (s_r)
                    {
                        // @@@DEPRECATED_2.110@@@.
                        // Deprecated in 2.088, made an error in 2.100
                        e.error("`%s` is obsolete.  Use `opBinaryRight(string op)(...) if (op == \"%s\")` instead.", id_r.toChars(), EXPtoString(e.op).ptr);
                        return ErrorExp.get();
                    }
                }
            }
            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;
                MatchAccumulator m;
                if (s)
                {
                    functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, &args2);
                    if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
                    {
                        return ErrorExp.get();
                    }
                }
                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.hasSemantic3Errors()))
                    {
                        return ErrorExp.get();
                    }
                }
                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 == MATCH.nomatch)
                {
                    if (tiargs)
                        goto L1;
                    m.lastf = null;
                }
                if (e.op == EXP.plusPlus || e.op == EXP.minusMinus)
                {
                    // 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()
                    return build_overload(e.loc, sc, e.e1, null, m.lastf ? m.lastf : s);
                }
                else if (lastf && m.lastf == lastf || !s_r && m.last == MATCH.nomatch)
                {
                    // Rewrite (e1 op e2) as e1.opfunc(e2)
                    return build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s);
                }
                else
                {
                    // Rewrite (e1 op e2) as e2.opfunc_r(e1)
                    return build_overload(e.loc, sc, e.e2, e.e1, m.lastf ? m.lastf : s_r);
                }
            }
        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);
                        }
                        MatchAccumulator m;
                        if (s_r)
                        {
                            functionResolve(m, s_r, e.loc, sc, tiargs, e.e1.type, &args2);
                            if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
                            {
                                return ErrorExp.get();
                            }
                        }
                        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.hasSemantic3Errors()))
                            {
                                return ErrorExp.get();
                            }
                        }
                        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 == MATCH.nomatch)
                        {
                            m.lastf = null;
                        }

                        if (lastf && m.lastf == lastf || !s && m.last == MATCH.nomatch)
                        {
                            // Rewrite (e1 op e2) as e1.opfunc_r(e2)
                            return build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s_r);
                        }
                        else
                        {
                            // Rewrite (e1 op e2) as e2.opfunc(e1)
                            Expression 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
                            if (pop)
                                *pop = reverseRelation(e.op);
                            return result;
                        }
                    }
                }
            }

            Expression rewrittenLhs;
            if (!(e.op == EXP.assign && ad2 && ad1 == ad2)) // https://issues.dlang.org/show_bug.cgi?id=2943
            {
                if (Expression result = checkAliasThisForLhs(ad1, sc, e))
                {
                    /* https://issues.dlang.org/show_bug.cgi?id=19441
                     *
                     * alias this may not be used for partial assignment.
                     * If a struct has a single member which is aliased this
                     * directly or aliased to a ref getter function that returns
                     * the mentioned member, then alias this may be
                     * used since the object will be fully initialised.
                     * If the struct is nested, the context pointer is considered
                     * one of the members, hence the `ad1.fields.dim == 2 && ad1.vthis`
                     * condition.
                     */
                    if (result.op != EXP.assign)
                        return result;     // i.e: Rewrote `e1 = e2` -> `e1(e2)`

                    auto ae = result.isAssignExp();
                    if (ae.e1.op != EXP.dotVariable)
                        return result;     // i.e: Rewrote `e1 = e2` -> `e1() = e2`

                    auto dve = ae.e1.isDotVarExp();
                    if (auto ad = dve.var.isMember2())
                    {
                        // i.e: Rewrote `e1 = e2` -> `e1.some.var = e2`
                        // Ensure that `var` is the only field member in `ad`
                        if (ad.fields.dim == 1 || (ad.fields.dim == 2 && ad.vthis))
                        {
                            if (dve.var == ad.aliasthis.sym)
                                return result;
                        }
                    }
                    rewrittenLhs = ae.e1;
                }
            }
            if (!(e.op == EXP.assign && ad1 && ad1 == ad2)) // https://issues.dlang.org/show_bug.cgi?id=2943
            {
                if (Expression result = checkAliasThisForRhs(ad2, sc, e))
                    return result;
            }
            if (rewrittenLhs)
            {
                e.error("cannot use `alias this` to partially initialize variable `%s` of type `%s`. Use `%s`",
                        e.e1.toChars(), ad1.toChars(), rewrittenLhs.toChars());
                return ErrorExp.get();
            }
            return null;
        }

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

            /* Array equality is handled by expressionSemantic() potentially
             * lowering to object.__equals(), which takes care of overloaded
             * operators for the element types.
             */
            if ((t1.ty == Tarray || t1.ty == Tsarray) &&
                (t2.ty == Tarray || t2.ty == Tsarray))
            {
                return null;
            }

            /* Check for class equality with null literal or typeof(null).
             */
            if (t1.ty == Tclass && e.e2.op == EXP.null_ ||
                t2.ty == Tclass && e.e1.op == EXP.null_)
            {
                e.error("use `%s` instead of `%s` when comparing with `null`",
                    EXPtoString(e.op == EXP.equal ? EXP.identity : EXP.notIdentity).ptr,
                    EXPtoString(e.op).ptr);
                return ErrorExp.get();
            }
            if (t1.ty == Tclass && t2.ty == Tnull ||
                t1.ty == Tnull && t2.ty == Tclass)
            {
                // Comparing a class with typeof(null) should not call opEquals
                return null;
            }

            /* Check for class equality.
             */
            if (t1.ty == Tclass && t2.ty == Tclass)
            {
                ClassDeclaration cd1 = t1.isClassHandle();
                ClassDeclaration cd2 = t2.isClassHandle();
                if (!(cd1.classKind == ClassKind.cpp || cd2.classKind == ClassKind.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());

                    Expression 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 == EXP.notEqual)
                        result = new NotExp(e.loc, result);
                    result = result.expressionSemantic(sc);
                    return result;
                }
            }

            if (Expression result = compare_overload(e, sc, Id.eq, null))
            {
                if (lastComma(result).op == EXP.call && e.op == EXP.notEqual)
                {
                    result = new NotExp(result.loc, result);
                    result = result.expressionSemantic(sc);
                }
                return result;
            }

            /* 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 == EXP.equal ? EXP.identity : EXP.notIdentity;
                Expression r = new IdentityExp(op2, e.loc, e.e1, e.e2);
                return r.expressionSemantic(sc);
            }

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

                import dmd.clone : needOpEquals;
                if (!global.params.fieldwise && !needOpEquals(sd))
                {
                    // Use bitwise equality.
                    auto op2 = e.op == EXP.equal ? EXP.identity : EXP.notIdentity;
                    Expression r = new IdentityExp(op2, e.loc, e.e1, e.e2);
                    return r.expressionSemantic(sc);
                }

                /* Do memberwise equality.
                 * https://dlang.org/spec/expression.html#equality_expressions
                 * 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.equivalent(e.att1)) return null;
                if (e.att2 && t2.equivalent(e.att2)) return null;

                e = e.copy().isEqualExp();
                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);

                auto sc2 = sc.push();
                sc2.flags |= SCOPE.noaccesscheck;
                Expression r = e.expressionSemantic(sc2);
                sc2.pop();

                /* 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 (r.op == e.op &&
                    r.isEqualExp().e1.type.toBasetype() == t1)
                {
                    e.error("cannot compare `%s` because its auto generated member-wise equality has recursive definition",
                        t1.toChars());
                    return ErrorExp.get();
                }
                return r;
            }

            /* Check for tuple equality.
             */
            if (e.e1.op == EXP.tuple && e.e2.op == EXP.tuple)
            {
                auto tup1 = e.e1.isTupleExp();
                auto tup2 = e.e2.isTupleExp();
                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);
                    return ErrorExp.get();
                }

                Expression result;
                if (dim == 0)
                {
                    // zero-length tuple comparison should always return true or false.
                    result = IntegerExp.createBool(e.op == EXP.equal);
                }
                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 == EXP.equal)
                            result = new LogicalExp(e.loc, EXP.andAnd, result, eeq);
                        else
                            result = new LogicalExp(e.loc, EXP.orOr, result, eeq);
                    }
                    assert(result);
                }
                result = Expression.combine(tup1.e0, tup2.e0, result);
                result = result.expressionSemantic(sc);

                return result;
            }
            return null;
        }

        Expression visitCmp(CmpExp e)
        {
            //printf("CmpExp:: () (%s)\n", e.toChars());
            return compare_overload(e, sc, Id.cmp, pop);
        }

        /*********************************
         * Operator overloading for op=
         */
        Expression visitBinAssign(BinAssignExp e)
        {
            //printf("BinAssignExp::op_overload() (%s)\n", e.toChars());
            if (auto ae = e.e1.isArrayExp())
            {
                ae.e1 = ae.e1.expressionSemantic(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 == EXP.interval);
                IntervalExp ie = null;
                if (maybeSlice && ae.arguments.dim)
                {
                    ie = (*ae.arguments)[0].isIntervalExp();
                }
                while (true)
                {
                    if (ae.e1.op == EXP.error)
                    {
                        return ae.e1;
                    }
                    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 $
                        Expression 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 == EXP.error)
                            return result;
                        result = e.e2.expressionSemantic(sc);
                        if (result.op == EXP.error)
                            return result;
                        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.expressionSemantic(sc);
                        if (result)
                        {
                            return Expression.combine(e0, result);
                        }
                    }
                Lfallback:
                    if (maybeSlice && search_function(ad, Id.opSliceOpAssign))
                    {
                        // Deal with $
                        Expression result = resolveOpDollar(sc, ae, ie, &e0);
                        if (result.op == EXP.error)
                            return result;
                        result = e.e2.expressionSemantic(sc);
                        if (result.op == EXP.error)
                            return result;
                        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.expressionSemantic(sc);
                        result = Expression.combine(e0, result);
                        return result;
                    }
                    // Didn't find it. Forward to aliasthis
                    if (ad.aliasthis && !isRecursiveAliasThis(ae.att1, ae.e1.type))
                    {
                        /* 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;
            }
            Expression result = e.binSemanticProp(sc);
            if (result)
                return result;
            // Don't attempt 'alias this' if an error occurred
            if (e.e1.type.ty == Terror || e.e2.type.ty == Terror)
            {
                return ErrorExp.get();
            }
            Identifier id = opId(e);
            Expressions args2;
            AggregateDeclaration ad1 = isAggregate(e.e1.type);
            Dsymbol s = null;
            Objects* tiargs = null;
            /* Try opOpAssign
             */
            if (ad1)
            {
                s = search_function(ad1, Id.opOpAssign);
                if (s && !s.isTemplateDeclaration())
                {
                    e.error("`%s.opOpAssign` isn't a template", e.e1.toChars());
                    return ErrorExp.get();
                }
            }
            // Set tiargs, the template argument list, which will be the operator string
            if (s)
            {
                id = Id.opOpAssign;
                tiargs = opToArg(sc, e.op);
            }

            // Try D1-style operator overload, deprecated
            if (!s && ad1 && id)
            {
                s = search_function(ad1, id);
                if (s)
                {
                    // @@@DEPRECATED_2.110@@@.
                    // Deprecated in 2.088, made an error in 2.100
                    scope char[] op = EXPtoString(e.op).dup;
                    op[$-1] = '\0'; // remove trailing `=`
                    e.error("`%s` is obsolete.  Use `opOpAssign(string op)(...) if (op == \"%s\")` instead.", id.toChars(), op.ptr);
                    return ErrorExp.get();
                }
            }

            if (s)
            {
                /* Try:
                 *      a.opOpAssign(b)
                 */
                args2.setDim(1);
                args2[0] = e.e2;
                expandTuples(&args2);
                MatchAccumulator m;
                if (s)
                {
                    functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, &args2);
                    if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
                    {
                        return ErrorExp.get();
                    }
                }
                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 == MATCH.nomatch)
                {
                    if (tiargs)
                        goto L1;
                    m.lastf = null;
                }
                // Rewrite (e1 op e2) as e1.opOpAssign(e2)
                return build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s);
            }
        L1:
            result = checkAliasThisForLhs(ad1, sc, e);
            if (result || !s) // no point in trying Rhs alias-this if there's no overload of any kind in lhs
                return result;

            return checkAliasThisForRhs(isAggregate(e.e2.type), sc, e);
        }

    if (pop)
        *pop = e.op;

    switch (e.op)
    {
        case EXP.cast_         : return visitCast(e.isCastExp());
        case EXP.array         : return visitArray(e.isArrayExp());

        case EXP.notEqual      :
        case EXP.equal         : return visitEqual(e.isEqualExp());

        case EXP.lessOrEqual   :
        case EXP.greaterThan   :
        case EXP.greaterOrEqual:
        case EXP.lessThan      : return visitCmp(cast(CmpExp)e);

        default:
            if (auto ex = e.isBinAssignExp()) return visitBinAssign(ex);
            if (auto ex = e.isBinExp())       return visitBin(ex);
            if (auto ex = e.isUnaExp())       return visitUna(ex);
            return visit(e);
    }
}

/******************************************
 * Common code for overloading of EqualExp and CmpExp
 */
private Expression compare_overload(BinExp e, Scope* sc, Identifier id, EXP* pop)
{
    //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(1);
        args1[0] = e.e1;
        expandTuples(&args1);
        Expressions args2 = Expressions(1);
        args2[0] = e.e2;
        expandTuples(&args2);
        MatchAccumulator m;
        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.hasSemantic3Errors()))
                return ErrorExp.get();
        }
        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.hasSemantic3Errors()))
                return ErrorExp.get();
        }
        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 == MATCH.nomatch)
        {
            m.lastf = null;
        }
        Expression result;
        if (lastf && m.lastf == lastf || !s_r && m.last == MATCH.nomatch)
        {
            // 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
            if (pop)
                *pop = reverseRelation(e.op);
        }
        return result;
    }
    /*
     * https://issues.dlang.org/show_bug.cgi?id=16657
     * at this point, no matching opEquals was found for structs,
     * so we should not follow the alias this comparison code.
     */
    if ((e.op == EXP.equal || e.op == EXP.notEqual) && ad1 == ad2)
        return null;
    Expression result = checkAliasThisForLhs(ad1, sc, e);
    return result ? result : checkAliasThisForRhs(isAggregate(e.e2.type), sc, e);
}

/***********************************
 * Utility to build a function call out of this reference and argument.
 */
Expression build_overload(const ref Loc loc, Scope* sc, Expression ethis, Expression earg, Dsymbol d)
{
    assert(d);
    Expression e;
    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.expressionSemantic(sc);
    return e;
}

/***************************************
 * Search for function funcid in aggregate ad.
 */
Dsymbol search_function(ScopeDsymbol ad, Identifier funcid)
{
    Dsymbol s = ad.search(Loc.initial, 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;
}

/**************************************
 * Figure out what is being foreach'd over by looking at the ForeachAggregate.
 * Params:
 *      sc = context
 *      isForeach = true for foreach, false for foreach_reverse
 *      feaggr = ForeachAggregate
 *      sapply = set to function opApply/opApplyReverse, or delegate, or null.
 *               Overload resolution is not done.
 * Returns:
 *      true if successfully figured it out; feaggr updated with semantic analysis.
 *      false for failed, which is an error.
 */
bool inferForeachAggregate(Scope* sc, bool isForeach, ref Expression feaggr, out Dsymbol sapply)
{
    //printf("inferForeachAggregate(%s)\n", feaggr.toChars());
    bool sliced;
    Type att = null;
    auto aggr = feaggr;
    while (1)
    {
        aggr = aggr.expressionSemantic(sc);
        aggr = resolveProperties(sc, aggr);
        aggr = aggr.optimize(WANTvalue);
        if (!aggr.type || aggr.op == EXP.error)
            return false;
        Type tab = aggr.type.toBasetype();
        switch (tab.ty)
        {
        case Tarray:            // https://dlang.org/spec/statement.html#foreach_over_arrays
        case Tsarray:           // https://dlang.org/spec/statement.html#foreach_over_arrays
        case Ttuple:            // https://dlang.org/spec/statement.html#foreach_over_tuples
        case Taarray:           // https://dlang.org/spec/statement.html#foreach_over_associative_arrays
            break;

        case Tclass:
        case Tstruct:
        {
            AggregateDeclaration ad = (tab.ty == Tclass) ? tab.isTypeClass().sym
                                                         : tab.isTypeStruct().sym;
            if (!sliced)
            {
                sapply = search_function(ad, isForeach ? Id.apply : Id.applyReverse);
                if (sapply)
                {
                    // https://dlang.org/spec/statement.html#foreach_over_struct_and_classes
                    // opApply aggregate
                    break;
                }
                if (feaggr.op != EXP.type)
                {
                    /* See if rewriting `aggr` to `aggr[]` will work
                     */
                    Expression rinit = new ArrayExp(aggr.loc, feaggr);
                    rinit = rinit.trySemantic(sc);
                    if (rinit) // if it worked
                    {
                        aggr = rinit;
                        sliced = true;  // only try it once
                        continue;
                    }
                }
            }
            if (ad.search(Loc.initial, isForeach ? Id.Ffront : Id.Fback))
            {
                // https://dlang.org/spec/statement.html#foreach-with-ranges
                // range aggregate
                break;
            }
            if (ad.aliasthis)
            {
                if (isRecursiveAliasThis(att, tab))     // error, circular alias this
                    return false;
                aggr = resolveAliasThis(sc, aggr);
                continue;
            }
            return false;
        }

        case Tdelegate:        // https://dlang.org/spec/statement.html#foreach_over_delegates
            if (auto de = aggr.isDelegateExp())
            {
                sapply = de.func;
            }
            break;

        case Terror:
            break;

        default:
            return false;
        }
        feaggr = aggr;
        return true;
    }
    assert(0);
}

/*****************************************
 * Given array of foreach parameters and an aggregate type,
 * find best opApply overload,
 * if any of the parameter types are missing, attempt to infer
 * them from the aggregate type.
 * Params:
 *      fes = the foreach statement
 *      sc = context
 *      sapply = null or opApply or delegate, overload resolution has not been done.
 *               Do overload resolution on sapply.
 * Returns:
 *      false for errors
 */
bool inferApplyArgTypes(ForeachStatement fes, Scope* sc, ref Dsymbol sapply)
{
    if (!fes.parameters || !fes.parameters.dim)
        return false;
    if (sapply) // prefer opApply
    {
        foreach (Parameter p; *fes.parameters)
        {
            if (p.type)
            {
                p.type = p.type.typeSemantic(fes.loc, sc);
                p.type = p.type.addStorageClass(p.storageClass);
            }
        }

        // Determine ethis for sapply
        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 == EXP.delegate_);
            ethis = fes.aggr.isDelegateExp().e1;
        }

        /* Look for like an
         *  int opApply(int delegate(ref Type [, ...]) dg);
         * overload
         */
        if (FuncDeclaration fd = sapply.isFuncDeclaration())
        {
            if (auto fdapply = findBestOpApplyMatch(ethis, fd, fes.parameters))
            {
                // Fill in any missing types on foreach parameters[]
                matchParamsToOpApply(fdapply.type.isTypeFunction(), fes.parameters, true);
                sapply = fdapply;
                return true;
            }
            return false;
        }
        return true;   // shouldn't this be false?
    }

    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 = tab.isTypeAArray();
            if (fes.parameters.dim == 2)
            {
                if (!p.type)
                {
                    p.type = taa.index; // key type
                    p.type = p.type.addStorageClass(p.storageClass);
                    if (p.storageClass & STC.ref_) // key must not be mutated via ref
                        p.type = p.type.addMod(MODFlags.const_);
                }
                p = (*fes.parameters)[1];
            }
            if (!p.type)
            {
                p.type = taa.next; // value type
                p.type = p.type.addStorageClass(p.storageClass);
            }
            break;
        }

    case Tclass:
    case Tstruct:
    {
        AggregateDeclaration ad = (tab.ty == Tclass) ? tab.isTypeClass().sym
                                                     : tab.isTypeStruct().sym;
        if (fes.parameters.dim == 1)
        {
            if (!p.type)
            {
                /* Look for a front() or back() overload
                 */
                Identifier id = (fes.op == TOK.foreach_) ? Id.Ffront : Id.Fback;
                Dsymbol s = ad.search(Loc.initial, 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 = s.isDeclaration().type;
                else
                    break;
            }
            break;
        }
        break;
    }

    case Tdelegate:
    {
        auto td = tab.isTypeDelegate();
        if (!matchParamsToOpApply(td.next.isTypeFunction(), fes.parameters, true))
            return false;
        break;
    }

    default:
        break; // ignore error, caught later
    }
    return true;
}

/*********************************************
 * Find best overload match on fstart given ethis and parameters[].
 * Params:
 *      ethis = expression to use for `this`
 *      fstart = opApply or foreach delegate
 *      parameters = ForeachTypeList (i.e. foreach parameters)
 * Returns:
 *      best match if there is one, null if error
 */
private FuncDeclaration findBestOpApplyMatch(Expression ethis, FuncDeclaration fstart, Parameters* parameters)
{
    MOD mod = ethis.type.mod;
    MATCH match = MATCH.nomatch;
    FuncDeclaration fd_best;
    FuncDeclaration fd_ambig;

    overloadApply(fstart, (Dsymbol s)
    {
        auto f = s.isFuncDeclaration();
        if (!f)
            return 0;           // continue
        auto tf = f.type.isTypeFunction();
        MATCH m = MATCH.exact;
        if (f.isThis())
        {
            if (!MODimplicitConv(mod, tf.mod))
                m = MATCH.nomatch;
            else if (mod != tf.mod)
                m = MATCH.constant;
        }
        if (!matchParamsToOpApply(tf, parameters, false))
            m = MATCH.nomatch;
        if (m > match)
        {
            fd_best = f;
            fd_ambig = null;
            match = m;
        }
        else if (m == match && m > MATCH.nomatch)
        {
            assert(fd_best);
            auto bestTf = fd_best.type.isTypeFunction();
            assert(bestTf);

            // Found another overload with different attributes?
            // e.g. @system vs. @safe opApply
            bool ambig = tf.attributesEqual(bestTf);

            // opApplies with identical attributes could still accept
            // different function bodies as delegate
            // => different parameters or attributes
            if (ambig)
            {
                // Fetch the delegates that receive the function body
                auto tfBody = tf.parameterList[0].type.isTypeDelegate().next;
                assert(tfBody);

                auto bestBody = bestTf.parameterList[0].type.isTypeDelegate().next;
                assert(bestBody);

                // Ignore covariant matches, as later on it can be redone
                // after the opApply delegate has its attributes inferred.
                ambig = !(tfBody.covariant(bestBody) == Covariant.yes || bestBody.covariant(tfBody) == Covariant.yes);
            }

            if (ambig)
                fd_ambig = f;                           // not covariant, so ambiguous
        }
        return 0;               // continue
    });

    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());
        return null;
    }

    return fd_best;
}

/******************************
 * Determine if foreach parameters match opApply parameters.
 * Infer missing foreach parameter types from type of opApply delegate.
 * Params:
 *      tf = type of opApply or delegate
 *      parameters = foreach parameters
 *      infer = infer missing parameter types
 * Returns:
 *      true for match for this function
 *      false for no match for this function
 */
private bool matchParamsToOpApply(TypeFunction tf, Parameters* parameters, bool infer)
{
    enum nomatch = false;

    /* opApply/delegate has exactly one parameter, and that parameter
     * is a delegate that looks like:
     *     int opApply(int delegate(ref Type [, ...]) dg);
     */
    if (tf.parameterList.length != 1)
        return nomatch;

    /* Get the type of opApply's dg parameter
     */
    Parameter p0 = tf.parameterList[0];
    auto de = p0.type.isTypeDelegate();
    if (!de)
        return nomatch;
    TypeFunction tdg = de.next.isTypeFunction();

    /* We now have tdg, the type of the delegate.
     * tdg's parameters must match that of the foreach arglist (i.e. parameters).
     * Fill in missing types in parameters.
     */
    const nparams = tdg.parameterList.length;
    if (nparams == 0 || nparams != parameters.dim || tdg.parameterList.varargs != VarArg.none)
        return nomatch; // parameter mismatch

    foreach (u, p; *parameters)
    {
        Parameter param = tdg.parameterList[u];
        if (p.type)
        {
            if (!p.type.equals(param.type))
                return nomatch;
        }
        else if (infer)
        {
            p.type = param.type;
            p.type = p.type.addStorageClass(p.storageClass);
        }
    }
    return true;
}

/**
 * Reverse relational operator, eg >= becomes <=
 * Note this is not negation.
 * Params:
 *      op = comparison operator to reverse
 * Returns:
 *      reverse of op
 */
private EXP reverseRelation(EXP op) pure
{
    switch (op)
    {
        case EXP.greaterOrEqual:  op = EXP.lessOrEqual;    break;
        case EXP.greaterThan:     op = EXP.lessThan;       break;
        case EXP.lessOrEqual:     op = EXP.greaterOrEqual; break;
        case EXP.lessThan:        op = EXP.greaterThan;    break;
        default:                  break;
    }
    return op;
}
