Commit 047575f2 authored by Jon Skeet's avatar Jon Skeet Committed by Jon Skeet

Support custom options in C#

This consists of:
- Changing the codegen for the fixed set of options protos, to parse unknown fields instead of skipping them
- Add a new CustomOptions type in the C# support library
- Expose CustomOptions properties from the immutable proto wrappers in the support library

Only single-value options are currently supported, and fetching options values requires getting the type right
and knowing the field number. Both of these can be addressed at a later time.

Fixes #2143, at least as a first pass.
parent eed99519
...@@ -60,6 +60,7 @@ csharp_EXTRA_DIST= \ ...@@ -60,6 +60,7 @@ csharp_EXTRA_DIST= \
csharp/keys/Google.Protobuf.public.snk \ csharp/keys/Google.Protobuf.public.snk \
csharp/keys/Google.Protobuf.snk \ csharp/keys/Google.Protobuf.snk \
csharp/keys/README.md \ csharp/keys/README.md \
csharp/protos/unittest_custom_options_proto3.proto \
csharp/protos/unittest_issues.proto \ csharp/protos/unittest_issues.proto \
csharp/src/AddressBook/AddPerson.cs \ csharp/src/AddressBook/AddPerson.cs \
csharp/src/AddressBook/Addressbook.cs \ csharp/src/AddressBook/Addressbook.cs \
...@@ -92,6 +93,7 @@ csharp_EXTRA_DIST= \ ...@@ -92,6 +93,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs \ csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs \
csharp/src/Google.Protobuf.Test/JsonParserTest.cs \ csharp/src/Google.Protobuf.Test/JsonParserTest.cs \
csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs \ csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs \
csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs \
csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs \ csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs \
csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs \ csharp/src/Google.Protobuf.Test/Reflection/FieldAccessTest.cs \
csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs \ csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs \
...@@ -101,6 +103,7 @@ csharp_EXTRA_DIST= \ ...@@ -101,6 +103,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs \ csharp/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs \
csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs \ csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs \
csharp/src/Google.Protobuf.Test/TestProtos/TestMessagesProto3.cs \ csharp/src/Google.Protobuf.Test/TestProtos/TestMessagesProto3.cs \
csharp/src/Google.Protobuf.Test/TestProtos/UnittestCustomOptionsProto3.cs \
csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportProto3.cs \ csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportProto3.cs \
csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportPublicProto3.cs \ csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportPublicProto3.cs \
csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs \ csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs \
...@@ -140,6 +143,7 @@ csharp_EXTRA_DIST= \ ...@@ -140,6 +143,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf/MessageParser.cs \ csharp/src/Google.Protobuf/MessageParser.cs \
csharp/src/Google.Protobuf/ProtoPreconditions.cs \ csharp/src/Google.Protobuf/ProtoPreconditions.cs \
csharp/src/Google.Protobuf/Properties/AssemblyInfo.cs \ csharp/src/Google.Protobuf/Properties/AssemblyInfo.cs \
csharp/src/Google.Protobuf/Reflection/CustomOptions.cs \
csharp/src/Google.Protobuf/Reflection/Descriptor.cs \ csharp/src/Google.Protobuf/Reflection/Descriptor.cs \
csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs \ csharp/src/Google.Protobuf/Reflection/DescriptorBase.cs \
csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs \ csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs \
......
...@@ -50,9 +50,10 @@ $PROTOC -Isrc --csharp_out=csharp/src/Google.Protobuf.Test \ ...@@ -50,9 +50,10 @@ $PROTOC -Isrc --csharp_out=csharp/src/Google.Protobuf.Test \
src/google/protobuf/unittest_well_known_types.proto src/google/protobuf/unittest_well_known_types.proto
# Different base namespace to the protos above # Different base namespace to the protos above
$PROTOC -Icsharp/protos --csharp_out=csharp/src/Google.Protobuf.Test \ $PROTOC -Isrc -Icsharp/protos --csharp_out=csharp/src/Google.Protobuf.Test \
--csharp_opt=base_namespace=UnitTest.Issues \ --csharp_opt=base_namespace=UnitTest.Issues \
csharp/protos/unittest_issues.proto csharp/protos/unittest_issues.proto \
csharp/protos/unittest_custom_options_proto3.proto
# Don't specify a base namespace at all; we just want to make sure the # Don't specify a base namespace at all; we just want to make sure the
# results end up in TestProtos. # results end up in TestProtos.
......
This diff is collapsed.
This diff is collapsed.
...@@ -2779,6 +2779,8 @@ namespace Google.Protobuf.Reflection { ...@@ -2779,6 +2779,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; } get { return Descriptor; }
} }
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public FileOptions() { public FileOptions() {
OnConstruction(); OnConstruction();
...@@ -3299,7 +3301,7 @@ namespace Google.Protobuf.Reflection { ...@@ -3299,7 +3301,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) { while ((tag = input.ReadTag()) != 0) {
switch(tag) { switch(tag) {
default: default:
input.SkipLastField(); CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break; break;
case 10: { case 10: {
JavaPackage = input.ReadString(); JavaPackage = input.ReadString();
...@@ -3411,6 +3413,8 @@ namespace Google.Protobuf.Reflection { ...@@ -3411,6 +3413,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; } get { return Descriptor; }
} }
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public MessageOptions() { public MessageOptions() {
OnConstruction(); OnConstruction();
...@@ -3646,7 +3650,7 @@ namespace Google.Protobuf.Reflection { ...@@ -3646,7 +3650,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) { while ((tag = input.ReadTag()) != 0) {
switch(tag) { switch(tag) {
default: default:
input.SkipLastField(); CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break; break;
case 8: { case 8: {
MessageSetWireFormat = input.ReadBool(); MessageSetWireFormat = input.ReadBool();
...@@ -3689,6 +3693,8 @@ namespace Google.Protobuf.Reflection { ...@@ -3689,6 +3693,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; } get { return Descriptor; }
} }
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public FieldOptions() { public FieldOptions() {
OnConstruction(); OnConstruction();
...@@ -3980,7 +3986,7 @@ namespace Google.Protobuf.Reflection { ...@@ -3980,7 +3986,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) { while ((tag = input.ReadTag()) != 0) {
switch(tag) { switch(tag) {
default: default:
input.SkipLastField(); CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break; break;
case 8: { case 8: {
ctype_ = (global::Google.Protobuf.Reflection.FieldOptions.Types.CType) input.ReadEnum(); ctype_ = (global::Google.Protobuf.Reflection.FieldOptions.Types.CType) input.ReadEnum();
...@@ -4062,6 +4068,8 @@ namespace Google.Protobuf.Reflection { ...@@ -4062,6 +4068,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; } get { return Descriptor; }
} }
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public OneofOptions() { public OneofOptions() {
OnConstruction(); OnConstruction();
...@@ -4147,7 +4155,7 @@ namespace Google.Protobuf.Reflection { ...@@ -4147,7 +4155,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) { while ((tag = input.ReadTag()) != 0) {
switch(tag) { switch(tag) {
default: default:
input.SkipLastField(); CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break; break;
case 7994: { case 7994: {
uninterpretedOption_.AddEntriesFrom(input, _repeated_uninterpretedOption_codec); uninterpretedOption_.AddEntriesFrom(input, _repeated_uninterpretedOption_codec);
...@@ -4174,6 +4182,8 @@ namespace Google.Protobuf.Reflection { ...@@ -4174,6 +4182,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; } get { return Descriptor; }
} }
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public EnumOptions() { public EnumOptions() {
OnConstruction(); OnConstruction();
...@@ -4317,7 +4327,7 @@ namespace Google.Protobuf.Reflection { ...@@ -4317,7 +4327,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) { while ((tag = input.ReadTag()) != 0) {
switch(tag) { switch(tag) {
default: default:
input.SkipLastField(); CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break; break;
case 16: { case 16: {
AllowAlias = input.ReadBool(); AllowAlias = input.ReadBool();
...@@ -4352,6 +4362,8 @@ namespace Google.Protobuf.Reflection { ...@@ -4352,6 +4362,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; } get { return Descriptor; }
} }
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public EnumValueOptions() { public EnumValueOptions() {
OnConstruction(); OnConstruction();
...@@ -4467,7 +4479,7 @@ namespace Google.Protobuf.Reflection { ...@@ -4467,7 +4479,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) { while ((tag = input.ReadTag()) != 0) {
switch(tag) { switch(tag) {
default: default:
input.SkipLastField(); CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break; break;
case 8: { case 8: {
Deprecated = input.ReadBool(); Deprecated = input.ReadBool();
...@@ -4498,6 +4510,8 @@ namespace Google.Protobuf.Reflection { ...@@ -4498,6 +4510,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; } get { return Descriptor; }
} }
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public ServiceOptions() { public ServiceOptions() {
OnConstruction(); OnConstruction();
...@@ -4613,7 +4627,7 @@ namespace Google.Protobuf.Reflection { ...@@ -4613,7 +4627,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) { while ((tag = input.ReadTag()) != 0) {
switch(tag) { switch(tag) {
default: default:
input.SkipLastField(); CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break; break;
case 264: { case 264: {
Deprecated = input.ReadBool(); Deprecated = input.ReadBool();
...@@ -4644,6 +4658,8 @@ namespace Google.Protobuf.Reflection { ...@@ -4644,6 +4658,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; } get { return Descriptor; }
} }
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public MethodOptions() { public MethodOptions() {
OnConstruction(); OnConstruction();
...@@ -4783,7 +4799,7 @@ namespace Google.Protobuf.Reflection { ...@@ -4783,7 +4799,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) { while ((tag = input.ReadTag()) != 0) {
switch(tag) { switch(tag) {
default: default:
input.SkipLastField(); CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break; break;
case 264: { case 264: {
Deprecated = input.ReadBool(); Deprecated = input.ReadBool();
......
...@@ -112,5 +112,10 @@ namespace Google.Protobuf.Reflection ...@@ -112,5 +112,10 @@ namespace Google.Protobuf.Reflection
{ {
return File.DescriptorPool.FindSymbol<EnumValueDescriptor>(FullName + "." + name); return File.DescriptorPool.FindSymbol<EnumValueDescriptor>(FullName + "." + name);
} }
/// <summary>
/// The (possibly empty) set of custom options for this enum.
/// </summary>
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
} }
} }
\ No newline at end of file
...@@ -66,5 +66,10 @@ namespace Google.Protobuf.Reflection ...@@ -66,5 +66,10 @@ namespace Google.Protobuf.Reflection
/// Returns the enum descriptor that this value is part of. /// Returns the enum descriptor that this value is part of.
/// </summary> /// </summary>
public EnumDescriptor EnumDescriptor { get { return enumDescriptor; } } public EnumDescriptor EnumDescriptor { get { return enumDescriptor; } }
/// <summary>
/// The (possibly empty) set of custom options for this enum value.
/// </summary>
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
} }
} }
\ No newline at end of file
...@@ -250,6 +250,11 @@ namespace Google.Protobuf.Reflection ...@@ -250,6 +250,11 @@ namespace Google.Protobuf.Reflection
} }
} }
/// <summary>
/// The (possibly empty) set of custom options for this field.
/// </summary>
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
/// <summary> /// <summary>
/// Look up and cross-link all field types etc. /// Look up and cross-link all field types etc.
/// </summary> /// </summary>
......
...@@ -329,5 +329,10 @@ namespace Google.Protobuf.Reflection ...@@ -329,5 +329,10 @@ namespace Google.Protobuf.Reflection
/// The file descriptor for <c>descriptor.proto</c>. /// The file descriptor for <c>descriptor.proto</c>.
/// </value> /// </value>
public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorReflection.Descriptor; } } public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorReflection.Descriptor; } }
/// <summary>
/// The (possibly empty) set of custom options for this file.
/// </summary>
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
} }
} }
...@@ -220,6 +220,11 @@ namespace Google.Protobuf.Reflection ...@@ -220,6 +220,11 @@ namespace Google.Protobuf.Reflection
public T FindDescriptor<T>(string name) where T : class, IDescriptor => public T FindDescriptor<T>(string name) where T : class, IDescriptor =>
File.DescriptorPool.FindSymbol<T>(FullName + "." + name); File.DescriptorPool.FindSymbol<T>(FullName + "." + name);
/// <summary>
/// The (possibly empty) set of custom options for this message.
/// </summary>
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
/// <summary> /// <summary>
/// Looks up and cross-links all fields and nested types. /// Looks up and cross-links all fields and nested types.
/// </summary> /// </summary>
......
...@@ -67,6 +67,11 @@ namespace Google.Protobuf.Reflection ...@@ -67,6 +67,11 @@ namespace Google.Protobuf.Reflection
/// </value> /// </value>
public bool IsServerStreaming { get { return proto.ServerStreaming; } } public bool IsServerStreaming { get { return proto.ServerStreaming; } }
/// <summary>
/// The (possibly empty) set of custom options for this method.
/// </summary>
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
internal MethodDescriptor(MethodDescriptorProto proto, FileDescriptor file, internal MethodDescriptor(MethodDescriptorProto proto, FileDescriptor file,
ServiceDescriptor parent, int index) ServiceDescriptor parent, int index)
: base(file, parent.FullName + "." + proto.Name, index) : base(file, parent.FullName + "." + proto.Name, index)
......
...@@ -90,6 +90,11 @@ namespace Google.Protobuf.Reflection ...@@ -90,6 +90,11 @@ namespace Google.Protobuf.Reflection
/// </value> /// </value>
public OneofAccessor Accessor { get { return accessor; } } public OneofAccessor Accessor { get { return accessor; } }
/// <summary>
/// The (possibly empty) set of custom options for this oneof.
/// </summary>
public CustomOptions CustomOptions => proto.Options?.CustomOptions ?? CustomOptions.Empty;
internal void CrossLink() internal void CrossLink()
{ {
List<FieldDescriptor> fieldCollection = new List<FieldDescriptor>(); List<FieldDescriptor> fieldCollection = new List<FieldDescriptor>();
......
...@@ -78,6 +78,11 @@ namespace Google.Protobuf.Reflection ...@@ -78,6 +78,11 @@ namespace Google.Protobuf.Reflection
return File.DescriptorPool.FindSymbol<MethodDescriptor>(FullName + "." + name); return File.DescriptorPool.FindSymbol<MethodDescriptor>(FullName + "." + name);
} }
/// <summary>
/// The (possibly empty) set of custom options for this service.
/// </summary>
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
internal void CrossLink() internal void CrossLink()
{ {
foreach (MethodDescriptor method in methods) foreach (MethodDescriptor method in methods)
......
...@@ -120,6 +120,22 @@ inline bool IsDescriptorProto(const FileDescriptor* descriptor) { ...@@ -120,6 +120,22 @@ inline bool IsDescriptorProto(const FileDescriptor* descriptor) {
return descriptor->name() == "google/protobuf/descriptor.proto"; return descriptor->name() == "google/protobuf/descriptor.proto";
} }
// Determines whether the given message is an options message within descriptor.proto.
inline bool IsDescriptorOptionMessage(const Descriptor* descriptor) {
if (!IsDescriptorProto(descriptor->file())) {
return false;
}
const string name = descriptor->full_name();
return name == "google.protobuf.FileOptions" ||
name == "google.protobuf.MessageOptions" ||
name == "google.protobuf.FieldOptions" ||
name == "google.protobuf.OneofOptions" ||
name == "google.protobuf.EnumOptions" ||
name == "google.protobuf.EnumValueOptions" ||
name == "google.protobuf.ServiceOptions" ||
name == "google.protobuf.MethodOptions";
}
inline bool IsWrapperType(const FieldDescriptor* descriptor) { inline bool IsWrapperType(const FieldDescriptor* descriptor) {
return descriptor->type() == FieldDescriptor::TYPE_MESSAGE && return descriptor->type() == FieldDescriptor::TYPE_MESSAGE &&
descriptor->message_type()->file()->name() == "google/protobuf/wrappers.proto"; descriptor->message_type()->file()->name() == "google/protobuf/wrappers.proto";
......
...@@ -151,6 +151,12 @@ void MessageGenerator::Generate(io::Printer* printer) { ...@@ -151,6 +151,12 @@ void MessageGenerator::Generate(io::Printer* printer) {
" get { return Descriptor; }\n" " get { return Descriptor; }\n"
"}\n" "}\n"
"\n"); "\n");
// CustomOptions property, only for options messages
if (IsDescriptorOptionMessage(descriptor_)) {
printer->Print(
"internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;\n"
"\n");
}
// Parameterless constructor and partial OnConstruction method. // Parameterless constructor and partial OnConstruction method.
WriteGeneratedCodeAttributes(printer); WriteGeneratedCodeAttributes(printer);
...@@ -475,10 +481,18 @@ void MessageGenerator::GenerateMergingMethods(io::Printer* printer) { ...@@ -475,10 +481,18 @@ void MessageGenerator::GenerateMergingMethods(io::Printer* printer) {
" switch(tag) {\n"); " switch(tag) {\n");
printer->Indent(); printer->Indent();
printer->Indent(); printer->Indent();
// Option messages need to store unknown fields so that options can be parsed later.
if (IsDescriptorOptionMessage(descriptor_)) {
printer->Print(
"default:\n"
" CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);\n"
" break;\n");
} else {
printer->Print( printer->Print(
"default:\n" "default:\n"
" input.SkipLastField();\n" // We're not storing the data, but we still need to consume it. " input.SkipLastField();\n" // We're not storing the data, but we still need to consume it.
" break;\n"); " break;\n");
}
for (int i = 0; i < fields_by_number().size(); i++) { for (int i = 0; i < fields_by_number().size(); i++) {
const FieldDescriptor* field = fields_by_number()[i]; const FieldDescriptor* field = fields_by_number()[i];
internal::WireFormatLite::WireType wt = internal::WireFormatLite::WireType wt =
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment