From 8a4d6814d152acaaa9b345317c33288de15654ea Mon Sep 17 00:00:00 2001 From: djgosnell Date: Thu, 27 Mar 2025 12:07:04 -0400 Subject: [PATCH 1/3] Allows interface and abstract classes to be MessagePack object containing types. --- .../MemoryPackGenerator.Emitter.cs | 14 ++++--- tests/MemoryPack.Tests/GeneratorTest.cs | 4 ++ tests/MemoryPack.Tests/Models/StandardType.cs | 37 ++++++++++++++++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs b/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs index d17735fa..2c87d132 100644 --- a/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs +++ b/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs @@ -312,14 +312,18 @@ public void Emit(StringBuilder writer, IGeneratorContext context) var containingTypeDeclarations = new List(); var containingType = Symbol.ContainingType; + while (containingType is not null) { - containingTypeDeclarations.Add((containingType.IsRecord, containingType.IsValueType) switch + var isInterface = containingType.TypeKind == TypeKind.Interface; + containingTypeDeclarations.Add((containingType.IsRecord, containingType.IsValueType, containingType.IsAbstract, isInterface) switch { - (true, true) => $"partial record struct {containingType.Name}", - (true, false) => $"partial record {containingType.Name}", - (false, true) => $"partial struct {containingType.Name}", - (false, false) => $"partial class {containingType.Name}", + (true, true, false, false) => $"partial record struct {containingType.Name}", + (true, false, false, false) => $"partial record {containingType.Name}", + (false, true, false, false) => $"partial struct {containingType.Name}", + (false, false, false, false) => $"partial class {containingType.Name}", + (false, false, true, false) => $"abstract partial class {containingType.Name}", + (false, false, true, true) => $"partial interface {containingType.Name}", }); containingType = containingType.ContainingType; } diff --git a/tests/MemoryPack.Tests/GeneratorTest.cs b/tests/MemoryPack.Tests/GeneratorTest.cs index cb388931..53567ad3 100644 --- a/tests/MemoryPack.Tests/GeneratorTest.cs +++ b/tests/MemoryPack.Tests/GeneratorTest.cs @@ -54,6 +54,10 @@ public void Standard() public void Nested() { VerifyEquivalent(new NestedContainer.StandardTypeNested() { One = 9999 }); + VerifyEquivalent(new NestedStructContainer.StandardTypeNested() { One = 9999 }); + VerifyEquivalent(new NestedRecordStructContainer.StandardTypeNested() { One = 9999 }); + VerifyEquivalent(new NestedInterfaceContainer.StandardTypeNested() { One = 9999 }); + VerifyEquivalent(new NestedAbstractClassContainer.StandardTypeNested() { One = 9999 }); VerifyEquivalent(new DoublyNestedContainer.DoublyNestedContainerInner.StandardTypeDoublyNested() { One = 9999 }); } diff --git a/tests/MemoryPack.Tests/Models/StandardType.cs b/tests/MemoryPack.Tests/Models/StandardType.cs index 484835fa..e95e1bfe 100644 --- a/tests/MemoryPack.Tests/Models/StandardType.cs +++ b/tests/MemoryPack.Tests/Models/StandardType.cs @@ -71,6 +71,42 @@ public partial class StandardTypeNested } } + public partial struct NestedStructContainer + { + [MemoryPackable] + public partial class StandardTypeNested + { + public int One { get; set; } + } + } + + public partial record struct NestedRecordStructContainer + { + [MemoryPackable] + public partial class StandardTypeNested + { + public int One { get; set; } + } + } + + public partial interface NestedInterfaceContainer + { + [MemoryPackable] + public partial class StandardTypeNested + { + public int One { get; set; } + } + } + + public abstract partial class NestedAbstractClassContainer + { + [MemoryPackable] + public partial class StandardTypeNested + { + public int One { get; set; } + } + } + public partial class DoublyNestedContainer { public partial class DoublyNestedContainerInner @@ -83,7 +119,6 @@ public partial class StandardTypeDoublyNested } } - [MemoryPackable] public partial class WithArray { From ac218b9f2027c099654b88c44cb89fd278284fec Mon Sep 17 00:00:00 2001 From: djgosnell Date: Thu, 27 Mar 2025 12:11:37 -0400 Subject: [PATCH 2/3] Fixed whitespaces. --- src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs | 1 - tests/MemoryPack.Tests/Models/StandardType.cs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs b/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs index 2c87d132..b5a297ae 100644 --- a/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs +++ b/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs @@ -312,7 +312,6 @@ public void Emit(StringBuilder writer, IGeneratorContext context) var containingTypeDeclarations = new List(); var containingType = Symbol.ContainingType; - while (containingType is not null) { var isInterface = containingType.TypeKind == TypeKind.Interface; diff --git a/tests/MemoryPack.Tests/Models/StandardType.cs b/tests/MemoryPack.Tests/Models/StandardType.cs index e95e1bfe..225439cb 100644 --- a/tests/MemoryPack.Tests/Models/StandardType.cs +++ b/tests/MemoryPack.Tests/Models/StandardType.cs @@ -119,6 +119,7 @@ public partial class StandardTypeDoublyNested } } + [MemoryPackable] public partial class WithArray { From 91090773c708368c5c9ab3a6c7e405fc74817984 Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Mon, 31 Mar 2025 22:16:46 -0400 Subject: [PATCH 3/3] Fixes issue where the XML remarks were wrongly being added to the outermost containing type when that type is nested inside other types. --- .../MemoryPackGenerator.Emitter.cs | 107 +++++++++++------- 1 file changed, 63 insertions(+), 44 deletions(-) diff --git a/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs b/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs index b5a297ae..be06a69a 100644 --- a/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs +++ b/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs @@ -105,6 +105,30 @@ static void Generate(TypeDeclarationSyntax syntax, Compilation compilation, stri } sb.AppendLine(); + // emit type info + if (unionFormatter) + { + AppendTypeRemarks(serializationInfoLogDirectoryPath, typeMeta, sb, fullType); + typeMeta.EmitUnionFormatterTemplate(sb, context, typeSymbol); + } + else + { + // Emit the type. Wrap the append method to capture the current variables. + typeMeta.Emit(sb, context, + () => AppendTypeRemarks(serializationInfoLogDirectoryPath, typeMeta, sb, fullType)); + } + + if (!ns.IsGlobalNamespace && !context.IsCSharp10OrGreater()) + { + sb.AppendLine($"}}"); + } + + var code = sb.ToString(); + context.AddSource($"{fullType}.MemoryPackFormatter.g.cs", code); + } + + static void AppendTypeRemarks(string? serializationInfoLogDirectoryPath, TypeMeta typeMeta, StringBuilder sb, string fullType) + { // Write document comment as remarks if (typeMeta.GenerateType is GenerateType.Object or GenerateType.VersionTolerant or GenerateType.CircularReference) { @@ -131,24 +155,6 @@ static void Generate(TypeDeclarationSyntax syntax, Compilation compilation, stri } } } - - // emit type info - if (unionFormatter) - { - typeMeta.EmitUnionFormatterTemplate(sb, context, typeSymbol); - } - else - { - typeMeta.Emit(sb, context); - } - - if (!ns.IsGlobalNamespace && !context.IsCSharp10OrGreater()) - { - sb.AppendLine($"}}"); - } - - var code = sb.ToString(); - context.AddSource($"{fullType}.MemoryPackFormatter.g.cs", code); } static bool IsPartial(TypeDeclarationSyntax typeDeclaration) @@ -253,17 +259,54 @@ string WithEscape(ISymbol symbol) public partial class TypeMeta { - public void Emit(StringBuilder writer, IGeneratorContext context) + public void Emit(StringBuilder writer, IGeneratorContext context, Action appendTypeRemarks) { + var containingTypeDeclarations = new List(); + var containingType = Symbol.ContainingType; + while (containingType is not null) + { + var isInterface = containingType.TypeKind == TypeKind.Interface; + containingTypeDeclarations.Add((containingType.IsRecord, containingType.IsValueType, containingType.IsAbstract, isInterface) switch + { + (true, true, false, false) => $"partial record struct {containingType.Name}", + (true, false, false, false) => $"partial record {containingType.Name}", + (false, true, false, false) => $"partial struct {containingType.Name}", + (false, false, false, false) => $"partial class {containingType.Name}", + (false, false, true, false) => $"abstract partial class {containingType.Name}", + (false, false, true, true) => $"partial interface {containingType.Name}", + _ => $"partial class {containingType.Name}" + }); + containingType = containingType.ContainingType; + } + containingTypeDeclarations.Reverse(); + + foreach (var declaration in containingTypeDeclarations) + { + writer.AppendLine(declaration); + writer.AppendLine("{"); + } + + + // Write the documentation header after any containing classes. + appendTypeRemarks.Invoke(); + if (IsUnion) { writer.AppendLine(EmitUnionTemplate(context)); - return; } if (GenerateType == GenerateType.Collection) { writer.AppendLine(EmitGenericCollectionTemplate(context)); + } + + // Write the closing braces. + if (IsUnion || GenerateType == GenerateType.Collection) + { + for(int i = 0; i < containingTypeDeclarations.Count; ++i) + { + writer.AppendLine("}"); + } return; } @@ -310,24 +353,6 @@ public void Emit(StringBuilder writer, IGeneratorContext context) (false, false) => "class", }; - var containingTypeDeclarations = new List(); - var containingType = Symbol.ContainingType; - while (containingType is not null) - { - var isInterface = containingType.TypeKind == TypeKind.Interface; - containingTypeDeclarations.Add((containingType.IsRecord, containingType.IsValueType, containingType.IsAbstract, isInterface) switch - { - (true, true, false, false) => $"partial record struct {containingType.Name}", - (true, false, false, false) => $"partial record {containingType.Name}", - (false, true, false, false) => $"partial struct {containingType.Name}", - (false, false, false, false) => $"partial class {containingType.Name}", - (false, false, true, false) => $"abstract partial class {containingType.Name}", - (false, false, true, true) => $"partial interface {containingType.Name}", - }); - containingType = containingType.ContainingType; - } - containingTypeDeclarations.Reverse(); - var nullable = IsValueType ? "" : "?"; string staticRegisterFormatterMethod, staticMemoryPackableMethod, scopedRef, constraint, registerBody, registerT; @@ -377,12 +402,6 @@ public void Emit(StringBuilder writer, IGeneratorContext context) ? "Serialize(ref MemoryPackWriter" : "Serialize(ref MemoryPackWriter"; - foreach (var declaration in containingTypeDeclarations) - { - writer.AppendLine(declaration); - writer.AppendLine("{"); - } - writer.AppendLine($$""" partial {{classOrStructOrRecord}} {{TypeName}} : IMemoryPackable<{{TypeName}}>{{fixedSizeInterface}} {