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>.
pull/5181/head
Jon Skeet 6 years ago committed by Jon Skeet
parent a6e1cc7e32
commit 1711999078
  1. 3
      csharp/generate_protos.sh
  2. 30
      csharp/protos/unittest_proto3.proto
  3. 4
      csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj
  4. 151
      csharp/src/Google.Protobuf.Test/Reflection/DescriptorDeclarationTest.cs
  5. 333
      csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs
  6. BIN
      csharp/src/Google.Protobuf.Test/testprotos.pb
  7. 45
      csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs
  8. 106
      csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs
  9. 11
      csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
  10. 80
      csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
  11. 15
      csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
  12. 12
      csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs

@ -43,6 +43,9 @@ $PROTOC -Isrc --csharp_out=csharp/src/Google.Protobuf \
# Test protos # Test protos
$PROTOC -Isrc -Icsharp/protos \ $PROTOC -Isrc -Icsharp/protos \
--csharp_out=csharp/src/Google.Protobuf.Test/TestProtos \ --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/map_unittest_proto3.proto \
csharp/protos/unittest_issues.proto \ csharp/protos/unittest_issues.proto \
csharp/protos/unittest_custom_options_proto3.proto \ csharp/protos/unittest_custom_options_proto3.proto \

@ -368,7 +368,9 @@ message FooResponse {}
message FooClientMessage {} message FooClientMessage {}
message FooServerMessage{} message FooServerMessage{}
// This is a test service
service TestService { service TestService {
// This is a test method
rpc Foo(FooRequest) returns (FooResponse); rpc Foo(FooRequest) returns (FooResponse);
rpc Bar(BarRequest) returns (BarResponse); rpc Bar(BarRequest) returns (BarResponse);
} }
@ -378,3 +380,31 @@ message BarRequest {}
message BarResponse {} message BarResponse {}
message TestEmptyMessage {} 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;
}

@ -27,4 +27,8 @@
<TargetFrameworks>netcoreapp1.0</TargetFrameworks> <TargetFrameworks>netcoreapp1.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="testprotos.pb" />
</ItemGroup>
</Project> </Project>

@ -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");
}
}
}

@ -139,23 +139,26 @@ namespace Google.Protobuf.TestProtos {
"NBj//w8gAygEIigKG1Rlc3RDb21tZW50SW5qZWN0aW9uTWVzc2FnZRIJCgFh", "NBj//w8gAygEIigKG1Rlc3RDb21tZW50SW5qZWN0aW9uTWVzc2FnZRIJCgFh",
"GAEgASgJIgwKCkZvb1JlcXVlc3QiDQoLRm9vUmVzcG9uc2UiEgoQRm9vQ2xp", "GAEgASgJIgwKCkZvb1JlcXVlc3QiDQoLRm9vUmVzcG9uc2UiEgoQRm9vQ2xp",
"ZW50TWVzc2FnZSISChBGb29TZXJ2ZXJNZXNzYWdlIgwKCkJhclJlcXVlc3Qi", "ZW50TWVzc2FnZSISChBGb29TZXJ2ZXJNZXNzYWdlIgwKCkJhclJlcXVlc3Qi",
"DQoLQmFyUmVzcG9uc2UiEgoQVGVzdEVtcHR5TWVzc2FnZSpZCgtGb3JlaWdu", "DQoLQmFyUmVzcG9uc2UiEgoQVGVzdEVtcHR5TWVzc2FnZSJwCg5Db21tZW50",
"RW51bRIXChNGT1JFSUdOX1VOU1BFQ0lGSUVEEAASDwoLRk9SRUlHTl9GT08Q", "TWVzc2FnZRIMCgR0ZXh0GAEgASgJGisKFE5lc3RlZENvbW1lbnRNZXNzYWdl",
"BBIPCgtGT1JFSUdOX0JBUhAFEg8KC0ZPUkVJR05fQkFaEAYqdQoUVGVzdEVu", "EhMKC25lc3RlZF90ZXh0GAEgASgJIiMKEU5lc3RlZENvbW1lbnRFbnVtEg4K",
"dW1XaXRoRHVwVmFsdWUSKAokVEVTVF9FTlVNX1dJVEhfRFVQX1ZBTFVFX1VO", "ClpFUk9fVkFMVUUQACpZCgtGb3JlaWduRW51bRIXChNGT1JFSUdOX1VOU1BF",
"U1BFQ0lGSUVEEAASCAoERk9PMRABEggKBEJBUjEQAhIHCgNCQVoQAxIICgRG", "Q0lGSUVEEAASDwoLRk9SRUlHTl9GT08QBBIPCgtGT1JFSUdOX0JBUhAFEg8K",
"T08yEAESCAoEQkFSMhACGgIQASqdAQoOVGVzdFNwYXJzZUVudW0SIAocVEVT", "C0ZPUkVJR05fQkFaEAYqdQoUVGVzdEVudW1XaXRoRHVwVmFsdWUSKAokVEVT",
"VF9TUEFSU0VfRU5VTV9VTlNQRUNJRklFRBAAEgwKCFNQQVJTRV9BEHsSDgoI", "VF9FTlVNX1dJVEhfRFVQX1ZBTFVFX1VOU1BFQ0lGSUVEEAASCAoERk9PMRAB",
"U1BBUlNFX0IQpucDEg8KCFNQQVJTRV9DELKxgAYSFQoIU1BBUlNFX0QQ8f//", "EggKBEJBUjEQAhIHCgNCQVoQAxIICgRGT08yEAESCAoEQkFSMhACGgIQASqd",
"////////ARIVCghTUEFSU0VfRRC03vz///////8BEgwKCFNQQVJTRV9HEAIy", "AQoOVGVzdFNwYXJzZUVudW0SIAocVEVTVF9TUEFSU0VfRU5VTV9VTlNQRUNJ",
"nQEKC1Rlc3RTZXJ2aWNlEkYKA0ZvbxIeLnByb3RvYnVmX3VuaXR0ZXN0My5G", "RklFRBAAEgwKCFNQQVJTRV9BEHsSDgoIU1BBUlNFX0IQpucDEg8KCFNQQVJT",
"b29SZXF1ZXN0Gh8ucHJvdG9idWZfdW5pdHRlc3QzLkZvb1Jlc3BvbnNlEkYK", "RV9DELKxgAYSFQoIU1BBUlNFX0QQ8f//////////ARIVCghTUEFSU0VfRRC0",
"A0JhchIeLnByb3RvYnVmX3VuaXR0ZXN0My5CYXJSZXF1ZXN0Gh8ucHJvdG9i", "3vz///////8BEgwKCFNQQVJTRV9HEAIqHQoLQ29tbWVudEVudW0SDgoKWkVS",
"dWZfdW5pdHRlc3QzLkJhclJlc3BvbnNlQixCDVVuaXR0ZXN0UHJvdG+qAhpH", "T19WQUxVRRAAMp0BCgtUZXN0U2VydmljZRJGCgNGb28SHi5wcm90b2J1Zl91",
"b29nbGUuUHJvdG9idWYuVGVzdFByb3Rvc2IGcHJvdG8z")); "bml0dGVzdDMuRm9vUmVxdWVzdBofLnByb3RvYnVmX3VuaXR0ZXN0My5Gb29S",
"ZXNwb25zZRJGCgNCYXISHi5wcm90b2J1Zl91bml0dGVzdDMuQmFyUmVxdWVz",
"dBofLnByb3RvYnVmX3VuaXR0ZXN0My5CYXJSZXNwb25zZUIsQg1Vbml0dGVz",
"dFByb3RvqgIaR29vZ2xlLlByb3RvYnVmLlRlc3RQcm90b3NiBnByb3RvMw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { global::Google.Protobuf.TestProtos.UnittestImportProto3Reflection.Descriptor, }, 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.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.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), 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.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.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.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 #endregion
@ -233,6 +237,16 @@ namespace Google.Protobuf.TestProtos {
[pbr::OriginalName("SPARSE_G")] SparseG = 2, [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 #endregion
#region Messages #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 #endregion
} }

