parent
119e71c40a
commit
450022de99
16 changed files with 809 additions and 67 deletions
@ -0,0 +1,373 @@ |
||||
#region Copyright notice and license |
||||
// Protocol Buffers - Google's data interchange format |
||||
// Copyright 2008 Google Inc. All rights reserved. |
||||
// |
||||
// Use of this source code is governed by a BSD-style |
||||
// license that can be found in the LICENSE file or at |
||||
// https://developers.google.com/open-source/licenses/bsd |
||||
#endregion |
||||
|
||||
using Google.Protobuf.Reflection; |
||||
using NUnit.Framework; |
||||
using Pb; |
||||
using static Google.Protobuf.Reflection.FieldDescriptorProto.Types; |
||||
using Type = Google.Protobuf.Reflection.FieldDescriptorProto.Types.Type; |
||||
|
||||
namespace Google.Protobuf.Test.Reflection; |
||||
|
||||
public class FeatureInheritanceTest |
||||
{ |
||||
// Note: there's no test for file defaults, as we don't have the same access to modify the |
||||
// global defaults in C# that exists in Java. |
||||
|
||||
[Test] |
||||
public void FileOverrides() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto, 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void FileMessageInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto, 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void FileMessageOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto, 3); |
||||
SetTestFeature(fileProto.MessageType[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void FileEnumInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto, 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.EnumTypes[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void FileEnumOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto, 3); |
||||
SetTestFeature(fileProto.EnumType[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.EnumTypes[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void FileExtensionInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto, 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.EnumTypes[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void FileExtensionOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto, 3); |
||||
SetTestFeature(fileProto.Extension[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.Extensions.UnorderedExtensions[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void FileServiceInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto, 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.Services[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void FileServiceOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto, 3); |
||||
SetTestFeature(fileProto.Service[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.Services[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageFieldInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Fields.InFieldNumberOrder()[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageFieldOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
SetTestFeature(fileProto.MessageType[0].Field[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Fields.InFieldNumberOrder()[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageEnumInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].EnumTypes[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageEnumOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
SetTestFeature(fileProto.MessageType[0].EnumType[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].EnumTypes[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageMessageInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].NestedTypes[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageMessageOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
SetTestFeature(fileProto.MessageType[0].NestedType[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].NestedTypes[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageExtensionInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Extensions.UnorderedExtensions[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageExtensionOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
SetTestFeature(fileProto.MessageType[0].Extension[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Extensions.UnorderedExtensions[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageOneofInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Oneofs[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void MessageOneofOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
SetTestFeature(fileProto.MessageType[0].OneofDecl[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Oneofs[0].Fields[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void OneofFieldInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.MessageTypes[0].Oneofs[0].Fields[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void OneofFieldOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
SetTestFeature(fileProto.MessageType[0].OneofDecl[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.MessageTypes[0].Oneofs[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void EnumValueInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.EnumType[0], 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.EnumTypes[0].Values[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void EnumValueOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.MessageType[0], 3); |
||||
SetTestFeature(fileProto.EnumType[0].Value[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.EnumTypes[0].Values[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void ServiceMethodInherit() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.Service[0], 3); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(3, GetTestFeature(fileDescriptor.Services[0].Methods[0].Features)); |
||||
} |
||||
|
||||
[Test] |
||||
public void ServiceMethodOverride() |
||||
{ |
||||
var fileProto = CreateFileProto(); |
||||
SetTestFeature(fileProto.Service[0], 3); |
||||
SetTestFeature(fileProto.Service[0].Method[0], 5); |
||||
var fileDescriptor = Build(fileProto); |
||||
Assert.AreEqual(5, GetTestFeature(fileDescriptor.Services[0].Methods[0].Features)); |
||||
} |
||||
|
||||
private static int GetTestFeature(FeatureSetDescriptor features) => |
||||
(features.Proto.GetExtension(UnittestFeaturesExtensions.Test) ?? new TestFeatures()).IntMultipleFeature; |
||||
|
||||
private static void SetTestFeature(FileDescriptorProto proto, int value) |
||||
{ |
||||
proto.Options ??= new FileOptions(); |
||||
proto.Options.Features ??= new FeatureSet(); |
||||
SetTestFeature(proto.Options.Features, value); |
||||
} |
||||
|
||||
private static void SetTestFeature(DescriptorProto proto, int value) |
||||
{ |
||||
proto.Options ??= new MessageOptions(); |
||||
proto.Options.Features ??= new FeatureSet(); |
||||
SetTestFeature(proto.Options.Features, value); |
||||
} |
||||
|
||||
private static void SetTestFeature(EnumDescriptorProto proto, int value) |
||||
{ |
||||
proto.Options ??= new EnumOptions(); |
||||
proto.Options.Features ??= new FeatureSet(); |
||||
SetTestFeature(proto.Options.Features, value); |
||||
} |
||||
|
||||
private static void SetTestFeature(EnumValueDescriptorProto proto, int value) |
||||
{ |
||||
proto.Options ??= new EnumValueOptions(); |
||||
proto.Options.Features ??= new FeatureSet(); |
||||
SetTestFeature(proto.Options.Features, value); |
||||
} |
||||
|
||||
private static void SetTestFeature(FieldDescriptorProto proto, int value) |
||||
{ |
||||
proto.Options ??= new FieldOptions(); |
||||
proto.Options.Features ??= new FeatureSet(); |
||||
SetTestFeature(proto.Options.Features, value); |
||||
} |
||||
|
||||
private static void SetTestFeature(ServiceDescriptorProto proto, int value) |
||||
{ |
||||
proto.Options ??= new ServiceOptions(); |
||||
proto.Options.Features ??= new FeatureSet(); |
||||
SetTestFeature(proto.Options.Features, value); |
||||
} |
||||
|
||||
private static void SetTestFeature(OneofDescriptorProto proto, int value) |
||||
{ |
||||
proto.Options ??= new OneofOptions(); |
||||
proto.Options.Features ??= new FeatureSet(); |
||||
SetTestFeature(proto.Options.Features, value); |
||||
} |
||||
|
||||
private static void SetTestFeature(MethodDescriptorProto proto, int value) |
||||
{ |
||||
proto.Options ??= new MethodOptions(); |
||||
proto.Options.Features ??= new FeatureSet(); |
||||
SetTestFeature(proto.Options.Features, value); |
||||
} |
||||
|
||||
private static void SetTestFeature(FeatureSet features, int value) => |
||||
features.SetExtension(UnittestFeaturesExtensions.Test, new TestFeatures { IntMultipleFeature = value }); |
||||
|
||||
private static FileDescriptor Build(FileDescriptorProto fileProto) => |
||||
FileDescriptor.BuildFromByteStrings(new[] { fileProto.ToByteString() }, new ExtensionRegistry { UnittestFeaturesExtensions.Test })[0]; |
||||
|
||||
private static FileDescriptorProto CreateFileProto() => new FileDescriptorProto |
||||
{ |
||||
Name = "some/filename/some.proto", |
||||
Package = "proto2_unittest", |
||||
Edition = Edition._2023, |
||||
Syntax = "editions", |
||||
Extension = |
||||
{ |
||||
new FieldDescriptorProto { Name = "top_extension", Number = 10, Type = Type.Int32, Label = Label.Optional, Extendee = ".proto2_unittest.TopMessage" } |
||||
}, |
||||
EnumType = |
||||
{ |
||||
new EnumDescriptorProto { Name = "TopEnum", Value = { new EnumValueDescriptorProto { Name = "TOP_VALUE", Number = 0 } } } |
||||
}, |
||||
MessageType = |
||||
{ |
||||
new DescriptorProto |
||||
{ |
||||
Name = "TopMessage", |
||||
Field = |
||||
{ |
||||
new FieldDescriptorProto { Name = "field", Number = 1, Type = Type.Int32, Label = Label.Optional }, |
||||
new FieldDescriptorProto { Name = "oneof_field", Number = 2, Type = Type.Int32, Label = Label.Optional, OneofIndex = 0 } |
||||
}, |
||||
Extension = |
||||
{ |
||||
new FieldDescriptorProto { Name = "nested_extension", Number = 11, Type = Type.Int32, Label = Label.Optional, Extendee = ".proto2_unittest.TopMessage" } |
||||
}, |
||||
NestedType = |
||||
{ |
||||
new DescriptorProto { Name = "NestedMessage" }, |
||||
}, |
||||
EnumType = |
||||
{ |
||||
new EnumDescriptorProto { Name = "NestedEnum", Value = { new EnumValueDescriptorProto { Name = "NESTED_VALUE", Number = 0 } } } |
||||
}, |
||||
OneofDecl = { new OneofDescriptorProto { Name = "Oneof" } } |
||||
} |
||||
}, |
||||
Service = |
||||
{ |
||||
new ServiceDescriptorProto |
||||
{ |
||||
Name = "TestService", |
||||
Method = { new MethodDescriptorProto { Name = "CallMethod", InputType = ".proto2_unittest.TopMessage", OutputType = ".proto2_unittest.TopMessage" } } |
||||
} |
||||
} |
||||
}; |
||||
} |
@ -0,0 +1,37 @@ |
||||
#region Copyright notice and license |
||||
// Protocol Buffers - Google's data interchange format |
||||
// Copyright 2008 Google Inc. All rights reserved. |
||||
// |
||||
// Use of this source code is governed by a BSD-style |
||||
// license that can be found in the LICENSE file or at |
||||
// https://developers.google.com/open-source/licenses/bsd |
||||
#endregion |
||||
|
||||
using Google.Protobuf.Reflection; |
||||
using NUnit.Framework; |
||||
using System; |
||||
using System.Linq; |
||||
|
||||
namespace Google.Protobuf.Test.Reflection; |
||||
|
||||
public class FeatureSetDescriptorTest |
||||
{ |
||||
// Canonical serialized form of the edition defaults, generated by embed_edition_defaults. |
||||
// TODO: Update this automatically. |
||||
private const string DefaultsBase64 = "CrEBEqsBCAEQAhgCIAMoATAC6vAEMQj+//////////8BEAEYASABKAEwATgBQAFIAVABWABlzcyMP2oAcAB4AYIBBDIwMjPy8AQxCP7//////////wEQARgBIAEoATABOAFAAUgBUAFYAGXNzIw/agBwAHgBggEEMjAyM/rwBDEI/v//////////ARABGAEgASgBMAE4AUABSAFQAVgAZc3MjD9qAHAAeAGCAQQyMDIzGOYHCrEBEqsBCAIQARgBIAIoATAB6vAEMQj9//////////8BEAEYASABKAEwATgBQAFIAVABWABlzcyMP2oAcAB4AYIBBDIwMjPy8AQxCP3//////////wEQARgBIAEoATABOAFAAUgBUAFYAGXNzIw/agBwAHgBggEEMjAyM/rwBDEI/f//////////ARABGAEgASgBMAE4AUABSAFQAVgAZc3MjD9qAHAAeAGCAQQyMDIzGOcHCsMBEr0BCAEQARgBIAIoATAB6vAENwgBEAEYASABKAEwATgBQAFIAVABWABlzcyMP2oPCAEQAR0AAMA/IgQyMDIzcAF4AYIBBDIwMjPy8AQ3CAEQARgBIAEoATABOAFAAUgBUAFYAGXNzIw/ag8IARABHQAAwD8iBDIwMjNwAXgBggEEMjAyM/rwBDcIARABGAEgASgBMAE4AUABSAFQAVgAZc3MjD9qDwgBEAEdAADAPyIEMjAyM3ABeAGCAQQyMDIzGOgHIOgHKOgH"; |
||||
|
||||
[Test] |
||||
[TestCase(Edition.Proto2)] |
||||
[TestCase(Edition.Proto3)] |
||||
[TestCase(Edition._2023)] |
||||
public void DefaultsMatchCanonicalSerializedForm(Edition edition) |
||||
{ |
||||
var canonicalDefaults = FeatureSetDefaults.Parser |
||||
.WithDiscardUnknownFields(true) // Discard language-specific extensions. |
||||
.ParseFrom(Convert.FromBase64String(DefaultsBase64)); |
||||
var canonicalEditionDefaults = canonicalDefaults.Defaults.Single(def => def.Edition == edition).Features; |
||||
var candidateEditionDefaults = FeatureSetDescriptor.GetEditionDefaults(edition).Proto; |
||||
|
||||
Assert.AreEqual(canonicalEditionDefaults, candidateEditionDefaults); |
||||
} |
||||
} |
@ -0,0 +1,118 @@ |
||||
#region Copyright notice and license |
||||
// Protocol Buffers - Google's data interchange format |
||||
// Copyright 2008 Google Inc. All rights reserved. |
||||
// |
||||
// Use of this source code is governed by a BSD-style |
||||
// license that can be found in the LICENSE file or at |
||||
// https://developers.google.com/open-source/licenses/bsd |
||||
#endregion |
||||
|
||||
using System; |
||||
using System.Collections.Concurrent; |
||||
using static Google.Protobuf.Reflection.FeatureSet.Types; |
||||
|
||||
namespace Google.Protobuf.Reflection; |
||||
|
||||
/// <summary> |
||||
/// A resolved set of features for a file, message etc. |
||||
/// </summary> |
||||
/// <remarks> |
||||
/// Only features supported by the C# runtime are exposed; currently |
||||
/// all enums in C# are open, and we never perform UTF-8 validation. |
||||
/// If either of those features are ever implemented in this runtime, |
||||
/// the feature settings will be exposed as properties in this class. |
||||
/// </remarks> |
||||
internal sealed class FeatureSetDescriptor |
||||
{ |
||||
private static readonly ConcurrentDictionary<FeatureSet, FeatureSetDescriptor> cache = new(); |
||||
|
||||
// Note: this approach is deliberately chosen to circumvent bootstrapping issues. |
||||
// This can still be tested using the binary representation. |
||||
// TODO: Generate this code (as a partial class) from the binary representation. |
||||
private static readonly FeatureSetDescriptor edition2023Defaults = new FeatureSetDescriptor( |
||||
new FeatureSet |
||||
{ |
||||
EnumType = EnumType.Open, |
||||
FieldPresence = FieldPresence.Explicit, |
||||
JsonFormat = JsonFormat.Allow, |
||||
MessageEncoding = MessageEncoding.LengthPrefixed, |
||||
RepeatedFieldEncoding = RepeatedFieldEncoding.Packed, |
||||
Utf8Validation = Utf8Validation.Verify, |
||||
}); |
||||
private static readonly FeatureSetDescriptor proto2Defaults = new FeatureSetDescriptor( |
||||
new FeatureSet |
||||
{ |
||||
EnumType = EnumType.Closed, |
||||
FieldPresence = FieldPresence.Explicit, |
||||
JsonFormat = JsonFormat.LegacyBestEffort, |
||||
MessageEncoding = MessageEncoding.LengthPrefixed, |
||||
RepeatedFieldEncoding = RepeatedFieldEncoding.Expanded, |
||||
Utf8Validation = Utf8Validation.None, |
||||
}); |
||||
private static readonly FeatureSetDescriptor proto3Defaults = new FeatureSetDescriptor( |
||||
new FeatureSet |
||||
{ |
||||
EnumType = EnumType.Open, |
||||
FieldPresence = FieldPresence.Implicit, |
||||
JsonFormat = JsonFormat.Allow, |
||||
MessageEncoding = MessageEncoding.LengthPrefixed, |
||||
RepeatedFieldEncoding = RepeatedFieldEncoding.Packed, |
||||
Utf8Validation = Utf8Validation.Verify, |
||||
}); |
||||
|
||||
internal static FeatureSetDescriptor GetEditionDefaults(Edition edition) => |
||||
edition switch |
||||
{ |
||||
Edition.Proto2 => proto2Defaults, |
||||
Edition.Proto3 => proto3Defaults, |
||||
Edition._2023 => edition2023Defaults, |
||||
_ => throw new ArgumentOutOfRangeException($"Unsupported edition: {edition}") |
||||
}; |
||||
|
||||
// Visible for testing. The underlying feature set proto, usually derived during |
||||
// feature resolution. |
||||
internal FeatureSet Proto { get; } |
||||
|
||||
/// <summary> |
||||
/// Only relevant to fields. Indicates if a field has explicit presence. |
||||
/// </summary> |
||||
internal FieldPresence FieldPresence => Proto.FieldPresence; |
||||
|
||||
/// <summary> |
||||
/// Only relevant to fields. Indicates how a repeated field should be encoded. |
||||
/// </summary> |
||||
internal RepeatedFieldEncoding RepeatedFieldEncoding => Proto.RepeatedFieldEncoding; |
||||
|
||||
/// <summary> |
||||
/// Only relevant to fields. Indicates how a message-valued field should be encoded. |
||||
/// </summary> |
||||
internal MessageEncoding MessageEncoding => Proto.MessageEncoding; |
||||
|
||||
private FeatureSetDescriptor(FeatureSet proto) |
||||
{ |
||||
Proto = proto; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Returns a new descriptor based on this one, with the specified overrides. |
||||
/// Multiple calls to this method that produce equivalent feature sets will return |
||||
/// the same instance. |
||||
/// </summary> |
||||
/// <param name="overrides">The proto representation of the "child" feature set to merge with this |
||||
/// one. May be null, in which case this descriptor is returned.</param> |
||||
/// <returns>A descriptor based on the current one, with the given set of overrides.</returns> |
||||
public FeatureSetDescriptor MergedWith(FeatureSet overrides) |
||||
{ |
||||
if (overrides is null) |
||||
{ |
||||
return this; |
||||
} |
||||
|
||||
// Note: It would be nice if we could avoid cloning unless |
||||
// there are actual changes, but this won't happen that often; |
||||
// it'll be temporary garbage. |
||||
var clone = Proto.Clone(); |
||||
clone.MergeFrom(overrides); |
||||
return cache.GetOrAdd(clone, clone => new FeatureSetDescriptor(clone)); |
||||
} |
||||
} |
Loading…
Reference in new issue