Merge pull request #1942 from thomasvl/objc_fix_oneof_zeros

Objc fix oneof zeros and more oneof zero conformance tests
diff --git a/conformance/conformance.proto b/conformance/conformance.proto
index fc96074..e1b797c 100644
--- a/conformance/conformance.proto
+++ b/conformance/conformance.proto
@@ -210,6 +210,11 @@
     NestedMessage oneof_nested_message = 112;
     string oneof_string = 113;
     bytes oneof_bytes = 114;
+    bool oneof_bool = 115;
+    uint64 oneof_uint64 = 116;
+    float oneof_float = 117;
+    double oneof_double = 118;
+    NestedEnum oneof_enum = 119;
   }
 
   // Well-known types
diff --git a/conformance/conformance_test.cc b/conformance/conformance_test.cc
index d46fa87..88b05a5 100644
--- a/conformance/conformance_test.cc
+++ b/conformance/conformance_test.cc
@@ -1256,6 +1256,21 @@
     message.set_oneof_bytes("");
     RunValidProtobufTest(
         "OneofZeroBytes", message, "oneof_bytes: \"\"");
+    message.set_oneof_bool(false);
+    RunValidProtobufTest(
+        "OneofZeroBool", message, "oneof_bool: false");
+    message.set_oneof_uint64(0);
+    RunValidProtobufTest(
+        "OneofZeroUint64", message, "oneof_uint64: 0");
+    message.set_oneof_float(0.0f);
+    RunValidProtobufTest(
+        "OneofZeroFloat", message, "oneof_float: 0");
+    message.set_oneof_double(0.0);
+    RunValidProtobufTest(
+        "OneofZeroDouble", message, "oneof_double: 0");
+    message.set_oneof_enum(TestAllTypes::FOO);
+    RunValidProtobufTest(
+        "OneofZeroEnum", message, "oneof_enum: FOO");
   }
   RunValidJsonTest(
       "OneofZeroUint32",
@@ -1269,6 +1284,21 @@
   RunValidJsonTest(
       "OneofZeroBytes",
       R"({"oneofBytes": ""})", "oneof_bytes: \"\"");
+  RunValidJsonTest(
+      "OneofZeroBool",
+      R"({"oneofBool": false})", "oneof_bool: false");
+  RunValidJsonTest(
+      "OneofZeroUint64",
+      R"({"oneofUint64": 0})", "oneof_uint64: 0");
+  RunValidJsonTest(
+      "OneofZeroFloat",
+      R"({"oneofFloat": 0.0})", "oneof_float: 0");
+  RunValidJsonTest(
+      "OneofZeroDouble",
+      R"({"oneofDouble": 0.0})", "oneof_double: 0");
+  RunValidJsonTest(
+      "OneofZeroEnum",
+      R"({"oneofEnum":"FOO"})", "oneof_enum: FOO");
 
   // Repeated fields.
   RunValidJsonTest(
diff --git a/conformance/failure_list_csharp.txt b/conformance/failure_list_csharp.txt
index 1716bcb..6f8a5b7 100644
--- a/conformance/failure_list_csharp.txt
+++ b/conformance/failure_list_csharp.txt
@@ -5,3 +5,23 @@
 JsonInput.FieldNameWithMixedCases.ProtobufOutput
 JsonInput.FieldNameWithMixedCases.Validator
 JsonInput.OriginalProtoFieldName.JsonOutput
+JsonInput.OneofZeroBool.JsonOutput
+JsonInput.OneofZeroBool.ProtobufOutput
+JsonInput.OneofZeroDouble.JsonOutput
+JsonInput.OneofZeroDouble.ProtobufOutput
+JsonInput.OneofZeroEnum.JsonOutput
+JsonInput.OneofZeroEnum.ProtobufOutput
+JsonInput.OneofZeroFloat.JsonOutput
+JsonInput.OneofZeroFloat.ProtobufOutput
+JsonInput.OneofZeroUint64.JsonOutput
+JsonInput.OneofZeroUint64.ProtobufOutput
+ProtobufInput.OneofZeroBool.JsonOutput
+ProtobufInput.OneofZeroBool.ProtobufOutput
+ProtobufInput.OneofZeroDouble.JsonOutput
+ProtobufInput.OneofZeroDouble.ProtobufOutput
+ProtobufInput.OneofZeroEnum.JsonOutput
+ProtobufInput.OneofZeroEnum.ProtobufOutput
+ProtobufInput.OneofZeroFloat.JsonOutput
+ProtobufInput.OneofZeroFloat.ProtobufOutput
+ProtobufInput.OneofZeroUint64.JsonOutput
+ProtobufInput.OneofZeroUint64.ProtobufOutput
diff --git a/conformance/failure_list_objc.txt b/conformance/failure_list_objc.txt
index ec7898c..dd538c1 100644
--- a/conformance/failure_list_objc.txt
+++ b/conformance/failure_list_objc.txt
@@ -1,6 +1,4 @@
-ProtobufInput.OneofZeroBytes.ProtobufOutput
-ProtobufInput.OneofZeroString.ProtobufOutput
-ProtobufInput.OneofZeroUint32.ProtobufOutput
+# All tests currently passing.
 #
-# json input or output tests are skipped (in conformance_objc.m) as mobile
-# platforms don't support json wire format to avoid code bloat.
+# JSON input or output tests are skipped (in conformance_objc.m) as mobile
+# platforms don't support JSON wire format to avoid code bloat.
diff --git a/conformance/failure_list_ruby.txt b/conformance/failure_list_ruby.txt
index 526c2e7..47d0c73 100644
--- a/conformance/failure_list_ruby.txt
+++ b/conformance/failure_list_ruby.txt
@@ -103,8 +103,14 @@
 JsonInput.MessageMapField.ProtobufOutput
 JsonInput.MessageRepeatedField.JsonOutput
 JsonInput.MessageRepeatedField.ProtobufOutput
+JsonInput.OneofZeroDouble.JsonOutput
+JsonInput.OneofZeroDouble.ProtobufOutput
+JsonInput.OneofZeroFloat.JsonOutput
+JsonInput.OneofZeroFloat.ProtobufOutput
 JsonInput.OneofZeroUint32.JsonOutput
 JsonInput.OneofZeroUint32.ProtobufOutput
+JsonInput.OneofZeroUint64.JsonOutput
+JsonInput.OneofZeroUint64.ProtobufOutput
 JsonInput.OptionalBoolWrapper.JsonOutput
 JsonInput.OptionalBoolWrapper.ProtobufOutput
 JsonInput.OptionalBytesWrapper.JsonOutput
diff --git a/objectivec/GPBUtilities.m b/objectivec/GPBUtilities.m
index ad876ed..80b85d0 100644
--- a/objectivec/GPBUtilities.m
+++ b/objectivec/GPBUtilities.m
@@ -218,9 +218,10 @@
 //%  TYPE *typePtr = (TYPE *)&storage[field->description_->offset];
 //%  *typePtr = value;
 //%  // proto2: any value counts as having been set; proto3, it
-//%  // has to be a non zero value.
-//%  BOOL hasValue =
-//%    (syntax == GPBFileSyntaxProto2) || (value != (TYPE)0);
+//%  // has to be a non zero value or be in a oneof.
+//%  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
+//%                   || (value != (TYPE)0)
+//%                   || (field->containingOneof_ != NULL));
 //%  GPBSetHasIvarField(self, field, hasValue);
 //%  GPBBecomeVisibleToAutocreator(self);
 //%}
@@ -337,8 +338,19 @@
     // zero, they are being cleared.
     if ((syntax == GPBFileSyntaxProto3) && !fieldIsMessage &&
         ([value length] == 0)) {
-      setHasValue = NO;
-      value = nil;
+      // Except, if the field was in a oneof, then it still gets recorded as
+      // having been set so the state of the oneof can be serialized back out.
+      if (!oneof) {
+        setHasValue = NO;
+      }
+      if (setHasValue) {
+        NSCAssert(value != nil, @"Should never be setting has for nil");
+      } else {
+        // The value passed in was retained, it must be released since we
+        // aren't saving anything in the field.
+        [value release];
+        value = nil;
+      }
     }
     GPBSetHasIvarField(self, field, setHasValue);
   }
@@ -524,9 +536,10 @@
   GPBSetHasIvar(self, (int32_t)(fieldDesc->offset), fieldDesc->number, value);
 
   // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value.
-  BOOL hasValue =
-    (syntax == GPBFileSyntaxProto2) || (value != (BOOL)0);
+  // has to be a non zero value or be in a oneof.
+  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
+                   || (value != (BOOL)0)
+                   || (field->containingOneof_ != NULL));
   GPBSetHasIvarField(self, field, hasValue);
   GPBBecomeVisibleToAutocreator(self);
 }
