From 1711999078ca1d435de3958bf963e95a742e972f Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 30 Aug 2018 14:53:06 +0100 Subject: 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 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 are unfortunate. They'll always work, because these lists are always ReadOnlyCollection for a descriptor type... but we can't use IList as that's not covariant, and it's annoyingly fiddly to change the field to be of type ReadOnlyCollection. --- csharp/generate_protos.sh | 3 + csharp/protos/unittest_proto3.proto | 30 ++ .../Google.Protobuf.Test.csproj | 4 + .../Reflection/DescriptorDeclarationTest.cs | 151 ++++++++++ .../TestProtos/UnittestProto3.cs | 333 ++++++++++++++++++++- csharp/src/Google.Protobuf.Test/testprotos.pb | Bin 0 -> 204268 bytes .../Google.Protobuf/Reflection/DescriptorBase.cs | 45 +-- .../Reflection/DescriptorDeclaration.cs | 106 +++++++ .../Google.Protobuf/Reflection/EnumDescriptor.cs | 11 + .../Google.Protobuf/Reflection/FileDescriptor.cs | 80 +++++ .../Reflection/MessageDescriptor.cs | 15 + .../Reflection/ServiceDescriptor.cs | 12 + 12 files changed, 755 insertions(+), 35 deletions(-) create mode 100644 csharp/src/Google.Protobuf.Test/Reflection/DescriptorDeclarationTest.cs create mode 100644 csharp/src/Google.Protobuf.Test/testprotos.pb create mode 100644 csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs 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 @@ netcoreapp1.0 + + + + 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("TestService"); + Assert.NotNull(service.Declaration); + Assert.AreEqual(" This is a test service\n", service.Declaration.LeadingComments); + } + + [Test] + public void MethodComments() + { + var service = unitTestProto3Descriptor.FindTypeByName("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("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("CommentEnum"); + Assert.NotNull(descriptor.Declaration); + Assert.AreEqual(" Leading enum comment\n", descriptor.Declaration.LeadingComments); + } + + [Test] + public void NestedMessageComments() + { + var outer = unitTestProto3Descriptor.FindTypeByName("CommentMessage"); + var nested = outer.FindDescriptor("NestedCommentMessage"); + Assert.NotNull(nested.Declaration); + Assert.AreEqual(" Leading nested message comment\n", nested.Declaration.LeadingComments); + } + + [Test] + public void NestedEnumComments() + { + var outer = unitTestProto3Descriptor.FindTypeByName("CommentMessage"); + var nested = outer.FindDescriptor("NestedCommentEnum"); + Assert.NotNull(nested.Declaration); + Assert.AreEqual(" Leading nested enum comment\n", nested.Declaration.LeadingComments); + } + + [Test] + public void FieldComments() + { + var message = unitTestProto3Descriptor.FindTypeByName("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("CommentMessage"); + var nested = outer.FindDescriptor("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("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("CommentMessage"); + var nested = outer.FindDescriptor("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, } + /// + /// Leading enum comment + /// + public enum CommentEnum { + /// + /// Zero value comment + /// + [pbr::OriginalName("ZERO_VALUE")] ZeroValue = 0, + } + #endregion #region Messages @@ -7301,6 +7315,293 @@ namespace Google.Protobuf.TestProtos { } + /// + /// This is a leading comment + /// + public sealed partial class CommentMessage : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CommentMessage()); + 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::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); + } + + /// Field number for the "text" field. + public const int TextFieldNumber = 1; + private string text_ = ""; + /// + /// Leading field comment + /// + [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 + /// Container for nested types declared in the CommentMessage message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static partial class Types { + /// + /// Leading nested enum comment + /// + public enum NestedCommentEnum { + /// + /// Zero value comment + /// + [pbr::OriginalName("ZERO_VALUE")] ZeroValue = 0, + } + + /// + /// Leading nested message comment + /// + public sealed partial class NestedCommentMessage : pb::IMessage { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new NestedCommentMessage()); + 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::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); + } + + /// Field number for the "nested_text" field. + public const int NestedTextFieldNumber = 1; + private string nestedText_ = ""; + /// + /// Leading nested message field comment + /// + [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 Binary files /dev/null and b/csharp/src/Google.Protobuf.Test/testprotos.pb 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 { /// @@ -37,15 +39,11 @@ namespace Google.Protobuf.Reflection /// 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; } /// @@ -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.) /// - public int Index - { - get { return index; } - } + public int Index { get; } /// /// Returns the name of the entity (field, message etc) being described. @@ -69,17 +64,29 @@ namespace Google.Protobuf.Reflection /// /// The fully qualified name of the descriptor's target. /// - public string FullName - { - get { return fullName; } - } + public string FullName { get; } /// /// The file this descriptor was declared in. /// - public FileDescriptor File - { - get { return file; } - } + public FileDescriptor File { get; } + + /// + /// The declaration information about the descriptor, or null if no declaration information + /// is available for this descriptor. + /// + /// + /// 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. + /// + public DescriptorDeclaration Declaration => File.GetDeclaration(this); + + /// + /// 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. + /// + internal virtual IReadOnlyList 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 +{ + /// + /// Provides additional information about the declaration of a descriptor, + /// such as source location and comments. + /// + public sealed class DescriptorDeclaration + { + /// + /// The descriptor this declaration relates to. + /// + public IDescriptor Descriptor { get; } + + /// + /// The start line of the declaration within the source file. This value is 1-based. + /// + public int StartLine { get; } + /// + /// The start column of the declaration within the source file. This value is 1-based. + /// + public int StartColumn { get; } + + /// + /// // The end line of the declaration within the source file. This value is 1-based. + /// + public int EndLine { get; } + /// + /// The end column of the declaration within the source file. This value is 1-based. + /// + public int EndColumn { get; } + + /// + /// Comments appearing before the declaration. Never null, but may be empty. + /// + public string LeadingComments { get; } + + /// + /// Comments appearing after the declaration. Never null, but may be empty. + /// + public string TrailingComments { get; } + + /// + /// 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. + /// + public IReadOnlyList 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(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 /// public override string Name { get { return proto.Name; } } + internal override IReadOnlyList GetNestedDescriptorListForField(int fieldNumber) + { + switch (fieldNumber) + { + case EnumDescriptorProto.ValueFieldNumber: + return (IReadOnlyList) Values; + default: + return null; + } + } + /// /// The CLR type for this enum. For generated code, this will be a CLR enum type. /// 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(); } + private readonly Lazy> declarations; + private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, IEnumerable 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>(CreateDeclarationMap, LazyThreadSafetyMode.ExecutionAndPublication); + } + + private Dictionary CreateDeclarationMap() + { + var dictionary = new Dictionary(); + foreach (var location in Proto.SourceCodeInfo?.Location ?? Enumerable.Empty()) + { + var descriptor = FindDescriptorForPath(location.Path); + if (descriptor != null) + { + dictionary[descriptor] = DescriptorDeclaration.FromProto(descriptor, location); + } + } + return dictionary; + + IDescriptor FindDescriptorForPath(IList 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 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 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 GetNestedDescriptorListForField(int fieldNumber) + { + switch (fieldNumber) + { + case FileDescriptorProto.ServiceFieldNumber: + return (IReadOnlyList) Services; + case FileDescriptorProto.MessageTypeFieldNumber: + return (IReadOnlyList) MessageTypes; + case FileDescriptorProto.EnumTypeFieldNumber: + return (IReadOnlyList) EnumTypes; + default: + return null; + } + } + } + + internal DescriptorDeclaration GetDeclaration(IDescriptor descriptor) + { + declarations.Value.TryGetValue(descriptor, out var declaration); + return declaration; } /// 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 /// public override string Name => Proto.Name; + internal override IReadOnlyList GetNestedDescriptorListForField(int fieldNumber) + { + switch (fieldNumber) + { + case DescriptorProto.FieldFieldNumber: + return (IReadOnlyList) fieldsInDeclarationOrder; + case DescriptorProto.NestedTypeFieldNumber: + return (IReadOnlyList) NestedTypes; + case DescriptorProto.EnumTypeFieldNumber: + return (IReadOnlyList) EnumTypes; + default: + return null; + } + } + internal DescriptorProto Proto { get; } /// 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 /// public override string Name { get { return proto.Name; } } + internal override IReadOnlyList GetNestedDescriptorListForField(int fieldNumber) + { + switch (fieldNumber) + { + case ServiceDescriptorProto.MethodFieldNumber: + return (IReadOnlyList) methods; + default: + return null; + } + } + internal ServiceDescriptorProto Proto { get { return proto; } } /// -- cgit v1.2.3