blob: 7baaeaa7fcfa673c0dd026aed162a9237ec52f91 [file] [log] [blame]
/**
* 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;
}