diff --git a/Makefile.am b/Makefile.am index ed852c8d51f3..cca346e9ca03 100644 --- a/Makefile.am +++ b/Makefile.am @@ -68,6 +68,9 @@ csharp_EXTRA_DIST= \ csharp/protos/map_unittest_proto3.proto \ csharp/protos/old_extensions1.proto \ csharp/protos/old_extensions2.proto \ + csharp/protos/unittest_issue6936_a.proto \ + csharp/protos/unittest_issue6936_b.proto \ + csharp/protos/unittest_issue6936_c.proto \ csharp/protos/unittest_custom_options_proto3.proto \ csharp/protos/unittest_import_public_proto3.proto \ csharp/protos/unittest_import_public.proto \ @@ -128,6 +131,9 @@ csharp_EXTRA_DIST= \ csharp/src/Google.Protobuf.Test/SampleNaNs.cs \ csharp/src/Google.Protobuf.Test/TestCornerCases.cs \ csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj \ + csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936A.cs \ + csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936B.cs \ + csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936C.cs \ csharp/src/Google.Protobuf.Test.TestProtos/ForeignMessagePartial.cs \ csharp/src/Google.Protobuf.Test.TestProtos/MapUnittestProto3.cs \ csharp/src/Google.Protobuf.Test.TestProtos/OldExtensions1.cs \ diff --git a/csharp/generate_protos.sh b/csharp/generate_protos.sh index 66d87c02f711..e6687c308e47 100755 --- a/csharp/generate_protos.sh +++ b/csharp/generate_protos.sh @@ -58,6 +58,9 @@ $PROTOC -Isrc -Icsharp/protos \ csharp/protos/unittest.proto \ csharp/protos/unittest_import.proto \ csharp/protos/unittest_import_public.proto \ + csharp/protos/unittest_issue6936_a.proto \ + csharp/protos/unittest_issue6936_b.proto \ + csharp/protos/unittest_issue6936_c.proto \ src/google/protobuf/unittest_well_known_types.proto \ src/google/protobuf/test_messages_proto3.proto \ src/google/protobuf/test_messages_proto2.proto diff --git a/csharp/protos/unittest_issue6936_a.proto b/csharp/protos/unittest_issue6936_a.proto new file mode 100644 index 000000000000..097d083a98a7 --- /dev/null +++ b/csharp/protos/unittest_issue6936_a.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package unittest_issues; + +option csharp_namespace = "UnitTest.Issues.TestProtos"; + +// This file is used as part of a unit test for issue 6936 +// We don't need to use it, we just have to import it in both +// "extensions_issue6936_b.proto" and "extensions_issue6936_c.proto" + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.MessageOptions { + string opt = 50000; +} \ No newline at end of file diff --git a/csharp/protos/unittest_issue6936_b.proto b/csharp/protos/unittest_issue6936_b.proto new file mode 100644 index 000000000000..8f71683ba675 --- /dev/null +++ b/csharp/protos/unittest_issue6936_b.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +import "unittest_issue6936_a.proto"; + +package unittest_issues; + +option csharp_namespace = "UnitTest.Issues.TestProtos"; + +// This file is used as part of a unit test for issue 6936 +// We don't need to use it, we just have to import it in "unittest_issue6936_c.proto" + +message Foo { + option (opt) = "foo"; +} \ No newline at end of file diff --git a/csharp/protos/unittest_issue6936_c.proto b/csharp/protos/unittest_issue6936_c.proto new file mode 100644 index 000000000000..40004ecaaee3 --- /dev/null +++ b/csharp/protos/unittest_issue6936_c.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +import "unittest_issue6936_a.proto"; +import "unittest_issue6936_b.proto"; + +package unittest_issues; + +option csharp_namespace = "UnitTest.Issues.TestProtos"; + +// This file is used as part of a unit test for issue 6936 +// We don't need to use it, we just have to load it at runtime + +message Bar { + option (opt) = "bar"; + Foo foo = 1; +} \ No newline at end of file diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936A.cs b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936A.cs new file mode 100644 index 000000000000..56fde4f00a24 --- /dev/null +++ b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936A.cs @@ -0,0 +1,46 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: unittest_issue6936_a.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace UnitTest.Issues.TestProtos { + + /// Holder for reflection information generated from unittest_issue6936_a.proto + public static partial class UnittestIssue6936AReflection { + + #region Descriptor + /// File descriptor for unittest_issue6936_a.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnittestIssue6936AReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "Chp1bml0dGVzdF9pc3N1ZTY5MzZfYS5wcm90bxIPdW5pdHRlc3RfaXNzdWVz", + "GiBnb29nbGUvcHJvdG9idWYvZGVzY3JpcHRvci5wcm90bzouCgNvcHQSHy5n", + "b29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMY0IYDIAEoCUIdqgIaVW5p", + "dFRlc3QuSXNzdWVzLlRlc3RQcm90b3NiBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Google.Protobuf.Reflection.DescriptorReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, new pb::Extension[] { UnittestIssue6936AExtensions.Opt }, null)); + } + #endregion + + } + /// Holder for extension identifiers generated from the top level of unittest_issue6936_a.proto + public static partial class UnittestIssue6936AExtensions { + public static readonly pb::Extension Opt = + new pb::Extension(50000, pb::FieldCodec.ForString(400002, "")); + } + +} + +#endregion Designer generated code diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936B.cs b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936B.cs new file mode 100644 index 000000000000..6b816f739b82 --- /dev/null +++ b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936B.cs @@ -0,0 +1,145 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: unittest_issue6936_b.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace UnitTest.Issues.TestProtos { + + /// Holder for reflection information generated from unittest_issue6936_b.proto + public static partial class UnittestIssue6936BReflection { + + #region Descriptor + /// File descriptor for unittest_issue6936_b.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnittestIssue6936BReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "Chp1bml0dGVzdF9pc3N1ZTY5MzZfYi5wcm90bxIPdW5pdHRlc3RfaXNzdWVz", + "Ghp1bml0dGVzdF9pc3N1ZTY5MzZfYS5wcm90byIOCgNGb286B4K1GANmb29C", + "HaoCGlVuaXRUZXN0Lklzc3Vlcy5UZXN0UHJvdG9zYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::UnitTest.Issues.TestProtos.UnittestIssue6936AReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.Foo), global::UnitTest.Issues.TestProtos.Foo.Parser, null, null, null, null, null) + })); + } + #endregion + + } + #region Messages + public sealed partial class Foo : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Foo()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::UnitTest.Issues.TestProtos.UnittestIssue6936BReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Foo() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Foo(Foo other) : this() { + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Foo Clone() { + return new Foo(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Foo); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Foo other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Foo other) { + if (other == null) { + return; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936C.cs b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936C.cs new file mode 100644 index 000000000000..a8a73f6cb6ec --- /dev/null +++ b/csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssue6936C.cs @@ -0,0 +1,181 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: unittest_issue6936_c.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace UnitTest.Issues.TestProtos { + + /// Holder for reflection information generated from unittest_issue6936_c.proto + public static partial class UnittestIssue6936CReflection { + + #region Descriptor + /// File descriptor for unittest_issue6936_c.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static UnittestIssue6936CReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "Chp1bml0dGVzdF9pc3N1ZTY5MzZfYy5wcm90bxIPdW5pdHRlc3RfaXNzdWVz", + "Ghp1bml0dGVzdF9pc3N1ZTY5MzZfYS5wcm90bxoadW5pdHRlc3RfaXNzdWU2", + "OTM2X2IucHJvdG8iMQoDQmFyEiEKA2ZvbxgBIAEoCzIULnVuaXR0ZXN0X2lz", + "c3Vlcy5Gb286B4K1GANiYXJCHaoCGlVuaXRUZXN0Lklzc3Vlcy5UZXN0UHJv", + "dG9zYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::UnitTest.Issues.TestProtos.UnittestIssue6936AReflection.Descriptor, global::UnitTest.Issues.TestProtos.UnittestIssue6936BReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.Bar), global::UnitTest.Issues.TestProtos.Bar.Parser, new[]{ "Foo" }, null, null, null, null) + })); + } + #endregion + + } + #region Messages + public sealed partial class Bar : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Bar()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor { + get { return global::UnitTest.Issues.TestProtos.UnittestIssue6936CReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Bar() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Bar(Bar other) : this() { + foo_ = other.foo_ != null ? other.foo_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Bar Clone() { + return new Bar(this); + } + + /// Field number for the "foo" field. + public const int FooFieldNumber = 1; + private global::UnitTest.Issues.TestProtos.Foo foo_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::UnitTest.Issues.TestProtos.Foo Foo { + get { return foo_; } + set { + foo_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) { + return Equals(other as Bar); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Bar other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (!object.Equals(Foo, other.Foo)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() { + int hash = 1; + if (foo_ != null) hash ^= Foo.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) { + if (foo_ != null) { + output.WriteRawTag(10); + output.WriteMessage(Foo); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() { + int size = 0; + if (foo_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Foo); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Bar other) { + if (other == null) { + return; + } + if (other.foo_ != null) { + if (foo_ == null) { + Foo = new global::UnitTest.Issues.TestProtos.Foo(); + } + Foo.MergeFrom(other.Foo); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + if (foo_ == null) { + Foo = new global::UnitTest.Issues.TestProtos.Foo(); + } + input.ReadMessage(Foo); + break; + } + } + } + } + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs index d65a6f202379..bfee5f5d439b 100644 --- a/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs +++ b/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs @@ -193,6 +193,16 @@ public void NoOptions() Assert.NotNull(TestAllTypes.Descriptor.Oneofs[0].CustomOptions); } + [Test] + public void MultipleImportOfSameFileWithExtension() + { + 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); + } + private void AssertOption(T expected, OptionFetcher fetcher, Extension extension, Func, T> descriptorOptionFetcher) where D : IExtendableMessage { T customOptionsValue; diff --git a/csharp/src/Google.Protobuf.Test/testprotos.pb b/csharp/src/Google.Protobuf.Test/testprotos.pb index 818b227b37a8..d00db7fdeb76 100644 Binary files a/csharp/src/Google.Protobuf.Test/testprotos.pb and b/csharp/src/Google.Protobuf.Test/testprotos.pb differ diff --git a/csharp/src/Google.Protobuf/ExtensionRegistry.cs b/csharp/src/Google.Protobuf/ExtensionRegistry.cs index 2318e449e6b0..e631ba353d72 100644 --- a/csharp/src/Google.Protobuf/ExtensionRegistry.cs +++ b/csharp/src/Google.Protobuf/ExtensionRegistry.cs @@ -42,6 +42,19 @@ namespace Google.Protobuf /// public sealed class ExtensionRegistry : ICollection, IDeepCloneable { + internal sealed class ExtensionComparer : IEqualityComparer + { + public bool Equals(Extension a, Extension b) + { + return new ObjectIntPair(a.TargetType, a.FieldNumber).Equals(new ObjectIntPair(b.TargetType, b.FieldNumber)); + } + public int GetHashCode(Extension a) + { + return new ObjectIntPair(a.TargetType, a.FieldNumber).GetHashCode(); + } + + internal static ExtensionComparer Instance = new ExtensionComparer(); + } private IDictionary, Extension> extensions; /// diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs index 388a40b4bb07..56c0caacfde3 100644 --- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs @@ -422,7 +422,8 @@ private void CrossLink() GeneratedClrTypeInfo generatedCodeInfo) { ExtensionRegistry registry = new ExtensionRegistry(); - AddAllExtensions(dependencies, generatedCodeInfo, registry); + registry.AddRange(GetAllExtensions(dependencies, generatedCodeInfo)); + FileDescriptorProto proto; try { @@ -445,9 +446,9 @@ private void CrossLink() } } - private static void AddAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo, ExtensionRegistry registry) + private static IEnumerable GetAllExtensions(FileDescriptor[] dependencies, GeneratedClrTypeInfo generatedInfo) { - registry.AddRange(dependencies.SelectMany(GetAllDependedExtensions).Concat(GetAllGeneratedExtensions(generatedInfo)).ToArray()); + return dependencies.SelectMany(GetAllDependedExtensions).Distinct(ExtensionRegistry.ExtensionComparer.Instance).Concat(GetAllGeneratedExtensions(generatedInfo)); } private static IEnumerable GetAllGeneratedExtensions(GeneratedClrTypeInfo generated)