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= \
csharp/keys/Google.Protobuf.public.snk \
csharp/keys/Google.Protobuf.snk \
csharp/keys/README.md \
csharp/protos/unittest_custom_options_proto3.proto \
csharp/protos/unittest_issues.proto \
csharp/src/AddressBook/AddPerson.cs \
csharp/src/AddressBook/Addressbook.cs \
......@@ -92,6 +93,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs \
csharp/src/Google.Protobuf.Test/JsonParserTest.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/FieldAccessTest.cs \
csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs \
......@@ -101,6 +103,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs \
csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.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/UnittestImportPublicProto3.cs \
csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs \
......@@ -140,6 +143,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf/MessageParser.cs \
csharp/src/Google.Protobuf/ProtoPreconditions.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/DescriptorBase.cs \
csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs \
......
......@@ -50,9 +50,10 @@ $PROTOC -Isrc --csharp_out=csharp/src/Google.Protobuf.Test \
src/google/protobuf/unittest_well_known_types.proto
# 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/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
# results end up in TestProtos.
......
This diff is collapsed.
This diff is collapsed.
......@@ -2779,6 +2779,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; }
}
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public FileOptions() {
OnConstruction();
......@@ -3299,7 +3301,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break;
case 10: {
JavaPackage = input.ReadString();
......@@ -3411,6 +3413,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; }
}
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public MessageOptions() {
OnConstruction();
......@@ -3646,7 +3650,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break;
case 8: {
MessageSetWireFormat = input.ReadBool();
......@@ -3689,6 +3693,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; }
}
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public FieldOptions() {
OnConstruction();
......@@ -3980,7 +3986,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break;
case 8: {
ctype_ = (global::Google.Protobuf.Reflection.FieldOptions.Types.CType) input.ReadEnum();
......@@ -4062,6 +4068,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; }
}
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public OneofOptions() {
OnConstruction();
......@@ -4147,7 +4155,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break;
case 7994: {
uninterpretedOption_.AddEntriesFrom(input, _repeated_uninterpretedOption_codec);
......@@ -4174,6 +4182,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; }
}
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public EnumOptions() {
OnConstruction();
......@@ -4317,7 +4327,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break;
case 16: {
AllowAlias = input.ReadBool();
......@@ -4352,6 +4362,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; }
}
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public EnumValueOptions() {
OnConstruction();
......@@ -4467,7 +4479,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break;
case 8: {
Deprecated = input.ReadBool();
......@@ -4498,6 +4510,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; }
}
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public ServiceOptions() {
OnConstruction();
......@@ -4613,7 +4627,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break;
case 264: {
Deprecated = input.ReadBool();
......@@ -4644,6 +4658,8 @@ namespace Google.Protobuf.Reflection {
get { return Descriptor; }
}
internal CustomOptions CustomOptions{ get; private set; } = CustomOptions.Empty;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public MethodOptions() {
OnConstruction();
......@@ -4783,7 +4799,7 @@ namespace Google.Protobuf.Reflection {
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
CustomOptions = CustomOptions.ReadOrSkipUnknownField(input);
break;
case 264: {
Deprecated = input.ReadBool();
......
......@@ -112,5 +112,10 @@ namespace Google.Protobuf.Reflection
{
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
/// Returns the enum descriptor that this value is part of.
/// </summary>
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
}
}
/// <summary>
/// The (possibly empty) set of custom options for this field.
/// </summary>
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;
/// <summary>
/// Look up and cross-link all field types etc.
/// </summary>
......
......@@ -329,5 +329,10 @@ namespace Google.Protobuf.Reflection
/// The file descriptor for <c>descriptor.proto</c>.
/// </value>
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
public T FindDescriptor<T>(string name) where T : class, IDescriptor =>
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>
/// Looks up and cross-links all fields and nested types.
/// </summary>
......
......@@ -67,6 +67,11 @@ namespace Google.Protobuf.Reflection
/// </value>
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,
ServiceDescriptor parent, int index)
: base(file, parent.FullName + "." + proto.Name, index)
......
......@@ -90,6 +90,11 @@ namespace Google.Protobuf.Reflection
/// </value>
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()
{
List<FieldDescriptor> fieldCollection = new List<FieldDescriptor>();
......
......@@ -78,6 +78,11 @@ namespace Google.Protobuf.Reflection
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()
{
foreach (MethodDescriptor method in methods)
......
......@@ -120,6 +120,22 @@ inline bool IsDescriptorProto(const FileDescriptor* descriptor) {
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) {
return descriptor->type() == FieldDescriptor::TYPE_MESSAGE &&
descriptor->message_type()->file()->name() == "google/protobuf/wrappers.proto";
......
......@@ -151,6 +151,12 @@ void MessageGenerator::Generate(io::Printer* printer) {
" get { return Descriptor; }\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.
WriteGeneratedCodeAttributes(printer);
......@@ -475,10 +481,18 @@ void MessageGenerator::GenerateMergingMethods(io::Printer* printer) {
" switch(tag) {\n");
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(
"default:\n"
" input.SkipLastField();\n" // We're not storing the data, but we still need to consume it.
" break;\n");
}
for (int i = 0; i < fields_by_number().size(); i++) {
const FieldDescriptor* field = fields_by_number()[i];
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