/** * Handles operator overloading. * * Specification: $(LINK2 https://dlang.org/spec/operatoroverloading.html, Operator Overloading) * * Copyright: Copyright (C) 1999-2025 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/opover.d, _opover.d) * Documentation: https://dlang.org/phobos/dmd_opover.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/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.dsymbolsem; import dmd.dtemplate; import dmd.errors; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.funcsem; import dmd.globals; import dmd.hdrgen; import dmd.id; import dmd.identifier; import dmd.location; import dmd.mtype; import dmd.optimize; import dmd.statement; import dmd.templatesem; 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) @safe { 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; } /// Returns: whether `op` can be overloaded with `opBinary` private bool hasOpBinary(EXP op) pure @safe { switch (op) { case EXP.add: return true; case EXP.min: return true; case EXP.mul: return true; case EXP.div: return true; case EXP.mod: return true; case EXP.and: return true; case EXP.or: return true; case EXP.xor: return true; case EXP.leftShift: return true; case EXP.rightShift: return true; case EXP.unsignedRightShift: return true; case EXP.concatenate: return true; case EXP.pow: return true; case EXP.in_: return true; default: return false; } } /** * Remove the = from op=, e.g. += becomes + * * Params: * op = tag for a binary assign operator * Returns: the corresponding binary operator, or `op` if it wasn't an assign operator */ private EXP stripAssignOp(EXP op) { switch (op) { case EXP.addAssign: return EXP.add; case EXP.minAssign: return EXP.min; case EXP.mulAssign: return EXP.mul; case EXP.divAssign: return EXP.div; case EXP.modAssign: return EXP.mod; case EXP.andAssign: return EXP.and; case EXP.orAssign: return EXP.or; case EXP.xorAssign: return EXP.xor; case EXP.leftShiftAssign: return EXP.leftShift; case EXP.rightShiftAssign: return EXP.rightShift; case EXP.unsignedRightShiftAssign: return EXP.unsignedRightShift; case EXP.concatenateAssign: return EXP.concatenate; case EXP.powAssign: return EXP.pow; default: return op; } } /******************************************* * Helper function to turn operator into template argument list */ Objects* opToArg(Scope* sc, EXP op) { Expression e = new StringExp(Loc.initial, EXPtoString(stripAssignOp(op))); e = e.expressionSemantic(sc); auto tiargs = new Objects(); tiargs.push(e); return tiargs; } // Try alias this on first operand Expression checkAliasThisForLhs(AggregateDeclaration ad, Scope* sc, BinExp e, Type[2] aliasThisStop) { if (!ad || !ad.aliasthis) return null; /* Rewrite (e1 op e2) as: * (e1.aliasthis op e2) */ if (isRecursiveAliasThis(aliasThisStop[0], 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.isAssignExp() !is null; be.e1 = resolveAliasThis(sc, e.e1, true, findOnly); if (!be.e1) return null; return be.trySemanticAliasThis(sc, aliasThisStop); } // Try alias this on second operand Expression checkAliasThisForRhs(AggregateDeclaration ad, Scope* sc, BinExp e, Type[2] aliasThisStop) { if (!ad || !ad.aliasthis) return null; /* Rewrite (e1 op e2) as: * (e1 op e2.aliasthis) */ if (isRecursiveAliasThis(aliasThisStop[1], 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; return be.trySemanticAliasThis(sc, aliasThisStop); } Expression opOverloadUnary(UnaExp 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].isIntervalExp()); 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.isErrorExp()) { return ae.e1; } Expression ae1save = ae.e1; ae.lengthVar = null; AggregateDeclaration ad = isAggregate(ae.e1.type); if (!ad) break; if (search_function(ad, Id.opIndexUnary)) { Expression e0; // Deal with $ Expression ae2 = resolveOpDollar(sc, ae, e0); if (!ae2) // op(a[i..j]) might be: a.opSliceUnary!(op)(i, j) goto Lfallback; if (ae2.isErrorExp()) return ae2; /* Rewrite op(a[arguments]) as: * a.opIndexUnary!(op)(arguments) */ Expression result = dotTemplateCall(ae.e1, Id.opIndexUnary, opToArg(sc, e.op), (*ae.arguments)[]); 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 $ Expression e0; auto ae2 = resolveOpDollar(sc, ae, ie, e0); if (ae2.isErrorExp()) return ae2; /* Rewrite op(a[i..j]) as: * a.opSliceUnary!(op)(i, j) */ Expression result = ie ? dotTemplateCall(ae.e1, Id.opSliceUnary, opToArg(sc, e.op), ie.lwr, ie.upr) : dotTemplateCall(ae.e1, Id.opSliceUnary, opToArg(sc, e.op)); return Expression.combine(e0, result.expressionSemantic(sc)); } // 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.isErrorExp()) { return e.e1; } AggregateDeclaration ad = isAggregate(e.e1.type); if (!ad) break; /* Rewrite as: * e1.opUnary!(op)() */ if (Dsymbol fd = search_function(ad, Id.opUnary)) return dotTemplateCall(e.e1, Id.opUnary, opToArg(sc, e.op)).expressionSemantic(sc); // 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; } // For ++ and --, rewrites to += and -= are also tried, so don't error yet if (!e.isPreExp()) { error(e.loc, "operator `%s` is not defined for `%s`", EXPtoString(e.op).ptr, ad.toChars()); errorSupplemental(ad.loc, "perhaps overload the operator with `auto opUnary(string op : \"%s\")() {}`", EXPtoString(e.op).ptr); return ErrorExp.get(); } break; } return null; } 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].isIntervalExp()); 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.isErrorExp()) { 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.isTypeExp()) { // Convert to SliceExp if (maybeSlice) return new SliceExp(ae.loc, ae.e1, ie).expressionSemantic(sc); // Convert to IndexExp if (ae.arguments.length == 1) return new IndexExp(ae.loc, ae.e1, (*ae.arguments)[0]).expressionSemantic(sc); } break; } if (search_function(ad, Id.opIndex)) { // Deal with $ auto ae2 = resolveOpDollar(sc, ae, e0); if (!ae2) // a[i..j] might be: a.opSlice(i, j) goto Lfallback; if (ae2.isErrorExp()) return ae2; /* Rewrite e1[arguments] as: * e1.opIndex(arguments) */ Expressions* a = ae.arguments.copy(); Expression result = new DotIdExp(ae.loc, ae.e1, Id.opIndex); 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.isTypeExp()) { Expression result = new SliceExp(ae.loc, ae.e1, ie); result = result.expressionSemantic(sc); return Expression.combine(e0, result); } if (maybeSlice && search_function(ad, Id.opSlice)) { // Deal with $ auto ae2 = resolveOpDollar(sc, ae, ie, e0); if (ae2.isErrorExp()) { if (!e0 && !search_function(ad, Id.dollar)) ad.loc.errorSupplemental("perhaps define `opDollar` for `%s`", ad.toChars()); return ae2; } /* Rewrite a[i..j] as: * a.opSlice(i, j) */ auto a = new Expressions(); if (ie) { a.push(ie.lwr); a.push(ie.upr); } Expression result = new DotIdExp(ae.loc, ae.e1, Id.opSlice); result = new CallExp(ae.loc, result, a); result = result.expressionSemantic(sc); return Expression.combine(e0, 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 null; } /*********************************************** * This is mostly the same as opOverloadUnary but has * a different rewrite. */ Expression opOverloadCast(CastExp e, Scope* sc, Type att = null) { AggregateDeclaration ad = isAggregate(e.e1.type); if (!ad) return null; // Rewrite as: e1.opCast!(T)() if (Dsymbol fd = search_function(ad, Id.opCast)) { 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); return dotTemplateCall(e.e1, Id.opCast, tiargs).expressionSemantic(sc); } // Didn't find it. Forward to aliasthis if (ad.aliasthis && !isRecursiveAliasThis(att, e.e1.type)) { // Rewrite `e1.opCast()` as `e1.aliasthis.opCast()` if (auto e1 = resolveAliasThis(sc, e.e1, true)) { CastExp result = e.copy().isCastExp(); result.e1 = e1; return result.opOverloadCast(sc, att); } } return null; } // 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, Type[2] aliasThisStop) { AggregateDeclaration ad1 = isAggregate(e.e1.type); AggregateDeclaration ad2 = isAggregate(e.e2.type); Expression rewrittenLhs; if (!(e.isAssignExp && ad2 && ad1 == ad2)) // https://issues.dlang.org/show_bug.cgi?id=2943 { if (Expression result = checkAliasThisForLhs(ad1, sc, e, aliasThisStop)) { /* 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. */ auto ae = result.isAssignExp(); if (!ae) return result; // i.e: Rewrote `e1 = e2` -> `e1(e2)` auto dve = ae.e1.isDotVarExp(); if (!dve) return result; // i.e: Rewrote `e1 = e2` -> `e1() = e2` 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.isAssignExp && ad1 && ad1 == ad2)) // https://issues.dlang.org/show_bug.cgi?id=2943 { if (Expression result = checkAliasThisForRhs(ad2, sc, e, aliasThisStop)) 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, Type[2] aliasThisStop) { 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 = search_function(ad1, Id.opAssign); bool choseReverse; if (auto result = pickBestBinaryOverload(sc, null, s, null, e, choseReverse)) return result; return binAliasThis(e, sc, aliasThisStop); } Expression opOverloadBinary(BinExp e, Scope* sc, Type[2] aliasThisStop) { if (Expression err = binSemanticProp(e, sc)) return err; AggregateDeclaration ad1 = isAggregate(e.e1.type); AggregateDeclaration ad2 = isAggregate(e.e2.type); // Try opBinary and opBinaryRight Dsymbol 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(); } Dsymbol 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; bool choseReverse; if (auto result = pickBestBinaryOverload(sc, opToArg(sc, e.op), s, s_r, e, choseReverse)) return result; return binAliasThis(e, sc, aliasThisStop); } /** * If applicable, print an error relating to implementing / fixing `opBinary` functions. * Params: * e = binary operation * sc = scope to try `opBinary!""` semantic in for error messages * Returns: `true` when an error related to `opBinary` was printed */ bool suggestBinaryOverloads(BinExp e, Scope* sc) { if (!e.op.hasOpBinary) return false; AggregateDeclaration ad1 = isAggregate(e.e1.type); AggregateDeclaration ad2 = isAggregate(e.e2.type); if (ad1) { if (Dsymbol s = search_function(ad1, Id.opBinary)) { // This expressionSemantic will fail, otherwise operator overloading would have succeeded before dotTemplateCall(e.e1, Id.opBinary, opToArg(sc, e.op), e.e2).expressionSemantic(sc); errorSupplemental(s.loc, "`opBinary` defined here"); return true; } error(e.loc, "operator `%s` is not defined for type `%s`", EXPtoString(e.op).ptr, e.e1.type.toChars); errorSupplemental(ad1.loc, "perhaps overload the operator with `auto opBinary(string op : \"%s\")(%s rhs) {}`", EXPtoString(e.op).ptr, e.e2.type.toChars); return true; } else if (ad2) { if (Dsymbol s_r = search_function(ad1, Id.opBinaryRight)) { dotTemplateCall(e.e2, Id.opBinaryRight, opToArg(sc, e.op), e.e1).expressionSemantic(sc); errorSupplemental(s_r.loc, "`opBinaryRight` defined here"); return true; } error(e.loc, "operator `%s` is not defined for type `%s`", EXPtoString(e.op).ptr, e.e2.type.toChars); errorSupplemental(ad2.loc, "perhaps overload the operator with `auto opBinaryRight(string op : \"%s\")(%s rhs) {}`", EXPtoString(e.op).ptr, e.e1.type.toChars); return true; } return false; } /** * If applicable, print an error relating to implementing / fixing `opOpAssign` or `opUnary` functions. * Params: * exp = binary operation * sc = scope to try `opOpAssign!""` semantic in for error messages * parent = if `exp` was lowered from this `PreExp` or `PostExp`, mention `opUnary` as well * Returns: `true` when an error related to `opOpAssign` was printed */ bool suggestOpOpAssign(BinAssignExp exp, Scope* sc, Expression parent) { auto ad = isAggregate(exp.e1.type); if (!ad) return false; if (parent && (parent.isPreExp() || parent.isPostExp())) { error(exp.loc, "operator `%s` not supported for `%s` of type `%s`", EXPtoString(parent.op).ptr, exp.e1.toChars(), ad.toChars()); errorSupplemental(ad.loc, "perhaps implement `auto opUnary(string op : \"%s\")() {}`"~ " or `auto opOpAssign(string op : \"%s\")(int) {}`", EXPtoString(stripAssignOp(parent.op)).ptr, EXPtoString(stripAssignOp(exp.op)).ptr ); return true; } if (const s = search_function(ad, Id.opOpAssign)) { // This expressionSemantic will fail, otherwise operator overloading would have succeeded before dotTemplateCall(exp.e1, Id.opOpAssign, opToArg(sc, exp.op), exp.e2).expressionSemantic(sc); } else { error(exp.loc, "operator `%s` not supported for `%s` of type `%s`", EXPtoString(exp.op).ptr, exp.e1.toChars(), ad.toChars()); errorSupplemental(ad.loc, "perhaps implement `auto opOpAssign(string op : \"%s\")(%s) {}`", EXPtoString(stripAssignOp(exp.op)).ptr, exp.e2.type.toChars()); } return true; } // Helper to construct e.id!tiargs(args), e.g. `lhs.opBinary!"+"(rhs)` private Expression dotTemplateCall(Expression e, Identifier id, Objects* tiargs, Expression[] args...) { auto ti = new DotTemplateInstanceExp(e.loc, e, id, tiargs); auto expressions = new Expressions(); expressions.pushSlice(args); return new CallExp(e.loc, ti, expressions); } Expression opOverloadEqual(EqualExp e, Scope* sc, Type[2] aliasThisStop) { 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.isStaticOrDynamicArray() && t2.isStaticOrDynamicArray()) { return null; } /* Check for class equality with null literal or typeof(null). */ if (t1.isTypeClass() && e.e2.isNullExp() || t2.isTypeClass() && e.e1.isNullExp()) { 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.isTypeClass() && t2.isTypeNull() || t1.isTypeNull() && t2.isTypeClass()) { // Comparing a class with typeof(null) should not call opEquals return null; } /* Check for class equality. */ if (t1.isTypeClass() && t2.isTypeClass()) { 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.opEquals); 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.opEquals, cmpOp, aliasThisStop)) { if (lastComma(result).isCallExp() && e.op == EXP.notEqual) { result = new NotExp(result.loc, result); result = result.expressionSemantic(sc); } return result; } /* Check for pointer equality. */ if (t1.isTypePointer() || t2.isTypePointer()) { /* 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.isTypeStruct() && t2.isTypeStruct()) { 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. */ auto tup1 = e.e1.isTupleExp(); auto tup2 = e.e2.isTupleExp(); if (tup1 && tup2) { 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, Type[2] aliasThisStop) { //printf("CmpExp:: () (%s)\n", e.toChars()); EXP cmpOp = exp.op; auto e = compare_overload(exp, sc, Id.opCmp, cmpOp, aliasThisStop); 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.isCallExp()) return e; Type t1 = exp.e1.type.toBasetype(); Type t2 = exp.e2.type.toBasetype(); if (!t1.isTypeClass() || !t2.isTypeClass()) { 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, Type[2] aliasThisStop) { 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].isIntervalExp()); 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.isErrorExp()) return ae.e1; Expression e0 = null; Expression ae1save = ae.e1; ae.lengthVar = null; AggregateDeclaration ad = isAggregate(ae.e1.type); if (!ad) break; if (search_function(ad, Id.opIndexOpAssign)) { // Deal with $ Expression ae2 = resolveOpDollar(sc, ae, e0); if (!ae2) // (a[i..j] op= e2) might be: a.opSliceOpAssign!(op)(e2, i, j) goto Lfallback; if (ae2.isErrorExp()) return ae2; e.e2 = e.e2.expressionSemantic(sc); if (e.e2.isErrorExp()) return e.e2; /* Rewrite a[arguments] op= e2 as: * a.opIndexOpAssign!(op)(e2, arguments) */ Expressions* a = ae.arguments.copy(); a.insert(0, e.e2); Expression result = dotTemplateCall(ae.e1, Id.opIndexOpAssign, opToArg(sc, e.op), (*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 ae2 = resolveOpDollar(sc, ae, ie, e0); if (ae2.isErrorExp()) return ae2; e.e2 = e.e2.expressionSemantic(sc); if (e.e2.isErrorExp()) return e.e2; /* Rewrite (a[i..j] op= e2) as: * a.opSliceOpAssign!(op)(e2, i, j) */ auto result = ie ? dotTemplateCall(ae.e1, Id.opSliceOpAssign, opToArg(sc, e.op), e.e2, ie.lwr, ie.upr) : dotTemplateCall(ae.e1, Id.opSliceOpAssign, opToArg(sc, e.op), e.e2); return Expression.combine(e0, result.expressionSemantic(sc)); } // 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; } if (Expression result = e.binSemanticProp(sc)) return result; // Don't attempt 'alias this' if an error occurred if (e.e1.type.isTypeError() || e.e2.type.isTypeError()) return ErrorExp.get(); AggregateDeclaration ad1 = isAggregate(e.e1.type); Dsymbol 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(); } bool choseReverse; if (auto res = pickBestBinaryOverload(sc, opToArg(sc, e.op), s, null, e, choseReverse)) return res; Expression result = checkAliasThisForLhs(ad1, sc, e, aliasThisStop); 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, aliasThisStop); } /** Given symbols `s` and `s_r`, try to instantiate `e.e1.s!tiargs(e.e2)` and `e.e2.s_r!tiargs(e.e1)`, and return the one with the best match level. Params: sc = scope tiargs = (optional) template arguments to instantiate symbols with s = (optional) symbol of straightforward template (e.g. opBinary) s_r = (optional) symbol of reversed template (e.g. opBinaryRight) e = binary expression being overloaded, supplying arguments to the function calls choseReverse = set to true when `s_r` was chosen instead of `s` Returns: Resulting operator overload function call, or `null` if neither symbol worked */ private Expression pickBestBinaryOverload(Scope* sc, Objects* tiargs, Dsymbol s, Dsymbol s_r, BinExp e, out bool choseReverse) { if (!s && !s_r) return null; Expressions* args1 = new Expressions(1); (*args1)[0] = e.e1; expandTuples(args1); Expressions* args2 = new Expressions(1); (*args2)[0] = e.e2; expandTuples(args2); MatchAccumulator m; if (s) { functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, ArgumentList(args2), null); 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, ArgumentList(args1), null); 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 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 null; m.lastf = null; } if (lastf && m.lastf == lastf || !s_r && m.last == MATCH.nomatch) { choseReverse = false; // Rewrite (e1 op e2) as e1.opfunc(e2) return build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s); } else { choseReverse = true; // 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); } } /****************************************** * Common code for overloading of EqualExp and CmpExp */ private Expression compare_overload(BinExp e, Scope* sc, Identifier id, ref EXP cmpOp, Type[2] aliasThisStop) { //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 = search_function(ad1, id); Dsymbol s_r = search_function(ad2, id); if (s == s_r) s_r = null; bool choseReverse; if (auto res = pickBestBinaryOverload(sc, null, s, s_r, e, choseReverse)) { if (choseReverse) cmpOp = reverseRelation(e.op); return res; } /* * 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.isEqualExp() && ad1 == ad2) return null; Expression result = checkAliasThisForLhs(ad1, sc, e, aliasThisStop); if (result) return result; result = checkAliasThisForRhs(isAggregate(e.e2.type), sc, e, aliasThisStop); if (result) return result; if (s || s_r) return null; Expression suggestOverloading(Expression other, AggregateDeclaration ad) { error(e.loc, "no operator `%s` for type `%s`", EXPtoString(e.op).ptr, ad.toChars); string op = e.isEqualExp() ? "bool" : "int"; errorSupplemental(ad.loc, "perhaps overload it with `%.*s %s(%s other) const {}`", op.fTuple.expand, id.toChars, other.type.toChars); return ErrorExp.get(); } // Classes have opCmp and opEquals defined in `Object` to fall back on already if (ad1 && ad1.isStructDeclaration) return suggestOverloading(e.e2, ad1); if (ad2 && ad2.isStructDeclaration) return suggestOverloading(e.e1, ad2); return null; } /*********************************** * Utility to build a function call out of this reference and argument. */ Expression build_overload(Loc loc, Scope* sc, Expression ethis, Expression earg, Dsymbol d) { assert(d); Expression e; if (Declaration decl = d.isDeclaration()) 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) { if (!ad) return null; if (Dsymbol s = ad.search(Loc.initial, funcid)) { //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.isTypeFunction()) return fd; if (TemplateDeclaration td = s2.isTemplateDeclaration()) 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.isErrorExp()) 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 = isAggregate(tab); 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.length) 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.isTypeClass() || tab.isTypeStruct()) ethis = fes.aggr; else { assert(tab.isTypeDelegate() && fes.aggr.isDelegateExp()); 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.length == 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.isTypeTuple()) { p.type = tab.nextOf(); // value type p.type = p.type.addStorageClass(p.storageClass); } break; case Taarray: { TypeAArray taa = tab.isTypeAArray(); if (fes.parameters.length == 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 = isAggregate(tab); if (fes.parameters.length == 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 // @@@DEPRECATED_2.112@@@ // See semantic2.d Semantic2Visitor.visit(FuncDeclaration): // Remove `false` after deprecation period is over. bool ambig = tf.attributesEqual(bestTf, false); // 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:", ethis.toChars(), fstart.ident.toChars()); .errorSupplemental(fd_best.loc, "`%s`\nand:", fd_best.type.toChars()); .errorSupplemental(fd_ambig.loc, "`%s`", 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.length || 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 @safe { 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; }