I'm currently trying to templatize the free function opEquals in druntime for issue# 9769, and a compiler bug is blocking me.
This piece of code (taken from dmd's test suite) triggers the problem:
----------------
struct T11875x(C)
{
C c;
}
class D11875a { D11875b c; alias c this; }
class D11875b { D11875a c; alias c this; }
static assert( is(T11875x!D11875a == T11875x!D, D) && is(D == D11875a));
void main()
{
}
----------------
If all you do is templatize opEquals in object_.d and copy it to object.di (since currently, object.di holds only the signatures for opEquals), then the compiler never stops (or if it does, it takes a very long time). i.e.
----------------
bool opEquals()(const Object lhs, const Object rhs)
{
// A hack for the moment.
return opEquals(cast()lhs, cast()rhs);
}
bool opEquals()(Object lhs, Object rhs)
{
// If aliased to the same object or both null => equal
if (lhs is rhs) return true;
// If either is null => non-equal
if (lhs is null || rhs is null) return false;
// If same exact type => one call to method opEquals
if (typeid(lhs) is typeid(rhs) || typeid(lhs).opEquals(typeid(rhs)))
return lhs.opEquals(rhs);
// General case => symmetric calls to method opEquals
return lhs.opEquals(rhs) && rhs.opEquals(lhs);
}
----------------
So, all that was added was the () after opEquals. Running in gdb, it looks like the stack trace goes on pretty much forever. However, the first portion looks like
#0 0x00000000004284dc in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#1 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#2 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#3 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#4 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#5 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#6 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#7 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#8 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#9 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#10 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#11 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#12 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#13 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#14 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#15 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#16 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#17 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#18 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#19 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
#20 0x00000000004284e7 in Expression_optimize(Expression*, int, bool)::OptimizeVisitor::visit(DotVarExp*) ()
...
But for some reason, the stack doesn't actually get blown (probably due to a tail recursion optimization, but I don't know).
If I use what is currently my actual solution
----------------
/************************
* Returns true if lhs and rhs are equal.
*/
bool opEquals(T, U)(T lhs, U rhs)
if (is(T == class) && is(U == class) &&
is(typeof(lhs.opEquals(rhs)) == bool) &&
is(typeof(rhs.opEquals(lhs)) == bool))
{
static if (is(T : U) || is(U : T))
{
// If aliased to the same object or both null => equal
if (lhs is rhs) return true;
}
// If either is null => non-equal
if (lhs is null || rhs is null) return false;
// If same exact type => one call to method opEquals
// General case => symmetric calls to method opEquals
return lhs.opEquals(rhs) &&
(typeid(lhs) is typeid(lhs) || typeid(lhs).opEquals(typeid(rhs)) || rhs.opEquals(lhs));
}
bool opEquals(T, U)(const T lhs, const U rhs)
if (is(T == class) && is(U == class) &&
!is(typeof(lhs.opEquals(rhs)) == bool) &&
!is(typeof(rhs.opEquals(lhs)) == bool))
{
// FIXME. This is a hack.
// We shouldn't need to cast away const, and if either lhs' or rhs' opEquals
// mutates either object, it's undefined behavior. But before we can remove
// this, we need to make it so that TypeInfo and friends have the corect
// definitions for opEquals so that they work with the other overload. And
// any user code using const objects but which doesn't define opEquals such
// that it works with const with the other overload will also break once
// this is removed. So, we need to get rid of this, but we need to be
// careful about how and when we do it.
return opEquals(cast()lhs, cast()rhs);
}
// This screwy overload is here to make typedef work, since while it _has_ been
// deprecated for a while now, it has yet to be removed, and typedefs won't
// work with the other two overloads, because bizarrely enough, if T is a typedefed
// class, then is(T == class) is false (which has got to wreak havoc with generic
// code).
bool opEquals(T, U)(const T lhs, const U rhs)
if (!is(T == class) && !is(U == class) &&
!is(T == struct) && !is(U == struct) &&
is(T : Object) && is(U : Object))
{
return opEquals(cast(Object)lhs, cast(Object)rhs);
}
----------------
then the stack _does_ get blown, and dmd segfaults. The top of the stack in that case looks like
#0 0x00000000004cb9b0 in resolvePropertiesX(Scope*, Expression*, Expression*) ()
#1 0x00000000004d6c30 in DotIdExp::semanticX(Scope*) ()
#2 0x00000000004d7015 in DotIdExp::semanticY(Scope*, int) ()
#3 0x00000000004d7baa in DotIdExp::semantic(Scope*) ()
#4 0x00000000004d4b2d in CastExp::semantic(Scope*) ()
#5 0x00000000004bb8b2 in Expression::trySemantic(Scope*) ()
#6 0x00000000004209ee in op_overload(Expression*, Scope*)::OpOverload::visit(CastExp*) ()
#7 0x0000000000420476 in op_overload(Expression*, Scope*) ()
#8 0x00000000004d4db2 in CastExp::semantic(Scope*) ()
#9 0x00000000004bb8b2 in Expression::trySemantic(Scope*) ()
#10 0x00000000004209ee in op_overload(Expression*, Scope*)::OpOverload::visit(CastExp*) ()
#11 0x0000000000420476 in op_overload(Expression*, Scope*) ()
#12 0x00000000004d4db2 in CastExp::semantic(Scope*) ()
#13 0x00000000004bb8b2 in Expression::trySemantic(Scope*) ()
#14 0x00000000004209ee in op_overload(Expression*, Scope*)::OpOverload::visit(CastExp*) ()
#15 0x0000000000420476 in op_overload(Expression*, Scope*) ()
#16 0x00000000004d4db2 in CastExp::semantic(Scope*) ()
#17 0x00000000004bb8b2 in Expression::trySemantic(Scope*) ()
#18 0x00000000004209ee in op_overload(Expression*, Scope*)::OpOverload::visit(CastExp*) ()
#19 0x0000000000420476 in op_overload(Expression*, Scope*) ()
#20 0x00000000004d4db2 in CastExp::semantic(Scope*) ()
#21 0x00000000004bb8b2 in Expression::trySemantic(Scope*) ()
#22 0x00000000004209ee in op_overload(Expression*, Scope*)::OpOverload::visit(CastExp*) ()
Given that the piece of code that triggers this doesn't actually use opEquals as far as I can tell (it uses == for is, but that's checking types, not comparing actual objects), I have no idea why templatizing opEquals would trigger it, but it does.
It's certainly possible that there's a bug in my changes, but simply templatizing opEquals shouldn't result in the compiler hitting infinite recursion, so I'm sure that there's a compiler bug here, and until it's fixed, I can't get my changes working.
Here's a goofy workaround that seems to work. Change
return opEquals(cast()lhs, cast()rhs);
to
import core.internal.traits : Unqual;
return opEquals(* cast(Unqual!T*) &lhs, * cast(Unqual!U*) &rhs);
Comment #3 by ag0aep6g — 2015-01-07T00:21:26Z
Reduced test case:
class A { B c; alias c this; }
class B { A c; alias c this; }
void main()
{
const A lhs;
auto x = cast() lhs;
}
Comment #4 by k.hara.pg — 2015-09-02T15:11:14Z
Reduced test case:
class C12537a { C12537b c; alias c this; }
class C12537b { C12537a c; alias c this; }
void equals()(Object lhs) {}
void main()
{
const C12537a c;
equals(c);
}