فهرست منبع

Merge pull request #7491 from haberman/cherry

Cherry-pick fixes onto 3.12.x branch for 3.12.0-rc2
Joshua Haberman 5 سال پیش
والد
کامیت
51199fcba2
25فایلهای تغییر یافته به همراه777 افزوده شده و 711 حذف شده
  1. 40 102
      csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto2.cs
  2. 107 279
      csharp/src/Google.Protobuf.Test.TestProtos/Unittest.cs
  3. 88 62
      csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
  4. 151 20
      csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs
  5. 6 0
      csharp/src/Google.Protobuf/Extension.cs
  6. 70 170
      csharp/src/Google.Protobuf/Reflection/Descriptor.cs
  7. 11 1
      csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
  8. 11 1
      csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs
  9. 27 2
      csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs
  10. 11 1
      csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
  11. 11 1
      csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
  12. 11 1
      csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs
  13. 11 1
      csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs
  14. 11 1
      csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
  15. 49 44
      csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs
  16. 1 0
      java/core/generate-test-sources-build.xml
  17. 108 0
      java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java
  18. 20 0
      java/core/src/test/java/com/google/protobuf/TextFormatTest.java
  19. 1 0
      java/lite/generate-test-sources-build.xml
  20. 1 4
      python/google/protobuf/internal/test_proto3_optional.proto
  21. 16 17
      src/google/protobuf/compiler/csharp/csharp_helpers.h
  22. 1 0
      src/google/protobuf/compiler/java/java_enum_field.cc
  23. 1 1
      src/google/protobuf/generated_message_reflection.cc
  24. 9 3
      src/google/protobuf/proto3_arena_unittest.cc
  25. 4 0
      src/google/protobuf/unittest_proto3_optional.proto

+ 40 - 102
csharp/src/Google.Protobuf.Test.TestProtos/TestMessagesProto2.cs

@@ -290,13 +290,13 @@ namespace ProtobufTestMessages.Proto2 {
       optionalBool_ = other.optionalBool_;
       optionalString_ = other.optionalString_;
       optionalBytes_ = other.optionalBytes_;
-      optionalNestedMessage_ = other.HasOptionalNestedMessage ? other.optionalNestedMessage_.Clone() : null;
-      optionalForeignMessage_ = other.HasOptionalForeignMessage ? other.optionalForeignMessage_.Clone() : null;
+      optionalNestedMessage_ = other.optionalNestedMessage_ != null ? other.optionalNestedMessage_.Clone() : null;
+      optionalForeignMessage_ = other.optionalForeignMessage_ != null ? other.optionalForeignMessage_.Clone() : null;
       optionalNestedEnum_ = other.optionalNestedEnum_;
       optionalForeignEnum_ = other.optionalForeignEnum_;
       optionalStringPiece_ = other.optionalStringPiece_;
       optionalCord_ = other.optionalCord_;
-      recursiveMessage_ = other.HasRecursiveMessage ? other.recursiveMessage_.Clone() : null;
+      recursiveMessage_ = other.recursiveMessage_ != null ? other.recursiveMessage_.Clone() : null;
       repeatedInt32_ = other.repeatedInt32_.Clone();
       repeatedInt64_ = other.repeatedInt64_.Clone();
       repeatedUint32_ = other.repeatedUint32_.Clone();
@@ -794,16 +794,6 @@ namespace ProtobufTestMessages.Proto2 {
         optionalNestedMessage_ = value;
       }
     }
-    /// <summary>Gets whether the optional_nested_message field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptionalNestedMessage {
-      get { return optionalNestedMessage_ != null; }
-    }
-    /// <summary>Clears the value of the optional_nested_message field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptionalNestedMessage() {
-      optionalNestedMessage_ = null;
-    }
 
     /// <summary>Field number for the "optional_foreign_message" field.</summary>
     public const int OptionalForeignMessageFieldNumber = 19;
@@ -815,16 +805,6 @@ namespace ProtobufTestMessages.Proto2 {
         optionalForeignMessage_ = value;
       }
     }
-    /// <summary>Gets whether the optional_foreign_message field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptionalForeignMessage {
-      get { return optionalForeignMessage_ != null; }
-    }
-    /// <summary>Clears the value of the optional_foreign_message field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptionalForeignMessage() {
-      optionalForeignMessage_ = null;
-    }
 
     /// <summary>Field number for the "optional_nested_enum" field.</summary>
     public const int OptionalNestedEnumFieldNumber = 21;
@@ -930,16 +910,6 @@ namespace ProtobufTestMessages.Proto2 {
         recursiveMessage_ = value;
       }
     }
-    /// <summary>Gets whether the recursive_message field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasRecursiveMessage {
-      get { return recursiveMessage_ != null; }
-    }
-    /// <summary>Clears the value of the recursive_message field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearRecursiveMessage() {
-      recursiveMessage_ = null;
-    }
 
     /// <summary>Field number for the "repeated_int32" field.</summary>
     public const int RepeatedInt32FieldNumber = 31;
@@ -1660,24 +1630,12 @@ namespace ProtobufTestMessages.Proto2 {
     public const int OneofNestedMessageFieldNumber = 112;
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public global::ProtobufTestMessages.Proto2.TestAllTypesProto2.Types.NestedMessage OneofNestedMessage {
-      get { return HasOneofNestedMessage ? (global::ProtobufTestMessages.Proto2.TestAllTypesProto2.Types.NestedMessage) oneofField_ : null; }
+      get { return oneofFieldCase_ == OneofFieldOneofCase.OneofNestedMessage ? (global::ProtobufTestMessages.Proto2.TestAllTypesProto2.Types.NestedMessage) oneofField_ : null; }
       set {
         oneofField_ = value;
         oneofFieldCase_ = value == null ? OneofFieldOneofCase.None : OneofFieldOneofCase.OneofNestedMessage;
       }
     }
-    /// <summary>Gets whether the "oneof_nested_message" field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOneofNestedMessage {
-      get { return oneofFieldCase_ == OneofFieldOneofCase.OneofNestedMessage; }
-    }
-    /// <summary> Clears the value of the oneof if it's currently set to "oneof_nested_message" </summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOneofNestedMessage() {
-      if (HasOneofNestedMessage) {
-        ClearOneofField();
-      }
-    }
 
     /// <summary>Field number for the "oneof_string" field.</summary>
     public const int OneofStringFieldNumber = 113;
