Bug 12537 – Templatizing opEquals results in infinite recursion in the compiler

Status
RESOLVED
Resolution
FIXED
Severity
blocker
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2014-04-07T04:40:00Z
Last change time
2016-10-01T11:45:27Z
Keywords
ice, pull
Assigned to
nobody
Creator
issues.dlang
Blocks
9769, 13933

Comments

Comment #0 by issues.dlang — 2014-04-07T04:40:32Z
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.
Comment #1 by issues.dlang — 2014-04-23T09:52:55Z
This is blocking https://github.com/D-Programming-Language/druntime/pull/459 so I'm changing its importance to "blocker," in case that will do any good at getting it fixed faster.
Comment #2 by ag0aep6g — 2015-01-05T02:27:49Z
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); }
Comment #5 by k.hara.pg — 2015-09-02T15:28:07Z
Comment #6 by github-bugzilla — 2016-03-28T15:22:33Z
Commits pushed to master at https://github.com/D-Programming-Language/dmd https://github.com/D-Programming-Language/dmd/commit/a7ff19ae302948c4fd9e71034cc343d784137803 fix Issue 12537 - Templatizing opEquals results in infinite recursion in the compiler https://github.com/D-Programming-Language/dmd/commit/c150f4165ea50cd77e5c9e2ee0ef2f4bfd9027bb Merge pull request #5028 from 9rnsr/fix12537 Issue 12537 - Templatizing opEquals results in infinite recursion in the compiler
Comment #7 by github-bugzilla — 2016-10-01T11:45:27Z
Commits pushed to stable at https://github.com/dlang/dmd https://github.com/dlang/dmd/commit/a7ff19ae302948c4fd9e71034cc343d784137803 fix Issue 12537 - Templatizing opEquals results in infinite recursion in the compiler https://github.com/dlang/dmd/commit/c150f4165ea50cd77e5c9e2ee0ef2f4bfd9027bb Merge pull request #5028 from 9rnsr/fix12537