@@ -573,9 +586,10 @@
   int32_t *typePtr = (int32_t *)&storage[field->description_->offset];
   *typePtr = value;
   // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value.
-  BOOL hasValue =
-    (syntax == GPBFileSyntaxProto2) || (value != (int32_t)0);
+  // has to be a non zero value or be in a oneof.
+  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
+                   || (value != (int32_t)0)
+                   || (field->containingOneof_ != NULL));
   GPBSetHasIvarField(self, field, hasValue);
   GPBBecomeVisibleToAutocreator(self);
 }
@@ -622,9 +636,10 @@
   uint32_t *typePtr = (uint32_t *)&storage[field->description_->offset];
   *typePtr = value;
   // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value.
-  BOOL hasValue =
-    (syntax == GPBFileSyntaxProto2) || (value != (uint32_t)0);
+  // has to be a non zero value or be in a oneof.
+  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
+                   || (value != (uint32_t)0)
+                   || (field->containingOneof_ != NULL));
   GPBSetHasIvarField(self, field, hasValue);
   GPBBecomeVisibleToAutocreator(self);
 }
@@ -671,9 +686,10 @@
   int64_t *typePtr = (int64_t *)&storage[field->description_->offset];
   *typePtr = value;
   // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value.
-  BOOL hasValue =
-    (syntax == GPBFileSyntaxProto2) || (value != (int64_t)0);
+  // has to be a non zero value or be in a oneof.
+  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
+                   || (value != (int64_t)0)
+                   || (field->containingOneof_ != NULL));
   GPBSetHasIvarField(self, field, hasValue);
   GPBBecomeVisibleToAutocreator(self);
 }
