THRIFT-2916 Add default toHash method to 'class' and 'struct' to meet D's associative arrays requirement.
Client: D
Patch: Phongphan Phuttha <phongphan@acm.org>

This closes #503
diff --git a/lib/d/src/thrift/codegen/base.d b/lib/d/src/thrift/codegen/base.d
index 35e566a..9061d0b 100644
--- a/lib/d/src/thrift/codegen/base.d
+++ b/lib/d/src/thrift/codegen/base.d
@@ -419,6 +419,10 @@
 
       return (cast()super).opEquals(other);
     }
+
+    size_t toHash() const {
+      return thriftToHashImpl();
+    }
   } else {
     string toString() const {
       return thriftToStringImpl();
@@ -427,6 +431,10 @@
     bool opEquals(ref const This other) const {
       return thriftOpEqualsImpl(other);
     }
+
+    size_t toHash() const @safe nothrow {
+      return thriftToHashImpl();
+    }
   }
 
   private string thriftToStringImpl() const {
@@ -459,6 +467,15 @@
     return true;
   }
 
+  private size_t thriftToHashImpl() const @trusted nothrow {
+    size_t hash = 0;
+    foreach (name; FieldNames!This) {
+      auto val = mixin("this." ~ name);
+      hash += typeid(val).getHash(&val);
+    }
+    return hash;
+  }
+
   static if (any!`!a.defaultValue.empty`(mergeFieldMeta!(This, fieldMetaData))) {
     static if (is(This _ == class)) {
       this() {
@@ -941,6 +958,29 @@
   static assert(__traits(compiles, { Test t; t.write(cast(TProtocol)null); }));
 }
 
+// Ensure opEquals and toHash consistency.
+unittest {
+  struct TestEquals {
+    int a1;
+
+    mixin TStructHelpers!([
+      TFieldMeta("a1", 1, TReq.OPT_IN_REQ_OUT),
+    ]);
+  }
+
+  TestEquals a, b;
+  assert(a == b);
+  assert(a.toHash() == b.toHash());
+
+  a.a1 = 42;
+  assert(a != b);
+  assert(a.toHash() != b.toHash());
+
+  b.a1 = 42;
+  assert(a == b);
+  assert(a.toHash() == b.toHash());
+}
+
 private {
   /*
    * Returns a D code string containing the matching TType value for a passed