aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Skeet <jonskeet@google.com>2018-08-30 14:53:06 +0100
committerJon Skeet <skeet@pobox.com>2018-09-22 09:09:15 +0100
commit1711999078ca1d435de3958bf963e95a742e972f (patch)
tree6ea6797a824da7cf26b2535ea4e3a9be9cd5ec95
parenta6e1cc7e328c45a0cb9856c530c8f6cd23314163 (diff)
downloadprotobuf-1711999078ca1d435de3958bf963e95a742e972f.tar.gz
protobuf-1711999078ca1d435de3958bf963e95a742e972f.tar.bz2
protobuf-1711999078ca1d435de3958bf963e95a742e972f.zip
Provide simple access to descriptor declarations in C#
This is primarily for access to comments, which would be expected to be available in a protoc plugin. The implementation has two fiddly aspects: - We use a Lazy<T> to avoid building the map before cross-linking. An alternative would be to crosslink at the end of the constructor, and remove the calls to CrossLink elsewhere. This would be generally better IMO, but deviate from the Java code. - The casts to IReadOnlyList<DescriptorBase> are unfortunate. They'll always work, because these lists are always ReadOnlyCollection<T> for a descriptor type... but we can't use IList<DescriptorBase> as that's not covariant, and it's annoyingly fiddly to change the field to be of type ReadOnlyCollection<T>.
-rwxr-xr-xcsharp/generate_protos.sh3
-rw-r--r--csharp/protos/unittest_proto3.proto30
-rw-r--r--csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj4
-rw-r--r--csharp/src/Google.Protobuf.Test/Reflection/DescriptorDeclarationTest.cs151
-rw-r--r--csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs333
-rw-r--r--csharp/src/Google.Protobuf.Test/testprotos.pbbin0 -> 204268 bytes
-rw-r--r--csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs45
-rw-r--r--csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs106
-rw-r--r--csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs11
-rw-r--r--csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs80
-rw-r--r--csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs15
-rw-r--r--csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs12
12 files changed, 755 insertions, 35 deletions
diff --git a/csharp/generate_protos.sh b/csharp/generate_protos.sh
index 5c748e35..31a4b90c 100755
--- a/csharp/generate_protos.sh
+++ b/csharp/generate_protos.sh
@@ -43,6 +43,9 @@ $PROTOC -Isrc --csharp_out=csharp/src/Google.Protobuf \
# Test protos
$PROTOC -Isrc -Icsharp/protos \
--csharp_out=csharp/src/Google.Protobuf.Test/TestProtos \
+ --descriptor_set_out=csharp/src/Google.Protobuf.Test/testprotos.pb \
+ --include_source_info \
+ --include_imports \
csharp/protos/map_unittest_proto3.proto \
csharp/protos/unittest_issues.proto \
csharp/protos/unittest_custom_options_proto3.proto \
diff --git a/csharp/protos/unittest_proto3.proto b/csharp/protos/unittest_proto3.proto
index ef4933a5..bf88f6bc 100644
--- a/csharp/protos/unittest_proto3.proto
+++ b/csharp/protos/unittest_proto3.proto
@@ -368,7 +368,9 @@ message FooResponse {}
message FooClientMessage {}
message FooServerMessage{}
+// This is a test service
service TestService {
+ // This is a test method
rpc Foo(FooRequest) returns (FooResponse);
rpc Bar(BarRequest) returns (BarResponse);
}
@@ -378,3 +380,31 @@ message BarRequest {}
message BarResponse {}
message TestEmptyMessage {}
+
+// This is leading detached comment 1
+
+// This is leading detached comment 2
+
+// This is a leading comment
+message CommentMessage {
+ // Leading nested message comment
+ message NestedCommentMessage {
+ // Leading nested message field comment
+ string nested_text = 1;
+ }
+
+ // Leading nested enum comment
+ enum NestedCommentEnum {
+ // Zero value comment
+ ZERO_VALUE = 0;
+ }
+
+ // Leading field comment
+ string text = 1; // Trailing field comment
+}
+
+// Leading enum comment
+enum CommentEnum {
+ // Zero value comment
+ ZERO_VALUE = 0;
+}
diff --git a/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj b/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj
index 6a430116..f286e0aa 100644
--- a/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj
+++ b/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj
@@ -27,4 +27,8 @@
<TargetFrameworks>netcoreapp1.0</TargetFrameworks>
</PropertyGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="testprotos.pb" />
+ </ItemGroup>
+
</Project>
diff --git a/csharp/src/Google.Protobuf.Test/Reflection/DescriptorDeclarationTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/DescriptorDeclarationTest.cs
new file mode 100644
index 00000000..b3237455
--- /dev/null
+++ b/csharp/src/Google.Protobuf.Test/Reflection/DescriptorDeclarationTest.cs
@@ -0,0 +1,151 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2018 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
+using Google.Protobuf.Reflection;
+using NUnit.Framework;
+using System.Linq;
+using System.Reflection;
+
+namespace Google.Protobuf.Test.Reflection
+{
+ // In reality this isn't a test for DescriptorDeclaration so much as the way they're loaded.
+ public class DescriptorDeclarationTest
+ {
+ static readonly FileDescriptor unitTestProto3Descriptor = LoadProtos();
+
+ [Test]
+ public void ServiceComments()
+ {
+ var service = unitTestProto3Descriptor.FindTypeByName<ServiceDescriptor>("TestService");
+ Assert.NotNull(service.Declaration);
+ Assert.AreEqual(" This is a test service\n", service.Declaration.LeadingComments);
+ }
+
+ [Test]
+ public void MethodComments()
+ {
+ var service = unitTestProto3Descriptor.FindTypeByName<ServiceDescriptor>("TestService");
+ var method = service.FindMethodByName("Foo");
+ Assert.NotNull(method.Declaration);
+ Assert.AreEqual(" This is a test method\n", method.Declaration.LeadingComments);
+ }
+
+ [Test]
+ public void MessageComments()
+ {
+ var message = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
+ Assert.NotNull(message.Declaration);
+ Assert.AreEqual(" This is a leading comment\n", message.Declaration.LeadingComments);
+ Assert.AreEqual(new[] { " This is leading detached comment 1\n", " This is leading detached comment 2\n" },
+ message.Declaration.LeadingDetachedComments);
+ }
+
+ [Test]
+ public void EnumComments()
+ {
+ var descriptor = unitTestProto3Descriptor.FindTypeByName<EnumDescriptor>("CommentEnum");
+ Assert.NotNull(descriptor.Declaration);
+ Assert.AreEqual(" Leading enum comment\n", descriptor.Declaration.LeadingComments);
+ }
+
+ [Test]
+ public void NestedMessageComments()
+ {
+ var outer = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
+ var nested = outer.FindDescriptor<MessageDescriptor>("NestedCommentMessage");
+ Assert.NotNull(nested.Declaration);
+ Assert.AreEqual(" Leading nested message comment\n", nested.Declaration.LeadingComments);
+ }
+
+ [Test]
+ public void NestedEnumComments()
+ {
+ var outer = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
+ var nested = outer.FindDescriptor<EnumDescriptor>("NestedCommentEnum");
+ Assert.NotNull(nested.Declaration);
+ Assert.AreEqual(" Leading nested enum comment\n", nested.Declaration.LeadingComments);
+ }
+
+ [Test]
+ public void FieldComments()
+ {
+ var message = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
+ var field = message.FindFieldByName("text");
+ Assert.NotNull(field.Declaration);
+ Assert.AreEqual(" Leading field comment\n", field.Declaration.LeadingComments);
+ Assert.AreEqual(" Trailing field comment\n", field.Declaration.TrailingComments);
+ }
+
+ [Test]
+ public void NestedMessageFieldComments()
+ {
+ var outer = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
+ var nested = outer.FindDescriptor<MessageDescriptor>("NestedCommentMessage");
+ var field = nested.FindFieldByName("nested_text");
+ Assert.NotNull(field.Declaration);
+ Assert.AreEqual(" Leading nested message field comment\n", field.Declaration.LeadingComments);
+ }
+
+ [Test]
+ public void EnumValueComments()
+ {
+ var enumDescriptor = unitTestProto3Descriptor.FindTypeByName<EnumDescriptor>("CommentEnum");
+ var value = enumDescriptor.FindValueByName("ZERO_VALUE");
+ Assert.NotNull(value.Declaration);
+ Assert.AreEqual(" Zero value comment\n", value.Declaration.LeadingComments);
+ }
+
+ [Test]
+ public void NestedEnumValueComments()
+ {
+ var outer = unitTestProto3Descriptor.FindTypeByName<MessageDescriptor>("CommentMessage");
+ var nested = outer.FindDescriptor<EnumDescriptor>("NestedCommentEnum");
+ var value = nested.FindValueByName("ZERO_VALUE");
+ Assert.NotNull(value.Declaration);
+ Assert.AreEqual(" Zero value comment\n", value.Declaration.LeadingComments);
+ }
+
+ private static FileDescriptor LoadProtos()
+ {
+ var type = typeof(DescriptorDeclarationTest);
+ // TODO: Make this simpler :)
+ FileDescriptorSet descriptorSet;
+ using (var stream = type.GetTypeInfo().Assembly.GetManifestResourceStream($"Google.Protobuf.Test.testprotos.pb"))
+ {
+ descriptorSet = FileDescriptorSet.Parser.ParseFrom(stream);
+ }
+ var byteStrings = descriptorSet.File.Select(f => f.ToByteString()).ToList();
+ var descriptors = FileDescriptor.BuildFromByteStrings(byteStrings);
+ return descriptors.Single(d => d.Name == "unittest_proto3.proto");
+ }
+ }
+}
diff --git a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs
index d5dbe866..04221536 100644
--- a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs
+++ b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs
@@ -139,23 +139,26 @@ namespace Google.Protobuf.TestProtos {
"NBj//w8gAygEIigKG1Rlc3RDb21tZW50SW5qZWN0aW9uTWVzc2FnZRIJCgFh",
"GAEgASgJIgwKCkZvb1JlcXVlc3QiDQoLRm9vUmVzcG9uc2UiEgoQRm9vQ2xp",
"ZW50TWVzc2FnZSISChBGb29TZXJ2ZXJNZXNzYWdlIgwKCkJhclJlcXVlc3Qi",
- "DQoLQmFyUmVzcG9uc2UiEgoQVGVzdEVtcHR5TWVzc2FnZSpZCgtGb3JlaWdu",
- "RW51bRIXChNGT1JFSUdOX1VOU1BFQ0lGSUVEEAASDwoLRk9SRUlHTl9GT08Q",
- "BBIPCgtGT1JFSUdOX0JBUhAFEg8KC0ZPUkVJR05fQkFaEAYqdQoUVGVzdEVu",
- "dW1XaXRoRHVwVmFsdWUSKAokVEVTVF9FTlVNX1dJVEhfRFVQX1ZBTFVFX1VO",
- "U1BFQ0lGSUVEEAASCAoERk9PMRABEggKBEJBUjEQAhIHCgNCQVoQAxIICgRG",
- "T08yEAESCAoEQkFSMhACGgIQASqdAQoOVGVzdFNwYXJzZUVudW0SIAocVEVT",
- "VF9TUEFSU0VfRU5VTV9VTlNQRUNJRklFRBAAEgwKCFNQQVJTRV9BEHsSDgoI",
- "U1BBUlNFX0IQpucDEg8KCFNQQVJTRV9DELKxgAYSFQoIU1BBUlNFX0QQ8f//",
- "////////ARIVCghTUEFSU0VfRRC03vz///////8BEgwKCFNQQVJTRV9HEAIy",
- "nQEKC1Rlc3RTZXJ2aWNlEkYKA0ZvbxIeLnByb3RvYnVmX3VuaXR0ZXN0My5G",
- "b29SZXF1ZXN0Gh8ucHJvdG9idWZfdW5pdHRlc3QzLkZvb1Jlc3BvbnNlEkYK",
- "A0JhchIeLnByb3RvYnVmX3VuaXR0ZXN0My5CYXJSZXF1ZXN0Gh8ucHJvdG9i",
- "dWZfdW5pdHRlc3QzLkJhclJlc3BvbnNlQixCDVVuaXR0ZXN0UHJvdG+qAhpH",
- "b29nbGUuUHJvdG9idWYuVGVzdFByb3Rvc2IGcHJvdG8z"));
+ "DQoLQmFyUmVzcG9uc2UiEgoQVGVzdEVtcHR5TWVzc2FnZSJwCg5Db21tZW50",
+ "TWVzc2FnZRIMCgR0ZXh0GAEgASgJGisKFE5lc3RlZENvbW1lbnRNZXNzYWdl",
+ "EhMKC25lc3RlZF90ZXh0GAEgASgJIiMKEU5lc3RlZENvbW1lbnRFbnVtEg4K",
+ "ClpFUk9fVkFMVUUQACpZCgtGb3JlaWduRW51bRIXChNGT1JFSUdOX1VOU1BF",
+ "Q0lGSUVEEAASDwoLRk9SRUlHTl9GT08QBBIPCgtGT1JFSUdOX0JBUhAFEg8K",
+ "C0ZPUkVJR05fQkFaEAYqdQoUVGVzdEVudW1XaXRoRHVwVmFsdWUSKAokVEVT",
+ "VF9FTlVNX1dJVEhfRFVQX1ZBTFVFX1VOU1BFQ0lGSUVEEAASCAoERk9PMRAB",
+ "EggKBEJBUjEQAhIHCgNCQVoQAxIICgRGT08yEAESCAoEQkFSMhACGgIQASqd",
+ "AQoOVGVzdFNwYXJzZUVudW0SIAocVEVTVF9TUEFSU0VfRU5VTV9VTlNQRUNJ",
+ "RklFRBAAEgwKCFNQQVJTRV9BEHsSDgoIU1BBUlNFX0IQpucDEg8KCFNQQVJT",
+ "RV9DELKxgAYSFQoIU1BBUlNFX0QQ8f//////////ARIVCghTUEFSU0VfRRC0",
+ "3vz///////8BEgwKCFNQQVJTRV9HEAIqHQoLQ29tbWVudEVudW0SDgoKWkVS",
+ "T19WQUxVRRAAMp0BCgtUZXN0U2VydmljZRJGCgNGb28SHi5wcm90b2J1Zl91",
+ "bml0dGVzdDMuRm9vUmVxdWVzdBofLnByb3RvYnVmX3VuaXR0ZXN0My5Gb29S",
+ "ZXNwb25zZRJGCgNCYXISHi5wcm90b2J1Zl91bml0dGVzdDMuQmFyUmVxdWVz",
+ "dBofLnByb3RvYnVmX3VuaXR0ZXN0My5CYXJSZXNwb25zZUIsQg1Vbml0dGVz",
+ "dFByb3RvqgIaR29vZ2xlLlByb3RvYnVmLlRlc3RQcm90b3NiBnByb3RvMw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { global::Google.Protobuf.TestProtos.UnittestImportProto3Reflection.Descriptor, },
- new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Google.Protobuf.TestProtos.ForeignEnum), typeof(global::Google.Protobuf.TestProtos.TestEnumWithDupValue), typeof(global::Google.Protobuf.TestProtos.TestSparseEnum), }, new pbr::GeneratedClrTypeInfo[] {
+ new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Google.Protobuf.TestProtos.ForeignEnum), typeof(global::Google.Protobuf.TestProtos.TestEnumWithDupValue), typeof(global::Google.Protobuf.TestProtos.TestSparseEnum), typeof(global::Google.Protobuf.TestProtos.CommentEnum), }, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.TestAllTypes), global::Google.Protobuf.TestProtos.TestAllTypes.Parser, new[]{ "SingleInt32", "SingleInt64", "SingleUint32", "SingleUint64", "SingleSint32", "SingleSint64", "SingleFixed32", "SingleFixed64", "SingleSfixed32", "SingleSfixed64", "SingleFloat", "SingleDouble", "SingleBool", "SingleString", "SingleBytes", "SingleNestedMessage", "SingleForeignMessage", "SingleImportMessage", "SingleNestedEnum", "SingleForeignEnum", "SingleImportEnum", "SinglePublicImportMessage", "RepeatedInt32", "RepeatedInt64", "RepeatedUint32", "RepeatedUint64", "RepeatedSint32", "RepeatedSint64", "RepeatedFixed32", "RepeatedFixed64", "RepeatedSfixed32", "RepeatedSfixed64", "RepeatedFloat", "RepeatedDouble", "RepeatedBool", "RepeatedString", "RepeatedBytes", "RepeatedNestedMessage", "RepeatedForeignMessage", "RepeatedImportMessage", "RepeatedNestedEnum", "RepeatedForeignEnum", "RepeatedImportEnum", "RepeatedPublicImportMessage", "OneofUint32", "OneofNestedMessage", "OneofString", "OneofBytes" }, new[]{ "OneofField" }, new[]{ typeof(global::Google.Protobuf.TestProtos.TestAllTypes.Types.NestedEnum) }, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.TestAllTypes.Types.NestedMessage), global::Google.Protobuf.TestProtos.TestAllTypes.Types.NestedMessage.Parser, new[]{ "Bb" }, null, null, null)}),
new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.NestedTestAllTypes), global::Google.Protobuf.TestProtos.NestedTestAllTypes.Parser, new[]{ "Child", "Payload", "RepeatedChild" }, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.TestDeprecatedFields), global::Google.Protobuf.TestProtos.TestDeprecatedFields.Parser, new[]{ "DeprecatedInt32" }, null, null, null),
@@ -190,7 +193,8 @@ namespace Google.Protobuf.TestProtos {
new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.FooServerMessage), global::Google.Protobuf.TestProtos.FooServerMessage.Parser, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.BarRequest), global::Google.Protobuf.TestProtos.BarRequest.Parser, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.BarResponse), global::Google.Protobuf.TestProtos.BarResponse.Parser, null, null, null, null),
- new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.TestEmptyMessage), global::Google.Protobuf.TestProtos.TestEmptyMessage.Parser, null, null, null, null)
+ new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.TestEmptyMessage), global::Google.Protobuf.TestProtos.TestEmptyMessage.Parser, null, null, null, null),
+ new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.CommentMessage), global::Google.Protobuf.TestProtos.CommentMessage.Parser, new[]{ "Text" }, null, new[]{ typeof(global::Google.Protobuf.TestProtos.CommentMessage.Types.NestedCommentEnum) }, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Google.Protobuf.TestProtos.CommentMessage.Types.NestedCommentMessage), global::Google.Protobuf.TestProtos.CommentMessage.Types.NestedCommentMessage.Parser, new[]{ "NestedText" }, null, null, null)})
}));
}
#endregion
@@ -233,6 +237,16 @@ namespace Google.Protobuf.TestProtos {
[pbr::OriginalName("SPARSE_G")] SparseG = 2,
}
+ /// <summary>
+ /// Leading enum comment
+ /// </summary>
+ public enum CommentEnum {
+ /// <summary>
+ /// Zero value comment
+ /// </summary>
+ [pbr::OriginalName("ZERO_VALUE")] ZeroValue = 0,
+ }
+
#endregion
#region Messages
@@ -7301,6 +7315,293 @@ namespace Google.Protobuf.TestProtos {
}
+ /// <summary>
+ /// This is a leading comment
+ /// </summary>
+ public sealed partial class CommentMessage : pb::IMessage<CommentMessage> {
+ private static readonly pb::MessageParser<CommentMessage> _parser = new pb::MessageParser<CommentMessage>(() => new CommentMessage());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pb::MessageParser<CommentMessage> Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Google.Protobuf.TestProtos.UnittestProto3Reflection.Descriptor.MessageTypes[35]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public CommentMessage() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public CommentMessage(CommentMessage other) : this() {
+ text_ = other.text_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public CommentMessage Clone() {
+ return new CommentMessage(this);
+ }
+
+ /// <summary>Field number for the "text" field.</summary>
+ public const int TextFieldNumber = 1;
+ private string text_ = "";
+ /// <summary>
+ /// Leading field comment
+ /// </summary>
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string Text {
+ get { return text_; }
+ set {
+ text_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override bool Equals(object other) {
+ return Equals(other as CommentMessage);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public bool Equals(CommentMessage other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (Text != other.Text) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (Text.Length != 0) hash ^= Text.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 (Text.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(Text);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int CalculateSize() {
+ int size = 0;
+ if (Text.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(Text);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(CommentMessage other) {
+ if (other == null) {
+ return;
+ }
+ if (other.Text.Length != 0) {
+ Text = other.Text;
+ }
+ _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: {
+ Text = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+
+ #region Nested types
+ /// <summary>Container for nested types declared in the CommentMessage message type.</summary>
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static partial class Types {
+ /// <summary>
+ /// Leading nested enum comment
+ /// </summary>
+ public enum NestedCommentEnum {
+ /// <summary>
+ /// Zero value comment
+ /// </summary>
+ [pbr::OriginalName("ZERO_VALUE")] ZeroValue = 0,
+ }
+
+ /// <summary>
+ /// Leading nested message comment
+ /// </summary>
+ public sealed partial class NestedCommentMessage : pb::IMessage<NestedCommentMessage> {
+ private static readonly pb::MessageParser<NestedCommentMessage> _parser = new pb::MessageParser<NestedCommentMessage>(() => new NestedCommentMessage());
+ private pb::UnknownFieldSet _unknownFields;
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pb::MessageParser<NestedCommentMessage> Parser { get { return _parser; } }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public static pbr::MessageDescriptor Descriptor {
+ get { return global::Google.Protobuf.TestProtos.CommentMessage.Descriptor.NestedTypes[0]; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ pbr::MessageDescriptor pb::IMessage.Descriptor {
+ get { return Descriptor; }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public NestedCommentMessage() {
+ OnConstruction();
+ }
+
+ partial void OnConstruction();
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public NestedCommentMessage(NestedCommentMessage other) : this() {
+ nestedText_ = other.nestedText_;
+ _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public NestedCommentMessage Clone() {
+ return new NestedCommentMessage(this);
+ }
+
+ /// <summary>Field number for the "nested_text" field.</summary>
+ public const int NestedTextFieldNumber = 1;
+ private string nestedText_ = "";
+ /// <summary>
+ /// Leading nested message field comment
+ /// </summary>
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public string NestedText {
+ get { return nestedText_; }
+ set {
+ nestedText_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override bool Equals(object other) {
+ return Equals(other as NestedCommentMessage);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public bool Equals(NestedCommentMessage other) {
+ if (ReferenceEquals(other, null)) {
+ return false;
+ }
+ if (ReferenceEquals(other, this)) {
+ return true;
+ }
+ if (NestedText != other.NestedText) return false;
+ return Equals(_unknownFields, other._unknownFields);
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public override int GetHashCode() {
+ int hash = 1;
+ if (NestedText.Length != 0) hash ^= NestedText.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 (NestedText.Length != 0) {
+ output.WriteRawTag(10);
+ output.WriteString(NestedText);
+ }
+ if (_unknownFields != null) {
+ _unknownFields.WriteTo(output);
+ }
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public int CalculateSize() {
+ int size = 0;
+ if (NestedText.Length != 0) {
+ size += 1 + pb::CodedOutputStream.ComputeStringSize(NestedText);
+ }
+ if (_unknownFields != null) {
+ size += _unknownFields.CalculateSize();
+ }
+ return size;
+ }
+
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+ public void MergeFrom(NestedCommentMessage other) {
+ if (other == null) {
+ return;
+ }
+ if (other.NestedText.Length != 0) {
+ NestedText = other.NestedText;
+ }
+ _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: {
+ NestedText = input.ReadString();
+ break;
+ }
+ }
+ }
+ }
+
+ }
+
+ }
+ #endregion
+
+ }
+
#endregion
}
diff --git a/csharp/src/Google.Protobuf.Test/testprotos.pb b/csharp/src/Google.Protobuf.Test/testprotos.pb
new file mode 100644
index 00000000..94ff3817
--- /dev/null
+++ b/csharp/src/Google.Protobuf.Test/testprotos.pb
Binary files differ
diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs
index 194041a8..bfbebf17 100644
--- a/csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs
+++ b/csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs
@@ -30,6 +30,8 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
+using System.Collections.Generic;
+
namespace Google.Protobuf.Reflection
{
/// <summary>
@@ -37,15 +39,11 @@ namespace Google.Protobuf.Reflection
/// </summary>
public abstract class DescriptorBase : IDescriptor
{
- private readonly FileDescriptor file;
- private readonly string fullName;
- private readonly int index;
-
internal DescriptorBase(FileDescriptor file, string fullName, int index)
{
- this.file = file;
- this.fullName = fullName;
- this.index = index;
+ File = file;
+ FullName = fullName;
+ Index = index;
}
/// <value>
@@ -56,10 +54,7 @@ namespace Google.Protobuf.Reflection
/// this descriptor's type. (There can be duplicate values for different
/// types, e.g. one enum type with index 0 and one message type with index 0.)
/// </remarks>
- public int Index
- {
- get { return index; }
- }
+ public int Index { get; }
/// <summary>
/// Returns the name of the entity (field, message etc) being described.
@@ -69,17 +64,29 @@ namespace Google.Protobuf.Reflection
/// <summary>
/// The fully qualified name of the descriptor's target.
/// </summary>
- public string FullName
- {
- get { return fullName; }
- }
+ public string FullName { get; }
/// <value>
/// The file this descriptor was declared in.
/// </value>
- public FileDescriptor File
- {
- get { return file; }
- }
+ public FileDescriptor File { get; }
+
+ /// <summary>
+ /// The declaration information about the descriptor, or null if no declaration information
+ /// is available for this descriptor.
+ /// </summary>
+ /// <remarks>
+ /// This information is typically only available for dynamically loaded descriptors,
+ /// for example within a protoc plugin where the full descriptors, including source info,
+ /// are passed to the code by protoc.
+ /// </remarks>
+ public DescriptorDeclaration Declaration => File.GetDeclaration(this);
+
+ /// <summary>
+ /// Retrieves the list of nested descriptors corresponding to the given field number, if any.
+ /// If the field is unknown or not a nested descriptor list, return null to terminate the search.
+ /// The default implementation returns null.
+ /// </summary>
+ internal virtual IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) => null;
}
} \ No newline at end of file
diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs
new file mode 100644
index 00000000..613d6545
--- /dev/null
+++ b/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs
@@ -0,0 +1,106 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2018 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using static Google.Protobuf.Reflection.SourceCodeInfo.Types;
+
+namespace Google.Protobuf.Reflection
+{
+ /// <summary>
+ /// Provides additional information about the declaration of a descriptor,
+ /// such as source location and comments.
+ /// </summary>
+ public sealed class DescriptorDeclaration
+ {
+ /// <summary>
+ /// The descriptor this declaration relates to.
+ /// </summary>
+ public IDescriptor Descriptor { get; }
+
+ /// <summary>
+ /// The start line of the declaration within the source file. This value is 1-based.
+ /// </summary>
+ public int StartLine { get; }
+ /// <summary>
+ /// The start column of the declaration within the source file. This value is 1-based.
+ /// </summary>
+ public int StartColumn { get; }
+
+ /// <summary>
+ /// // The end line of the declaration within the source file. This value is 1-based.
+ /// </summary>
+ public int EndLine { get; }
+ /// <summary>
+ /// The end column of the declaration within the source file. This value is 1-based.
+ /// </summary>
+ public int EndColumn { get; }
+
+ /// <summary>
+ /// Comments appearing before the declaration. Never null, but may be empty.
+ /// </summary>
+ public string LeadingComments { get; }
+
+ /// <summary>
+ /// Comments appearing after the declaration. Never null, but may be empty.
+ /// </summary>
+ public string TrailingComments { get; }
+
+ /// <summary>
+ /// Comments appearing before the declaration, but separated from it by blank
+ /// lines. Each string represents a paragraph of comments. The list is never null,
+ /// but may be empty. Likewise each element is never null, but may be empty.
+ /// </summary>
+ public IReadOnlyList<string> LeadingDetachedComments { get; }
+
+ private DescriptorDeclaration(IDescriptor descriptor, Location location)
+ {
+ // TODO: Validation
+ Descriptor = descriptor;
+ bool hasEndLine = location.Span.Count == 4;
+ // Lines and columns are 0-based in the proto.
+ StartLine = location.Span[0] + 1;
+ StartColumn = location.Span[1] + 1;
+ EndLine = hasEndLine ? location.Span[2] + 1 : StartLine;
+ EndColumn = location.Span[hasEndLine ? 3 : 2] + 1;
+ LeadingComments = location.LeadingComments;
+ TrailingComments = location.TrailingComments;
+ LeadingDetachedComments = new ReadOnlyCollection<string>(location.LeadingDetachedComments.ToList());
+ }
+
+ internal static DescriptorDeclaration FromProto(IDescriptor descriptor, Location location) =>
+ new DescriptorDeclaration(descriptor, location);
+ }
+}
diff --git a/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
index 89c73a61..4c5457a7 100644
--- a/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
@@ -72,6 +72,17 @@ namespace Google.Protobuf.Reflection
/// </summary>
public override string Name { get { return proto.Name; } }
+ internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
+ {
+ switch (fieldNumber)
+ {
+ case EnumDescriptorProto.ValueFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) Values;
+ default:
+ return null;
+ }
+ }
+
/// <summary>
/// The CLR type for this enum. For generated code, this will be a CLR enum type.
/// </summary>
diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
index 216e03cc..6d4520c0 100644
--- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
@@ -34,7 +34,10 @@ using Google.Protobuf.WellKnownTypes;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Diagnostics;
using System.Linq;
+using System.Threading;
+using static Google.Protobuf.Reflection.SourceCodeInfo.Types;
namespace Google.Protobuf.Reflection
{
@@ -55,6 +58,8 @@ namespace Google.Protobuf.Reflection
ForceReflectionInitialization<Value.KindOneofCase>();
}
+ private readonly Lazy<Dictionary<IDescriptor, DescriptorDeclaration>> declarations;
+
private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
{
SerializedData = descriptorData;
@@ -77,6 +82,81 @@ namespace Google.Protobuf.Reflection
Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service,
(service, index) =>
new ServiceDescriptor(service, this, index));
+
+ declarations = new Lazy<Dictionary<IDescriptor, DescriptorDeclaration>>(CreateDeclarationMap, LazyThreadSafetyMode.ExecutionAndPublication);
+ }
+
+ private Dictionary<IDescriptor, DescriptorDeclaration> CreateDeclarationMap()
+ {
+ var dictionary = new Dictionary<IDescriptor, DescriptorDeclaration>();
+ foreach (var location in Proto.SourceCodeInfo?.Location ?? Enumerable.Empty<Location>())
+ {
+ var descriptor = FindDescriptorForPath(location.Path);
+ if (descriptor != null)
+ {
+ dictionary[descriptor] = DescriptorDeclaration.FromProto(descriptor, location);
+ }
+ }
+ return dictionary;
+
+ IDescriptor FindDescriptorForPath(IList<int> path)
+ {
+ // All complete declarations have an even, non-empty path length
+ // (There can be an empty path for a descriptor declaration, but that can't have any comments,
+ // so we currently ignore it.)
+ if (path.Count == 0 || (path.Count & 1) != 0)
+ {
+ return null;
+ }
+ IReadOnlyList<DescriptorBase> topLevelList = GetNestedDescriptorListForField(path[0]);
+ DescriptorBase current = GetDescriptorFromList(topLevelList, path[1]);
+
+ for (int i = 2; current != null && i < path.Count; i += 2)
+ {
+ var list = current.GetNestedDescriptorListForField(path[i]);
+ current = GetDescriptorFromList(list, path[i + 1]);
+ }
+ return current;
+ }
+
+ DescriptorBase GetDescriptorFromList(IReadOnlyList<DescriptorBase> list, int index)
+ {
+ // This is fine: it may be a newer version of protobuf than we understand, with a new descriptor
+ // field.
+ if (list == null)
+ {
+ return null;
+ }
+ // We *could* return null to silently continue, but this is basically data corruption.
+ if (index < 0 || index >= list.Count)
+ {
+ // We don't have much extra information to give at this point unfortunately. If this becomes a problem,
+ // we can pass in the complete path and report that and the file name.
+ throw new InvalidProtocolBufferException($"Invalid descriptor location path: index out of range");
+ }
+ return list[index];
+ }
+
+ IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
+ {
+ switch (fieldNumber)
+ {
+ case FileDescriptorProto.ServiceFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) Services;
+ case FileDescriptorProto.MessageTypeFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) MessageTypes;
+ case FileDescriptorProto.EnumTypeFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) EnumTypes;
+ default:
+ return null;
+ }
+ }
+ }
+
+ internal DescriptorDeclaration GetDeclaration(IDescriptor descriptor)
+ {
+ declarations.Value.TryGetValue(descriptor, out var declaration);
+ return declaration;
}
/// <summary>
diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
index dbb6768b..03fd63ce 100644
--- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
@@ -115,6 +115,21 @@ namespace Google.Protobuf.Reflection
/// </summary>
public override string Name => Proto.Name;
+ internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
+ {
+ switch (fieldNumber)
+ {
+ case DescriptorProto.FieldFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) fieldsInDeclarationOrder;
+ case DescriptorProto.NestedTypeFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) NestedTypes;
+ case DescriptorProto.EnumTypeFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) EnumTypes;
+ default:
+ return null;
+ }
+ }
+
internal DescriptorProto Proto { get; }
/// <summary>
diff --git a/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
index fe5c072c..da570551 100644
--- a/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
@@ -32,6 +32,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
namespace Google.Protobuf.Reflection
{
@@ -58,6 +59,17 @@ namespace Google.Protobuf.Reflection
/// </summary>
public override string Name { get { return proto.Name; } }
+ internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
+ {
+ switch (fieldNumber)
+ {
+ case ServiceDescriptorProto.MethodFieldNumber:
+ return (IReadOnlyList<DescriptorBase>) methods;
+ default:
+ return null;
+ }
+ }
+
internal ServiceDescriptorProto Proto { get { return proto; } }
/// <value>