@ -30,6 +30,8 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion #endregion
using System.Collections.Generic;
namespace Google.Protobuf.Reflection namespace Google.Protobuf.Reflection
{ {
/// <summary> /// <summary>
@ -37,15 +39,11 @@ namespace Google.Protobuf.Reflection
/// </summary> /// </summary>
public abstract class DescriptorBase : IDescriptor 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) internal DescriptorBase(FileDescriptor file, string fullName, int index)
{ {
this.file = file; File = file;
this.fullName = fullName; FullName = fullName;
this.index = index; Index = index;
} }
/// <value> /// <value>
@ -56,10 +54,7 @@ namespace Google.Protobuf.Reflection
/// this descriptor's type. (There can be duplicate values for different /// 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.) /// types, e.g. one enum type with index 0 and one message type with index 0.)
/// </remarks> /// </remarks>
public int Index public int Index { get; }
{
get { return index; }
}
/// <summary> /// <summary>
/// Returns the name of the entity (field, message etc) being described. /// Returns the name of the entity (field, message etc) being described.
@ -69,17 +64,29 @@ namespace Google.Protobuf.Reflection
/// <summary> /// <summary>
/// The fully qualified name of the descriptor's target. /// The fully qualified name of the descriptor's target.
/// </summary> /// </summary>
public string FullName public string FullName { get; }
{
get { return fullName; }
}
/// <value> /// <value>
/// The file this descriptor was declared in. /// The file this descriptor was declared in.
/// </value> /// </value>
public FileDescriptor File public FileDescriptor File { get; }
{
get { return file; } /// <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;
} }
} }

@ -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);
}
}

@ -72,6 +72,17 @@ namespace Google.Protobuf.Reflection
/// </summary> /// </summary>
public override string Name { get { return proto.Name; } } 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> /// <summary>
/// The CLR type for this enum. For generated code, this will be a CLR enum type. /// The CLR type for this enum. For generated code, this will be a CLR enum type.
/// </summary> /// </summary>

@ -34,7 +34,10 @@ using Google.Protobuf.WellKnownTypes;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading;
using static Google.Protobuf.Reflection.SourceCodeInfo.Types;
namespace Google.Protobuf.Reflection namespace Google.Protobuf.Reflection
{ {
@ -55,6 +58,8 @@ namespace Google.Protobuf.Reflection
ForceReflectionInitialization<Value.KindOneofCase>(); 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) private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable<FileDescriptor> dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
{ {
SerializedData = descriptorData; SerializedData = descriptorData;
@ -77,6 +82,81 @@ namespace Google.Protobuf.Reflection
Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service, Services = DescriptorUtil.ConvertAndMakeReadOnly(proto.Service,
(service, index) => (service, index) =>
new ServiceDescriptor(service, this, 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> /// <summary>

@ -115,6 +115,21 @@ namespace Google.Protobuf.Reflection
/// </summary> /// </summary>
public override string Name => Proto.Name; 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; } internal DescriptorProto Proto { get; }
/// <summary> /// <summary>

@ -32,6 +32,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Google.Protobuf.Reflection namespace Google.Protobuf.Reflection
{ {
@ -58,6 +59,17 @@ namespace Google.Protobuf.Reflection
/// </summary> /// </summary>
public override string Name { get { return proto.Name; } } 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; } } internal ServiceDescriptorProto Proto { get { return proto; } }
/// <value> /// <value>

Loading…
Cancel
Save