196 changes: 189 additions & 7 deletions src/opover.d
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,74 @@ extern (C++) Expression op_overload(Expression e, Scope* sc)
//printf("EqualExp::op_overload() (%s)\n", e->toChars());
Type t1 = e.e1.type.toBasetype();
Type t2 = e.e2.type.toBasetype();

/* Check for array equality.
*/
if ((t1.ty == Tarray || t1.ty == Tsarray) &&
(t2.ty == Tarray || t2.ty == Tsarray))
{
bool needsDirectEq()
{
Type t1n = t1.nextOf().toBasetype();
Type t2n = t2.nextOf().toBasetype();
if (((t1n.ty == Tchar || t1n.ty == Twchar || t1n.ty == Tdchar) &&
(t2n.ty == Tchar || t2n.ty == Twchar || t2n.ty == Tdchar)) ||
(t1n.ty == Tvoid || t2n.ty == Tvoid))
{
return false;
}
if (t1n.constOf() != t2n.constOf())
return true;

Type t = t1n;
while (t.toBasetype().nextOf())
t = t.nextOf().toBasetype();
if (t.ty != Tstruct)
return false;

semanticTypeInfo(sc, t);
return (cast(TypeStruct)t).sym.hasIdentityEquals;
}

if (needsDirectEq())
{
/* Rewrite as:
* _ArrayEq(e1, e2)
*/
Expression eeq = new IdentifierExp(e.loc, Id._ArrayEq);
result = new CallExp(e.loc, eeq, e.e1, e.e2);
if (e.op == TOKnotequal)
result = new NotExp(e.loc, result);
result = result.trySemantic(sc); // for better error message
if (!result)
{
e.error("cannot compare %s and %s", t1.toChars(), t2.toChars());
result = new ErrorExp();
}
return;
}
}

/* Check for class equality with null literal or typeof(null).
*/
if (t1.ty == Tclass && e.e2.op == TOKnull ||
t2.ty == Tclass && e.e1.op == TOKnull)
{
e.error("use '%s' instead of '%s' when comparing with null",
Token.toChars(e.op == TOKequal ? TOKidentity : TOKnotidentity),
Token.toChars(e.op));
result = new ErrorExp();
return;
}
if (t1.ty == Tclass && t2.ty == Tnull ||
t1.ty == Tnull && t2.ty == Tclass)
{
// Comparing a class with typeof(null) should not call opEquals
return;
}

/* Check for class equality.
*/
if (t1.ty == Tclass && t2.ty == Tclass)
{
ClassDeclaration cd1 = t1.isClassHandle();
Expand All @@ -1102,30 +1170,144 @@ extern (C++) Expression op_overload(Expression e, Scope* sc)
*/
Expression e1x = e.e1;
Expression e2x = e.e2;
/*
* The explicit cast is necessary for interfaces,
* see http://d.puremagic.com/issues/show_bug.cgi?id=4088

/* The explicit cast is necessary for interfaces,
* see Bugzilla 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());

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 == TOKnotequal)
result = new NotExp(e.loc, result);
result = result.semantic(sc);
return;
}
}
// Comparing a class with typeof(null) should not call opEquals
if (t1.ty == Tclass && t2.ty == Tnull || t1.ty == Tnull && t2.ty == Tclass)

result = compare_overload(e, sc, Id.eq);
if (result)
{
if (result.op == TOKcall && e.op == TOKnotequal)
{
result = new NotExp(result.loc, result);
result = result.semantic(sc);
}
return;
}
else

/* 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 == TOKequal ? TOKidentity : TOKnotidentity;
result = new IdentityExp(op2, e.loc, e.e1, e.e2);
result = result.semantic(sc);
return;
}

/* Check for struct equality without opEquals.
*/
if (t1.ty == Tstruct && t2.ty == Tstruct)
{
result = compare_overload(e, sc, Id.eq);
auto sd = (cast(TypeStruct)t1).sym;
if (sd != (cast(TypeStruct)t2).sym)
return;

import ddmd.clone : needOpEquals;
if (!needOpEquals(sd))
{
// Use bitwise equality.
auto op2 = e.op == TOKequal ? TOKidentity : TOKnotidentity;
result = new IdentityExp(op2, e.loc, e.e1, e.e2);
result = result.semantic(sc);
return;
}

/* Rewrite:
* e1 == e2
* as:
* e1.tupleof == e2.tupleof
*/
if (e.att1 && t1 == e.att1) return;
if (e.att2 && t2 == e.att2) return;

e = cast(EqualExp)e.copy();
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);
result = e.semantic(sc);

/* Bugzilla 15292, if the rewrite result is same with the original,
* the equality is unresolvable because it has recursive definition.
*/
if (result.op == e.op &&
(cast(EqualExp)result).e1.type.toBasetype() == t1)
{
e.error("cannot compare %s because its auto generated member-wise equality has recursive definition",
t1.toChars());
result = new ErrorExp();
}
return;
}

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

if (dim == 0)
{
// zero-length tuple comparison should always return true or false.
result = new IntegerExp(e.loc, (e.op == TOKequal), Type.tbool);
}
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 == TOKequal)
result = new AndAndExp(e.loc, result, eeq);
else
result = new OrOrExp(e.loc, result, eeq);
}
assert(result);
}
result = Expression.combine(Expression.combine(tup1.e0, tup2.e0), result);
result = result.semantic(sc);
return;
}
}

Expand Down
28 changes: 28 additions & 0 deletions test/fail_compilation/fail15292.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// REQUIRED_ARGS: -o-
/*
TEST_OUTPUT:
---
fail_compilation/fail15292.d(27): Error: cannot compare S15292 because its auto generated member-wise equality has recursive definition
---
*/

struct NullableRef15292(T)
{
inout(T) get() inout
{
assert(false);
}

alias get this;
}

struct S15292
{
NullableRef15292!S15292 n;
}

void main()
{
S15292 s;
assert(s == s);
}
30 changes: 30 additions & 0 deletions test/runnable/aliasthis.d
Original file line number Diff line number Diff line change
Expand Up @@ -1908,6 +1908,36 @@ void test14948()
int[HTTP14948] aa;
}

/***************************************************/
// 15292

struct NullableRef15292(T)
{
inout(T) get() inout
{
assert(false);
}

alias get this;
}

struct S15292
{
NullableRef15292!S15292 n; // -> no segfault

/* The field 'n' contains alias this, so to use it for the equality,
* following helper function is automatically generated in buildXopEquals().
*
* static bool __xopEquals(ref const S15292 p, ref const S15292 q)
* {
* return p == q;
* }
*
* In its definition, const(S15292) equality is analyzed. It fails, then
* the error is gagged.
*/
}

/***************************************************/

int main()
Expand Down