@@ -2479,13 +2437,13 @@ namespace ProtobufTestMessages.Proto2 {
       if (HasOptionalBool) hash ^= OptionalBool.GetHashCode();
       if (HasOptionalString) hash ^= OptionalString.GetHashCode();
       if (HasOptionalBytes) hash ^= OptionalBytes.GetHashCode();
-      if (HasOptionalNestedMessage) hash ^= OptionalNestedMessage.GetHashCode();
-      if (HasOptionalForeignMessage) hash ^= OptionalForeignMessage.GetHashCode();
+      if (optionalNestedMessage_ != null) hash ^= OptionalNestedMessage.GetHashCode();
+      if (optionalForeignMessage_ != null) hash ^= OptionalForeignMessage.GetHashCode();
       if (HasOptionalNestedEnum) hash ^= OptionalNestedEnum.GetHashCode();
       if (HasOptionalForeignEnum) hash ^= OptionalForeignEnum.GetHashCode();
       if (HasOptionalStringPiece) hash ^= OptionalStringPiece.GetHashCode();
       if (HasOptionalCord) hash ^= OptionalCord.GetHashCode();
-      if (HasRecursiveMessage) hash ^= RecursiveMessage.GetHashCode();
+      if (recursiveMessage_ != null) hash ^= RecursiveMessage.GetHashCode();
       hash ^= repeatedInt32_.GetHashCode();
       hash ^= repeatedInt64_.GetHashCode();
       hash ^= repeatedUint32_.GetHashCode();
@@ -2555,7 +2513,7 @@ namespace ProtobufTestMessages.Proto2 {
       hash ^= MapStringNestedEnum.GetHashCode();
       hash ^= MapStringForeignEnum.GetHashCode();
       if (HasOneofUint32) hash ^= OneofUint32.GetHashCode();
-      if (HasOneofNestedMessage) hash ^= OneofNestedMessage.GetHashCode();
+      if (oneofFieldCase_ == OneofFieldOneofCase.OneofNestedMessage) hash ^= OneofNestedMessage.GetHashCode();
       if (HasOneofString) hash ^= OneofString.GetHashCode();
       if (HasOneofBytes) hash ^= OneofBytes.GetHashCode();
       if (HasOneofBool) hash ^= OneofBool.GetHashCode();
@@ -2659,11 +2617,11 @@ namespace ProtobufTestMessages.Proto2 {
         output.WriteRawTag(122);
         output.WriteBytes(OptionalBytes);
       }
-      if (HasOptionalNestedMessage) {
+      if (optionalNestedMessage_ != null) {
         output.WriteRawTag(146, 1);
         output.WriteMessage(OptionalNestedMessage);
       }
-      if (HasOptionalForeignMessage) {
+      if (optionalForeignMessage_ != null) {
         output.WriteRawTag(154, 1);
         output.WriteMessage(OptionalForeignMessage);
       }
@@ -2683,7 +2641,7 @@ namespace ProtobufTestMessages.Proto2 {
         output.WriteRawTag(202, 1);
         output.WriteString(OptionalCord);
       }
-      if (HasRecursiveMessage) {
+      if (recursiveMessage_ != null) {
         output.WriteRawTag(218, 1);
         output.WriteMessage(RecursiveMessage);
       }
@@ -2759,7 +2717,7 @@ namespace ProtobufTestMessages.Proto2 {
         output.WriteRawTag(248, 6);
         output.WriteUInt32(OneofUint32);
       }
-      if (HasOneofNestedMessage) {
+      if (oneofFieldCase_ == OneofFieldOneofCase.OneofNestedMessage) {
         output.WriteRawTag(130, 7);
         output.WriteMessage(OneofNestedMessage);
       }
@@ -2924,10 +2882,10 @@ namespace ProtobufTestMessages.Proto2 {
       if (HasOptionalBytes) {
         size += 1 + pb::CodedOutputStream.ComputeBytesSize(OptionalBytes);
       }
-      if (HasOptionalNestedMessage) {
+      if (optionalNestedMessage_ != null) {
         size += 2 + pb::CodedOutputStream.ComputeMessageSize(OptionalNestedMessage);
       }
-      if (HasOptionalForeignMessage) {
+      if (optionalForeignMessage_ != null) {
         size += 2 + pb::CodedOutputStream.ComputeMessageSize(OptionalForeignMessage);
       }
       if (HasOptionalNestedEnum) {
@@ -2942,7 +2900,7 @@ namespace ProtobufTestMessages.Proto2 {
       if (HasOptionalCord) {
         size += 2 + pb::CodedOutputStream.ComputeStringSize(OptionalCord);
       }
-      if (HasRecursiveMessage) {
+      if (recursiveMessage_ != null) {
         size += 2 + pb::CodedOutputStream.ComputeMessageSize(RecursiveMessage);
       }
       size += repeatedInt32_.CalculateSize(_repeated_repeatedInt32_codec);
@@ -3016,7 +2974,7 @@ namespace ProtobufTestMessages.Proto2 {
       if (HasOneofUint32) {
         size += 2 + pb::CodedOutputStream.ComputeUInt32Size(OneofUint32);
       }
-      if (HasOneofNestedMessage) {
+      if (oneofFieldCase_ == OneofFieldOneofCase.OneofNestedMessage) {
         size += 2 + pb::CodedOutputStream.ComputeMessageSize(OneofNestedMessage);
       }
       if (HasOneofString) {
@@ -3156,14 +3114,14 @@ namespace ProtobufTestMessages.Proto2 {
       if (other.HasOptionalBytes) {
         OptionalBytes = other.OptionalBytes;
       }
-      if (other.HasOptionalNestedMessage) {
-        if (!HasOptionalNestedMessage) {
+      if (other.optionalNestedMessage_ != null) {
+        if (optionalNestedMessage_ == null) {
           OptionalNestedMessage = new global::ProtobufTestMessages.Proto2.TestAllTypesProto2.Types.NestedMessage();
         }
         OptionalNestedMessage.MergeFrom(other.OptionalNestedMessage);
       }
-      if (other.HasOptionalForeignMessage) {
-        if (!HasOptionalForeignMessage) {
+      if (other.optionalForeignMessage_ != null) {
+        if (optionalForeignMessage_ == null) {
           OptionalForeignMessage = new global::ProtobufTestMessages.Proto2.ForeignMessageProto2();
         }
         OptionalForeignMessage.MergeFrom(other.OptionalForeignMessage);
@@ -3180,8 +3138,8 @@ namespace ProtobufTestMessages.Proto2 {
       if (other.HasOptionalCord) {
         OptionalCord = other.OptionalCord;
       }
-      if (other.HasRecursiveMessage) {
-        if (!HasRecursiveMessage) {
+      if (other.recursiveMessage_ != null) {
+        if (recursiveMessage_ == null) {
           RecursiveMessage = new global::ProtobufTestMessages.Proto2.TestAllTypesProto2();
         }
         RecursiveMessage.MergeFrom(other.RecursiveMessage);
@@ -3422,14 +3380,14 @@ namespace ProtobufTestMessages.Proto2 {
             break;
           }
           case 146: {
-            if (!HasOptionalNestedMessage) {
+            if (optionalNestedMessage_ == null) {
               OptionalNestedMessage = new global::ProtobufTestMessages.Proto2.TestAllTypesProto2.Types.NestedMessage();
             }
             input.ReadMessage(OptionalNestedMessage);
             break;
           }
           case 154: {
-            if (!HasOptionalForeignMessage) {
+            if (optionalForeignMessage_ == null) {
               OptionalForeignMessage = new global::ProtobufTestMessages.Proto2.ForeignMessageProto2();
             }
             input.ReadMessage(OptionalForeignMessage);
@@ -3452,7 +3410,7 @@ namespace ProtobufTestMessages.Proto2 {
             break;
           }
           case 218: {
-            if (!HasRecursiveMessage) {
+            if (recursiveMessage_ == null) {
               RecursiveMessage = new global::ProtobufTestMessages.Proto2.TestAllTypesProto2();
             }
             input.ReadMessage(RecursiveMessage);
@@ -3779,7 +3737,7 @@ namespace ProtobufTestMessages.Proto2 {
           }
           case 898: {
             global::ProtobufTestMessages.Proto2.TestAllTypesProto2.Types.NestedMessage subBuilder = new global::ProtobufTestMessages.Proto2.TestAllTypesProto2.Types.NestedMessage();
-            if (HasOneofNestedMessage) {
+            if (oneofFieldCase_ == OneofFieldOneofCase.OneofNestedMessage) {
               subBuilder.MergeFrom(OneofNestedMessage);
             }
             input.ReadMessage(subBuilder);
@@ -3962,7 +3920,7 @@ namespace ProtobufTestMessages.Proto2 {
         public NestedMessage(NestedMessage other) : this() {
           _hasBits0 = other._hasBits0;
           a_ = other.a_;
-          corecursive_ = other.HasCorecursive ? other.corecursive_.Clone() : null;
+          corecursive_ = other.corecursive_ != null ? other.corecursive_.Clone() : null;
           _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
         }
 
@@ -4005,16 +3963,6 @@ namespace ProtobufTestMessages.Proto2 {
             corecursive_ = value;
           }
         }
-        /// <summary>Gets whether the corecursive field is set</summary>
-        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-        public bool HasCorecursive {
-          get { return corecursive_ != null; }
-        }
-        /// <summary>Clears the value of the corecursive field</summary>
-        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-        public void ClearCorecursive() {
-          corecursive_ = null;
-        }
 
         [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
         public override bool Equals(object other) {
@@ -4038,7 +3986,7 @@ namespace ProtobufTestMessages.Proto2 {
         public override int GetHashCode() {
           int hash = 1;
           if (HasA) hash ^= A.GetHashCode();
-          if (HasCorecursive) hash ^= Corecursive.GetHashCode();
+          if (corecursive_ != null) hash ^= Corecursive.GetHashCode();
           if (_unknownFields != null) {
             hash ^= _unknownFields.GetHashCode();
           }
@@ -4056,7 +4004,7 @@ namespace ProtobufTestMessages.Proto2 {
             output.WriteRawTag(8);
             output.WriteInt32(A);
           }
-          if (HasCorecursive) {
+          if (corecursive_ != null) {
             output.WriteRawTag(18);
             output.WriteMessage(Corecursive);
           }
@@ -4071,7 +4019,7 @@ namespace ProtobufTestMessages.Proto2 {
           if (HasA) {
             size += 1 + pb::CodedOutputStream.ComputeInt32Size(A);
           }
-          if (HasCorecursive) {
+          if (corecursive_ != null) {
             size += 1 + pb::CodedOutputStream.ComputeMessageSize(Corecursive);
           }
           if (_unknownFields != null) {
@@ -4088,8 +4036,8 @@ namespace ProtobufTestMessages.Proto2 {
           if (other.HasA) {
             A = other.A;
           }
-          if (other.HasCorecursive) {
-            if (!HasCorecursive) {
+          if (other.corecursive_ != null) {
+            if (corecursive_ == null) {
               Corecursive = new global::ProtobufTestMessages.Proto2.TestAllTypesProto2();
             }
             Corecursive.MergeFrom(other.Corecursive);
@@ -4110,7 +4058,7 @@ namespace ProtobufTestMessages.Proto2 {
                 break;
               }
               case 18: {
-                if (!HasCorecursive) {
+                if (corecursive_ == null) {
                   Corecursive = new global::ProtobufTestMessages.Proto2.TestAllTypesProto2();
                 }
                 input.ReadMessage(Corecursive);
@@ -4937,7 +4885,7 @@ namespace ProtobufTestMessages.Proto2 {
       _hasBits0 = other._hasBits0;
       optionalInt32_ = other.optionalInt32_;
       optionalString_ = other.optionalString_;
-      nestedMessage_ = other.HasNestedMessage ? other.nestedMessage_.Clone() : null;
+      nestedMessage_ = other.nestedMessage_ != null ? other.nestedMessage_.Clone() : null;
       optionalGroup_ = other.HasOptionalGroup ? other.optionalGroup_.Clone() : null;
       optionalBool_ = other.optionalBool_;
       repeatedInt32_ = other.repeatedInt32_.Clone();
@@ -5006,16 +4954,6 @@ namespace ProtobufTestMessages.Proto2 {
         nestedMessage_ = value;
       }
     }
-    /// <summary>Gets whether the nested_message field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasNestedMessage {
-      get { return nestedMessage_ != null; }
-    }
-    /// <summary>Clears the value of the nested_message field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearNestedMessage() {
-      nestedMessage_ = null;
-    }
 
     /// <summary>Field number for the "optionalgroup" field.</summary>
     public const int OptionalGroupFieldNumber = 1004;
@@ -5099,7 +5037,7 @@ namespace ProtobufTestMessages.Proto2 {
       int hash = 1;
       if (HasOptionalInt32) hash ^= OptionalInt32.GetHashCode();
       if (HasOptionalString) hash ^= OptionalString.GetHashCode();
-      if (HasNestedMessage) hash ^= NestedMessage.GetHashCode();
+      if (nestedMessage_ != null) hash ^= NestedMessage.GetHashCode();
       if (HasOptionalGroup) hash ^= OptionalGroup.GetHashCode();
       if (HasOptionalBool) hash ^= OptionalBool.GetHashCode();
       hash ^= repeatedInt32_.GetHashCode();
@@ -5124,7 +5062,7 @@ namespace ProtobufTestMessages.Proto2 {
         output.WriteRawTag(210, 62);
         output.WriteString(OptionalString);
       }
-      if (HasNestedMessage) {
+      if (nestedMessage_ != null) {
         output.WriteRawTag(218, 62);
         output.WriteMessage(NestedMessage);
       }
@@ -5152,7 +5090,7 @@ namespace ProtobufTestMessages.Proto2 {
       if (HasOptionalString) {
         size += 2 + pb::CodedOutputStream.ComputeStringSize(OptionalString);
       }
-      if (HasNestedMessage) {
+      if (nestedMessage_ != null) {
         size += 2 + pb::CodedOutputStream.ComputeMessageSize(NestedMessage);
       }
       if (HasOptionalGroup) {
@@ -5179,8 +5117,8 @@ namespace ProtobufTestMessages.Proto2 {
       if (other.HasOptionalString) {
         OptionalString = other.OptionalString;
       }
-      if (other.HasNestedMessage) {
-        if (!HasNestedMessage) {
+      if (other.nestedMessage_ != null) {
+        if (nestedMessage_ == null) {
           NestedMessage = new global::ProtobufTestMessages.Proto2.ForeignMessageProto2();
         }
         NestedMessage.MergeFrom(other.NestedMessage);
@@ -5215,7 +5153,7 @@ namespace ProtobufTestMessages.Proto2 {
             break;
           }
           case 8026: {
-            if (!HasNestedMessage) {
+            if (nestedMessage_ == null) {
               NestedMessage = new global::ProtobufTestMessages.Proto2.ForeignMessageProto2();
             }
             input.ReadMessage(NestedMessage);

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 107 - 279
csharp/src/Google.Protobuf.Test.TestProtos/Unittest.cs


+ 88 - 62
csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs

@@ -71,25 +71,49 @@ namespace Google.Protobuf.Test.Reflection
             };
         }
 
+        [Test]
+        public void BuiltinOptionsCanBeRetrieved()
+        {
+            // non-custom options (that are not extensions but regular fields) can only be accessed via descriptor.Options
+            var fileOptions = UnittestProto3Reflection.Descriptor.GetOptions();
+            Assert.AreEqual("Google.Protobuf.TestProtos", fileOptions.CsharpNamespace);
+        }
+
+        [Test]
+        public void OptionPresenceCanBeDetected()
+        {
+            // case 1: the descriptor has no options at all so the options message is not present
+            Assert.IsNull(TestAllTypes.Descriptor.GetOptions());
+
+            // case 2: the descriptor has some options, but not the one we're looking for
+            // HasExtension will be false and GetExtension returns extension's default value
+            Assert.IsFalse(UnittestProto3Reflection.Descriptor.GetOptions().HasExtension(FileOpt1));
+            Assert.AreEqual(0, UnittestProto3Reflection.Descriptor.GetOptions().GetExtension(FileOpt1));
+
+            // case 3: option is present
+            Assert.IsTrue(UnittestCustomOptionsProto3Reflection.Descriptor.GetOptions().HasExtension(FileOpt1));
+            Assert.AreEqual(9876543210UL, UnittestCustomOptionsProto3Reflection.Descriptor.GetOptions().GetExtension(FileOpt1));
+        }
+
         [Test]
         public void ScalarOptions()
         {
             var d = CustomOptionOtherValues.Descriptor;
-            var options = d.CustomOptions;
-            AssertOption(-100, options.TryGetInt32, Int32Opt, d.GetOption);
-            AssertOption(12.3456789f, options.TryGetFloat, FloatOpt, d.GetOption);
-            AssertOption(1.234567890123456789d, options.TryGetDouble, DoubleOpt, d.GetOption);
-            AssertOption("Hello, \"World\"", options.TryGetString, StringOpt, d.GetOption);
-            AssertOption(ByteString.CopyFromUtf8("Hello\0World"), options.TryGetBytes, BytesOpt, d.GetOption);
-            AssertOption(TestEnumType.TestOptionEnumType2, EnumFetcher<TestEnumType>(options), EnumOpt, d.GetOption);
+            var customOptions = d.CustomOptions;
+            AssertOption(-100, customOptions.TryGetInt32, Int32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(12.3456789f, customOptions.TryGetFloat, FloatOpt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(1.234567890123456789d, customOptions.TryGetDouble, DoubleOpt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption("Hello, \"World\"", customOptions.TryGetString, StringOpt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(ByteString.CopyFromUtf8("Hello\0World"), customOptions.TryGetBytes, BytesOpt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(TestEnumType.TestOptionEnumType2, EnumFetcher<TestEnumType>(customOptions), EnumOpt, d.GetOption, d.GetOptions().GetExtension);
         }
 
         [Test]
         public void MessageOptions()
         {
             var d = VariousComplexOptions.Descriptor;
-            var options = d.CustomOptions;
-            AssertOption(new ComplexOptionType1 { Foo = 42, Foo4 = { 99, 88 } }, options.TryGetMessage, ComplexOpt1, d.GetOption);
+            var customOptions = d.CustomOptions;
+            AssertOption(new ComplexOptionType1 { Foo = 42, Foo4 = { 99, 88 } }, customOptions.TryGetMessage, ComplexOpt1, d.GetOption, d.GetOptions().GetExtension);
             AssertOption(new ComplexOptionType2
             {
                 Baz = 987,
@@ -97,85 +121,84 @@ namespace Google.Protobuf.Test.Reflection
                 Fred = new ComplexOptionType4 { Waldo = 321 },
                 Barney = { new ComplexOptionType4 { Waldo = 101 }, new ComplexOptionType4 { Waldo = 212 } }
             },
-                options.TryGetMessage, ComplexOpt2, d.GetOption);
-            AssertOption(new ComplexOptionType3 { Qux = 9 }, options.TryGetMessage, ComplexOpt3, d.GetOption);
+                customOptions.TryGetMessage, ComplexOpt2, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(new ComplexOptionType3 { Qux = 9 }, customOptions.TryGetMessage, ComplexOpt3, d.GetOption, d.GetOptions().GetExtension);
         }
 
         [Test]
         public void OptionLocations()
         {
-            var fileOptions = UnittestCustomOptionsProto3Reflection.Descriptor.CustomOptions;
-            AssertOption(9876543210UL, fileOptions.TryGetUInt64, FileOpt1, UnittestCustomOptionsProto3Reflection.Descriptor.GetOption);
+            var fileDescriptor = UnittestCustomOptionsProto3Reflection.Descriptor;
+            AssertOption(9876543210UL, fileDescriptor.CustomOptions.TryGetUInt64, FileOpt1, fileDescriptor.GetOption, fileDescriptor.GetOptions().GetExtension);
 
-            var messageOptions = TestMessageWithCustomOptions.Descriptor.CustomOptions;
-            AssertOption(-56, messageOptions.TryGetInt32, MessageOpt1, TestMessageWithCustomOptions.Descriptor.GetOption);
+            var messageDescriptor = TestMessageWithCustomOptions.Descriptor;
+            AssertOption(-56, messageDescriptor.CustomOptions.TryGetInt32, MessageOpt1, messageDescriptor.GetOption, messageDescriptor.GetOptions().GetExtension);
 
-            var fieldOptions = TestMessageWithCustomOptions.Descriptor.Fields["field1"].CustomOptions;
-            AssertOption(8765432109UL, fieldOptions.TryGetFixed64, FieldOpt1, TestMessageWithCustomOptions.Descriptor.Fields["field1"].GetOption);
+            var fieldDescriptor = TestMessageWithCustomOptions.Descriptor.Fields["field1"];
+            AssertOption(8765432109UL, fieldDescriptor.CustomOptions.TryGetFixed64, FieldOpt1, fieldDescriptor.GetOption, fieldDescriptor.GetOptions().GetExtension);
 
-            var oneofOptions = TestMessageWithCustomOptions.Descriptor.Oneofs[0].CustomOptions;
-            AssertOption(-99, oneofOptions.TryGetInt32, OneofOpt1, TestMessageWithCustomOptions.Descriptor.Oneofs[0].GetOption);
+            var oneofDescriptor = TestMessageWithCustomOptions.Descriptor.Oneofs[0];
+            AssertOption(-99, oneofDescriptor.CustomOptions.TryGetInt32, OneofOpt1, oneofDescriptor.GetOption, oneofDescriptor.GetOptions().GetExtension);
 
-            var enumOptions = TestMessageWithCustomOptions.Descriptor.EnumTypes[0].CustomOptions;
-            AssertOption(-789, enumOptions.TryGetSFixed32, EnumOpt1, TestMessageWithCustomOptions.Descriptor.EnumTypes[0].GetOption);
+            var enumDescriptor = TestMessageWithCustomOptions.Descriptor.EnumTypes[0];
+            AssertOption(-789, enumDescriptor.CustomOptions.TryGetSFixed32, EnumOpt1, enumDescriptor.GetOption, enumDescriptor.GetOptions().GetExtension);
 
-            var enumValueOptions = TestMessageWithCustomOptions.Descriptor.EnumTypes[0].FindValueByNumber(2).CustomOptions;
-            AssertOption(123, enumValueOptions.TryGetInt32, EnumValueOpt1, TestMessageWithCustomOptions.Descriptor.EnumTypes[0].FindValueByNumber(2).GetOption);
+            var enumValueDescriptor = TestMessageWithCustomOptions.Descriptor.EnumTypes[0].FindValueByNumber(2);
+            AssertOption(123, enumValueDescriptor.CustomOptions.TryGetInt32, EnumValueOpt1, enumValueDescriptor.GetOption, enumValueDescriptor.GetOptions().GetExtension);
 
-            var service = UnittestCustomOptionsProto3Reflection.Descriptor.Services
+            var serviceDescriptor = UnittestCustomOptionsProto3Reflection.Descriptor.Services
                 .Single(s => s.Name == "TestServiceWithCustomOptions");
-            var serviceOptions = service.CustomOptions;
-            AssertOption(-9876543210, serviceOptions.TryGetSInt64, ServiceOpt1, service.GetOption);
+            AssertOption(-9876543210, serviceDescriptor.CustomOptions.TryGetSInt64, ServiceOpt1, serviceDescriptor.GetOption, serviceDescriptor.GetOptions().GetExtension);
 
-            var methodOptions = service.Methods[0].CustomOptions;
-            AssertOption(UnitTest.Issues.TestProtos.MethodOpt1.Val2, EnumFetcher<UnitTest.Issues.TestProtos.MethodOpt1>(methodOptions), UnittestCustomOptionsProto3Extensions.MethodOpt1, service.Methods[0].GetOption);
+            var methodDescriptor = serviceDescriptor.Methods[0];
+            AssertOption(UnitTest.Issues.TestProtos.MethodOpt1.Val2, EnumFetcher<UnitTest.Issues.TestProtos.MethodOpt1>(methodDescriptor.CustomOptions), UnittestCustomOptionsProto3Extensions.MethodOpt1, methodDescriptor.GetOption, methodDescriptor.GetOptions().GetExtension);
         }
 
         [Test]
         public void MinValues()
         {
             var d = CustomOptionMinIntegerValues.Descriptor;
-            var options = d.CustomOptions;
-            AssertOption(false, options.TryGetBool, BoolOpt, d.GetOption);
-            AssertOption(int.MinValue, options.TryGetInt32, Int32Opt, d.GetOption);
-            AssertOption(long.MinValue, options.TryGetInt64, Int64Opt, d.GetOption);
-            AssertOption(uint.MinValue, options.TryGetUInt32, Uint32Opt, d.GetOption);
-            AssertOption(ulong.MinValue, options.TryGetUInt64, Uint64Opt, d.GetOption);
-            AssertOption(int.MinValue, options.TryGetSInt32, Sint32Opt, d.GetOption);
-            AssertOption(long.MinValue, options.TryGetSInt64, Sint64Opt, d.GetOption);
-            AssertOption(uint.MinValue, options.TryGetUInt32, Fixed32Opt, d.GetOption);
-            AssertOption(ulong.MinValue, options.TryGetUInt64, Fixed64Opt, d.GetOption);
-            AssertOption(int.MinValue, options.TryGetInt32, Sfixed32Opt, d.GetOption);
-            AssertOption(long.MinValue, options.TryGetInt64, Sfixed64Opt, d.GetOption);
+            var customOptions = d.CustomOptions;
+            AssertOption(false, customOptions.TryGetBool, BoolOpt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(int.MinValue, customOptions.TryGetInt32, Int32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(long.MinValue, customOptions.TryGetInt64, Int64Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(uint.MinValue, customOptions.TryGetUInt32, Uint32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(ulong.MinValue, customOptions.TryGetUInt64, Uint64Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(int.MinValue, customOptions.TryGetSInt32, Sint32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(long.MinValue, customOptions.TryGetSInt64, Sint64Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(uint.MinValue, customOptions.TryGetUInt32, Fixed32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(ulong.MinValue, customOptions.TryGetUInt64, Fixed64Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(int.MinValue, customOptions.TryGetInt32, Sfixed32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(long.MinValue, customOptions.TryGetInt64, Sfixed64Opt, d.GetOption, d.GetOptions().GetExtension);
         }
 
         [Test]
         public void MaxValues()
         {
             var d = CustomOptionMaxIntegerValues.Descriptor;
-            var options = d.CustomOptions;
-            AssertOption(true, options.TryGetBool, BoolOpt, d.GetOption);
-            AssertOption(int.MaxValue, options.TryGetInt32, Int32Opt, d.GetOption);
-            AssertOption(long.MaxValue, options.TryGetInt64, Int64Opt, d.GetOption);
-            AssertOption(uint.MaxValue, options.TryGetUInt32, Uint32Opt, d.GetOption);
-            AssertOption(ulong.MaxValue, options.TryGetUInt64, Uint64Opt, d.GetOption);
-            AssertOption(int.MaxValue, options.TryGetSInt32, Sint32Opt, d.GetOption);
-            AssertOption(long.MaxValue, options.TryGetSInt64, Sint64Opt, d.GetOption);
-            AssertOption(uint.MaxValue, options.TryGetFixed32, Fixed32Opt, d.GetOption);
-            AssertOption(ulong.MaxValue, options.TryGetFixed64, Fixed64Opt, d.GetOption);
-            AssertOption(int.MaxValue, options.TryGetSFixed32, Sfixed32Opt, d.GetOption);
-            AssertOption(long.MaxValue, options.TryGetSFixed64, Sfixed64Opt, d.GetOption);
+            var customOptions = d.CustomOptions;
+            AssertOption(true, customOptions.TryGetBool, BoolOpt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(int.MaxValue, customOptions.TryGetInt32, Int32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(long.MaxValue, customOptions.TryGetInt64, Int64Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(uint.MaxValue, customOptions.TryGetUInt32, Uint32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(ulong.MaxValue, customOptions.TryGetUInt64, Uint64Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(int.MaxValue, customOptions.TryGetSInt32, Sint32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(long.MaxValue, customOptions.TryGetSInt64, Sint64Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(uint.MaxValue, customOptions.TryGetFixed32, Fixed32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(ulong.MaxValue, customOptions.TryGetFixed64, Fixed64Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(int.MaxValue, customOptions.TryGetSFixed32, Sfixed32Opt, d.GetOption, d.GetOptions().GetExtension);
+            AssertOption(long.MaxValue, customOptions.TryGetSFixed64, Sfixed64Opt, d.GetOption, d.GetOptions().GetExtension);
         }
 
         [Test]
         public void AggregateOptions()
         {
             // Just two examples
-            var messageOptions = AggregateMessage.Descriptor.CustomOptions;
-            AssertOption(new Aggregate { I = 101, S = "MessageAnnotation" }, messageOptions.TryGetMessage, Msgopt, AggregateMessage.Descriptor.GetOption);
+            var messageDescriptor = AggregateMessage.Descriptor;
+            AssertOption(new Aggregate { I = 101, S = "MessageAnnotation" }, messageDescriptor.CustomOptions.TryGetMessage, Msgopt, messageDescriptor.GetOption, messageDescriptor.GetOptions().GetExtension);
 
-            var fieldOptions = AggregateMessage.Descriptor.Fields["fieldname"].CustomOptions;
-            AssertOption(new Aggregate { S = "FieldAnnotation" }, fieldOptions.TryGetMessage, Fieldopt, AggregateMessage.Descriptor.Fields["fieldname"].GetOption);
+            var fieldDescriptor = messageDescriptor.Fields["fieldname"];
+            AssertOption(new Aggregate { S = "FieldAnnotation" }, fieldDescriptor.CustomOptions.TryGetMessage, Fieldopt, fieldDescriptor.GetOption, fieldDescriptor.GetOptions().GetExtension);
         }
 
         [Test]
@@ -199,16 +222,19 @@ namespace Google.Protobuf.Test.Reflection
             var descriptor = UnittestIssue6936CReflection.Descriptor;
             var foo = Foo.Descriptor;
             var bar = Bar.Descriptor;
-            AssertOption("foo", foo.CustomOptions.TryGetString, UnittestIssue6936AExtensions.Opt, foo.GetOption);
-            AssertOption("bar", bar.CustomOptions.TryGetString, UnittestIssue6936AExtensions.Opt, bar.GetOption);
+            AssertOption("foo", foo.CustomOptions.TryGetString, UnittestIssue6936AExtensions.Opt, foo.GetOption, foo.GetOptions().GetExtension);
+            AssertOption("bar", bar.CustomOptions.TryGetString, UnittestIssue6936AExtensions.Opt, bar.GetOption, bar.GetOptions().GetExtension);
         }
 
-        private void AssertOption<T, D>(T expected, OptionFetcher<T> fetcher, Extension<D, T> extension, Func<Extension<D, T>, T> descriptorOptionFetcher) where D : IExtendableMessage<D>
+        private void AssertOption<T, D>(T expected, OptionFetcher<T> customOptionFetcher, Extension<D, T> extension, Func<Extension<D, T>, T> getOptionFetcher, Func<Extension<D, T>, T> extensionFetcher) where D : IExtendableMessage<D>
         {
-            T customOptionsValue;
-            T extensionValue = descriptorOptionFetcher(extension);
-            Assert.IsTrue(fetcher(extension.FieldNumber, out customOptionsValue));
+            Assert.IsTrue(customOptionFetcher(extension.FieldNumber, out T customOptionsValue));
             Assert.AreEqual(expected, customOptionsValue);
+
+            T getOptionValue = getOptionFetcher(extension);
+            Assert.AreEqual(expected, getOptionValue);
+
+            T extensionValue = extensionFetcher(extension);
             Assert.AreEqual(expected, extensionValue);
         }
     }

+ 151 - 20
csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs

@@ -98,7 +98,48 @@ namespace Google.Protobuf.Reflection
         }
 
         [Test]
-        public void HasValue_Proto3()
+        public void HasValue_Proto3_Message()
+        {
+            var message = new TestAllTypes();
+            var accessor = ((IMessage) message).Descriptor.Fields[TestProtos.TestAllTypes.SingleForeignMessageFieldNumber].Accessor;
+            Assert.False(accessor.HasValue(message));
+            message.SingleForeignMessage = new ForeignMessage();
+            Assert.True(accessor.HasValue(message));
+            message.SingleForeignMessage = null;
+            Assert.False(accessor.HasValue(message));
+        }
+
+        [Test]
+        public void HasValue_Proto3_Oneof()
+        {
+            TestAllTypes message = new TestAllTypes();
+            var accessor = ((IMessage) message).Descriptor.Fields[TestProtos.TestAllTypes.OneofStringFieldNumber].Accessor;
+            Assert.False(accessor.HasValue(message));
+            // Even though it's the default value, we still have a value.
+            message.OneofString = "";
+            Assert.True(accessor.HasValue(message));
+            message.OneofString = "hello";
+            Assert.True(accessor.HasValue(message));
+            message.OneofUint32 = 10;
+            Assert.False(accessor.HasValue(message));
+        }
+
+        [Test]
+        public void HasValue_Proto3_Primitive_Optional()
+        {
+            var message = new TestProto3Optional();
+            var accessor = ((IMessage) message).Descriptor.Fields[TestProto3Optional.OptionalInt64FieldNumber].Accessor;
+            Assert.IsFalse(accessor.HasValue(message));
+            message.OptionalInt64 = 5L;
+            Assert.IsTrue(accessor.HasValue(message));
+            message.ClearOptionalInt64();
+            Assert.IsFalse(accessor.HasValue(message));
+            message.OptionalInt64 = 0L;
+            Assert.IsTrue(accessor.HasValue(message));
+        }
+
+        [Test]
+        public void HasValue_Proto3_Primitive_NotOptional()
         {
             IMessage message = SampleMessages.CreateFullTestAllTypes();
             var fields = message.Descriptor.Fields;
@@ -106,36 +147,63 @@ namespace Google.Protobuf.Reflection
         }
 
         [Test]
-        public void HasValue_Proto3Optional()
+        public void HasValue_Proto3_Repeated()
         {
-            IMessage message = new TestProto3Optional
-            {
-                OptionalInt32 = 0,
-                LazyNestedMessage = new TestProto3Optional.Types.NestedMessage()
-            };
-            var fields = message.Descriptor.Fields;
-            Assert.IsFalse(fields[TestProto3Optional.OptionalInt64FieldNumber].Accessor.HasValue(message));
-            Assert.IsFalse(fields[TestProto3Optional.OptionalNestedMessageFieldNumber].Accessor.HasValue(message));
-            Assert.IsTrue(fields[TestProto3Optional.LazyNestedMessageFieldNumber].Accessor.HasValue(message));
-            Assert.IsTrue(fields[TestProto3Optional.OptionalInt32FieldNumber].Accessor.HasValue(message));
+            var message = new TestAllTypes();
+            var accessor = ((IMessage) message).Descriptor.Fields[TestProtos.TestAllTypes.RepeatedBoolFieldNumber].Accessor;
+            Assert.Throws<InvalidOperationException>(() => accessor.HasValue(message));
         }
 
         [Test]
-        public void HasValue()
+        public void HasValue_Proto2_Primitive()
         {
-            IMessage message = new Proto2.TestAllTypes();
-            var fields = message.Descriptor.Fields;
-            var accessor = fields[Proto2.TestAllTypes.OptionalBoolFieldNumber].Accessor;
+            var message = new Proto2.TestAllTypes();
+            var accessor = ((IMessage) message).Descriptor.Fields[Proto2.TestAllTypes.OptionalInt64FieldNumber].Accessor;
+
+            Assert.IsFalse(accessor.HasValue(message));
+            message.OptionalInt64 = 5L;
+            Assert.IsTrue(accessor.HasValue(message));
+            message.ClearOptionalInt64();
+            Assert.IsFalse(accessor.HasValue(message));
+            message.OptionalInt64 = 0L;
+            Assert.IsTrue(accessor.HasValue(message));
+        }
 
-            Assert.False(accessor.HasValue(message));
+        [Test]
+        public void HasValue_Proto2_Message()
+        {
+            var message = new Proto2.TestAllTypes();
+            var field = ((IMessage) message).Descriptor.Fields[Proto2.TestAllTypes.OptionalForeignMessageFieldNumber];
+            Assert.False(field.Accessor.HasValue(message));
+            message.OptionalForeignMessage = new Proto2.ForeignMessage();
+            Assert.True(field.Accessor.HasValue(message));
+            message.OptionalForeignMessage = null;
+            Assert.False(field.Accessor.HasValue(message));
+        }
 
-            accessor.SetValue(message, true);
+        [Test]
+        public void HasValue_Proto2_Oneof()
+        {
+            var message = new Proto2.TestAllTypes();
+            var accessor = ((IMessage) message).Descriptor.Fields[Proto2.TestAllTypes.OneofStringFieldNumber].Accessor;
+            Assert.False(accessor.HasValue(message));
+            // Even though it's the default value, we still have a value.
+            message.OneofString = "";
             Assert.True(accessor.HasValue(message));
-
-            accessor.Clear(message);
+            message.OneofString = "hello";
+            Assert.True(accessor.HasValue(message));
+            message.OneofUint32 = 10;
             Assert.False(accessor.HasValue(message));
         }
 
+        [Test]
+        public void HasValue_Proto2_Repeated()
+        {
+            var message = new Proto2.TestAllTypes();
+            var accessor = ((IMessage) message).Descriptor.Fields[Proto2.TestAllTypes.RepeatedBoolFieldNumber].Accessor;
+            Assert.Throws<InvalidOperationException>(() => accessor.HasValue(message));
+        }
+
         [Test]
         public void SetValue_SingleFields()
         {
@@ -262,6 +330,42 @@ namespace Google.Protobuf.Reflection
             Assert.Null(message.OptionalNestedMessage);
         }
 
+        [Test]
+        public void Clear_Proto3_Oneof()
+        {
+            var message = new TestAllTypes();
+            var accessor = ((IMessage) message).Descriptor.Fields[TestProtos.TestAllTypes.OneofUint32FieldNumber].Accessor;
+
+            // The field accessor Clear method only affects a oneof if the current case is the one being cleared.
+            message.OneofString = "hello";
+            Assert.AreEqual(TestProtos.TestAllTypes.OneofFieldOneofCase.OneofString, message.OneofFieldCase);
+            accessor.Clear(message);
+            Assert.AreEqual(TestProtos.TestAllTypes.OneofFieldOneofCase.OneofString, message.OneofFieldCase);
+
+            message.OneofUint32 = 100;
+            Assert.AreEqual(TestProtos.TestAllTypes.OneofFieldOneofCase.OneofUint32, message.OneofFieldCase);
+            accessor.Clear(message);
+            Assert.AreEqual(TestProtos.TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase);
+        }
+
+        [Test]
+        public void Clear_Proto2_Oneof()
+        {
+            var message = new Proto2.TestAllTypes();
+            var accessor = ((IMessage) message).Descriptor.Fields[Proto2.TestAllTypes.OneofUint32FieldNumber].Accessor;
+
+            // The field accessor Clear method only affects a oneof if the current case is the one being cleared.
+            message.OneofString = "hello";
+            Assert.AreEqual(Proto2.TestAllTypes.OneofFieldOneofCase.OneofString, message.OneofFieldCase);
+            accessor.Clear(message);
+            Assert.AreEqual(Proto2.TestAllTypes.OneofFieldOneofCase.OneofString, message.OneofFieldCase);
+
+            message.OneofUint32 = 100;
+            Assert.AreEqual(Proto2.TestAllTypes.OneofFieldOneofCase.OneofUint32, message.OneofFieldCase);
+            accessor.Clear(message);
+            Assert.AreEqual(Proto2.TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase);
+        }
+
         [Test]
         public void FieldDescriptor_ByName()
         {
@@ -301,5 +405,32 @@ namespace Google.Protobuf.Reflection
             message.ClearExtension(RepeatedBoolExtension);
             Assert.IsNull(message.GetExtension(RepeatedBoolExtension));
         }
+
+        [Test]
+        public void HasPresence()
+        {
+            // Proto3
+            var fields = TestProtos.TestAllTypes.Descriptor.Fields;
+            Assert.IsFalse(fields[TestProtos.TestAllTypes.SingleBoolFieldNumber].HasPresence);
+            Assert.IsTrue(fields[TestProtos.TestAllTypes.OneofBytesFieldNumber].HasPresence);
+            Assert.IsTrue(fields[TestProtos.TestAllTypes.SingleForeignMessageFieldNumber].HasPresence);
+            Assert.IsFalse(fields[TestProtos.TestAllTypes.RepeatedBoolFieldNumber].HasPresence);
+
+            fields = TestMap.Descriptor.Fields;
+            Assert.IsFalse(fields[TestMap.MapBoolBoolFieldNumber].HasPresence);
+
+            fields = TestProto3Optional.Descriptor.Fields;
+            Assert.IsTrue(fields[TestProto3Optional.OptionalBoolFieldNumber].HasPresence);
+
+            // Proto2
+            fields = Proto2.TestAllTypes.Descriptor.Fields;
+            Assert.IsTrue(fields[Proto2.TestAllTypes.OptionalBoolFieldNumber].HasPresence);
+            Assert.IsTrue(fields[Proto2.TestAllTypes.OneofBytesFieldNumber].HasPresence);
+            Assert.IsTrue(fields[Proto2.TestAllTypes.OptionalForeignMessageFieldNumber].HasPresence);
+            Assert.IsFalse(fields[Proto2.TestAllTypes.RepeatedBoolFieldNumber].HasPresence);
+
+            fields = Proto2.TestRequired.Descriptor.Fields;
+            Assert.IsTrue(fields[Proto2.TestRequired.AFieldNumber].HasPresence);
+        }
     }
 }

+ 6 - 0
csharp/src/Google.Protobuf/Extension.cs

@@ -55,6 +55,8 @@ namespace Google.Protobuf
         /// Gets the field number of this extension
         /// </summary>
         public int FieldNumber { get; }
+
+        internal abstract bool IsRepeated { get; }
     }
 
     /// <summary>
@@ -79,6 +81,8 @@ namespace Google.Protobuf
 
         internal override Type TargetType => typeof(TTarget);
 
+        internal override bool IsRepeated => false;
+
         internal override IExtensionValue CreateValue()
         {
             return new ExtensionValue<TValue>(codec);
@@ -105,6 +109,8 @@ namespace Google.Protobuf
 
         internal override Type TargetType => typeof(TTarget);
 
+        internal override bool IsRepeated => true;
+
         internal override IExtensionValue CreateValue()
         {
             return new RepeatedExtensionValue<TValue>(codec);

+ 70 - 170
csharp/src/Google.Protobuf/Reflection/Descriptor.cs

@@ -352,8 +352,8 @@ namespace Google.Protobuf.Reflection {
       enumType_ = other.enumType_.Clone();
       service_ = other.service_.Clone();
       extension_ = other.extension_.Clone();
-      options_ = other.HasOptions ? other.options_.Clone() : null;
-      sourceCodeInfo_ = other.HasSourceCodeInfo ? other.sourceCodeInfo_.Clone() : null;
+      options_ = other.options_ != null ? other.options_.Clone() : null;
+      sourceCodeInfo_ = other.sourceCodeInfo_ != null ? other.sourceCodeInfo_.Clone() : null;
       syntax_ = other.syntax_;
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
@@ -508,16 +508,6 @@ namespace Google.Protobuf.Reflection {
         options_ = value;
       }
     }
-    /// <summary>Gets whether the options field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptions {
-      get { return options_ != null; }
-    }
-    /// <summary>Clears the value of the options field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptions() {
-      options_ = null;
-    }
 
     /// <summary>Field number for the "source_code_info" field.</summary>
     public const int SourceCodeInfoFieldNumber = 9;
@@ -535,16 +525,6 @@ namespace Google.Protobuf.Reflection {
         sourceCodeInfo_ = value;
       }
     }
-    /// <summary>Gets whether the source_code_info field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasSourceCodeInfo {
-      get { return sourceCodeInfo_ != null; }
-    }
-    /// <summary>Clears the value of the source_code_info field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearSourceCodeInfo() {
-      sourceCodeInfo_ = null;
-    }
 
     /// <summary>Field number for the "syntax" field.</summary>
     public const int SyntaxFieldNumber = 12;
@@ -613,8 +593,8 @@ namespace Google.Protobuf.Reflection {
       hash ^= enumType_.GetHashCode();
       hash ^= service_.GetHashCode();
       hash ^= extension_.GetHashCode();
-      if (HasOptions) hash ^= Options.GetHashCode();
-      if (HasSourceCodeInfo) hash ^= SourceCodeInfo.GetHashCode();
+      if (options_ != null) hash ^= Options.GetHashCode();
+      if (sourceCodeInfo_ != null) hash ^= SourceCodeInfo.GetHashCode();
       if (HasSyntax) hash ^= Syntax.GetHashCode();
       if (_unknownFields != null) {
         hash ^= _unknownFields.GetHashCode();
@@ -642,11 +622,11 @@ namespace Google.Protobuf.Reflection {
       enumType_.WriteTo(output, _repeated_enumType_codec);
       service_.WriteTo(output, _repeated_service_codec);
       extension_.WriteTo(output, _repeated_extension_codec);
-      if (HasOptions) {
+      if (options_ != null) {
         output.WriteRawTag(66);
         output.WriteMessage(Options);
       }
-      if (HasSourceCodeInfo) {
+      if (sourceCodeInfo_ != null) {
         output.WriteRawTag(74);
         output.WriteMessage(SourceCodeInfo);
       }
@@ -677,10 +657,10 @@ namespace Google.Protobuf.Reflection {
       size += enumType_.CalculateSize(_repeated_enumType_codec);
       size += service_.CalculateSize(_repeated_service_codec);
       size += extension_.CalculateSize(_repeated_extension_codec);
-      if (HasOptions) {
+      if (options_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options);
       }
-      if (HasSourceCodeInfo) {
+      if (sourceCodeInfo_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(SourceCodeInfo);
       }
       if (HasSyntax) {
@@ -710,14 +690,14 @@ namespace Google.Protobuf.Reflection {
       enumType_.Add(other.enumType_);
       service_.Add(other.service_);
       extension_.Add(other.extension_);
-      if (other.HasOptions) {
-        if (!HasOptions) {
+      if (other.options_ != null) {
+        if (options_ == null) {
           Options = new global::Google.Protobuf.Reflection.FileOptions();
         }
         Options.MergeFrom(other.Options);
       }
-      if (other.HasSourceCodeInfo) {
-        if (!HasSourceCodeInfo) {
+      if (other.sourceCodeInfo_ != null) {
+        if (sourceCodeInfo_ == null) {
           SourceCodeInfo = new global::Google.Protobuf.Reflection.SourceCodeInfo();
         }
         SourceCodeInfo.MergeFrom(other.SourceCodeInfo);
@@ -765,14 +745,14 @@ namespace Google.Protobuf.Reflection {
             break;
           }
           case 66: {
-            if (!HasOptions) {
+            if (options_ == null) {
               Options = new global::Google.Protobuf.Reflection.FileOptions();
             }
             input.ReadMessage(Options);
             break;
           }
           case 74: {
-            if (!HasSourceCodeInfo) {
+            if (sourceCodeInfo_ == null) {
               SourceCodeInfo = new global::Google.Protobuf.Reflection.SourceCodeInfo();
             }
             input.ReadMessage(SourceCodeInfo);
@@ -833,7 +813,7 @@ namespace Google.Protobuf.Reflection {
       enumType_ = other.enumType_.Clone();
       extensionRange_ = other.extensionRange_.Clone();
       oneofDecl_ = other.oneofDecl_.Clone();
-      options_ = other.HasOptions ? other.options_.Clone() : null;
+      options_ = other.options_ != null ? other.options_.Clone() : null;
       reservedRange_ = other.reservedRange_.Clone();
       reservedName_ = other.reservedName_.Clone();
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
@@ -937,16 +917,6 @@ namespace Google.Protobuf.Reflection {
         options_ = value;
       }
     }
-    /// <summary>Gets whether the options field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptions {
-      get { return options_ != null; }
-    }
-    /// <summary>Clears the value of the options field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptions() {
-      options_ = null;
-    }
 
     /// <summary>Field number for the "reserved_range" field.</summary>
     public const int ReservedRangeFieldNumber = 9;
@@ -1008,7 +978,7 @@ namespace Google.Protobuf.Reflection {
       hash ^= enumType_.GetHashCode();
       hash ^= extensionRange_.GetHashCode();
       hash ^= oneofDecl_.GetHashCode();
-      if (HasOptions) hash ^= Options.GetHashCode();
+      if (options_ != null) hash ^= Options.GetHashCode();
       hash ^= reservedRange_.GetHashCode();
       hash ^= reservedName_.GetHashCode();
       if (_unknownFields != null) {
@@ -1033,7 +1003,7 @@ namespace Google.Protobuf.Reflection {
       enumType_.WriteTo(output, _repeated_enumType_codec);
       extensionRange_.WriteTo(output, _repeated_extensionRange_codec);
       extension_.WriteTo(output, _repeated_extension_codec);
-      if (HasOptions) {
+      if (options_ != null) {
         output.WriteRawTag(58);
         output.WriteMessage(Options);
       }
@@ -1057,7 +1027,7 @@ namespace Google.Protobuf.Reflection {
       size += enumType_.CalculateSize(_repeated_enumType_codec);
       size += extensionRange_.CalculateSize(_repeated_extensionRange_codec);
       size += oneofDecl_.CalculateSize(_repeated_oneofDecl_codec);
-      if (HasOptions) {
+      if (options_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options);
       }
       size += reservedRange_.CalculateSize(_repeated_reservedRange_codec);
@@ -1082,8 +1052,8 @@ namespace Google.Protobuf.Reflection {
       enumType_.Add(other.enumType_);
       extensionRange_.Add(other.extensionRange_);
       oneofDecl_.Add(other.oneofDecl_);
-      if (other.HasOptions) {
-        if (!HasOptions) {
+      if (other.options_ != null) {
+        if (options_ == null) {
           Options = new global::Google.Protobuf.Reflection.MessageOptions();
         }
         Options.MergeFrom(other.Options);
@@ -1126,7 +1096,7 @@ namespace Google.Protobuf.Reflection {
             break;
           }
           case 58: {
-            if (!HasOptions) {
+            if (options_ == null) {
               Options = new global::Google.Protobuf.Reflection.MessageOptions();
             }
             input.ReadMessage(Options);
@@ -1181,7 +1151,7 @@ namespace Google.Protobuf.Reflection {
           _hasBits0 = other._hasBits0;
           start_ = other.start_;
           end_ = other.end_;
-          options_ = other.HasOptions ? other.options_.Clone() : null;
+          options_ = other.options_ != null ? other.options_.Clone() : null;
           _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
         }
 
@@ -1254,16 +1224,6 @@ namespace Google.Protobuf.Reflection {
             options_ = value;
           }
         }
-        /// <summary>Gets whether the options field is set</summary>
-        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-        public bool HasOptions {
-          get { return options_ != null; }
-        }
-        /// <summary>Clears the value of the options field</summary>
-        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-        public void ClearOptions() {
-          options_ = null;
-        }
 
         [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
         public override bool Equals(object other) {
@@ -1289,7 +1249,7 @@ namespace Google.Protobuf.Reflection {
           int hash = 1;
           if (HasStart) hash ^= Start.GetHashCode();
           if (HasEnd) hash ^= End.GetHashCode();
-          if (HasOptions) hash ^= Options.GetHashCode();
+          if (options_ != null) hash ^= Options.GetHashCode();
           if (_unknownFields != null) {
             hash ^= _unknownFields.GetHashCode();
           }
@@ -1311,7 +1271,7 @@ namespace Google.Protobuf.Reflection {
             output.WriteRawTag(16);
             output.WriteInt32(End);
           }
-          if (HasOptions) {
+          if (options_ != null) {
             output.WriteRawTag(26);
             output.WriteMessage(Options);
           }
@@ -1329,7 +1289,7 @@ namespace Google.Protobuf.Reflection {
           if (HasEnd) {
             size += 1 + pb::CodedOutputStream.ComputeInt32Size(End);
           }
-          if (HasOptions) {
+          if (options_ != null) {
             size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options);
           }
           if (_unknownFields != null) {
@@ -1349,8 +1309,8 @@ namespace Google.Protobuf.Reflection {
           if (other.HasEnd) {
             End = other.End;
           }
-          if (other.HasOptions) {
-            if (!HasOptions) {
+          if (other.options_ != null) {
+            if (options_ == null) {
               Options = new global::Google.Protobuf.Reflection.ExtensionRangeOptions();
             }
             Options.MergeFrom(other.Options);
@@ -1375,7 +1335,7 @@ namespace Google.Protobuf.Reflection {
                 break;
               }
               case 26: {
-                if (!HasOptions) {
+                if (options_ == null) {
                   Options = new global::Google.Protobuf.Reflection.ExtensionRangeOptions();
                 }
                 input.ReadMessage(Options);
@@ -1791,7 +1751,7 @@ namespace Google.Protobuf.Reflection {
       defaultValue_ = other.defaultValue_;
       oneofIndex_ = other.oneofIndex_;
       jsonName_ = other.jsonName_;
-      options_ = other.HasOptions ? other.options_.Clone() : null;
+      options_ = other.options_ != null ? other.options_.Clone() : null;
       proto3Optional_ = other.proto3Optional_;
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
@@ -2054,16 +2014,6 @@ namespace Google.Protobuf.Reflection {
         options_ = value;
       }
     }
-    /// <summary>Gets whether the options field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptions {
-      get { return options_ != null; }
-    }
-    /// <summary>Clears the value of the options field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptions() {
-      options_ = null;
-    }
 
     /// <summary>Field number for the "proto3_optional" field.</summary>
     public const int Proto3OptionalFieldNumber = 17;
@@ -2151,7 +2101,7 @@ namespace Google.Protobuf.Reflection {
       if (HasDefaultValue) hash ^= DefaultValue.GetHashCode();
       if (HasOneofIndex) hash ^= OneofIndex.GetHashCode();
       if (HasJsonName) hash ^= JsonName.GetHashCode();
-      if (HasOptions) hash ^= Options.GetHashCode();
+      if (options_ != null) hash ^= Options.GetHashCode();
       if (HasProto3Optional) hash ^= Proto3Optional.GetHashCode();
       if (_unknownFields != null) {
         hash ^= _unknownFields.GetHashCode();
@@ -2194,7 +2144,7 @@ namespace Google.Protobuf.Reflection {
         output.WriteRawTag(58);
         output.WriteString(DefaultValue);
       }
-      if (HasOptions) {
+      if (options_ != null) {
         output.WriteRawTag(66);
         output.WriteMessage(Options);
       }
@@ -2245,7 +2195,7 @@ namespace Google.Protobuf.Reflection {
       if (HasJsonName) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(JsonName);
       }
-      if (HasOptions) {
+      if (options_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options);
       }
       if (HasProto3Optional) {
@@ -2289,8 +2239,8 @@ namespace Google.Protobuf.Reflection {
       if (other.HasJsonName) {
         JsonName = other.JsonName;
       }
-      if (other.HasOptions) {
-        if (!HasOptions) {
+      if (other.options_ != null) {
+        if (options_ == null) {
           Options = new global::Google.Protobuf.Reflection.FieldOptions();
         }
         Options.MergeFrom(other.Options);
@@ -2338,7 +2288,7 @@ namespace Google.Protobuf.Reflection {
             break;
           }
           case 66: {
-            if (!HasOptions) {
+            if (options_ == null) {
               Options = new global::Google.Protobuf.Reflection.FieldOptions();
             }
             input.ReadMessage(Options);
@@ -2458,7 +2408,7 @@ namespace Google.Protobuf.Reflection {
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public OneofDescriptorProto(OneofDescriptorProto other) : this() {
       name_ = other.name_;
-      options_ = other.HasOptions ? other.options_.Clone() : null;
+      options_ = other.options_ != null ? other.options_.Clone() : null;
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
@@ -2500,16 +2450,6 @@ namespace Google.Protobuf.Reflection {
         options_ = value;
       }
     }
-    /// <summary>Gets whether the options field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptions {
-      get { return options_ != null; }
-    }
-    /// <summary>Clears the value of the options field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptions() {
-      options_ = null;
-    }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override bool Equals(object other) {
@@ -2533,7 +2473,7 @@ namespace Google.Protobuf.Reflection {
     public override int GetHashCode() {
       int hash = 1;
       if (HasName) hash ^= Name.GetHashCode();
-      if (HasOptions) hash ^= Options.GetHashCode();
+      if (options_ != null) hash ^= Options.GetHashCode();
       if (_unknownFields != null) {
         hash ^= _unknownFields.GetHashCode();
       }
@@ -2551,7 +2491,7 @@ namespace Google.Protobuf.Reflection {
         output.WriteRawTag(10);
         output.WriteString(Name);
       }
-      if (HasOptions) {
+      if (options_ != null) {
         output.WriteRawTag(18);
         output.WriteMessage(Options);
       }
@@ -2566,7 +2506,7 @@ namespace Google.Protobuf.Reflection {
       if (HasName) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
       }
-      if (HasOptions) {
+      if (options_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options);
       }
       if (_unknownFields != null) {
@@ -2583,8 +2523,8 @@ namespace Google.Protobuf.Reflection {
       if (other.HasName) {
         Name = other.Name;
       }
-      if (other.HasOptions) {
-        if (!HasOptions) {
+      if (other.options_ != null) {
+        if (options_ == null) {
           Options = new global::Google.Protobuf.Reflection.OneofOptions();
         }
         Options.MergeFrom(other.Options);
@@ -2605,7 +2545,7 @@ namespace Google.Protobuf.Reflection {
             break;
           }
           case 18: {
-            if (!HasOptions) {
+            if (options_ == null) {
               Options = new global::Google.Protobuf.Reflection.OneofOptions();
             }
             input.ReadMessage(Options);
@@ -2647,7 +2587,7 @@ namespace Google.Protobuf.Reflection {
     public EnumDescriptorProto(EnumDescriptorProto other) : this() {
       name_ = other.name_;
       value_ = other.value_.Clone();
-      options_ = other.HasOptions ? other.options_.Clone() : null;
+      options_ = other.options_ != null ? other.options_.Clone() : null;
       reservedRange_ = other.reservedRange_.Clone();
       reservedName_ = other.reservedName_.Clone();
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
@@ -2701,16 +2641,6 @@ namespace Google.Protobuf.Reflection {
         options_ = value;
       }
     }
-    /// <summary>Gets whether the options field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptions {
-      get { return options_ != null; }
-    }
-    /// <summary>Clears the value of the options field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptions() {
-      options_ = null;
-    }
 
     /// <summary>Field number for the "reserved_range" field.</summary>
     public const int ReservedRangeFieldNumber = 4;
@@ -2767,7 +2697,7 @@ namespace Google.Protobuf.Reflection {
       int hash = 1;
       if (HasName) hash ^= Name.GetHashCode();
       hash ^= value_.GetHashCode();
-      if (HasOptions) hash ^= Options.GetHashCode();
+      if (options_ != null) hash ^= Options.GetHashCode();
       hash ^= reservedRange_.GetHashCode();
       hash ^= reservedName_.GetHashCode();
       if (_unknownFields != null) {
@@ -2788,7 +2718,7 @@ namespace Google.Protobuf.Reflection {
         output.WriteString(Name);
       }
       value_.WriteTo(output, _repeated_value_codec);
-      if (HasOptions) {
+      if (options_ != null) {
         output.WriteRawTag(26);
         output.WriteMessage(Options);
       }
@@ -2806,7 +2736,7 @@ namespace Google.Protobuf.Reflection {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
       }
       size += value_.CalculateSize(_repeated_value_codec);
-      if (HasOptions) {
+      if (options_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options);
       }
       size += reservedRange_.CalculateSize(_repeated_reservedRange_codec);
@@ -2826,8 +2756,8 @@ namespace Google.Protobuf.Reflection {
         Name = other.Name;
       }
       value_.Add(other.value_);
-      if (other.HasOptions) {
-        if (!HasOptions) {
+      if (other.options_ != null) {
+        if (options_ == null) {
           Options = new global::Google.Protobuf.Reflection.EnumOptions();
         }
         Options.MergeFrom(other.Options);
@@ -2854,7 +2784,7 @@ namespace Google.Protobuf.Reflection {
             break;
           }
           case 26: {
-            if (!HasOptions) {
+            if (options_ == null) {
               Options = new global::Google.Protobuf.Reflection.EnumOptions();
             }
             input.ReadMessage(Options);
@@ -3112,7 +3042,7 @@ namespace Google.Protobuf.Reflection {
       _hasBits0 = other._hasBits0;
       name_ = other.name_;
       number_ = other.number_;
-      options_ = other.HasOptions ? other.options_.Clone() : null;
+      options_ = other.options_ != null ? other.options_.Clone() : null;
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
@@ -3178,16 +3108,6 @@ namespace Google.Protobuf.Reflection {
         options_ = value;
       }
     }
-    /// <summary>Gets whether the options field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptions {
-      get { return options_ != null; }
-    }
-    /// <summary>Clears the value of the options field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptions() {
-      options_ = null;
-    }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override bool Equals(object other) {
@@ -3213,7 +3133,7 @@ namespace Google.Protobuf.Reflection {
       int hash = 1;
       if (HasName) hash ^= Name.GetHashCode();
       if (HasNumber) hash ^= Number.GetHashCode();
-      if (HasOptions) hash ^= Options.GetHashCode();
+      if (options_ != null) hash ^= Options.GetHashCode();
       if (_unknownFields != null) {
         hash ^= _unknownFields.GetHashCode();
       }
@@ -3235,7 +3155,7 @@ namespace Google.Protobuf.Reflection {
         output.WriteRawTag(16);
         output.WriteInt32(Number);
       }
-      if (HasOptions) {
+      if (options_ != null) {
         output.WriteRawTag(26);
         output.WriteMessage(Options);
       }
@@ -3253,7 +3173,7 @@ namespace Google.Protobuf.Reflection {
       if (HasNumber) {
         size += 1 + pb::CodedOutputStream.ComputeInt32Size(Number);
       }
-      if (HasOptions) {
+      if (options_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options);
       }
       if (_unknownFields != null) {
@@ -3273,8 +3193,8 @@ namespace Google.Protobuf.Reflection {
       if (other.HasNumber) {
         Number = other.Number;
       }
-      if (other.HasOptions) {
-        if (!HasOptions) {
+      if (other.options_ != null) {
+        if (options_ == null) {
           Options = new global::Google.Protobuf.Reflection.EnumValueOptions();
         }
         Options.MergeFrom(other.Options);
@@ -3299,7 +3219,7 @@ namespace Google.Protobuf.Reflection {
             break;
           }
           case 26: {
-            if (!HasOptions) {
+            if (options_ == null) {
               Options = new global::Google.Protobuf.Reflection.EnumValueOptions();
             }
             input.ReadMessage(Options);
@@ -3341,7 +3261,7 @@ namespace Google.Protobuf.Reflection {
     public ServiceDescriptorProto(ServiceDescriptorProto other) : this() {
       name_ = other.name_;
       method_ = other.method_.Clone();
-      options_ = other.HasOptions ? other.options_.Clone() : null;
+      options_ = other.options_ != null ? other.options_.Clone() : null;
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
 
@@ -3393,16 +3313,6 @@ namespace Google.Protobuf.Reflection {
         options_ = value;
       }
     }
-    /// <summary>Gets whether the options field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptions {
-      get { return options_ != null; }
-    }
-    /// <summary>Clears the value of the options field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptions() {
-      options_ = null;
-    }
 
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override bool Equals(object other) {
@@ -3428,7 +3338,7 @@ namespace Google.Protobuf.Reflection {
       int hash = 1;
       if (HasName) hash ^= Name.GetHashCode();
       hash ^= method_.GetHashCode();
-      if (HasOptions) hash ^= Options.GetHashCode();
+      if (options_ != null) hash ^= Options.GetHashCode();
       if (_unknownFields != null) {
         hash ^= _unknownFields.GetHashCode();
       }
@@ -3447,7 +3357,7 @@ namespace Google.Protobuf.Reflection {
         output.WriteString(Name);
       }
       method_.WriteTo(output, _repeated_method_codec);
-      if (HasOptions) {
+      if (options_ != null) {
         output.WriteRawTag(26);
         output.WriteMessage(Options);
       }
@@ -3463,7 +3373,7 @@ namespace Google.Protobuf.Reflection {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
       }
       size += method_.CalculateSize(_repeated_method_codec);
-      if (HasOptions) {
+      if (options_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options);
       }
       if (_unknownFields != null) {
@@ -3481,8 +3391,8 @@ namespace Google.Protobuf.Reflection {
         Name = other.Name;
       }
       method_.Add(other.method_);
-      if (other.HasOptions) {
-        if (!HasOptions) {
+      if (other.options_ != null) {
+        if (options_ == null) {
           Options = new global::Google.Protobuf.Reflection.ServiceOptions();
         }
         Options.MergeFrom(other.Options);
@@ -3507,7 +3417,7 @@ namespace Google.Protobuf.Reflection {
             break;
           }
           case 26: {
-            if (!HasOptions) {
+            if (options_ == null) {
               Options = new global::Google.Protobuf.Reflection.ServiceOptions();
             }
             input.ReadMessage(Options);
@@ -3552,7 +3462,7 @@ namespace Google.Protobuf.Reflection {
       name_ = other.name_;
       inputType_ = other.inputType_;
       outputType_ = other.outputType_;
-      options_ = other.HasOptions ? other.options_.Clone() : null;
+      options_ = other.options_ != null ? other.options_.Clone() : null;
       clientStreaming_ = other.clientStreaming_;
       serverStreaming_ = other.serverStreaming_;
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
@@ -3646,16 +3556,6 @@ namespace Google.Protobuf.Reflection {
         options_ = value;
       }
     }
-    /// <summary>Gets whether the options field is set</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public bool HasOptions {
-      get { return options_ != null; }
-    }
-    /// <summary>Clears the value of the options field</summary>
-    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
-    public void ClearOptions() {
-      options_ = null;
-    }
 
     /// <summary>Field number for the "client_streaming" field.</summary>
     public const int ClientStreamingFieldNumber = 5;
@@ -3739,7 +3639,7 @@ namespace Google.Protobuf.Reflection {
       if (HasName) hash ^= Name.GetHashCode();
       if (HasInputType) hash ^= InputType.GetHashCode();
       if (HasOutputType) hash ^= OutputType.GetHashCode();
-      if (HasOptions) hash ^= Options.GetHashCode();
+      if (options_ != null) hash ^= Options.GetHashCode();
       if (HasClientStreaming) hash ^= ClientStreaming.GetHashCode();
       if (HasServerStreaming) hash ^= ServerStreaming.GetHashCode();
       if (_unknownFields != null) {
@@ -3767,7 +3667,7 @@ namespace Google.Protobuf.Reflection {
         output.WriteRawTag(26);
         output.WriteString(OutputType);
       }
-      if (HasOptions) {
+      if (options_ != null) {
         output.WriteRawTag(34);
         output.WriteMessage(Options);
       }
@@ -3796,7 +3696,7 @@ namespace Google.Protobuf.Reflection {
       if (HasOutputType) {
         size += 1 + pb::CodedOutputStream.ComputeStringSize(OutputType);
       }
-      if (HasOptions) {
+      if (options_ != null) {
         size += 1 + pb::CodedOutputStream.ComputeMessageSize(Options);
       }
       if (HasClientStreaming) {
@@ -3825,8 +3725,8 @@ namespace Google.Protobuf.Reflection {
       if (other.HasOutputType) {
         OutputType = other.OutputType;
       }
-      if (other.HasOptions) {
-        if (!HasOptions) {
+      if (other.options_ != null) {
+        if (options_ == null) {
           Options = new global::Google.Protobuf.Reflection.MethodOptions();
         }
         Options.MergeFrom(other.Options);
@@ -3861,7 +3761,7 @@ namespace Google.Protobuf.Reflection {
             break;
           }
           case 34: {
-            if (!HasOptions) {
+            if (options_ == null) {
               Options = new global::Google.Protobuf.Reflection.MethodOptions();
             }
             input.ReadMessage(Options);

+ 11 - 1
csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs

@@ -128,12 +128,21 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// The (possibly empty) set of custom options for this enum.
         /// </summary>
-        [Obsolete("CustomOptions are obsolete. Use GetOption")]
+        [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
+        /// <summary>
+        /// The <c>EnumOptions</c>, defined in <c>descriptor.proto</c>.
+        /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
+        /// Custom options can be retrieved as extensions of the returned message.
+        /// NOTE: A defensive copy is created each time this property is retrieved.
+        /// </summary>
+        public EnumOptions GetOptions() => Proto.Options?.Clone();
+
         /// <summary>
         /// Gets a single value enum option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public T GetOption<T>(Extension<EnumOptions, T> extension)
         {
             var value = Proto.Options.GetExtension(extension);
@@ -143,6 +152,7 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// Gets a repeated value enum option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public RepeatedField<T> GetOption<T>(RepeatedExtension<EnumOptions, T> extension)
         {
             return Proto.Options.GetExtension(extension).Clone();

+ 11 - 1
csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs

@@ -73,12 +73,21 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// The (possibly empty) set of custom options for this enum value.
         /// </summary>
-        [Obsolete("CustomOptions are obsolete. Use GetOption")]
+        [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
+        /// <summary>
+        /// The <c>EnumValueOptions</c>, defined in <c>descriptor.proto</c>.
+        /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
+        /// Custom options can be retrieved as extensions of the returned message.
+        /// NOTE: A defensive copy is created each time this property is retrieved.
+        /// </summary>
+        public EnumValueOptions GetOptions() => Proto.Options?.Clone();
+
         /// <summary>
         /// Gets a single value enum value option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public T GetOption<T>(Extension<EnumValueOptions, T> extension)
         {
             var value = Proto.Options.GetExtension(extension);
@@ -88,6 +97,7 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// Gets a repeated value enum value option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public RepeatedField<T> GetOption<T>(RepeatedExtension<EnumValueOptions, T> extension)
         {
             return Proto.Options.GetExtension(extension).Clone();

+ 27 - 2
csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs

@@ -70,6 +70,21 @@ namespace Google.Protobuf.Reflection
         /// </summary>
         public string JsonName { get; }
 
+        /// <summary>
+        /// Indicates whether this field supports presence, either implicitly (e.g. due to it being a message
+        /// type field) or explicitly via Has/Clear members. If this returns true, it is safe to call
+        /// <see cref="IFieldAccessor.Clear(IMessage)"/> and <see cref="IFieldAccessor.HasValue(IMessage)"/>
+        /// on this field's accessor with a suitable message.
+        /// </summary>
+        public bool HasPresence =>
+            Extension != null ? !Extension.IsRepeated
+            : IsRepeated ? false
+            : IsMap ? false
+            : FieldType == FieldType.Message ? true
+            // This covers "real oneof members" and "proto3 optional fields"
+            : ContainingOneof != null ? true
+            : File.Syntax == Syntax.Proto2;
+
         internal FieldDescriptorProto Proto { get; }
 
         /// <summary>
@@ -304,12 +319,21 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// The (possibly empty) set of custom options for this field.
         /// </summary>
-        [Obsolete("CustomOptions are obsolete. Use GetOption")]
+        [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
+        /// <summary>
+        /// The <c>FieldOptions</c>, defined in <c>descriptor.proto</c>.
+        /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
+        /// Custom options can be retrieved as extensions of the returned message.
+        /// NOTE: A defensive copy is created each time this property is retrieved.
+        /// </summary>
+        public FieldOptions GetOptions() => Proto.Options?.Clone();
+
         /// <summary>
         /// Gets a single value field option for this descriptor
         /// </summary>
+         [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public T GetOption<T>(Extension<FieldOptions, T> extension)
         {
             var value = Proto.Options.GetExtension(extension);
@@ -319,6 +343,7 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// Gets a repeated value field option for this descriptor
         /// </summary>
+         [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public RepeatedField<T> GetOption<T>(RepeatedExtension<FieldOptions, T> extension)
         {
             return Proto.Options.GetExtension(extension).Clone();
@@ -394,7 +419,7 @@ namespace Google.Protobuf.Reflection
 
             File.DescriptorPool.AddFieldByNumber(this);
 
-            if (ContainingType != null && ContainingType.Proto.HasOptions && ContainingType.Proto.Options.MessageSetWireFormat)
+            if (ContainingType != null && ContainingType.Proto.Options != null && ContainingType.Proto.Options.MessageSetWireFormat)
             {
                 throw new DescriptorValidationException(this, "MessageSet format is not supported.");
             }

+ 11 - 1
csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs

@@ -547,12 +547,21 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// The (possibly empty) set of custom options for this file.
         /// </summary>
-        [Obsolete("CustomOptions are obsolete. Use GetOption")]
+        [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
+        /// <summary>
+        /// The <c>FileOptions</c>, defined in <c>descriptor.proto</c>.
+        /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
+        /// Custom options can be retrieved as extensions of the returned message.
+        /// NOTE: A defensive copy is created each time this property is retrieved.
+        /// </summary>
+        public FileOptions GetOptions() => Proto.Options?.Clone();
+
         /// <summary>
         /// Gets a single value file option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public T GetOption<T>(Extension<FileOptions, T> extension)
         {
             var value = Proto.Options.GetExtension(extension);
@@ -562,6 +571,7 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// Gets a repeated value file option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public RepeatedField<T> GetOption<T>(RepeatedExtension<FileOptions, T> extension)
         {
             return Proto.Options.GetExtension(extension).Clone();

+ 11 - 1
csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs

@@ -287,12 +287,21 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// The (possibly empty) set of custom options for this message.
         /// </summary>
-        [Obsolete("CustomOptions are obsolete. Use GetOption")]
+        [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
+        /// <summary>
+        /// The <c>MessageOptions</c>, defined in <c>descriptor.proto</c>.
+        /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
+        /// Custom options can be retrieved as extensions of the returned message.
+        /// NOTE: A defensive copy is created each time this property is retrieved.
+        /// </summary>
+        public MessageOptions GetOptions() => Proto.Options?.Clone();
+
         /// <summary>
         /// Gets a single value message option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public T GetOption<T>(Extension<MessageOptions, T> extension)
         {
             var value = Proto.Options.GetExtension(extension);
@@ -302,6 +311,7 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// Gets a repeated value message option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public Collections.RepeatedField<T> GetOption<T>(RepeatedExtension<MessageOptions, T> extension)
         {
             return Proto.Options.GetExtension(extension).Clone();

+ 11 - 1
csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs

@@ -73,12 +73,21 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// The (possibly empty) set of custom options for this method.
         /// </summary>
-        [Obsolete("CustomOptions are obsolete. Use GetOption")]
+        [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
+        /// <summary>
+        /// The <c>MethodOptions</c>, defined in <c>descriptor.proto</c>.
+        /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
+        /// Custom options can be retrieved as extensions of the returned message.
+        /// NOTE: A defensive copy is created each time this property is retrieved.
+        /// </summary>
+        public MethodOptions GetOptions() => Proto.Options?.Clone();
+
         /// <summary>
         /// Gets a single value method option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public T GetOption<T>(Extension<MethodOptions, T> extension)
         {
             var value = Proto.Options.GetExtension(extension);
@@ -88,6 +97,7 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// Gets a repeated value method option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public RepeatedField<T> GetOption<T>(RepeatedExtension<MethodOptions, T> extension)
         {
             return Proto.Options.GetExtension(extension).Clone();

+ 11 - 1
csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs

@@ -117,12 +117,21 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// The (possibly empty) set of custom options for this oneof.
         /// </summary>
-        [Obsolete("CustomOptions are obsolete. Use GetOption")]
+        [Obsolete("CustomOptions are obsolete. Use the GetOptions method.")]
         public CustomOptions CustomOptions => new CustomOptions(proto.Options?._extensions?.ValuesByNumber);
 
+        /// <summary>
+        /// The <c>OneofOptions</c>, defined in <c>descriptor.proto</c>.
+        /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
+        /// Custom options can be retrieved as extensions of the returned message.
+        /// NOTE: A defensive copy is created each time this property is retrieved.
+        /// </summary>
+        public OneofOptions GetOptions() => proto.Options?.Clone();
+
         /// <summary>
         /// Gets a single value oneof option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public T GetOption<T>(Extension<OneofOptions, T> extension)
         {
             var value = proto.Options.GetExtension(extension);
@@ -132,6 +141,7 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// Gets a repeated value oneof option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public RepeatedField<T> GetOption<T>(RepeatedExtension<OneofOptions, T> extension)
         {
             return proto.Options.GetExtension(extension).Clone();

+ 11 - 1
csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs

@@ -94,12 +94,21 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// The (possibly empty) set of custom options for this service.
         /// </summary>
-        [Obsolete("CustomOptions are obsolete. Use GetOption")]
+        [Obsolete("CustomOptions are obsolete. Use the GetOptions() method.")]
         public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
+        /// <summary>
+        /// The <c>ServiceOptions</c>, defined in <c>descriptor.proto</c>.
+        /// If the options message is not present (i.e. there are no options), <c>null</c> is returned.
+        /// Custom options can be retrieved as extensions of the returned message.
+        /// NOTE: A defensive copy is created each time this property is retrieved.
+        /// </summary>
+        public ServiceOptions GetOptions() => Proto.Options?.Clone();
+
         /// <summary>
         /// Gets a single value service option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public T GetOption<T>(Extension<ServiceOptions, T> extension)
         {
             var value = Proto.Options.GetExtension(extension);
@@ -109,6 +118,7 @@ namespace Google.Protobuf.Reflection
         /// <summary>
         /// Gets a repeated value service option for this descriptor
         /// </summary>
+        [Obsolete("GetOption is obsolete. Use the GetOptions() method.")]
         public RepeatedField<T> GetOption<T>(RepeatedExtension<ServiceOptions, T> extension)
         {
             return Proto.Options.GetExtension(extension).Clone();

+ 49 - 44
csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs

@@ -57,63 +57,68 @@ namespace Google.Protobuf.Reflection
                 throw new ArgumentException("Not all required properties/methods available");
             }
             setValueDelegate = ReflectionUtil.CreateActionIMessageObject(property.GetSetMethod());
-            if (descriptor.File.Syntax == Syntax.Proto3 && !descriptor.Proto.Proto3Optional)
+
+            // Note: this looks worrying in that we access the containing oneof, which isn't valid until cross-linking
+            // is complete... but field accessors aren't created until after cross-linking.
+            // The oneof itself won't be cross-linked yet, but that's okay: the oneof accessor is created
+            // earlier.
+
+            // Message fields always support presence, via null checks.
+            if (descriptor.FieldType == FieldType.Message)
+            {
+                hasDelegate = message => GetValue(message) != null;
+                clearDelegate = message => SetValue(message, null);
+            }
+            // Oneof fields always support presence, via case checks.
+            // Note that clearing the field is a no-op unless that specific field is the current "case".
+            else if (descriptor.RealContainingOneof != null)
             {
-                hasDelegate = message =>
+                var oneofAccessor = descriptor.RealContainingOneof.Accessor;
+                hasDelegate = message => oneofAccessor.GetCaseFieldDescriptor(message) == descriptor;
+                clearDelegate = message =>
                 {
-                    throw new InvalidOperationException("HasValue is not implemented for non-optional proto3 fields");
+                    // Clear on a field only affects the oneof itself if the current case is the field we're accessing.
+                    if (oneofAccessor.GetCaseFieldDescriptor(message) == descriptor)
+                    {
+                        oneofAccessor.Clear(message);
+                    }
                 };
-                var clrType = property.PropertyType;
-
-                // TODO: Validate that this is a reasonable single field? (Should be a value type, a message type, or string/ByteString.)
-                object defaultValue =
-                    descriptor.FieldType == FieldType.Message ? null
-                    : clrType == typeof(string) ? ""
-                    : clrType == typeof(ByteString) ? ByteString.Empty
-                    : Activator.CreateInstance(clrType);
-                clearDelegate = message => SetValue(message, defaultValue);
             }
-            else
+            // Primitive fields always support presence in proto2, and support presence in proto3 for optional fields.
+            else if (descriptor.File.Syntax == Syntax.Proto2 || descriptor.Proto.Proto3Optional)
             {
-                // For message fields, just compare with null and set to null.
-                // For primitive fields, use the Has/Clear methods.
-
-                if (descriptor.FieldType == FieldType.Message)
+                MethodInfo hasMethod = property.DeclaringType.GetRuntimeProperty("Has" + property.Name).GetMethod;
+                if (hasMethod == null)
                 {
-                    hasDelegate = message => GetValue(message) != null;
-                    clearDelegate = message => SetValue(message, null);
+                    throw new ArgumentException("Not all required properties/methods are available");
                 }
-                else
+                hasDelegate = ReflectionUtil.CreateFuncIMessageBool(hasMethod);
+                MethodInfo clearMethod = property.DeclaringType.GetRuntimeMethod("Clear" + property.Name, ReflectionUtil.EmptyTypes);
+                if (clearMethod == null)
                 {
-                    MethodInfo hasMethod = property.DeclaringType.GetRuntimeProperty("Has" + property.Name).GetMethod;
-                    if (hasMethod == null)
-                    {
-                        throw new ArgumentException("Not all required properties/methods are available");
-                    }
-                    hasDelegate = ReflectionUtil.CreateFuncIMessageBool(hasMethod);
-                    MethodInfo clearMethod = property.DeclaringType.GetRuntimeMethod("Clear" + property.Name, ReflectionUtil.EmptyTypes);
-                    if (clearMethod == null)
-                    {
-                        throw new ArgumentException("Not all required properties/methods are available");
-                    }
-                    clearDelegate = ReflectionUtil.CreateActionIMessage(clearMethod);
+                    throw new ArgumentException("Not all required properties/methods are available");
                 }
+                clearDelegate = ReflectionUtil.CreateActionIMessage(clearMethod);
             }
-        }
+            // What's left?
+            // Primitive proto3 fields without the optional keyword, which aren't in oneofs.
+            else
+            {
+                hasDelegate = message => { throw new InvalidOperationException("Presence is not implemented for this field"); };
 
-        public override void Clear(IMessage message)
-        {
-            clearDelegate(message);
-        }
+                // While presence isn't supported, clearing still is; it's just setting to a default value.
+                var clrType = property.PropertyType;
 
-        public override bool HasValue(IMessage message)
-        {
-            return hasDelegate(message);
+                object defaultValue =
+                    clrType == typeof(string) ? ""
+                    : clrType == typeof(ByteString) ? ByteString.Empty
+                    : Activator.CreateInstance(clrType);
+                clearDelegate = message => SetValue(message, defaultValue);
+            }
         }
 
-        public override void SetValue(IMessage message, object value)
-        {
-            setValueDelegate(message, value);
-        }
+        public override void Clear(IMessage message) => clearDelegate(message);
+        public override bool HasValue(IMessage message) => hasDelegate(message);
+        public override void SetValue(IMessage message, object value) => setValueDelegate(message, value);
     }
 }

+ 1 - 0
java/core/generate-test-sources-build.xml

@@ -17,6 +17,7 @@
         <arg value="${protobuf.source.dir}/google/protobuf/unittest_no_generic_services.proto"/>
         <arg value="${protobuf.source.dir}/google/protobuf/unittest_optimize_for.proto"/>
         <arg value="${protobuf.source.dir}/google/protobuf/unittest_proto3.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_proto3_optional.proto"/>
         <arg value="${protobuf.source.dir}/google/protobuf/unittest_well_known_types.proto"/>
         <arg value="${test.proto.dir}/com/google/protobuf/any_test.proto"/>
         <arg value="${test.proto.dir}/com/google/protobuf/cached_field_size_test.proto"/>

+ 108 - 0
java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java

@@ -38,6 +38,7 @@ import com.google.protobuf.Descriptors.OneofDescriptor;
 import com.google.protobuf.FieldPresenceTestProto.TestAllTypes;
 import com.google.protobuf.FieldPresenceTestProto.TestOptionalFieldsOnly;
 import com.google.protobuf.FieldPresenceTestProto.TestRepeatedFieldsOnly;
+import com.google.protobuf.testing.proto.TestProto3Optional;
 import protobuf_unittest.UnittestProto;
 import junit.framework.TestCase;
 
@@ -101,6 +102,113 @@ public class FieldPresenceTest extends TestCase {
         UnittestProto.TestAllTypes.Builder.class, TestAllTypes.Builder.class, "OneofBytes");
   }
 
+  public void testHasMethodForProto3Optional() throws Exception {
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalInt32());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalInt64());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalUint32());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalUint64());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalSint32());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalSint64());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalFixed32());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalFixed64());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalFloat());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalDouble());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalBool());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalString());
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOptionalBytes());
+
+    TestProto3Optional.Builder builder = TestProto3Optional.newBuilder().setOptionalInt32(0);
+    assertTrue(builder.hasOptionalInt32());
+    assertTrue(builder.build().hasOptionalInt32());
+
+    TestProto3Optional.Builder otherBuilder = TestProto3Optional.newBuilder().setOptionalInt32(1);
+    otherBuilder.mergeFrom(builder.build());
+    assertTrue(otherBuilder.hasOptionalInt32());
+    assertEquals(0, otherBuilder.getOptionalInt32());
+
+    TestProto3Optional.Builder builder3 =
+        TestProto3Optional.newBuilder().setOptionalNestedEnumValue(5);
+    assertTrue(builder3.hasOptionalNestedEnum());
+
+    TestProto3Optional.Builder builder4 =
+        TestProto3Optional.newBuilder().setOptionalNestedEnum(TestProto3Optional.NestedEnum.FOO);
+    assertTrue(builder4.hasOptionalNestedEnum());
+
+    TestProto3Optional proto = TestProto3Optional.parseFrom(builder.build().toByteArray());
+    assertTrue(proto.hasOptionalInt32());
+    assertTrue(proto.toBuilder().hasOptionalInt32());
+  }
+
+  private static void assertProto3OptionalReflection(String name) throws Exception {
+    FieldDescriptor fieldDescriptor = TestProto3Optional.getDescriptor().findFieldByName(name);
+    OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
+    assertNotNull(fieldDescriptor.getContainingOneof());
+    assertTrue(fieldDescriptor.hasOptionalKeyword());
+    assertTrue(fieldDescriptor.hasPresence());
+
+    assertFalse(TestProto3Optional.getDefaultInstance().hasOneof(oneofDescriptor));
+    assertNull(TestProto3Optional.getDefaultInstance().getOneofFieldDescriptor(oneofDescriptor));
+
+    TestProto3Optional.Builder builder = TestProto3Optional.newBuilder();
+    builder.setField(fieldDescriptor, fieldDescriptor.getDefaultValue());
+    assertTrue(builder.hasField(fieldDescriptor));
+    assertEquals(fieldDescriptor.getDefaultValue(), builder.getField(fieldDescriptor));
+    assertTrue(builder.build().hasField(fieldDescriptor));
+    assertEquals(fieldDescriptor.getDefaultValue(), builder.build().getField(fieldDescriptor));
+    assertTrue(builder.hasOneof(oneofDescriptor));
+    assertEquals(fieldDescriptor, builder.getOneofFieldDescriptor(oneofDescriptor));
+    assertTrue(builder.build().hasOneof(oneofDescriptor));
+    assertEquals(fieldDescriptor, builder.build().getOneofFieldDescriptor(oneofDescriptor));
+
+    TestProto3Optional.Builder otherBuilder = TestProto3Optional.newBuilder();
+    otherBuilder.mergeFrom(builder.build());
+    assertTrue(otherBuilder.hasField(fieldDescriptor));
+    assertEquals(fieldDescriptor.getDefaultValue(), otherBuilder.getField(fieldDescriptor));
+
+    TestProto3Optional proto = TestProto3Optional.parseFrom(builder.build().toByteArray());
+    assertTrue(proto.hasField(fieldDescriptor));
+    assertTrue(proto.toBuilder().hasField(fieldDescriptor));
+
+    DynamicMessage.Builder dynamicBuilder =
+        DynamicMessage.newBuilder(TestProto3Optional.getDescriptor());
+    dynamicBuilder.setField(fieldDescriptor, fieldDescriptor.getDefaultValue());
+    assertTrue(dynamicBuilder.hasField(fieldDescriptor));
+    assertEquals(fieldDescriptor.getDefaultValue(), dynamicBuilder.getField(fieldDescriptor));
+    assertTrue(dynamicBuilder.build().hasField(fieldDescriptor));
+    assertEquals(
+        fieldDescriptor.getDefaultValue(), dynamicBuilder.build().getField(fieldDescriptor));
+    assertTrue(dynamicBuilder.hasOneof(oneofDescriptor));
+    assertEquals(fieldDescriptor, dynamicBuilder.getOneofFieldDescriptor(oneofDescriptor));
+    assertTrue(dynamicBuilder.build().hasOneof(oneofDescriptor));
+    assertEquals(fieldDescriptor, dynamicBuilder.build().getOneofFieldDescriptor(oneofDescriptor));
+
+    DynamicMessage.Builder otherDynamicBuilder =
+        DynamicMessage.newBuilder(TestProto3Optional.getDescriptor());
+    otherDynamicBuilder.mergeFrom(dynamicBuilder.build());
+    assertTrue(otherDynamicBuilder.hasField(fieldDescriptor));
+    assertEquals(fieldDescriptor.getDefaultValue(), otherDynamicBuilder.getField(fieldDescriptor));
+
+    DynamicMessage dynamicProto =
+        DynamicMessage.parseFrom(TestProto3Optional.getDescriptor(), builder.build().toByteArray());
+    assertTrue(dynamicProto.hasField(fieldDescriptor));
+    assertTrue(dynamicProto.toBuilder().hasField(fieldDescriptor));
+  }
+
+  public void testProto3Optional_reflection() throws Exception {
+    assertProto3OptionalReflection("optional_int32");
+    assertProto3OptionalReflection("optional_int64");
+    assertProto3OptionalReflection("optional_uint32");
+    assertProto3OptionalReflection("optional_uint64");
+    assertProto3OptionalReflection("optional_sint32");
+    assertProto3OptionalReflection("optional_sint64");
+    assertProto3OptionalReflection("optional_fixed32");
+    assertProto3OptionalReflection("optional_fixed64");
+    assertProto3OptionalReflection("optional_float");
+    assertProto3OptionalReflection("optional_double");
+    assertProto3OptionalReflection("optional_bool");
+    assertProto3OptionalReflection("optional_string");
+    assertProto3OptionalReflection("optional_bytes");
+  }
 
   public void testOneofEquals() throws Exception {
     TestAllTypes.Builder builder = TestAllTypes.newBuilder();

+ 20 - 0
java/core/src/test/java/com/google/protobuf/TextFormatTest.java

@@ -40,6 +40,8 @@ import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
 import com.google.protobuf.Descriptors.FileDescriptor;
 import com.google.protobuf.TextFormat.Parser.SingularOverwritePolicy;
+import com.google.protobuf.testing.proto.TestProto3Optional;
+import com.google.protobuf.testing.proto.TestProto3Optional.NestedEnum;
 import any_test.AnyTestProto.TestAny;
 import map_test.MapTestProto.TestMap;
 import protobuf_unittest.UnittestMset.TestMessageSetExtension1;
@@ -319,6 +321,24 @@ public class TextFormatTest extends TestCase {
     assertEquals(canonicalExoticText, message.toString());
   }
 
+  public void testRoundtripProto3Optional() throws Exception {
+    Message message =
+        TestProto3Optional.newBuilder()
+            .setOptionalInt32(1)
+            .setOptionalInt64(2)
+            .setOptionalNestedEnum(NestedEnum.BAZ)
+            .build();
+    TestProto3Optional.Builder message2 = TestProto3Optional.newBuilder();
+    TextFormat.merge(message.toString(), message2);
+
+    assertTrue(message2.hasOptionalInt32());
+    assertTrue(message2.hasOptionalInt64());
+    assertTrue(message2.hasOptionalNestedEnum());
+    assertEquals(1, message2.getOptionalInt32());
+    assertEquals(2, message2.getOptionalInt64());
+    assertEquals(NestedEnum.BAZ, message2.getOptionalNestedEnum());
+  }
+
   public void testPrintMessageSet() throws Exception {
     TestMessageSet messageSet =
         TestMessageSet.newBuilder()

+ 1 - 0
java/lite/generate-test-sources-build.xml

@@ -15,6 +15,7 @@
         <arg value="${protobuf.source.dir}/google/protobuf/unittest_no_generic_services.proto"/>
         <arg value="${protobuf.source.dir}/google/protobuf/unittest_optimize_for.proto"/>
         <arg value="${protobuf.source.dir}/google/protobuf/unittest_proto3.proto"/>
+        <arg value="${protobuf.source.dir}/google/protobuf/unittest_proto3_optional.proto"/>
         <arg value="${protobuf.source.dir}/google/protobuf/unittest_well_known_types.proto"/>
         <arg value="${protobuf.basedir}/java/core/${test.proto.dir}/com/google/protobuf/any_test.proto"/>
         <arg value="${protobuf.basedir}/java/core/${test.proto.dir}/com/google/protobuf/cached_field_size_test.proto"/>

+ 1 - 4
python/google/protobuf/internal/test_proto3_optional.proto

@@ -30,10 +30,7 @@
 
 syntax = "proto3";
 
-package protobuf_unittest;
-
-option java_multiple_files = true;
-option java_package = "com.google.protobuf.testing.proto";
+package google.protobuf.python.internal;
 
 message TestProto3Optional {
   message NestedMessage {

+ 16 - 17
src/google/protobuf/compiler/csharp/csharp_helpers.h

@@ -159,24 +159,23 @@ inline bool IsProto2(const FileDescriptor* descriptor) {
 }
 
 inline bool SupportsPresenceApi(const FieldDescriptor* descriptor) {
-  // We don't use descriptor->is_singular_with_presence() as C# has slightly
-  // different behavior to other languages.
- 
-  if (IsProto2(descriptor->file())) {
-    // We generate Has/Clear for oneof fields in C#, as well as for messages.
-    // It's possible that we shouldn't, but stopping doing so would be a
-    // breaking change for proto2. Fortunately the decision is moot for
-    // onoeof in proto3: you can't use "optional" inside a oneof.
-    // Proto2: every singular field has presence. (Even fields in oneofs.)
-    return !descriptor->is_repeated();
-  } else {
-    // Proto3: only for explictly-optional fields that aren't messages.
-    // (Repeated fields can never be explicitly optional, so we don't need
-    // to check for that.) Currently we can't get at proto3_optional directly,
-    // but we can use has_optional_keyword() as a surrogate check.    
-    return descriptor->has_optional_keyword() &&
-      descriptor->type() != FieldDescriptor::TYPE_MESSAGE;
+  // Unlike most languages, we don't generate Has/Clear members for message
+  // types, because they can always be set to null in C#. They're not really
+  // needed for oneof fields in proto2 either, as everything can be done via
+  // oneof case, but we follow the convention from other languages. Proto3
+  // oneof fields never have Has/Clear members - but will also never have
+  // the explicit optional keyword either.
+  //
+  // None of the built-in helpers (descriptor->has_presence() etc) describe
+  // quite the behavior we want, so the rules are explicit below.
+
+  if (descriptor->is_repeated() ||
+      descriptor->type() == FieldDescriptor::TYPE_MESSAGE) {
+    return false;
   }
+  // has_optional_keyword() has more complex rules for proto2, but that
+  // doesn't matter given the first part of this condition.
+  return IsProto2(descriptor->file()) || descriptor->has_optional_keyword();
 }
 
 inline bool RequiresPresenceBit(const FieldDescriptor* descriptor) {

+ 1 - 0
src/google/protobuf/compiler/java/java_enum_field.cc

@@ -225,6 +225,7 @@ void ImmutableEnumFieldGenerator::GenerateBuilderMembers(
     printer->Print(variables_,
                    "$deprecation$public Builder "
                    "${$set$capitalized_name$Value$}$(int value) {\n"
+                   "  $set_has_field_bit_builder$\n"
                    "  $name$_ = value;\n"
                    "  $on_changed$\n"
                    "  return this;\n"

+ 1 - 1
src/google/protobuf/generated_message_reflection.cc

@@ -1061,7 +1061,7 @@ void Reflection::ListFields(const Message& message,
         if (oneof_case_array[containing_oneof->index()] == field->number()) {
           output->push_back(field);
         }
-      } else if (has_bits) {
+      } else if (has_bits && has_bits_indices[i] != -1) {
         // Equivalent to: HasBit(message, field)
         if (IsIndexInHasBitSet(has_bits, has_bits_indices[i])) {
           output->push_back(field);

+ 9 - 3
src/google/protobuf/proto3_arena_unittest.cc

@@ -217,9 +217,15 @@ TEST(Proto3OptionalTest, OptionalFieldDescriptor) {
 
   for (int i = 0; i < d->field_count(); i++) {
     const FieldDescriptor* f = d->field(i);
-    EXPECT_TRUE(f->has_optional_keyword()) << f->full_name();
-    EXPECT_TRUE(f->has_presence()) << f->full_name();
-    EXPECT_TRUE(f->containing_oneof()) << f->full_name();
+    if (HasPrefixString(f->name(), "singular")) {
+      EXPECT_FALSE(f->has_optional_keyword()) << f->full_name();
+      EXPECT_FALSE(f->has_presence()) << f->full_name();
+      EXPECT_FALSE(f->containing_oneof()) << f->full_name();
+    } else {
+      EXPECT_TRUE(f->has_optional_keyword()) << f->full_name();
+      EXPECT_TRUE(f->has_presence()) << f->full_name();
+      EXPECT_TRUE(f->containing_oneof()) << f->full_name();
+    }
   }
 }
 

+ 4 - 0
src/google/protobuf/unittest_proto3_optional.proto

@@ -72,4 +72,8 @@ message TestProto3Optional {
   optional NestedMessage optional_nested_message = 18;
   optional NestedMessage lazy_nested_message = 19 [lazy = true];
   optional NestedEnum optional_nested_enum = 21;
+
+  // Add some non-optional fields to verify we can mix them.
+  int32 singular_int32 = 22;
+  int64 singular_int64 = 23;
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است