@@ -720,9 +736,10 @@
   uint64_t *typePtr = (uint64_t *)&storage[field->description_->offset];
   *typePtr = value;
   // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value.
-  BOOL hasValue =
-    (syntax == GPBFileSyntaxProto2) || (value != (uint64_t)0);
+  // has to be a non zero value or be in a oneof.
+  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
+                   || (value != (uint64_t)0)
+                   || (field->containingOneof_ != NULL));
   GPBSetHasIvarField(self, field, hasValue);
   GPBBecomeVisibleToAutocreator(self);
 }
@@ -769,9 +786,10 @@
   float *typePtr = (float *)&storage[field->description_->offset];
   *typePtr = value;
   // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value.
-  BOOL hasValue =
-    (syntax == GPBFileSyntaxProto2) || (value != (float)0);
+  // has to be a non zero value or be in a oneof.
+  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
+                   || (value != (float)0)
+                   || (field->containingOneof_ != NULL));
   GPBSetHasIvarField(self, field, hasValue);
   GPBBecomeVisibleToAutocreator(self);
 }
@@ -818,9 +836,10 @@
   double *typePtr = (double *)&storage[field->description_->offset];
   *typePtr = value;
   // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value.
-  BOOL hasValue =
-    (syntax == GPBFileSyntaxProto2) || (value != (double)0);
+  // has to be a non zero value or be in a oneof.
+  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
+                   || (value != (double)0)
+                   || (field->containingOneof_ != NULL));
   GPBSetHasIvarField(self, field, hasValue);
   GPBBecomeVisibleToAutocreator(self);
 }
diff --git a/objectivec/Tests/GPBMessageTests+Runtime.m b/objectivec/Tests/GPBMessageTests+Runtime.m
index 1520381..e963d18 100644
--- a/objectivec/Tests/GPBMessageTests+Runtime.m
+++ b/objectivec/Tests/GPBMessageTests+Runtime.m
@@ -1972,6 +1972,254 @@
   [msg release];
 }
 
