Skip to content

Commit

Permalink
Use TypeInfo in serialization (#171)
Browse files Browse the repository at this point in the history
Adds TypeInfo overloads to a lot of ISerialize methods, allowing
TypeInfo to be used instead of separately passing information that is otherwise
stored on TypeInfo. Also removes all "ProvideAttributes" overloads,
since they are no longer used.
  • Loading branch information
agocke committed Jun 30, 2024
1 parent 0611d30 commit 2a802dd
Show file tree
Hide file tree
Showing 169 changed files with 697 additions and 451 deletions.
4 changes: 3 additions & 1 deletion perf/bench/SampleTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ public partial record Location

public partial record LocationWrap : IDeserialize<Location>
{
private static readonly TypeInfo s_fieldMap = TypeInfo.Create(TypeInfo.TypeKind.CustomType, [
private static readonly TypeInfo s_fieldMap = TypeInfo.Create(
"Location",
TypeInfo.TypeKind.CustomType, [
("id", typeof(Location).GetProperty("Id")!),
("address1", typeof(Location).GetProperty("Address1")!),
("address2", typeof(Location).GetProperty("Address2")!),
Expand Down
15 changes: 15 additions & 0 deletions serde-dn.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "trace", "perf\trace\trace.c
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bench", "perf\bench\bench.csproj", "{906DE118-AC37-4203-BD9E-25C67F55AD28}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serde.Xml", "src\serde-xml\Serde.Xml.csproj", "{14FA7DDD-0FBB-4505-A928-B518629C1F15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -117,6 +119,18 @@ Global
{906DE118-AC37-4203-BD9E-25C67F55AD28}.Release|x64.Build.0 = Release|Any CPU
{906DE118-AC37-4203-BD9E-25C67F55AD28}.Release|x86.ActiveCfg = Release|Any CPU
{906DE118-AC37-4203-BD9E-25C67F55AD28}.Release|x86.Build.0 = Release|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Debug|x64.ActiveCfg = Debug|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Debug|x64.Build.0 = Debug|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Debug|x86.ActiveCfg = Debug|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Debug|x86.Build.0 = Debug|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Release|Any CPU.Build.0 = Release|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Release|x64.ActiveCfg = Release|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Release|x64.Build.0 = Release|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Release|x86.ActiveCfg = Release|Any CPU
{14FA7DDD-0FBB-4505-A928-B518629C1F15}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -131,5 +145,6 @@ Global
{382F0F9B-7B6C-439A-96B4-BFCF433FB611} = {278C5DCE-D96F-46AC-8865-622418F28A8D}
{EE25A330-0FC2-4273-A355-8B8A5FF47851} = {73D0A8B2-6EF0-4C84-9917-08A003FF0013}
{906DE118-AC37-4203-BD9E-25C67F55AD28} = {73D0A8B2-6EF0-4C84-9917-08A003FF0013}
{14FA7DDD-0FBB-4505-A928-B518629C1F15} = {C29CDEE5-5D85-496E-8E43-27A474AF8AEB}
EndGlobalSection
EndGlobal
2 changes: 0 additions & 2 deletions src/generator/DataMemberSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ static bool IsNullable(ISymbol symbol)

public bool ThrowIfMissing => _memberOptions.ThrowIfMissing;

public bool ProvideAttributes => _memberOptions.ProvideAttributes;

public bool SerializeNull => _memberOptions.SerializeNull ?? _typeOptions.SerializeNull;

public ImmutableArray<AttributeData> Attributes => Symbol.GetAttributes();
Expand Down
1 change: 1 addition & 0 deletions src/generator/Generator.SerdeTypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ internal static class SerdeTypeInfoGenerator
internal static class {{typeName}}SerdeTypeInfo
{
internal static readonly Serde.TypeInfo TypeInfo = Serde.TypeInfo.Create(
"{{typeName}}",
Serde.TypeInfo.TypeKind.CustomType,
new (string, System.Reflection.MemberInfo)[] {
{{string.Join("," + Environment.NewLine,
Expand Down
79 changes: 15 additions & 64 deletions src/generator/Generator.Serialize.Generic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,29 +181,20 @@ partial record struct {{wrapperName}} : Serde.ISerializeWrap<{{wrappedName}}, {{
else
{
// The generated body of ISerialize is
// `var type = serializer.SerializeType("TypeName", numFields)`
// `var _l_typeInfo = {TypeName}SerdeTypeInfo.TypeInfo;`
// `var type = serializer.SerializeType(_l_typeInfo);
// type.SerializeField<FieldType, Serialize>("FieldName", receiver.FieldValue);
// type.End();

// `var type = serializer.SerializeType("TypeName", numFields)`
statements.Add(LocalDeclarationStatement(VariableDeclaration(
IdentifierName(Identifier("var")),
SeparatedList(new[] {
VariableDeclarator(
Identifier("type"),
argumentList: null,
EqualsValueClause(InvocationExpression(
QualifiedName(IdentifierName("serializer"), IdentifierName("SerializeType")),
ArgumentList(SeparatedList(new [] {
Argument(StringLiteral(receiverType.Name)), Argument(NumericLiteral(fieldsAndProps.Count))
}))
))
)
})
)));
// `var _l_typeInfo = {TypeName}SerdeTypeInfo.TypeInfo;`
statements.Add(ParseStatement($"var _l_typeInfo = {receiverType.Name}SerdeTypeInfo.TypeInfo;"));

// `var type = serializer.SerializeType(_l_typeInfo);`
statements.Add(ParseStatement("var type = serializer.SerializeType(_l_typeInfo);"));

foreach (var m in fieldsAndProps)
for (int i = 0; i < fieldsAndProps.Count; i++)
{
var m = fieldsAndProps[i];
// Generate statements of the form `type.SerializeField<FieldType, Serialize>("FieldName", receiver.FieldValue)`
var memberExpr = MakeMemberAccessExpr(m, receiverExpr);
var typeAndWrapperOpt = MakeSerializeType(m, context, memberExpr, inProgress);
Expand All @@ -219,7 +210,7 @@ partial record struct {{wrapperName}} : Serde.ISerializeWrap<{{wrappedName}}, {{
}
else
{
statements.Add(MakeSerializeFieldStmt(m, memberExpr, typeAndWrapper, receiverExpr));
statements.Add(MakeSerializeFieldStmt(m, i, memberExpr, typeAndWrapper, receiverExpr));
}
}

Expand Down Expand Up @@ -260,13 +251,16 @@ partial record struct {{wrapperName}} : Serde.ISerializeWrap<{{wrappedName}}, {{
// Make a statement like `type.SerializeField<valueType, SerializeType>("member.Name", value)`
static ExpressionStatementSyntax MakeSerializeFieldStmt(
DataMemberSymbol member,
int index,
ExpressionSyntax value,
TypeAndWrapper typeAndWrapper,
ExpressionSyntax receiver)
{
var arguments = new List<ExpressionSyntax>() {
// "FieldName"u8
ParseExpression($"\"{member.GetFormattedName()}\""),
// _l_typeInfo
ParseExpression("_l_typeInfo"),
// Index
LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(index)),
// Value
value,
};
Expand All @@ -286,49 +280,6 @@ partial record struct {{wrapperName}} : Serde.ISerializeWrap<{{wrappedName}}, {{
methodName = "SerializeField";
}

// If the member is marked as providing attributes we will need to create an array of the
// attributes and pass it as the last argument
if (member.ProvideAttributes)
{
var attributeExpressions = member.Attributes.SelectNotNull(attributeData =>
{
if (attributeData.AttributeClass is not { } attrClass)
{
return null;
}
// Construct the positional arguments to the attribute constructor
var args = attributeData.ConstructorArguments
.Select(a => Argument(ParseExpression(a.ToCSharpString()))).ToList();
// Construct the named arguments to the attribute constructor
var assignments = attributeData.NamedArguments.Select(pair =>
(ExpressionSyntax)AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName(pair.Key),
ParseExpression(pair.Value.ToCSharpString()))).ToList();
return (ExpressionSyntax)ObjectCreationExpression(
attrClass.ToFqnSyntax(),
ArgumentList(SeparatedList(args)),
InitializerExpression(
SyntaxKind.ObjectInitializerExpression,
SeparatedList(assignments)));
}).ToList();

if (attributeExpressions.Count > 0)
{
arguments.Add(ArrayCreationExpression(
ArrayType(
ParseTypeName("System.Attribute"),
SingletonList(ArrayRankSpecifier(
SingletonSeparatedList((ExpressionSyntax)OmittedArraySizeExpression())))),
InitializerExpression(
SyntaxKind.ArrayInitializerExpression,
SeparatedList(attributeExpressions))));
}
}

return ExpressionStatement(InvocationExpression(
// type.SerializeField
QualifiedName(IdentifierName("type"),
Expand Down
42 changes: 0 additions & 42 deletions src/generator/Generator.Serialize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,48 +170,6 @@ static ExpressionStatementSyntax MakeSerializeFieldStmt(DataMemberSymbol member,
methodName = "SerializeField";
}

// If the member is marked as providing attributes we will need to create an array of the
// attributes and pass it as the last argument
if (member.ProvideAttributes)
{
var attributeExpressions = member.Attributes.SelectNotNull(attributeData =>
{
if (attributeData.AttributeClass is not { } attrClass)
{
return null;
}
// Construct the positional arguments to the attribute constructor
var args = attributeData.ConstructorArguments
.Select(a => Argument(ParseExpression(a.ToCSharpString()))).ToList();
// Construct the named arguments to the attribute constructor
var assignments = attributeData.NamedArguments.Select(pair =>
(ExpressionSyntax)AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName(pair.Key),
ParseExpression(pair.Value.ToCSharpString()))).ToList();
return (ExpressionSyntax)ObjectCreationExpression(
attrClass.ToFqnSyntax(),
ArgumentList(SeparatedList(args)),
InitializerExpression(
SyntaxKind.ObjectInitializerExpression,
SeparatedList(assignments)));
}).ToList();

if (attributeExpressions.Count > 0)
{
arguments.Add(ArrayCreationExpression(
ArrayType(
ParseTypeName("System.Attribute"),
SingletonList(ArrayRankSpecifier(
SingletonSeparatedList((ExpressionSyntax)OmittedArraySizeExpression())))),
InitializerExpression(
SyntaxKind.ArrayInitializerExpression,
SeparatedList(attributeExpressions))));
}
}

return ExpressionStatement(InvocationExpression(
// type.SerializeField
Expand Down
53 changes: 49 additions & 4 deletions src/serde-xml/XmlSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ public sealed partial class XmlSerializer
/// <summary>
/// Serialize the given type to a string.
/// </summary>
public static string SerializeIndented<T>(T t) where T : ISerialize
public static string SerializeIndented<T>(T t) where T : ISerialize<T>
=> Serialize(t, new XmlWriterSettings() { Indent = true });

public static string Serialize<T>(T t) where T : ISerialize
public static string Serialize<T>(T t) where T : ISerialize<T>
=> Serialize(t, new XmlWriterSettings() { Indent = true });

private static string Serialize<T>(T s, XmlWriterSettings? settings) where T : ISerialize
private static string Serialize<T>(T s, XmlWriterSettings? settings) where T : ISerialize<T>
{
using var stringWriter = new StringWriter();
using (var writer = XmlWriter.Create(stringWriter, settings))
{
var serializer = new XmlSerializer(writer);
writer.WriteProcessingInstruction("xml", $"version=\"1.0\" encoding=\"{stringWriter.Encoding.WebName}\"");
s.Serialize(serializer);
s.Serialize(s, serializer);
}
return stringWriter.ToString();
}
Expand Down Expand Up @@ -223,6 +223,23 @@ public ISerializeType SerializeType(string name, int numFields)
return new XmlTypeSerializer(writeEnd, this, saved);
}

public ISerializeType SerializeType(TypeInfo typeInfo)
{
var saved = _state;
bool writeEnd;
if (_state is State.Start or State.Enumerable)
{
_writer.WriteStartElement(typeInfo.TypeName);
writeEnd = true;
}
else
{
writeEnd = false;
}
_state = State.Type;
return new XmlTypeSerializer(writeEnd, this, saved);
}

private sealed class XmlTypeSerializer : ISerializeType
{
private readonly bool _writeEnd;
Expand All @@ -236,6 +253,34 @@ public XmlTypeSerializer(bool writeEnd, XmlSerializer parent, State savedState)
_savedState = savedState;
}

public void SerializeField<T>(TypeInfo typeInfo, int fieldIndex, T value)
where T : ISerialize<T>
=> SerializeField(typeInfo, fieldIndex, value, value);

public void SerializeField<T, U>(TypeInfo typeInfo, int fieldIndex, T value)
where U : struct, ISerialize<T>
=> SerializeField(typeInfo, fieldIndex, value, default(U));

private void SerializeField<T, U>(TypeInfo typeInfo, int fieldIndex, T value, U impl)
where U : ISerialize<T>
{
var name = typeInfo.GetStringSerializeName(fieldIndex);
foreach (var attr in typeInfo.GetCustomAttributeData(fieldIndex))
{
if (attr.AttributeType == typeof(XmlAttributeAttribute))
{
_parent._writer.WriteStartAttribute(name);
impl.Serialize(value, _parent);
_parent._writer.WriteEndAttribute();
return;
}
}

_parent._writer.WriteStartElement(name);
impl.Serialize(value, _parent);
_parent._writer.WriteEndElement();
}

public void SerializeField<T>(string name, T value)
where T : ISerialize
{
Expand Down
Loading

0 comments on commit 2a802dd

Please sign in to comment.