blob: dbd761fed2f4955ad3c621f578c4a2b6a7f89ad5 [file] [log] [blame]
/**
* Handles operator overloading.
*
* Specification: $(LINK2 https://dlang.org/spec/operatoroverloading.html, Operator Overloading)
*
* Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/opover.d, _opover.d)
* Documentation: https://dlang.org/phobos/dmd_opover.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/opover.d
*/
module dmd.opover;
import core.stdc.stdio;
import dmd.aggregate;
import dmd.aliasthis;
import dmd.arraytypes;
import dmd.astenums;
import dmd.dclass;
import dmd.declaration;
import dmd.dscope;
import dmd.dstruct;
import dmd.dsymbol;
import dmd.dtemplate;
import dmd.errors;
import dmd.expression;
import dmd.expressionsem;
import dmd.func;
import dmd.globals;
import dmd.hdrgen;
import dmd.id;
import dmd.identifier;
import dmd.mtype;
import dmd.statement;
import dmd.tokens;
import dmd.typesem;
import dmd.visitor;
/***********************************
* Determine if operands of binary op can be reversed
* to fit operator overload.
*/
bool isCommutative(EXP op)
{
switch (op)
{
case EXP.add:
case EXP.mul:
case EXP.and:
case EXP.or:
case EXP.xor:
// EqualExp
case EXP.equal:
case EXP.notEqual:
// CmpExp
case EXP.lessThan:
case EXP.lessOrEqual:
case EXP.greaterThan:
case EXP.greaterOrEqual:
return true;
default:
break;
}
return false;
}
/***********************************
* Get Identifier for operator overload.
*/
private Identifier opId(Expression e)
{
switch (e.op)
{
case EXP.uadd: return Id.uadd;
case EXP.negate: return Id.neg;
case EXP.tilde: return Id.com;
case EXP.cast_: return Id._cast;
case EXP.in_: return Id.opIn;
case EXP.plusPlus: return Id.postinc;
case EXP.minusMinus: return Id.postdec;
case EXP.add: return Id.add;
case EXP.min: return Id.sub;
case EXP.mul: return Id.mul;
case EXP.div: return Id.div;
case EXP.mod: return Id.mod;
case EXP.pow: return Id.pow;
case EXP.leftShift: return Id.shl;
case EXP.rightShift: return Id.shr;
case EXP.unsignedRightShift: return Id.ushr;
case EXP.and: return Id.iand;
case EXP.or: return Id.ior;
case EXP.xor: return Id.ixor;
case EXP.concatenate: return Id.cat;
case EXP.assign: return Id.assign;
case EXP.addAssign: return Id.addass;
case EXP.minAssign: return Id.subass;
case EXP.mulAssign: return Id.mulass;
case EXP.divAssign: return Id.divass;
case EXP.modAssign: return Id.modass;
case EXP.powAssign: return Id.powass;
case EXP.leftShiftAssign: return Id.shlass;
case EXP.rightShiftAssign: return Id.shrass;
case EXP.unsignedRightShiftAssign: return Id.ushrass;
case EXP.andAssign: return Id.andass;
case EXP.orAssign: return Id.orass;
case EXP.xorAssign: return Id.xorass;
case EXP.concatenateAssign: return Id.catass;
case EXP.equal: return Id.eq;
case EXP.lessThan:
case EXP.lessOrEqual:
case EXP.greaterThan:
case EXP.greaterOrEqual: return Id.cmp;
case EXP.array: return Id.index;
case EXP.star: return Id.opStar;
default: assert(0);
}
}
/***********************************
* Get Identifier for reverse operator overload,
* `null` if not supported for this operator.
*/
private Identifier opId_r(Expression e)
{
switch (e.op)
{
case EXP.in_: return Id.opIn_r;
case EXP.add: return Id.add_r;
case EXP.min: return Id.sub_r;
case EXP.mul: return Id.mul_r;
case EXP.div: return Id.div_r;
case EXP.mod: return Id.mod_r;
case EXP.pow: return Id.pow_r;
case EXP.leftShift: return Id.shl_r;
case EXP.rightShift: return Id.shr_r;
case EXP.unsignedRightShift:return Id.ushr_r;
case EXP.and: return Id.iand_r;
case EXP.or: return Id.ior_r;
case EXP.xor: return Id.ixor_r;
case EXP.concatenate: return Id.cat_r;
default: return null;
}
}
/*******************************************
* Helper function to turn operator into template argument list
*/
Objects* opToArg(Scope* sc, EXP op)
{
/* Remove the = from op=
*/
switch (op)
{
case EXP.addAssign:
op = EXP.add;
break;
case EXP.minAssign:
op = EXP.min;
break;
case EXP.mulAssign:
op = EXP.mul;
break;
case EXP.divAssign:
op = EXP.div;
break;
case EXP.modAssign:
op = EXP.mod;
break;
case EXP.andAssign:
op = EXP.and;
break;
case EXP.orAssign:
op = EXP.or;
break;
case EXP.xorAssign:
op = EXP.xor;
break;
case EXP.leftShiftAssign:
op = EXP.leftShift;
break;
case EXP.rightShiftAssign:
op = EXP.rightShift;
break;
case EXP.unsignedRightShiftAssign:
op = EXP.unsignedRightShift;
break;
case EXP.concatenateAssign:
op = EXP.concatenate;
break;
case EXP.powAssign:
op = EXP.pow;
break;
default:
break;
}
Expression e = new StringExp(Loc.initial, EXPtoString(op));
e = e.expressionSemantic(sc);
auto tiargs = new Objects();
tiargs.push(e);
return tiargs;
}
// Try alias this on first operand
private Expression checkAliasThisForLhs(AggregateDeclaration ad, Scope* sc, BinExp e)
{
if (!ad || !ad.aliasthis)
return null;
/* Rewrite (e1 op e2) as:
* (e1.aliasthis op e2)
*/
if (isRecursiveAliasThis(e.att1, e.e1.type))
return null;
//printf("att %s e1 = %s\n", Token::toChars(e.op), e.e1.type.toChars());
BinExp be = cast(BinExp)e.copy();
// Resolve 'alias this' but in case of assigment don't resolve properties yet
// because 'e1 = e2' could mean 'e1(e2)' or 'e1() = e2'
bool findOnly = (e.op == EXP.assign);
be.e1 = resolveAliasThis(sc, e.e1, true, findOnly);
if (!be.e1)
return null;
Expression result;
if (be.op == EXP.concatenateAssign)
result = be.op_overload(sc);
else
result = be.trySemantic(sc);
return result;
}
// Try alias this on second operand
private Expression checkAliasThisForRhs(AggregateDeclaration ad, Scope* sc, BinExp e)
{
if (!ad || !ad.aliasthis)
return null;
/* Rewrite (e1 op e2) as:
* (e1 op e2.aliasthis)
*/
if (isRecursiveAliasThis(e.att2, e.e2.type))
return null;
//printf("att %s e2 = %s\n", Token::toChars(e.op), e.e2.type.toChars());
BinExp be = cast(BinExp)e.copy();
be.e2 = resolveAliasThis(sc, e.e2, true);
if (!be.e2)
return null;
Expression result;
if (be.op == EXP.concatenateAssign)
result = be.op_overload(sc);
else
result = be.trySemantic(sc);
return result;
}
/************************************
* Operator overload.
* Check for operator overload, if so, replace
* with function call.
* Params:
* e = expression with operator
* sc = context
* pop = if not null, is set to the operator that was actually overloaded,
* which may not be `e.op`. Happens when operands are reversed to
* match an overload
* Returns:
* `null` if not an operator overload,
* otherwise the lowered expression
*/
Expression op_overload(Expression e, Scope* sc, EXP* pop = null)
{
Expression visit(Expression e)
{
assert(0);
}
Expression visitUna(UnaExp e)
{
//printf("UnaExp::op_overload() (%s)\n", e.toChars());
Expression result;
if (auto ae = e.e1.isArrayExp())
{
ae.e1 = ae.e1.expressionSemantic(sc);
ae.e1 = resolveProperties(sc, ae.e1);
Expression ae1old = ae.e1;
const(bool) maybeSlice = (ae.arguments.dim == 0 || ae.arguments.dim == 1 && (*ae.arguments)[0].op == EXP.interval);
IntervalExp ie = null;
if (maybeSlice && ae.arguments.dim)
{
ie = (*ae.arguments)[0].isIntervalExp();
}
while (true)
{
if (ae.e1.op == EXP.error)
{
return ae.e1;
}
Expression e0 = null;
Expression ae1save = ae.e1;
ae.lengthVar = null;
Type t1b = ae.e1.type.toBasetype();
AggregateDeclaration ad = isAggregate(t1b);
if (!ad)
break;
if (search_function(ad, Id.opIndexUnary))
{
// Deal with $
result = resolveOpDollar(sc, ae, &e0);
if (!result) // op(a[i..j]) might be: a.opSliceUnary!(op)(i, j)
goto Lfallback;
if (result.op == EXP.error)
return result;
/* Rewrite op(a[arguments]) as:
* a.opIndexUnary!(op)(arguments)
*/
Expressions* a = ae.arguments.copy();
Objects* tiargs = opToArg(sc, e.op);
result = new DotTemplateInstanceExp(e.loc, ae.e1, Id.opIndexUnary, tiargs);
result = new CallExp(e.loc, result, a);
if (maybeSlice) // op(a[]) might be: a.opSliceUnary!(op)()
result = result.trySemantic(sc);
else
result = result.expressionSemantic(sc);
if (result)
{
return Expression.combine(e0, result);
}
}
Lfallback:
if (maybeSlice && search_function(ad, Id.opSliceUnary))
{
// Deal with $
result = resolveOpDollar(sc, ae, ie, &e0);
if (result.op == EXP.error)
return result;
/* Rewrite op(a[i..j]) as:
* a.opSliceUnary!(op)(i, j)
*/
auto a = new Expressions();
if (ie)
{
a.push(ie.lwr);
a.push(ie.upr);
}
Objects* tiargs = opToArg(sc, e.op);
result = new DotTemplateInstanceExp(e.loc, ae.e1, Id.opSliceUnary, tiargs);
result = new CallExp(e.loc, result, a);
result = result.expressionSemantic(sc);
result = Expression.combine(e0, result);
return result;
}
// Didn't find it. Forward to aliasthis
if (ad.aliasthis && !isRecursiveAliasThis(ae.att1, ae.e1.type))
{
/* Rewrite op(a[arguments]) as:
* op(a.aliasthis[arguments])
*/
ae.e1 = resolveAliasThis(sc, ae1save, true);
if (ae.e1)
continue;
}
break;
}
ae.e1 = ae1old; // recovery
ae.lengthVar = null;
}
e.e1 = e.e1.expressionSemantic(sc);
e.e1 = resolveProperties(sc, e.e1);
if (e.e1.op == EXP.error)
{
return e.e1;
}
AggregateDeclaration ad = isAggregate(e.e1.type);
if (ad)
{
Dsymbol fd = null;
/* Rewrite as:
* e1.opUnary!(op)()
*/
fd = search_function(ad, Id.opUnary);
if (fd)
{
Objects* tiargs = opToArg(sc, e.op);
result = new DotTemplateInstanceExp(e.loc, e.e1, fd.ident, tiargs);
result = new CallExp(e.loc, result);
result = result.expressionSemantic(sc);
return result;
}
// D1-style operator overloads, deprecated
if (e.op != EXP.prePlusPlus && e.op != EXP.preMinusMinus)
{
auto id = opId(e);
fd = search_function(ad, id);
if (fd)
{
// @@@DEPRECATED_2.110@@@.
// Deprecated in 2.088, made an error in 2.100
e.error("`%s` is obsolete. Use `opUnary(string op)() if (op == \"%s\")` instead.", id.toChars(), EXPtoString(e.op).ptr);
return ErrorExp.get();
}
}
// Didn't find it. Forward to aliasthis
if (ad.aliasthis && !isRecursiveAliasThis(e.att1, e.e1.type))
{
/* Rewrite op(e1) as:
* op(e1.aliasthis)
*/
//printf("att una %s e1 = %s\n", EXPtoString(op).ptr, this.e1.type.toChars());
Expression e1 = new DotIdExp(e.loc, e.e1, ad.aliasthis.ident);
UnaExp ue = cast(UnaExp)e.copy();
ue.e1 = e1;
result = ue.trySemantic(sc);
return result;
}
}
return result;
}
Expression visitArray(ArrayExp ae)
{
//printf("ArrayExp::op_overload() (%s)\n", ae.toChars());
ae.e1 = ae.e1.expressionSemantic(sc);
ae.e1 = resolveProperties(sc, ae.e1);
Expression ae1old = ae.e1;
const(bool) maybeSlice = (ae.arguments.dim == 0 || ae.arguments.dim == 1 && (*ae.arguments)[0].op == EXP.interval);
IntervalExp ie = null;
if (maybeSlice && ae.arguments.dim)
{
ie = (*ae.arguments)[0].isIntervalExp();
}
Expression result;
while (true)
{
if (ae.e1.op == EXP.error)
{
return ae.e1;
}
Expression e0 = null;
Expression ae1save = ae.e1;
ae.lengthVar = null;
Type t1b = ae.e1.type.toBasetype();
AggregateDeclaration ad = isAggregate(t1b);
if (!ad)
{
// If the non-aggregate expression ae.e1 is indexable or sliceable,
// convert it to the corresponding concrete expression.
if (isIndexableNonAggregate(t1b) || ae.e1.op == EXP.type)
{
// Convert to SliceExp
if (maybeSlice)
{
result = new SliceExp(ae.loc, ae.e1, ie);
result = result.expressionSemantic(sc);
return result;
}
// Convert to IndexExp
if (ae.arguments.dim == 1)
{
result = new IndexExp(ae.loc, ae.e1, (*ae.arguments)[0]);
result = result.expressionSemantic(sc);
return result;
}
}
break;
}
if (search_function(ad, Id.index))
{
// Deal with $
result = resolveOpDollar(sc, ae, &e0);
if (!result) // a[i..j] might be: a.opSlice(i, j)
goto Lfallback;
if (result.op == EXP.error)
return result;
/* Rewrite e1[arguments] as:
* e1.opIndex(arguments)
*/
Expressions* a = ae.arguments.copy();
result = new DotIdExp(ae.loc, ae.e1, Id.index);
result = new CallExp(ae.loc, result, a);
if (maybeSlice) // a[] might be: a.opSlice()
result = result.trySemantic(sc);
else
result = result.expressionSemantic(sc);
if (result)
{
return Expression.combine(e0, result);
}
}
Lfallback:
if (maybeSlice && ae.e1.op == EXP.type)
{
result = new SliceExp(ae.loc, ae.e1, ie);
result = result.expressionSemantic(sc);
result = Expression.combine(e0, result);
return result;
}
if (maybeSlice && search_function(ad, Id.slice))
{
// Deal with $
result = resolveOpDollar(sc, ae, ie, &e0);
if (result.op == EXP.error)
{
if (!e0 && !search_function(ad, Id.dollar)) {
ae.loc.errorSupplemental("Aggregate declaration '%s' does not define 'opDollar'", ae.e1.toChars());
}
return result;
}
/* Rewrite a[i..j] as:
* a.opSlice(i, j)
*/
auto a = new Expressions();
if (ie)
{
a.push(ie.lwr);
a.push(ie.upr);
}
result = new DotIdExp(ae.loc, ae.e1, Id.slice);
result = new CallExp(ae.loc, result, a);
result = result.expressionSemantic(sc);
result = Expression.combine(e0, result);
return result;
}
// Didn't find it. Forward to aliasthis
if (ad.aliasthis && !isRecursiveAliasThis(ae.att1, ae.e1.type))
{
//printf("att arr e1 = %s\n", this.e1.type.toChars());
/* Rewrite op(a[arguments]) as:
* op(a.aliasthis[arguments])
*/
ae.e1 = resolveAliasThis(sc, ae1save, true);
if (ae.e1)
continue;
}
break;
}
ae.e1 = ae1old; // recovery
ae.lengthVar = null;
return result;
}
/***********************************************
* This is mostly the same as UnaryExp::op_overload(), but has
* a different rewrite.
*/
Expression visitCast(CastExp e)
{
//printf("CastExp::op_overload() (%s)\n", e.toChars());
Expression result;
AggregateDeclaration ad = isAggregate(e.e1.type);
if (ad)
{
Dsymbol fd = null;
/* Rewrite as:
* e1.opCast!(T)()
*/
fd = search_function(ad, Id._cast);
if (fd)
{
version (all)
{
// Backwards compatibility with D1 if opCast is a function, not a template
if (fd.isFuncDeclaration())
{
// Rewrite as: e1.opCast()
return build_overload(e.loc, sc, e.e1, null, fd);
}
}
auto tiargs = new Objects();
tiargs.push(e.to);
result = new DotTemplateInstanceExp(e.loc, e.e1, fd.ident, tiargs);
result = new CallExp(e.loc, result);
result = result.expressionSemantic(sc);
return result;
}
// Didn't find it. Forward to aliasthis
if (ad.aliasthis && !isRecursiveAliasThis(e.att1, e.e1.type))
{
/* Rewrite op(e1) as:
* op(e1.aliasthis)
*/
if (auto e1 = resolveAliasThis(sc, e.e1, true))
{
result = e.copy();
(cast(UnaExp)result).e1 = e1;
result = result.op_overload(sc);
return result;
}
}
}
return result;
}
Expression visitBin(BinExp e)
{
//printf("BinExp::op_overload() (%s)\n", e.toChars());
Identifier id = opId(e);
Identifier id_r = opId_r(e);
Expressions args1;
Expressions args2;
int argsset = 0;
AggregateDeclaration ad1 = isAggregate(e.e1.type);
AggregateDeclaration ad2 = isAggregate(e.e2.type);
if (e.op == EXP.assign && ad1 == ad2)
{
StructDeclaration sd = ad1.isStructDeclaration();
if (sd &&
(!sd.hasIdentityAssign ||
/* Do a blit if we can and the rvalue is something like .init,
* where a postblit is not necessary.
*/
(sd.hasBlitAssign && !e.e2.isLvalue())))
{
/* This is bitwise struct assignment. */
return null;
}
}
Dsymbol s = null;
Dsymbol s_r = null;
Objects* tiargs = null;
if (e.op == EXP.plusPlus || e.op == EXP.minusMinus)
{
// Bug4099 fix
if (ad1 && search_function(ad1, Id.opUnary))
return null;
}
if (e.op != EXP.equal && e.op != EXP.notEqual && e.op != EXP.assign && e.op != EXP.plusPlus && e.op != EXP.minusMinus)
{
/* Try opBinary and opBinaryRight
*/
if (ad1)
{
s = search_function(ad1, Id.opBinary);
if (s && !s.isTemplateDeclaration())
{
e.e1.error("`%s.opBinary` isn't a template", e.e1.toChars());
return ErrorExp.get();
}
}
if (ad2)
{
s_r = search_function(ad2, Id.opBinaryRight);
if (s_r && !s_r.isTemplateDeclaration())
{
e.e2.error("`%s.opBinaryRight` isn't a template", e.e2.toChars());
return ErrorExp.get();
}
if (s_r && s_r == s) // https://issues.dlang.org/show_bug.cgi?id=12778
s_r = null;
}
// Set tiargs, the template argument list, which will be the operator string
if (s || s_r)
{
id = Id.opBinary;
id_r = Id.opBinaryRight;
tiargs = opToArg(sc, e.op);
}
}
if (!s && !s_r)
{
// Try the D1-style operators, deprecated
if (ad1 && id)
{
s = search_function(ad1, id);
if (s && id != Id.assign)
{
// @@@DEPRECATED_2.110@@@.
// Deprecated in 2.088, made an error in 2.100
if (id == Id.postinc || id == Id.postdec)
e.error("`%s` is obsolete. Use `opUnary(string op)() if (op == \"%s\")` instead.", id.toChars(), EXPtoString(e.op).ptr);
else
e.error("`%s` is obsolete. Use `opBinary(string op)(...) if (op == \"%s\")` instead.", id.toChars(), EXPtoString(e.op).ptr);
return ErrorExp.get();
}
}
if (ad2 && id_r)
{
s_r = search_function(ad2, id_r);
// https://issues.dlang.org/show_bug.cgi?id=12778
// If both x.opBinary(y) and y.opBinaryRight(x) found,
// and they are exactly same symbol, x.opBinary(y) should be preferred.
if (s_r && s_r == s)
s_r = null;
if (s_r)
{
// @@@DEPRECATED_2.110@@@.
// Deprecated in 2.088, made an error in 2.100
e.error("`%s` is obsolete. Use `opBinaryRight(string op)(...) if (op == \"%s\")` instead.", id_r.toChars(), EXPtoString(e.op).ptr);
return ErrorExp.get();
}
}
}
if (s || s_r)
{
/* Try:
* a.opfunc(b)
* b.opfunc_r(a)
* and see which is better.
*/
args1.setDim(1);
args1[0] = e.e1;
expandTuples(&args1);
args2.setDim(1);
args2[0] = e.e2;
expandTuples(&args2);
argsset = 1;
MatchAccumulator m;
if (s)
{
functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, &args2);
if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
{
return ErrorExp.get();
}
}
FuncDeclaration lastf = m.lastf;
if (s_r)
{
functionResolve(m, s_r, e.loc, sc, tiargs, e.e2.type, &args1);
if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
{
return ErrorExp.get();
}
}
if (m.count > 1)
{
// Error, ambiguous
e.error("overloads `%s` and `%s` both match argument list for `%s`", m.lastf.type.toChars(), m.nextf.type.toChars(), m.lastf.toChars());
}
else if (m.last == MATCH.nomatch)
{
if (tiargs)
goto L1;
m.lastf = null;
}
if (e.op == EXP.plusPlus || e.op == EXP.minusMinus)
{
// Kludge because operator overloading regards e++ and e--
// as unary, but it's implemented as a binary.
// Rewrite (e1 ++ e2) as e1.postinc()
// Rewrite (e1 -- e2) as e1.postdec()
return build_overload(e.loc, sc, e.e1, null, m.lastf ? m.lastf : s);
}
else if (lastf && m.lastf == lastf || !s_r && m.last == MATCH.nomatch)
{
// Rewrite (e1 op e2) as e1.opfunc(e2)
return build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s);
}
else
{
// Rewrite (e1 op e2) as e2.opfunc_r(e1)
return build_overload(e.loc, sc, e.e2, e.e1, m.lastf ? m.lastf : s_r);
}
}
L1:
version (all)
{
// Retained for D1 compatibility
if (isCommutative(e.op) && !tiargs)
{
s = null;
s_r = null;
if (ad1 && id_r)
{
s_r = search_function(ad1, id_r);
}
if (ad2 && id)
{
s = search_function(ad2, id);
if (s && s == s_r) // https://issues.dlang.org/show_bug.cgi?id=12778
s = null;
}
if (s || s_r)
{
/* Try:
* a.opfunc_r(b)
* b.opfunc(a)
* and see which is better.
*/
if (!argsset)
{
args1.setDim(1);
args1[0] = e.e1;
expandTuples(&args1);
args2.setDim(1);
args2[0] = e.e2;
expandTuples(&args2);
}
MatchAccumulator m;
if (s_r)
{
functionResolve(m, s_r, e.loc, sc, tiargs, e.e1.type, &args2);
if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
{
return ErrorExp.get();
}
}
FuncDeclaration lastf = m.lastf;
if (s)
{
functionResolve(m, s, e.loc, sc, tiargs, e.e2.type, &args1);
if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
{
return ErrorExp.get();
}
}
if (m.count > 1)
{
// Error, ambiguous
e.error("overloads `%s` and `%s` both match argument list for `%s`", m.lastf.type.toChars(), m.nextf.type.toChars(), m.lastf.toChars());
}
else if (m.last == MATCH.nomatch)
{
m.lastf = null;
}
if (lastf && m.lastf == lastf || !s && m.last == MATCH.nomatch)
{
// Rewrite (e1 op e2) as e1.opfunc_r(e2)
return build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s_r);
}
else
{
// Rewrite (e1 op e2) as e2.opfunc(e1)
Expression result = build_overload(e.loc, sc, e.e2, e.e1, m.lastf ? m.lastf : s);
// When reversing operands of comparison operators,
// need to reverse the sense of the op
if (pop)
*pop = reverseRelation(e.op);
return result;
}
}
}
}
Expression rewrittenLhs;
if (!(e.op == EXP.assign && ad2 && ad1 == ad2)) // https://issues.dlang.org/show_bug.cgi?id=2943
{
if (Expression result = checkAliasThisForLhs(ad1, sc, e))
{
/* https://issues.dlang.org/show_bug.cgi?id=19441
*
* alias this may not be used for partial assignment.
* If a struct has a single member which is aliased this
* directly or aliased to a ref getter function that returns
* the mentioned member, then alias this may be
* used since the object will be fully initialised.
* If the struct is nested, the context pointer is considered
* one of the members, hence the `ad1.fields.dim == 2 && ad1.vthis`
* condition.
*/
if (result.op != EXP.assign)
return result; // i.e: Rewrote `e1 = e2` -> `e1(e2)`
auto ae = result.isAssignExp();
if (ae.e1.op != EXP.dotVariable)
return result; // i.e: Rewrote `e1 = e2` -> `e1() = e2`
auto dve = ae.e1.isDotVarExp();
if (auto ad = dve.var.isMember2())
{
// i.e: Rewrote `e1 = e2` -> `e1.some.var = e2`
// Ensure that `var` is the only field member in `ad`
if (ad.fields.dim == 1 || (ad.fields.dim == 2 && ad.vthis))
{
if (dve.var == ad.aliasthis.sym)
return result;
}
}
rewrittenLhs = ae.e1;
}
}
if (!(e.op == EXP.assign && ad1 && ad1 == ad2)) // https://issues.dlang.org/show_bug.cgi?id=2943
{
if (Expression result = checkAliasThisForRhs(ad2, sc, e))
return result;
}
if (rewrittenLhs)
{
e.error("cannot use `alias this` to partially initialize variable `%s` of type `%s`. Use `%s`",
e.e1.toChars(), ad1.toChars(), rewrittenLhs.toChars());
return ErrorExp.get();
}
return null;
}
Expression visitEqual(EqualExp e)
{
//printf("EqualExp::op_overload() (%s)\n", e.toChars());
Type t1 = e.e1.type.toBasetype();
Type t2 = e.e2.type.toBasetype();
/* Array equality is handled by expressionSemantic() potentially
* lowering to object.__equals(), which takes care of overloaded
* operators for the element types.
*/
if ((t1.ty == Tarray || t1.ty == Tsarray) &&
(t2.ty == Tarray || t2.ty == Tsarray))
{
return null;
}
/* Check for class equality with null literal or typeof(null).
*/
if (t1.ty == Tclass && e.e2.op == EXP.null_ ||
t2.ty == Tclass && e.e1.op == EXP.null_)
{
e.error("use `%s` instead of `%s` when comparing with `null`",
EXPtoString(e.op == EXP.equal ? EXP.identity : EXP.notIdentity).ptr,
EXPtoString(e.op).ptr);
return ErrorExp.get();
}
if (t1.ty == Tclass && t2.ty == Tnull ||
t1.ty == Tnull && t2.ty == Tclass)
{
// Comparing a class with typeof(null) should not call opEquals
return null;
}
/* Check for class equality.
*/
if (t1.ty == Tclass && t2.ty == Tclass)
{
ClassDeclaration cd1 = t1.isClassHandle();
ClassDeclaration cd2 = t2.isClassHandle();
if (!(cd1.classKind == ClassKind.cpp || cd2.classKind == ClassKind.cpp))
{
/* Rewrite as:
* .object.opEquals(e1, e2)
*/
Expression e1x = e.e1;
Expression e2x = e.e2;
/* The explicit cast is necessary for interfaces
* https://issues.dlang.org/show_bug.cgi?id=4088
*/
Type to = ClassDeclaration.object.getType();
if (cd1.isInterfaceDeclaration())
e1x = new CastExp(e.loc, e.e1, t1.isMutable() ? to : to.constOf());
if (cd2.isInterfaceDeclaration())
e2x = new CastExp(e.loc, e.e2, t2.isMutable() ? to : to.constOf());
Expression result = new IdentifierExp(e.loc, Id.empty);
result = new DotIdExp(e.loc, result, Id.object);
result = new DotIdExp(e.loc, result, Id.eq);
result = new CallExp(e.loc, result, e1x, e2x);
if (e.op == EXP.notEqual)
result = new NotExp(e.loc, result);
result = result.expressionSemantic(sc);
return result;
}
}
if (Expression result = compare_overload(e, sc, Id.eq, null))
{
if (lastComma(result).op == EXP.call && e.op == EXP.notEqual)
{
result = new NotExp(result.loc, result);
result = result.expressionSemantic(sc);
}
return result;
}
/* Check for pointer equality.
*/
if (t1.ty == Tpointer || t2.ty == Tpointer)
{
/* Rewrite:
* ptr1 == ptr2
* as:
* ptr1 is ptr2
*
* This is just a rewriting for deterministic AST representation
* as the backend input.
*/
auto op2 = e.op == EXP.equal ? EXP.identity : EXP.notIdentity;
Expression r = new IdentityExp(op2, e.loc, e.e1, e.e2);
return r.expressionSemantic(sc);
}
/* Check for struct equality without opEquals.
*/
if (t1.ty == Tstruct && t2.ty == Tstruct)
{
auto sd = t1.isTypeStruct().sym;
if (sd != t2.isTypeStruct().sym)
return null;
import dmd.clone : needOpEquals;
if (!global.params.fieldwise && !needOpEquals(sd))
{
// Use bitwise equality.
auto op2 = e.op == EXP.equal ? EXP.identity : EXP.notIdentity;
Expression r = new IdentityExp(op2, e.loc, e.e1, e.e2);
return r.expressionSemantic(sc);
}
/* Do memberwise equality.
* https://dlang.org/spec/expression.html#equality_expressions
* Rewrite:
* e1 == e2
* as:
* e1.tupleof == e2.tupleof
*
* If sd is a nested struct, and if it's nested in a class, it will
* also compare the parent class's equality. Otherwise, compares
* the identity of parent context through void*.
*/
if (e.att1 && t1.equivalent(e.att1)) return null;
if (e.att2 && t2.equivalent(e.att2)) return null;
e = e.copy().isEqualExp();
if (!e.att1) e.att1 = t1;
if (!e.att2) e.att2 = t2;
e.e1 = new DotIdExp(e.loc, e.e1, Id._tupleof);
e.e2 = new DotIdExp(e.loc, e.e2, Id._tupleof);
auto sc2 = sc.push();
sc2.flags |= SCOPE.noaccesscheck;
Expression r = e.expressionSemantic(sc2);
sc2.pop();
/* https://issues.dlang.org/show_bug.cgi?id=15292
* if the rewrite result is same with the original,
* the equality is unresolvable because it has recursive definition.
*/
if (r.op == e.op &&
r.isEqualExp().e1.type.toBasetype() == t1)
{
e.error("cannot compare `%s` because its auto generated member-wise equality has recursive definition",
t1.toChars());
return ErrorExp.get();
}
return r;
}
/* Check for tuple equality.
*/
if (e.e1.op == EXP.tuple && e.e2.op == EXP.tuple)
{
auto tup1 = e.e1.isTupleExp();
auto tup2 = e.e2.isTupleExp();
size_t dim = tup1.exps.dim;
if (dim != tup2.exps.dim)
{
e.error("mismatched tuple lengths, `%d` and `%d`",
cast(int)dim, cast(int)tup2.exps.dim);
return ErrorExp.get();
}
Expression result;
if (dim == 0)
{
// zero-length tuple comparison should always return true or false.
result = IntegerExp.createBool(e.op == EXP.equal);
}
else
{
for (size_t i = 0; i < dim; i++)
{
auto ex1 = (*tup1.exps)[i];
auto ex2 = (*tup2.exps)[i];
auto eeq = new EqualExp(e.op, e.loc, ex1, ex2);
eeq.att1 = e.att1;
eeq.att2 = e.att2;
if (!result)
result = eeq;
else if (e.op == EXP.equal)
result = new LogicalExp(e.loc, EXP.andAnd, result, eeq);
else
result = new LogicalExp(e.loc, EXP.orOr, result, eeq);
}
assert(result);
}
result = Expression.combine(tup1.e0, tup2.e0, result);
result = result.expressionSemantic(sc);
return result;
}
return null;
}
Expression visitCmp(CmpExp e)
{
//printf("CmpExp:: () (%s)\n", e.toChars());
return compare_overload(e, sc, Id.cmp, pop);
}
/*********************************
* Operator overloading for op=
*/
Expression visitBinAssign(BinAssignExp e)
{
//printf("BinAssignExp::op_overload() (%s)\n", e.toChars());
if (auto ae = e.e1.isArrayExp())
{
ae.e1 = ae.e1.expressionSemantic(sc);
ae.e1 = resolveProperties(sc, ae.e1);
Expression ae1old = ae.e1;
const(bool) maybeSlice = (ae.arguments.dim == 0 || ae.arguments.dim == 1 && (*ae.arguments)[0].op == EXP.interval);
IntervalExp ie = null;
if (maybeSlice && ae.arguments.dim)
{
ie = (*ae.arguments)[0].isIntervalExp();
}
while (true)
{
if (ae.e1.op == EXP.error)
{
return ae.e1;
}
Expression e0 = null;
Expression ae1save = ae.e1;
ae.lengthVar = null;
Type t1b = ae.e1.type.toBasetype();
AggregateDeclaration ad = isAggregate(t1b);
if (!ad)
break;
if (search_function(ad, Id.opIndexOpAssign))
{
// Deal with $
Expression result = resolveOpDollar(sc, ae, &e0);
if (!result) // (a[i..j] op= e2) might be: a.opSliceOpAssign!(op)(e2, i, j)
goto Lfallback;
if (result.op == EXP.error)
return result;
result = e.e2.expressionSemantic(sc);
if (result.op == EXP.error)
return result;
e.e2 = result;
/* Rewrite a[arguments] op= e2 as:
* a.opIndexOpAssign!(op)(e2, arguments)
*/
Expressions* a = ae.arguments.copy();
a.insert(0, e.e2);
Objects* tiargs = opToArg(sc, e.op);
result = new DotTemplateInstanceExp(e.loc, ae.e1, Id.opIndexOpAssign, tiargs);
result = new CallExp(e.loc, result, a);
if (maybeSlice) // (a[] op= e2) might be: a.opSliceOpAssign!(op)(e2)
result = result.trySemantic(sc);
else
result = result.expressionSemantic(sc);
if (result)
{
return Expression.combine(e0, result);
}
}
Lfallback:
if (maybeSlice && search_function(ad, Id.opSliceOpAssign))
{
// Deal with $
Expression result = resolveOpDollar(sc, ae, ie, &e0);
if (result.op == EXP.error)
return result;
result = e.e2.expressionSemantic(sc);
if (result.op == EXP.error)
return result;
e.e2 = result;
/* Rewrite (a[i..j] op= e2) as:
* a.opSliceOpAssign!(op)(e2, i, j)
*/
auto a = new Expressions();
a.push(e.e2);
if (ie)
{
a.push(ie.lwr);
a.push(ie.upr);
}
Objects* tiargs = opToArg(sc, e.op);
result = new DotTemplateInstanceExp(e.loc, ae.e1, Id.opSliceOpAssign, tiargs);
result = new CallExp(e.loc, result, a);
result = result.expressionSemantic(sc);
result = Expression.combine(e0, result);
return result;
}
// Didn't find it. Forward to aliasthis
if (ad.aliasthis && !isRecursiveAliasThis(ae.att1, ae.e1.type))
{
/* Rewrite (a[arguments] op= e2) as:
* a.aliasthis[arguments] op= e2
*/
ae.e1 = resolveAliasThis(sc, ae1save, true);
if (ae.e1)
continue;
}
break;
}
ae.e1 = ae1old; // recovery
ae.lengthVar = null;
}
Expression result = e.binSemanticProp(sc);
if (result)
return result;
// Don't attempt 'alias this' if an error occurred
if (e.e1.type.ty == Terror || e.e2.type.ty == Terror)
{
return ErrorExp.get();
}
Identifier id = opId(e);
Expressions args2;
AggregateDeclaration ad1 = isAggregate(e.e1.type);
Dsymbol s = null;
Objects* tiargs = null;
/* Try opOpAssign
*/
if (ad1)
{
s = search_function(ad1, Id.opOpAssign);
if (s && !s.isTemplateDeclaration())
{
e.error("`%s.opOpAssign` isn't a template", e.e1.toChars());
return ErrorExp.get();
}
}
// Set tiargs, the template argument list, which will be the operator string
if (s)
{
id = Id.opOpAssign;
tiargs = opToArg(sc, e.op);
}
// Try D1-style operator overload, deprecated
if (!s && ad1 && id)
{
s = search_function(ad1, id);
if (s)
{
// @@@DEPRECATED_2.110@@@.
// Deprecated in 2.088, made an error in 2.100
scope char[] op = EXPtoString(e.op).dup;
op[$-1] = '\0'; // remove trailing `=`
e.error("`%s` is obsolete. Use `opOpAssign(string op)(...) if (op == \"%s\")` instead.", id.toChars(), op.ptr);
return ErrorExp.get();
}
}
if (s)
{
/* Try:
* a.opOpAssign(b)
*/
args2.setDim(1);
args2[0] = e.e2;
expandTuples(&args2);
MatchAccumulator m;
if (s)
{
functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, &args2);
if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
{
return ErrorExp.get();
}
}
if (m.count > 1)
{
// Error, ambiguous
e.error("overloads `%s` and `%s` both match argument list for `%s`", m.lastf.type.toChars(), m.nextf.type.toChars(), m.lastf.toChars());
}
else if (m.last == MATCH.nomatch)
{
if (tiargs)
goto L1;
m.lastf = null;
}
// Rewrite (e1 op e2) as e1.opOpAssign(e2)
return build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s);
}
L1:
result = checkAliasThisForLhs(ad1, sc, e);
if (result || !s) // no point in trying Rhs alias-this if there's no overload of any kind in lhs
return result;
return checkAliasThisForRhs(isAggregate(e.e2.type), sc, e);
}
if (pop)
*pop = e.op;
switch (e.op)
{
case EXP.cast_ : return visitCast(e.isCastExp());
case EXP.array : return visitArray(e.isArrayExp());
case EXP.notEqual :
case EXP.equal : return visitEqual(e.isEqualExp());
case EXP.lessOrEqual :
case EXP.greaterThan :
case EXP.greaterOrEqual:
case EXP.lessThan : return visitCmp(cast(CmpExp)e);
default:
if (auto ex = e.isBinAssignExp()) return visitBinAssign(ex);
if (auto ex = e.isBinExp()) return visitBin(ex);
if (auto ex = e.isUnaExp()) return visitUna(ex);
return visit(e);
}
}
/******************************************
* Common code for overloading of EqualExp and CmpExp
*/
private Expression compare_overload(BinExp e, Scope* sc, Identifier id, EXP* pop)
{
//printf("BinExp::compare_overload(id = %s) %s\n", id.toChars(), e.toChars());
AggregateDeclaration ad1 = isAggregate(e.e1.type);
AggregateDeclaration ad2 = isAggregate(e.e2.type);
Dsymbol s = null;
Dsymbol s_r = null;
if (ad1)
{
s = search_function(ad1, id);
}
if (ad2)
{
s_r = search_function(ad2, id);
if (s == s_r)
s_r = null;
}
Objects* tiargs = null;
if (s || s_r)
{
/* Try:
* a.opEquals(b)
* b.opEquals(a)
* and see which is better.
*/
Expressions args1 = Expressions(1);
args1[0] = e.e1;
expandTuples(&args1);
Expressions args2 = Expressions(1);
args2[0] = e.e2;
expandTuples(&args2);
MatchAccumulator m;
if (0 && s && s_r)
{
printf("s : %s\n", s.toPrettyChars());
printf("s_r: %s\n", s_r.toPrettyChars());
}
if (s)
{
functionResolve(m, s, e.loc, sc, tiargs, e.e1.type, &args2);
if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
return ErrorExp.get();
}
FuncDeclaration lastf = m.lastf;
int count = m.count;
if (s_r)
{
functionResolve(m, s_r, e.loc, sc, tiargs, e.e2.type, &args1);
if (m.lastf && (m.lastf.errors || m.lastf.hasSemantic3Errors()))
return ErrorExp.get();
}
if (m.count > 1)
{
/* The following if says "not ambiguous" if there's one match
* from s and one from s_r, in which case we pick s.
* This doesn't follow the spec, but is a workaround for the case
* where opEquals was generated from templates and we cannot figure
* out if both s and s_r came from the same declaration or not.
* The test case is:
* import std.typecons;
* void main() {
* assert(tuple("has a", 2u) == tuple("has a", 1));
* }
*/
if (!(m.lastf == lastf && m.count == 2 && count == 1))
{
// Error, ambiguous
e.error("overloads `%s` and `%s` both match argument list for `%s`", m.lastf.type.toChars(), m.nextf.type.toChars(), m.lastf.toChars());
}
}
else if (m.last == MATCH.nomatch)
{
m.lastf = null;
}
Expression result;
if (lastf && m.lastf == lastf || !s_r && m.last == MATCH.nomatch)
{
// Rewrite (e1 op e2) as e1.opfunc(e2)
result = build_overload(e.loc, sc, e.e1, e.e2, m.lastf ? m.lastf : s);
}
else
{
// Rewrite (e1 op e2) as e2.opfunc_r(e1)
result = build_overload(e.loc, sc, e.e2, e.e1, m.lastf ? m.lastf : s_r);
// When reversing operands of comparison operators,
// need to reverse the sense of the op
if (pop)
*pop = reverseRelation(e.op);
}
return result;
}
/*
* https://issues.dlang.org/show_bug.cgi?id=16657
* at this point, no matching opEquals was found for structs,
* so we should not follow the alias this comparison code.
*/
if ((e.op == EXP.equal || e.op == EXP.notEqual) && ad1 == ad2)
return null;
Expression result = checkAliasThisForLhs(ad1, sc, e);
return result ? result : checkAliasThisForRhs(isAggregate(e.e2.type), sc, e);
}
/***********************************
* Utility to build a function call out of this reference and argument.
*/
Expression build_overload(const ref Loc loc, Scope* sc, Expression ethis, Expression earg, Dsymbol d)
{
assert(d);
Expression e;
Declaration decl = d.isDeclaration();
if (decl)
e = new DotVarExp(loc, ethis, decl, false);
else
e = new DotIdExp(loc, ethis, d.ident);
e = new CallExp(loc, e, earg);
e = e.expressionSemantic(sc);
return e;
}
/***************************************
* Search for function funcid in aggregate ad.
*/
Dsymbol search_function(ScopeDsymbol ad, Identifier funcid)
{
Dsymbol s = ad.search(Loc.initial, funcid);
if (s)
{
//printf("search_function: s = '%s'\n", s.kind());
Dsymbol s2 = s.toAlias();
//printf("search_function: s2 = '%s'\n", s2.kind());
FuncDeclaration fd = s2.isFuncDeclaration();
if (fd && fd.type.ty == Tfunction)
return fd;
TemplateDeclaration td = s2.isTemplateDeclaration();
if (td)
return td;
}
return null;
}
/**************************************
* Figure out what is being foreach'd over by looking at the ForeachAggregate.
* Params:
* sc = context
* isForeach = true for foreach, false for foreach_reverse
* feaggr = ForeachAggregate
* sapply = set to function opApply/opApplyReverse, or delegate, or null.
* Overload resolution is not done.
* Returns:
* true if successfully figured it out; feaggr updated with semantic analysis.
* false for failed, which is an error.
*/
bool inferForeachAggregate(Scope* sc, bool isForeach, ref Expression feaggr, out Dsymbol sapply)
{
//printf("inferForeachAggregate(%s)\n", feaggr.toChars());
bool sliced;
Type att = null;
auto aggr = feaggr;
while (1)
{
aggr = aggr.expressionSemantic(sc);
aggr = resolveProperties(sc, aggr);
aggr = aggr.optimize(WANTvalue);
if (!aggr.type || aggr.op == EXP.error)
return false;
Type tab = aggr.type.toBasetype();
switch (tab.ty)
{
case Tarray: // https://dlang.org/spec/statement.html#foreach_over_arrays
case Tsarray: // https://dlang.org/spec/statement.html#foreach_over_arrays
case Ttuple: // https://dlang.org/spec/statement.html#foreach_over_tuples
case Taarray: // https://dlang.org/spec/statement.html#foreach_over_associative_arrays
break;
case Tclass:
case Tstruct:
{
AggregateDeclaration ad = (tab.ty == Tclass) ? tab.isTypeClass().sym
: tab.isTypeStruct().sym;
if (!sliced)
{
sapply = search_function(ad, isForeach ? Id.apply : Id.applyReverse);
if (sapply)
{
// https://dlang.org/spec/statement.html#foreach_over_struct_and_classes
// opApply aggregate
break;
}
if (feaggr.op != EXP.type)
{
/* See if rewriting `aggr` to `aggr[]` will work
*/
Expression rinit = new ArrayExp(aggr.loc, feaggr);
rinit = rinit.trySemantic(sc);
if (rinit) // if it worked
{
aggr = rinit;
sliced = true; // only try it once
continue;
}
}
}
if (ad.search(Loc.initial, isForeach ? Id.Ffront : Id.Fback))
{
// https://dlang.org/spec/statement.html#foreach-with-ranges
// range aggregate
break;
}
if (ad.aliasthis)
{
if (isRecursiveAliasThis(att, tab)) // error, circular alias this
return false;
aggr = resolveAliasThis(sc, aggr);
continue;
}
return false;
}
case Tdelegate: // https://dlang.org/spec/statement.html#foreach_over_delegates
if (auto de = aggr.isDelegateExp())
{
sapply = de.func;
}
break;
case Terror:
break;
default:
return false;
}
feaggr = aggr;
return true;
}
assert(0);
}
/*****************************************
* Given array of foreach parameters and an aggregate type,
* find best opApply overload,
* if any of the parameter types are missing, attempt to infer
* them from the aggregate type.
* Params:
* fes = the foreach statement
* sc = context
* sapply = null or opApply or delegate, overload resolution has not been done.
* Do overload resolution on sapply.
* Returns:
* false for errors
*/
bool inferApplyArgTypes(ForeachStatement fes, Scope* sc, ref Dsymbol sapply)
{
if (!fes.parameters || !fes.parameters.dim)
return false;
if (sapply) // prefer opApply
{
foreach (Parameter p; *fes.parameters)
{
if (p.type)
{
p.type = p.type.typeSemantic(fes.loc, sc);
p.type = p.type.addStorageClass(p.storageClass);
}
}
// Determine ethis for sapply
Expression ethis;
Type tab = fes.aggr.type.toBasetype();
if (tab.ty == Tclass || tab.ty == Tstruct)
ethis = fes.aggr;
else
{
assert(tab.ty == Tdelegate && fes.aggr.op == EXP.delegate_);
ethis = fes.aggr.isDelegateExp().e1;
}
/* Look for like an
* int opApply(int delegate(ref Type [, ...]) dg);
* overload
*/
if (FuncDeclaration fd = sapply.isFuncDeclaration())
{
if (auto fdapply = findBestOpApplyMatch(ethis, fd, fes.parameters))
{
// Fill in any missing types on foreach parameters[]
matchParamsToOpApply(fdapply.type.isTypeFunction(), fes.parameters, true);
sapply = fdapply;
return true;
}
return false;
}
return true; // shouldn't this be false?
}
Parameter p = (*fes.parameters)[0];
Type taggr = fes.aggr.type;
assert(taggr);
Type tab = taggr.toBasetype();
switch (tab.ty)
{
case Tarray:
case Tsarray:
case Ttuple:
if (fes.parameters.dim == 2)
{
if (!p.type)
{
p.type = Type.tsize_t; // key type
p.type = p.type.addStorageClass(p.storageClass);
}
p = (*fes.parameters)[1];
}
if (!p.type && tab.ty != Ttuple)
{
p.type = tab.nextOf(); // value type
p.type = p.type.addStorageClass(p.storageClass);
}
break;
case Taarray:
{
TypeAArray taa = tab.isTypeAArray();
if (fes.parameters.dim == 2)
{
if (!p.type)
{
p.type = taa.index; // key type
p.type = p.type.addStorageClass(p.storageClass);
if (p.storageClass & STC.ref_) // key must not be mutated via ref
p.type = p.type.addMod(MODFlags.const_);
}
p = (*fes.parameters)[1];
}
if (!p.type)
{
p.type = taa.next; // value type
p.type = p.type.addStorageClass(p.storageClass);
}
break;
}
case Tclass:
case Tstruct:
{
AggregateDeclaration ad = (tab.ty == Tclass) ? tab.isTypeClass().sym
: tab.isTypeStruct().sym;
if (fes.parameters.dim == 1)
{
if (!p.type)
{
/* Look for a front() or back() overload
*/
Identifier id = (fes.op == TOK.foreach_) ? Id.Ffront : Id.Fback;
Dsymbol s = ad.search(Loc.initial, id);
FuncDeclaration fd = s ? s.isFuncDeclaration() : null;
if (fd)
{
// Resolve inout qualifier of front type
p.type = fd.type.nextOf();
if (p.type)
{
p.type = p.type.substWildTo(tab.mod);
p.type = p.type.addStorageClass(p.storageClass);
}
}
else if (s && s.isTemplateDeclaration())
{
}
else if (s && s.isDeclaration())
p.type = s.isDeclaration().type;
else
break;
}
break;
}
break;
}
case Tdelegate:
{
auto td = tab.isTypeDelegate();
if (!matchParamsToOpApply(td.next.isTypeFunction(), fes.parameters, true))
return false;
break;
}
default:
break; // ignore error, caught later
}
return true;
}
/*********************************************
* Find best overload match on fstart given ethis and parameters[].
* Params:
* ethis = expression to use for `this`
* fstart = opApply or foreach delegate
* parameters = ForeachTypeList (i.e. foreach parameters)
* Returns:
* best match if there is one, null if error
*/
private FuncDeclaration findBestOpApplyMatch(Expression ethis, FuncDeclaration fstart, Parameters* parameters)
{
MOD mod = ethis.type.mod;
MATCH match = MATCH.nomatch;
FuncDeclaration fd_best;
FuncDeclaration fd_ambig;
overloadApply(fstart, (Dsymbol s)
{
auto f = s.isFuncDeclaration();
if (!f)
return 0; // continue
auto tf = f.type.isTypeFunction();
MATCH m = MATCH.exact;
if (f.isThis())
{
if (!MODimplicitConv(mod, tf.mod))
m = MATCH.nomatch;
else if (mod != tf.mod)
m = MATCH.constant;
}
if (!matchParamsToOpApply(tf, parameters, false))
m = MATCH.nomatch;
if (m > match)
{
fd_best = f;
fd_ambig = null;
match = m;
}
else if (m == match && m > MATCH.nomatch)
{
assert(fd_best);
auto bestTf = fd_best.type.isTypeFunction();
assert(bestTf);
// Found another overload with different attributes?
// e.g. @system vs. @safe opApply
bool ambig = tf.attributesEqual(bestTf);
// opApplies with identical attributes could still accept
// different function bodies as delegate
// => different parameters or attributes
if (ambig)
{
// Fetch the delegates that receive the function body
auto tfBody = tf.parameterList[0].type.isTypeDelegate().next;
assert(tfBody);
auto bestBody = bestTf.parameterList[0].type.isTypeDelegate().next;
assert(bestBody);
// Ignore covariant matches, as later on it can be redone
// after the opApply delegate has its attributes inferred.
ambig = !(tfBody.covariant(bestBody) == Covariant.yes || bestBody.covariant(tfBody) == Covariant.yes);
}
if (ambig)
fd_ambig = f; // not covariant, so ambiguous
}
return 0; // continue
});
if (fd_ambig)
{
.error(ethis.loc, "`%s.%s` matches more than one declaration:\n`%s`: `%s`\nand:\n`%s`: `%s`",
ethis.toChars(), fstart.ident.toChars(),
fd_best.loc.toChars(), fd_best.type.toChars(),
fd_ambig.loc.toChars(), fd_ambig.type.toChars());
return null;
}
return fd_best;
}
/******************************
* Determine if foreach parameters match opApply parameters.
* Infer missing foreach parameter types from type of opApply delegate.
* Params:
* tf = type of opApply or delegate
* parameters = foreach parameters
* infer = infer missing parameter types
* Returns:
* true for match for this function
* false for no match for this function
*/
private bool matchParamsToOpApply(TypeFunction tf, Parameters* parameters, bool infer)
{
enum nomatch = false;
/* opApply/delegate has exactly one parameter, and that parameter
* is a delegate that looks like:
* int opApply(int delegate(ref Type [, ...]) dg);
*/
if (tf.parameterList.length != 1)
return nomatch;
/* Get the type of opApply's dg parameter
*/
Parameter p0 = tf.parameterList[0];
auto de = p0.type.isTypeDelegate();
if (!de)
return nomatch;
TypeFunction tdg = de.next.isTypeFunction();
/* We now have tdg, the type of the delegate.
* tdg's parameters must match that of the foreach arglist (i.e. parameters).
* Fill in missing types in parameters.
*/
const nparams = tdg.parameterList.length;
if (nparams == 0 || nparams != parameters.dim || tdg.parameterList.varargs != VarArg.none)
return nomatch; // parameter mismatch
foreach (u, p; *parameters)
{
Parameter param = tdg.parameterList[u];
if (p.type)
{
if (!p.type.equals(param.type))
return nomatch;
}
else if (infer)
{
p.type = param.type;
p.type = p.type.addStorageClass(p.storageClass);
}
}
return true;
}
/**
* Reverse relational operator, eg >= becomes <=
* Note this is not negation.
* Params:
* op = comparison operator to reverse
* Returns:
* reverse of op
*/
private EXP reverseRelation(EXP op) pure
{
switch (op)
{
case EXP.greaterOrEqual: op = EXP.lessOrEqual; break;
case EXP.greaterThan: op = EXP.lessThan; break;
case EXP.lessOrEqual: op = EXP.greaterOrEqual; break;
case EXP.lessThan: op = EXP.greaterThan; break;
default: break;
}
return op;
}