digitalmars.D.bugs - [Issue 12537] New: Templatizing opEquals results in infinite recursion in the compiler
- d-bugmail puremagic.com (196/196) Apr 07 2014 https://d.puremagic.com/issues/show_bug.cgi?id=12537
https://d.puremagic.com/issues/show_bug.cgi?id=12537 Summary: Templatizing opEquals results in infinite recursion in the compiler Product: D Version: D2 Platform: All OS/Version: All Status: NEW Severity: normal Priority: P2 Component: DMD AssignedTo: nobody puremagic.com ReportedBy: jmdavisProg gmx.com PDT --- I'm currently trying to templatize the free function opEquals in druntime for 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 bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () bool)::OptimizeVisitor::visit(DotVarExp*) () 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 () Scope*)::OpOverload::visit(CastExp*) () Scope*)::OpOverload::visit(CastExp*) () Scope*)::OpOverload::visit(CastExp*) () Scope*)::OpOverload::visit(CastExp*) () 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. -- Configure issuemail: https://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: -------
Apr 07 2014