diff --git a/compiler/src/dmd/clone.d b/compiler/src/dmd/clone.d index d7658c68ec..20c6c6e2e2 100644 --- a/compiler/src/dmd/clone.d +++ b/compiler/src/dmd/clone.d @@ -537,7 +537,7 @@ private FuncDeclaration hasIdentityOpEquals(AggregateDeclaration ad, Scope* sc) * opEquals is changed to be never implicitly generated. * Now, struct objects comparison s1 == s2 is translated to: * s1.tupleof == s2.tupleof - * to calculate structural equality. See EqualExp.op_overload. + * to calculate structural equality. See `opOverloadEquals`. */ FuncDeclaration buildOpEquals(StructDeclaration sd, Scope* sc) { diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 481334f44c..18238380ec 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -7536,13 +7536,13 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor override void visit(BinAssignExp exp) { - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadBinaryAssign(sc)) { result = e; return; } + Expression e; if (exp.e1.op == EXP.arrayLength) { // arr.length op= e2; @@ -8777,8 +8777,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor printf("PtrExp::semantic('%s')\n", exp.toChars()); } - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadUnary(sc)) { result = e; return; @@ -8835,8 +8834,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor printf("NegExp::semantic('%s')\n", exp.toChars()); } - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadUnary(sc)) { result = e; return; @@ -8876,8 +8874,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor printf("UAddExp::semantic('%s')\n", exp.toChars()); } - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadUnary(sc)) { result = e; return; @@ -8902,8 +8899,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor override void visit(ComExp exp) { - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadUnary(sc)) { result = e; return; @@ -9165,7 +9161,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if (!exp.to.equals(exp.e1.type) && exp.mod == cast(ubyte)~0) { - if (Expression e = exp.op_overload(sc)) + if (Expression e = exp.opOverloadCast(sc)) { result = e.implicitCastTo(sc, exp.to); return; @@ -9699,8 +9695,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if (result) return; - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadArray(sc)) { result = e; return; @@ -10122,13 +10117,6 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } exp.e1 = e1x; - Expression e = exp.op_overload(sc); - if (e) - { - result = e; - return; - } - if (exp.e1.checkReadModifyWrite(exp.op)) return setError(); @@ -10171,7 +10159,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor // Combine de,ea,eb,ec if (de) ea = new CommaExp(exp.loc, de, ea); - e = new CommaExp(exp.loc, ea, eb); + Expression e = new CommaExp(exp.loc, ea, eb); e = new CommaExp(exp.loc, e, ec); e = e.expressionSemantic(sc); result = e; @@ -10181,7 +10169,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor exp.e1 = exp.e1.modifiableLvalue(sc); exp.e1 = exp.e1.optimize(WANTvalue, /*keepLvalue*/ true); - e = exp; + Expression e = exp; if (exp.e1.checkScalar() || exp.e1.checkSharedAccess(sc)) return setError(); @@ -10198,15 +10186,15 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor override void visit(PreExp exp) { - Expression e = exp.op_overload(sc); // printf("PreExp::semantic('%s')\n", toChars()); - if (e) + if (Expression e = exp.opOverloadUnary(sc)) { result = e; return; } // Rewrite as e1+=1 or e1-=1 + Expression e; if (exp.op == EXP.prePlusPlus) e = new AddAssignExp(exp.loc, exp.e1, IntegerExp.literal!1); else @@ -10976,8 +10964,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor ae.e1 = ae.e1.expressionSemantic(sc); ae.e1 = ae.e1.optimize(WANTvalue); ae.e2 = ev; - Expression e = ae.op_overload(sc); - if (e) + if (Expression e = ae.opOverloadAssign(sc)) { Expression ey = null; if (t2.ty == Tstruct && sd == t2.toDsymbol(sc)) @@ -11027,14 +11014,10 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor return; } } - else + else if (Expression e = exp.isAssignExp().opOverloadAssign(sc)) { - Expression e = exp.op_overload(sc); - if (e) - { - result = e; - return; - } + result = e; + return; } } else @@ -11051,8 +11034,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor // Disallow assignment operator overloads for same type if (exp.op == EXP.assign && !exp.e2.implicitConvTo(exp.e1.type)) { - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.isAssignExp().opOverloadAssign(sc)) { result = e; return; @@ -11723,9 +11705,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor override void visit(PowAssignExp exp) { - - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadBinaryAssign(sc)) { result = e; return; @@ -11772,7 +11752,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if ((exp.e1.type.isIntegral() || exp.e1.type.isFloating()) && (exp.e2.type.isIntegral() || exp.e2.type.isFloating())) { Expression e0 = null; - e = exp.reorderSettingAAElem(sc); + Expression e = exp.reorderSettingAAElem(sc); e = Expression.extractLast(e, e0); assert(e == exp); @@ -11804,8 +11784,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor { //printf("CatAssignExp::semantic() %s\n", exp.toChars()); - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadBinaryAssign(sc)) { result = e; return; @@ -12099,8 +12078,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = ex; return; } - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadBinary(sc)) { result = e; return; @@ -12201,8 +12179,8 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = ex; return; } - Expression e = exp.op_overload(sc); - if (e) + + if (Expression e = exp.opOverloadBinary(sc)) { result = e; return; @@ -12235,6 +12213,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if (t1.ty == Tpointer) { + Expression e; if (t2.ty == Tpointer) { // https://dlang.org/spec/expression.html#add_expressions @@ -12446,8 +12425,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = ex; return; } - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadBinary(sc)) { result = e; return; @@ -12599,6 +12577,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } Type t1 = exp.e1.type.toBasetype(); Type t2 = exp.e2.type.toBasetype(); + Expression e; if ((t1.ty == Tarray || t1.ty == Tsarray) && (t2.ty == Tarray || t2.ty == Tsarray)) { @@ -12629,8 +12608,8 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = ex; return true; } - Expression e = exp.op_overload(sc); - if (e) + + if (Expression e = exp.opOverloadBinary(sc)) { result = e; return true; @@ -12874,8 +12853,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = ex; return; } - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadBinary(sc)) { result = e; return; @@ -12922,8 +12900,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = ex; return; } - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadBinary(sc)) { result = e; return; @@ -13081,7 +13058,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor return setError(); } - if (auto e = exp.op_overload(sc)) + if (auto e = exp.opOverloadCmp(sc)) { result = e; return; @@ -13190,8 +13167,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = ex; return; } - Expression e = exp.op_overload(sc); - if (e) + if (Expression e = exp.opOverloadBinary(sc)) { result = e; return; @@ -13355,13 +13331,12 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor return false; } - if (auto e = exp.op_overload(sc)) + if (auto e = exp.opOverloadEqual(sc)) { result = e; return; } - const isArrayComparison = (t1.ty == Tarray || t1.ty == Tsarray) && (t2.ty == Tarray || t2.ty == Tsarray); const needsArrayLowering = isArrayComparison && needsDirectEq(t1, t2, sc); diff --git a/compiler/src/dmd/opover.d b/compiler/src/dmd/opover.d index 494853329b..cbecc7d173 100644 --- a/compiler/src/dmd/opover.d +++ b/compiler/src/dmd/opover.d @@ -151,7 +151,7 @@ private Expression checkAliasThisForLhs(AggregateDeclaration ad, Scope* sc, BinE Expression result; if (be.op == EXP.concatenateAssign) - result = be.op_overload(sc); + result = be.isBinAssignExp().opOverloadBinaryAssign(sc); else result = be.trySemantic(sc); @@ -176,172 +176,18 @@ private Expression checkAliasThisForRhs(AggregateDeclaration ad, Scope* sc, BinE Expression result; if (be.op == EXP.concatenateAssign) - result = be.op_overload(sc); + result = be.isBinAssignExp().opOverloadBinaryAssign(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 - * Returns: - * `null` if not an operator overload, - * otherwise the lowered expression - */ -Expression op_overload(Expression e, Scope* sc) +Expression opOverloadUnary(UnaExp e, Scope* sc) { - Expression visit(Expression e) + Expression result; + if (auto ae = e.e1.isArrayExp()) { - 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.length == 0 || ae.arguments.length == 1 && (*ae.arguments)[0].op == EXP.interval); - IntervalExp ie = null; - if (maybeSlice && ae.arguments.length) - { - ie = (*ae.arguments)[0].isIntervalExp(); - } - Type att = null; // first cyclic `alias this` type - 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(att, 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); - Type att = null; // first cyclic `alias this` type - while (1) - { - if (e.e1.op == EXP.error) - { - return e.e1; - } - - AggregateDeclaration ad = isAggregate(e.e1.type); - if (!ad) - break; - - 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; - } - - // Didn't find it. Forward to aliasthis - if (ad.aliasthis && !isRecursiveAliasThis(att, e.e1.type)) - { - /* Rewrite op(e1) as: - * op(e1.aliasthis) - */ - //printf("att una %s e1 = %s\n", EXPtoString(op).ptr, this.e1.type.toChars()); - if (auto e1 = resolveAliasThis(sc, e.e1, true)) - { - e.e1 = e1; - continue; - } - break; - } - break; - } - 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; @@ -351,7 +197,6 @@ Expression op_overload(Expression e, Scope* sc) { ie = (*ae.arguments)[0].isIntervalExp(); } - Expression result; Type att = null; // first cyclic `alias this` type while (true) { @@ -365,43 +210,23 @@ Expression op_overload(Expression e, Scope* sc) 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.length == 1) - { - result = new IndexExp(ae.loc, ae.e1, (*ae.arguments)[0]); - result = result.expressionSemantic(sc); - return result; - } - } break; - } - if (search_function(ad, Id.index)) + if (search_function(ad, Id.opIndexUnary)) { // Deal with $ result = resolveOpDollar(sc, ae, &e0); - if (!result) // a[i..j] might be: a.opSlice(i, j) + if (!result) // op(a[i..j]) might be: a.opSliceUnary!(op)(i, j) goto Lfallback; if (result.op == EXP.error) return result; - /* Rewrite e1[arguments] as: - * e1.opIndex(arguments) - */ + /* Rewrite op(a[arguments]) as: + * a.opIndexUnary!(op)(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() + 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); @@ -411,36 +236,24 @@ Expression op_overload(Expression e, Scope* sc) } } 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)) + if (maybeSlice && search_function(ad, Id.opSliceUnary)) { // 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) - */ + /* 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); } - result = new DotIdExp(ae.loc, ae.e1, Id.slice); - result = new CallExp(ae.loc, result, a); + 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; @@ -448,9 +261,751 @@ Expression op_overload(Expression e, Scope* sc) // Didn't find it. Forward to aliasthis if (ad.aliasthis && !isRecursiveAliasThis(att, ae.e1.type)) { - //printf("att arr e1 = %s\n", this.e1.type.toChars()); /* Rewrite op(a[arguments]) as: - * op(a.aliasthis[arguments]) + * 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); + Type att = null; // first cyclic `alias this` type + while (1) + { + if (e.e1.op == EXP.error) + { + return e.e1; + } + + AggregateDeclaration ad = isAggregate(e.e1.type); + if (!ad) + break; + + 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; + } + + // Didn't find it. Forward to aliasthis + if (ad.aliasthis && !isRecursiveAliasThis(att, e.e1.type)) + { + /* Rewrite op(e1) as: + * op(e1.aliasthis) + */ + //printf("att una %s e1 = %s\n", EXPtoString(op).ptr, this.e1.type.toChars()); + if (auto e1 = resolveAliasThis(sc, e.e1, true)) + { + e.e1 = e1; + continue; + } + break; + } + break; + } + return result; +} + +Expression opOverloadArray(ArrayExp ae, Scope* sc) +{ + ae.e1 = ae.e1.expressionSemantic(sc); + ae.e1 = resolveProperties(sc, ae.e1); + Expression ae1old = ae.e1; + const(bool) maybeSlice = (ae.arguments.length == 0 || ae.arguments.length == 1 && (*ae.arguments)[0].op == EXP.interval); + IntervalExp ie = null; + if (maybeSlice && ae.arguments.length) + { + ie = (*ae.arguments)[0].isIntervalExp(); + } + Expression result; + Type att = null; // first cyclic `alias this` type + 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.length == 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(att, 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 opOverloadUnary but has + * a different rewrite. + */ +Expression opOverloadCast(CastExp e, Scope* sc, Type att = null) +{ + Expression result; + if (AggregateDeclaration ad = isAggregate(e.e1.type)) + { + 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(att, 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 = opOverloadCast(result.isCastExp(), sc, att); + return result; + } + } + } + return result; +} + +// When no operator overload functions are found for `e`, recursively try with `alias this` +// Returns: `null` when still no overload found, otherwise resolved lowering +Expression binAliasThis(BinExp e, Scope* sc, AggregateDeclaration ad1, AggregateDeclaration ad2) +{ + 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.length == 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.length == 1 || (ad.fields.length == 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) + { + error(e.loc, "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 opOverloadAssign(AssignExp e, Scope* sc) +{ + AggregateDeclaration ad1 = isAggregate(e.e1.type); + AggregateDeclaration ad2 = isAggregate(e.e2.type); + if (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; + if (ad1) + s = search_function(ad1, Id.assign); + + if (!s) + return binAliasThis(e, sc, ad1, ad2); + + Expressions* args2 = new Expressions(); + args2.setDim(1); + (*args2)[0] = e.e2; + expandTuples(args2); + MatchAccumulator m; + Objects* tiargs = null; + functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, ArgumentList(args2)); + if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors())) + return ErrorExp.get(); + + if (m.last == MATCH.nomatch) + { + if (tiargs) + return binAliasThis(e, sc, ad1, ad2); + m.lastf = null; + } + return build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s); +} + +Expression opOverloadBinary(BinExp e, Scope* sc) +{ + AggregateDeclaration ad1 = isAggregate(e.e1.type); + AggregateDeclaration ad2 = isAggregate(e.e2.type); + Dsymbol s = null; + Dsymbol s_r = null; + Objects* tiargs = null; + + // Try opBinary and opBinaryRight + if (ad1) + { + s = search_function(ad1, Id.opBinary); + if (s && !s.isTemplateDeclaration()) + { + error(e.e1.loc, "`%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()) + { + error(e.e2.loc, "`%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) + { + tiargs = opToArg(sc, e.op); + } + if (!s && !s_r) + return binAliasThis(e, sc, ad1, ad2); + + // Try opBinary and opBinaryRight and see which is better. + Expressions* args1 = new Expressions(); + Expressions* args2 = new Expressions(); + args1.setDim(1); + (*args1)[0] = e.e1; + expandTuples(args1); + args2.setDim(1); + (*args2)[0] = e.e2; + expandTuples(args2); + MatchAccumulator m; + if (s) + { + functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, ArgumentList(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, ArgumentList(args1)); + if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors())) + { + return ErrorExp.get(); + } + } + if (m.count > 1) + { + // Error, ambiguous + error(e.loc, "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) + return binAliasThis(e, sc, ad1, ad2); + m.lastf = null; + } + 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); + } +} + +Expression opOverloadEqual(EqualExp e, Scope* sc) +{ + 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_) + { + error(e.loc, "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) + */ + if (!ClassDeclaration.object) + { + error(e.loc, "cannot compare classes for equality because `object.Object` was not declared"); + return null; + } + + 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; + } + } + + EXP cmpOp; + if (Expression result = compare_overload(e, sc, Id.eq, cmpOp)) + { + 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 (!sc.previews.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*. + */ + e = e.copy().isEqualExp(); + 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.noAccessCheck = true; + Expression r = e.expressionSemantic(sc2); + sc2.pop(); + 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.length; + if (dim != tup2.exps.length) + { + error(e.loc, "mismatched sequence lengths, `%d` and `%d`", + cast(int)dim, cast(int)tup2.exps.length); + 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); + + 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 opOverloadCmp(CmpExp exp, Scope* sc) +{ + //printf("CmpExp:: () (%s)\n", e.toChars()); + EXP cmpOp = exp.op; + auto e = compare_overload(exp, sc, Id.cmp, cmpOp); + if (!e) + return null; + + if (!e.type.isScalar() && e.type.equals(exp.e1.type)) + { + error(e.loc, "recursive `opCmp` expansion"); + return ErrorExp.get(); + } + if (e.op != EXP.call) + return e; + + Type t1 = exp.e1.type.toBasetype(); + Type t2 = exp.e2.type.toBasetype(); + if (t1.ty != Tclass || t2.ty != Tclass) + { + return new CmpExp(cmpOp, exp.loc, e, IntegerExp.literal!0).expressionSemantic(sc); + } + + // Lower to object.__cmp(e1, e2) + Expression cl = new IdentifierExp(exp.loc, Id.empty); + cl = new DotIdExp(exp.loc, cl, Id.object); + cl = new DotIdExp(exp.loc, cl, Id.__cmp); + cl = cl.expressionSemantic(sc); + + auto arguments = new Expressions(); + // Check if op_overload found a better match by calling e2.opCmp(e1) + // If the operands were swapped, then the result must be reversed + // e1.opCmp(e2) == -e2.opCmp(e1) + // cmpop takes care of this + if (exp.op == cmpOp) + { + arguments.push(exp.e1); + arguments.push(exp.e2); + } + else + { + // Use better match found by op_overload + arguments.push(exp.e2); + arguments.push(exp.e1); + } + + cl = new CallExp(e.loc, cl, arguments); + cl = new CmpExp(cmpOp, exp.loc, cl, new IntegerExp(0)); + return cl.expressionSemantic(sc); +} + +/********************************* + * Operator overloading for op= + */ +Expression opOverloadBinaryAssign(BinAssignExp e, Scope* sc) +{ + 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.length == 0 || ae.arguments.length == 1 && (*ae.arguments)[0].op == EXP.interval); + IntervalExp ie = null; + if (maybeSlice && ae.arguments.length) + { + ie = (*ae.arguments)[0].isIntervalExp(); + } + Type att = null; // first cyclic `alias this` type + 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(att, ae.e1.type)) + { + /* Rewrite (a[arguments] op= e2) as: + * a.aliasthis[arguments] op= e2 */ ae.e1 = resolveAliasThis(sc, ae1save, true); if (ae.e1) @@ -460,223 +1015,51 @@ Expression op_overload(Expression e, Scope* sc) } ae.e1 = ae1old; // recovery ae.lengthVar = null; - return result; } - - /*********************************************** - * This is mostly the same as UnaryExp::op_overload(), but has - * a different rewrite. + 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(); + } + Expressions* args2 = new Expressions(); + AggregateDeclaration ad1 = isAggregate(e.e1.type); + Dsymbol s = null; + Objects* tiargs = null; + /* Try opOpAssign */ - Expression visitCast(CastExp e, Type att = null) + if (ad1) { - //printf("CastExp::op_overload() (%s)\n", e.toChars()); - Expression result; - if (AggregateDeclaration ad = isAggregate(e.e1.type)) + s = search_function(ad1, Id.opOpAssign); + if (s && !s.isTemplateDeclaration()) { - 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(att, 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 = visitCast(result.isCastExp(), att); - return result; - } - } - } - return result; - } - - // When no operator overload functions are found for `e`, recursively try with `alias this` - // Returns: `null` when still no overload found, otherwise resolved lowering - Expression binAliasThis(BinExp e, AggregateDeclaration ad1, AggregateDeclaration ad2) - { - 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.length == 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.length == 1 || (ad.fields.length == 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) - { - error(e.loc, "cannot use `alias this` to partially initialize variable `%s` of type `%s`. Use `%s`", - e.e1.toChars(), ad1.toChars(), rewrittenLhs.toChars()); + error(e.loc, "`%s.opOpAssign` isn't a template", e.e1.toChars()); return ErrorExp.get(); } - return null; + } + // Set tiargs, the template argument list, which will be the operator string + Identifier id; + if (s) + { + id = Id.opOpAssign; + tiargs = opToArg(sc, e.op); } - Expression visitAssign(AssignExp e) + if (s) { - AggregateDeclaration ad1 = isAggregate(e.e1.type); - AggregateDeclaration ad2 = isAggregate(e.e2.type); - if (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; - if (ad1) - s = search_function(ad1, Id.assign); - - if (!s) - return binAliasThis(e, ad1, ad2); - - Expressions* args2 = new Expressions(); + /* Try: + * a.opOpAssign(b) + */ args2.setDim(1); (*args2)[0] = e.e2; expandTuples(args2); MatchAccumulator m; - Objects* tiargs = null; functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, ArgumentList(args2)); if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors())) + { return ErrorExp.get(); - - if (m.last == MATCH.nomatch) - { - if (tiargs) - return binAliasThis(e, ad1, ad2); - m.lastf = null; - } - return build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s); - } - - Expression visitBin(BinExp e) - { - //printf("BinExp::op_overload() (%s)\n", e.toChars()); - AggregateDeclaration ad1 = isAggregate(e.e1.type); - AggregateDeclaration ad2 = isAggregate(e.e2.type); - Dsymbol s = null; - Dsymbol s_r = null; - Objects* tiargs = null; - - // Try opBinary and opBinaryRight - if (ad1) - { - s = search_function(ad1, Id.opBinary); - if (s && !s.isTemplateDeclaration()) - { - error(e.e1.loc, "`%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()) - { - error(e.e2.loc, "`%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) - { - tiargs = opToArg(sc, e.op); - } - if (!s && !s_r) - return binAliasThis(e, ad1, ad2); - - // Try opBinary and opBinaryRight and see which is better. - Expressions* args1 = new Expressions(); - Expressions* args2 = new Expressions(); - args1.setDim(1); - (*args1)[0] = e.e1; - expandTuples(args1); - args2.setDim(1); - (*args2)[0] = e.e2; - expandTuples(args2); - MatchAccumulator m; - if (s) - { - functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, ArgumentList(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, ArgumentList(args1)); - if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors())) - { - return ErrorExp.get(); - } } if (m.count > 1) { @@ -686,451 +1069,18 @@ Expression op_overload(Expression e, Scope* sc) else if (m.last == MATCH.nomatch) { if (tiargs) - return binAliasThis(e, ad1, ad2); + goto L1; m.lastf = null; } - 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); - } + // 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; - 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_) - { - error(e.loc, "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) - */ - if (!ClassDeclaration.object) - { - error(e.loc, "cannot compare classes for equality because `object.Object` was not declared"); - return null; - } - - 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; - } - } - - EXP cmpOp; - if (Expression result = compare_overload(e, sc, Id.eq, cmpOp)) - { - 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 (!sc.previews.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*. - */ - e = e.copy().isEqualExp(); - 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.noAccessCheck = true; - Expression r = e.expressionSemantic(sc2); - sc2.pop(); - 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.length; - if (dim != tup2.exps.length) - { - error(e.loc, "mismatched sequence lengths, `%d` and `%d`", - cast(int)dim, cast(int)tup2.exps.length); - 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); - - 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 exp) - { - //printf("CmpExp:: () (%s)\n", e.toChars()); - EXP cmpOp = exp.op; - auto e = compare_overload(exp, sc, Id.cmp, cmpOp); - if (!e) - return null; - - if (!e.type.isScalar() && e.type.equals(exp.e1.type)) - { - error(e.loc, "recursive `opCmp` expansion"); - return ErrorExp.get(); - } - if (e.op != EXP.call) - return e; - - Type t1 = exp.e1.type.toBasetype(); - Type t2 = exp.e2.type.toBasetype(); - if (t1.ty != Tclass || t2.ty != Tclass) - { - return new CmpExp(cmpOp, exp.loc, e, IntegerExp.literal!0).expressionSemantic(sc); - } - - // Lower to object.__cmp(e1, e2) - Expression cl = new IdentifierExp(exp.loc, Id.empty); - cl = new DotIdExp(exp.loc, cl, Id.object); - cl = new DotIdExp(exp.loc, cl, Id.__cmp); - cl = cl.expressionSemantic(sc); - - auto arguments = new Expressions(); - // Check if op_overload found a better match by calling e2.opCmp(e1) - // If the operands were swapped, then the result must be reversed - // e1.opCmp(e2) == -e2.opCmp(e1) - // cmpop takes care of this - if (exp.op == cmpOp) - { - arguments.push(exp.e1); - arguments.push(exp.e2); - } - else - { - // Use better match found by op_overload - arguments.push(exp.e2); - arguments.push(exp.e1); - } - - cl = new CallExp(e.loc, cl, arguments); - cl = new CmpExp(cmpOp, exp.loc, cl, new IntegerExp(0)); - return cl.expressionSemantic(sc); - } - - /********************************* - * 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.length == 0 || ae.arguments.length == 1 && (*ae.arguments)[0].op == EXP.interval); - IntervalExp ie = null; - if (maybeSlice && ae.arguments.length) - { - ie = (*ae.arguments)[0].isIntervalExp(); - } - Type att = null; // first cyclic `alias this` type - 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(att, 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(); - } - Expressions* args2 = new Expressions(); - 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()) - { - error(e.loc, "`%s.opOpAssign` isn't a template", e.e1.toChars()); - return ErrorExp.get(); - } - } - // Set tiargs, the template argument list, which will be the operator string - Identifier id; - 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); - MatchAccumulator m; - functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, ArgumentList(args2)); - if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors())) - { - return ErrorExp.get(); - } - if (m.count > 1) - { - // Error, ambiguous - error(e.loc, "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); - } - - 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); - case EXP.assign : return visitAssign(e.isAssignExp()); - - // These are a kludgy BinExp, operator overloading is handled by EXP.prePlusPlus / EXP.preMinusMinus in the UnaExp case - case EXP.plusPlus : return null; - case EXP.minusMinus : return null; - - 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); - } + return checkAliasThisForRhs(isAggregate(e.e2.type), sc, e); } /******************************************