+- (void)testProto2OneofSetToDefault {
+
+  // proto3 doesn't normally write out zero (default) fields, but if they are
+  // in a oneof it does.  proto2 doesn't have this special behavior, but we
+  // still confirm setting to the explicit default does set the case to be
+  // sure the runtime is working correctly.
+
+  NSString *oneofStringDefault = @"string";
+  NSData *oneofBytesDefault = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
+
+  Message2 *msg = [[Message2 alloc] init];
+
+  uint32_t values[] = {
+    Message2_O_OneOfCase_OneofInt32,
+    Message2_O_OneOfCase_OneofInt64,
+    Message2_O_OneOfCase_OneofUint32,
+    Message2_O_OneOfCase_OneofUint64,
+    Message2_O_OneOfCase_OneofSint32,
+    Message2_O_OneOfCase_OneofSint64,
+    Message2_O_OneOfCase_OneofFixed32,
+    Message2_O_OneOfCase_OneofFixed64,
+    Message2_O_OneOfCase_OneofSfixed32,
+    Message2_O_OneOfCase_OneofSfixed64,
+    Message2_O_OneOfCase_OneofFloat,
+    Message2_O_OneOfCase_OneofDouble,
+    Message2_O_OneOfCase_OneofBool,
+    Message2_O_OneOfCase_OneofString,
+    Message2_O_OneOfCase_OneofBytes,
+    // Skip group
+    // Skip message
+    Message2_O_OneOfCase_OneofEnum,
+  };
+
+  for (size_t i = 0; i < GPBARRAYSIZE(values); ++i) {
+    switch (values[i]) {
+      case Message3_O_OneOfCase_OneofInt32:
+        msg.oneofInt32 = 100;
+        break;
+      case Message3_O_OneOfCase_OneofInt64:
+        msg.oneofInt64 = 101;
+        break;
+      case Message3_O_OneOfCase_OneofUint32:
+        msg.oneofUint32 = 102;
+        break;
+      case Message3_O_OneOfCase_OneofUint64:
+        msg.oneofUint64 = 103;
+        break;
+      case Message3_O_OneOfCase_OneofSint32:
+        msg.oneofSint32 = 104;
+        break;
+      case Message3_O_OneOfCase_OneofSint64:
+        msg.oneofSint64 = 105;
+        break;
+      case Message3_O_OneOfCase_OneofFixed32:
+        msg.oneofFixed32 = 106;
+        break;
+      case Message3_O_OneOfCase_OneofFixed64:
+        msg.oneofFixed64 = 107;
+        break;
+      case Message3_O_OneOfCase_OneofSfixed32:
+        msg.oneofSfixed32 = 108;
+        break;
+      case Message3_O_OneOfCase_OneofSfixed64:
+        msg.oneofSfixed64 = 109;
+        break;
+      case Message3_O_OneOfCase_OneofFloat:
+        msg.oneofFloat = 110.0f;
+        break;
+      case Message3_O_OneOfCase_OneofDouble:
+        msg.oneofDouble = 111.0;
+        break;
+      case Message3_O_OneOfCase_OneofBool:
+        msg.oneofBool = YES;
+        break;
+      case Message3_O_OneOfCase_OneofString:
+        msg.oneofString = oneofStringDefault;
+        break;
+      case Message3_O_OneOfCase_OneofBytes:
+        msg.oneofBytes = oneofBytesDefault;
+        break;
+      case Message3_O_OneOfCase_OneofEnum:
+        msg.oneofEnum = Message3_Enum_Baz;
+        break;
+      default:
+        XCTFail(@"shouldn't happen, loop: %zd, value: %d", i, values[i]);
+        break;
+    }
+
+    // Should be set to the correct case.
+    XCTAssertEqual(msg.oOneOfCase, values[i], "Loop: %zd", i);
+
+    // Confirm everything is back as the defaults.
+    XCTAssertEqual(msg.oneofInt32, 100, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofInt64, 101, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofUint32, 102U, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofUint64, 103U, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofSint32, 104, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofSint64, 105, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofFixed32, 106U, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofFixed64, 107U, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofSfixed32, 108, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofSfixed64, 109, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofFloat, 110.0f, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofDouble, 111.0, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofBool, YES, "Loop: %zd", i);
+    XCTAssertEqualObjects(msg.oneofString, oneofStringDefault, "Loop: %zd", i);
+    XCTAssertEqualObjects(msg.oneofBytes, oneofBytesDefault, "Loop: %zd", i);
+    XCTAssertNotNil(msg.oneofGroup, "Loop: %zd", i);
+    // Skip group
+    // Skip message
+    XCTAssertEqual(msg.oneofEnum, Message2_Enum_Baz, "Loop: %zd", i);
+  }
+
+  // We special case nil on string, data, message, ensure they work as expected.
+  // i.e. - it clears the case.
+  msg.oneofString = nil;
+  XCTAssertEqual(msg.oOneOfCase, Message3_O_OneOfCase_GPBUnsetOneOfCase);
+  msg.oneofBytes = nil;
+  XCTAssertEqual(msg.oOneOfCase, Message3_O_OneOfCase_GPBUnsetOneOfCase);
+  msg.oneofMessage = nil;
+  XCTAssertEqual(msg.oOneOfCase, Message3_O_OneOfCase_GPBUnsetOneOfCase);
+
+  [msg release];
+}
+
+- (void)testProto3OneofSetToZero {
+
+  // Normally setting a proto3 field to the zero value should result in it being
+  // reset/cleared.  But in a oneof, it still gets recored so it can go out
+  // over the wire and the other side can see what was set in the oneof.
+
+  NSString *oneofStringDefault = @"";
+  NSData *oneofBytesDefault = [NSData data];
+
+  Message3 *msg = [[Message3 alloc] init];
+
+  uint32_t values[] = {
+    Message3_O_OneOfCase_OneofInt32,
+    Message3_O_OneOfCase_OneofInt64,
+    Message3_O_OneOfCase_OneofUint32,
+    Message3_O_OneOfCase_OneofUint64,
+    Message3_O_OneOfCase_OneofSint32,
+    Message3_O_OneOfCase_OneofSint64,
+    Message3_O_OneOfCase_OneofFixed32,
+    Message3_O_OneOfCase_OneofFixed64,
+    Message3_O_OneOfCase_OneofSfixed32,
+    Message3_O_OneOfCase_OneofSfixed64,
+    Message3_O_OneOfCase_OneofFloat,
+    Message3_O_OneOfCase_OneofDouble,
+    Message3_O_OneOfCase_OneofBool,
+    Message3_O_OneOfCase_OneofString,
+    Message3_O_OneOfCase_OneofBytes,
+    Message3_O_OneOfCase_OneofMessage,
+    Message3_O_OneOfCase_OneofEnum,
+  };
+
+  for (size_t i = 0; i < GPBARRAYSIZE(values); ++i) {
+    switch (values[i]) {
+      case Message3_O_OneOfCase_OneofInt32:
+        msg.oneofInt32 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofInt64:
+        msg.oneofInt64 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofUint32:
+        msg.oneofUint32 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofUint64:
+        msg.oneofUint64 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofSint32:
+        msg.oneofSint32 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofSint64:
+        msg.oneofSint64 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofFixed32:
+        msg.oneofFixed32 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofFixed64:
+        msg.oneofFixed64 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofSfixed32:
+        msg.oneofSfixed32 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofSfixed64:
+        msg.oneofSfixed64 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofFloat:
+        msg.oneofFloat = 0.0f;
+        break;
+      case Message3_O_OneOfCase_OneofDouble:
+        msg.oneofDouble = 0.0;
+        break;
+      case Message3_O_OneOfCase_OneofBool:
+        msg.oneofBool = NO;
+        break;
+      case Message3_O_OneOfCase_OneofString:
+        msg.oneofString = oneofStringDefault;
+        break;
+      case Message3_O_OneOfCase_OneofBytes:
+        msg.oneofBytes = oneofBytesDefault;
+        break;
+      case Message3_O_OneOfCase_OneofMessage:
+        msg.oneofMessage.optionalInt32 = 0;
+        break;
+      case Message3_O_OneOfCase_OneofEnum:
+        msg.oneofEnum = Message3_Enum_Foo;
+        break;
+      default:
+        XCTFail(@"shouldn't happen, loop: %zd, value: %d", i, values[i]);
+        break;
+    }
+
+    // Should be set to the correct case.
+    XCTAssertEqual(msg.oOneOfCase, values[i], "Loop: %zd", i);
+
+    // Confirm everything is still zeros.
+    XCTAssertEqual(msg.oneofInt32, 0, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofInt64, 0, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofUint32, 0U, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofUint64, 0U, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofSint32, 0, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofSint64, 0, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofFixed32, 0U, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofFixed64, 0U, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofSfixed32, 0, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofSfixed64, 0, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofFloat, 0.0f, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofDouble, 0.0, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofBool, NO, "Loop: %zd", i);
+    XCTAssertEqualObjects(msg.oneofString, oneofStringDefault, "Loop: %zd", i);
+    XCTAssertEqualObjects(msg.oneofBytes, oneofBytesDefault, "Loop: %zd", i);
+    XCTAssertNotNil(msg.oneofMessage, "Loop: %zd", i);
+    XCTAssertEqual(msg.oneofEnum, Message3_Enum_Foo, "Loop: %zd", i);
+  }
+
+  // We special case nil on string, data, message, ensure they work as expected.
+  msg.oneofString = nil;
+  XCTAssertEqual(msg.oOneOfCase, Message3_O_OneOfCase_GPBUnsetOneOfCase);
+  msg.oneofBytes = nil;
+  XCTAssertEqual(msg.oOneOfCase, Message3_O_OneOfCase_GPBUnsetOneOfCase);
+  msg.oneofMessage = nil;
+  XCTAssertEqual(msg.oOneOfCase, Message3_O_OneOfCase_GPBUnsetOneOfCase);
+
+  [msg release];
+}
+
 - (void)testCopyingMakesUniqueObjects {
   const int repeatCount = 5;
   TestAllTypes *msg1 = [TestAllTypes message];