Skip to content

Commit

Permalink
Get rid of old ISerialize, non-TypeInfo support (#172)
Browse files Browse the repository at this point in the history
These are now obsolete. ISerialize<T> is used everywhere and TypeInfo is
preferred over all previous methods of handling collections and types.

Remove support for non-generic ISerialize
  • Loading branch information
agocke authored Jul 1, 2024
1 parent 2a802dd commit 0e67e04
Show file tree
Hide file tree
Showing 195 changed files with 1,396 additions and 3,455 deletions.
6 changes: 3 additions & 3 deletions perf/bench/DataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ namespace Benchmarks
{
internal static class DataGenerator
{
public static T GenerateSerialize<T>() where T : Serde.ISerialize
public static T GenerateSerialize<T>() where T : Serde.ISerialize<T>
{
if (typeof(T) == typeof(LoginViewModel))
return (T)(object)CreateLoginViewModel();
if (typeof(T) == typeof(Location))
return (T)(object)CreateLocation();
return (T)(object)Location.Sample;
if (typeof(T) == typeof(Serde.Test.AllInOne))
return (T)(object)new Serde.Test.AllInOne();
return (T)(object)Serde.Test.AllInOne.Sample;

throw new InvalidOperationException();

Expand Down
3 changes: 2 additions & 1 deletion perf/bench/JsonToString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Benchmarks
[GenericTypeArguments(typeof(LoginViewModel))]
[GenericTypeArguments(typeof(Location))]
[GenericTypeArguments(typeof(Serde.Test.AllInOne))]
public class JsonToString<T> where T : Serde.ISerialize, Serde.ISerialize<T>
public class SerializeToString<T> where T : Serde.ISerialize<T>
{
private JsonSerializerOptions _options = null!;
private T value = default!;
Expand All @@ -20,6 +20,7 @@ public void Setup()
{
_options = new JsonSerializerOptions();
_options.IncludeFields = true;
_options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
value = DataGenerator.GenerateSerialize<T>();
}

Expand Down
2 changes: 1 addition & 1 deletion perf/bench/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ Serialization is not correct
}

var config = DefaultConfig.Instance.AddDiagnoser(MemoryDiagnoser.Default);
var summary = BenchmarkSwitcher.FromAssembly(typeof(JsonToString<>).Assembly)
var summary = BenchmarkSwitcher.FromAssembly(typeof(SerializeToString<>).Assembly)
.Run(args, config);
5 changes: 3 additions & 2 deletions src/generator/Generator.Deserialize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Serde.Diagnostics;
using static Serde.SerdeImplRoslynGenerator;

namespace Serde
{
partial class SerdeImplRoslynGenerator
internal class DeserializeImplGenerator
{
private const string GeneratedVisitorName = "SerdeVisitor";

Expand Down Expand Up @@ -229,7 +230,7 @@ private static MethodDeclarationSyntax GenerateOldDeserializeMethod(
// var fieldNames = new[] { 'field1', 'field2', 'field3' ... };
// return deserializer.DeserializeType('TypeName', fieldNames, visitor);

var serdeName = SerdeBuiltInName(typeSymbol.SpecialType);
var serdeName = SerdeImplRoslynGenerator.SerdeBuiltInName(typeSymbol.SpecialType);
var typeSyntax = ParseTypeName(typeSymbol.ToString());
if (serdeName is not null)
{
Expand Down
75 changes: 75 additions & 0 deletions src/generator/Generator.Helpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Serde.Diagnostics;
using static Serde.WellKnownTypes;

namespace Serde
{
public partial class SerdeImplRoslynGenerator
{
// If the target is a core type, we can wrap it
internal static NameSyntax? TryGetPrimitiveWrapper(ITypeSymbol type, SerdeUsage usage)
{
if (type.NullableAnnotation == NullableAnnotation.Annotated)
{
return null;
}
var name = type.SpecialType switch
{
SpecialType.System_Boolean => "BoolWrap",
SpecialType.System_Char => "CharWrap",
SpecialType.System_Byte => "ByteWrap",
SpecialType.System_UInt16 => "UInt16Wrap",
SpecialType.System_UInt32 => "UInt32Wrap",
SpecialType.System_UInt64 => "UInt64Wrap",
SpecialType.System_SByte => "SByteWrap",
SpecialType.System_Int16 => "Int16Wrap",
SpecialType.System_Int32 => "Int32Wrap",
SpecialType.System_Int64 => "Int64Wrap",
SpecialType.System_String => "StringWrap",
SpecialType.System_Single => "FloatWrap",
SpecialType.System_Double => "DoubleWrap",
SpecialType.System_Decimal => "DecimalWrap",
_ => null
};
return name is null ? null : IdentifierName(name);
}

internal static ParameterSyntax Parameter(string typeName, string paramName, bool byRef = false) => SyntaxFactory.Parameter(
attributeLists: default,
modifiers: default,
type: byRef ? SyntaxFactory.RefType(IdentifierName(typeName)) : IdentifierName(typeName),
Identifier(paramName),
default
);

private static ParameterSyntax Parameter(TypeSyntax typeSyntax, string paramName) => SyntaxFactory.Parameter(
attributeLists: default,
modifiers: default,
type: typeSyntax,
Identifier(paramName),
default
);

internal static LiteralExpressionSyntax NumericLiteral(int num)
=> LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(num));

internal static LiteralExpressionSyntax StringLiteral(string text)
=> LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(text));

private static MemberAccessExpressionSyntax MakeMemberAccessExpr(DataMemberSymbol m, ExpressionSyntax receiverExpr)
=> MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
receiverExpr,
IdentifierName(m.Name));
}
}
71 changes: 7 additions & 64 deletions src/generator/Generator.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,11 @@ internal static void GenerateImpl(

ITypeSymbol receiverType;
ExpressionSyntax receiverExpr;
bool wrapper;
string? wrapperName;
string? wrappedName;
// If the Through property is set, then we are implementing a wrapper type
if (attributeData.NamedArguments is [ (nameof(GenerateSerialize.Through), { Value: string memberName }) ])
{
wrapper = true;
var members = model.LookupSymbols(typeDecl.SpanStart, typeSymbol, memberName);
if (members.Length != 1)
{
Expand All @@ -93,7 +91,6 @@ internal static void GenerateImpl(
// Enums are also always wrapped, but the attribute is on the enum itself
else if (typeDecl.IsKind(SyntaxKind.EnumDeclaration))
{
wrapper = true;
receiverType = typeSymbol;
receiverExpr = IdentifierName("Value");
wrappedName = typeDecl.Identifier.ValueText;
Expand All @@ -102,7 +99,6 @@ internal static void GenerateImpl(
// Just a normal interface implementation
else
{
wrapper = false;
wrapperName = null;
wrappedName = null;
if (!typeDecl.Modifiers.Any(tok => tok.IsKind(SyntaxKind.PartialKeyword)))
Expand All @@ -118,28 +114,6 @@ internal static void GenerateImpl(
receiverExpr = ThisExpression();
}

if (wrapper && usage.HasFlag(SerdeUsage.Serialize))
{
// If we're implementing ISerialize, also implement ISerializeWrap
GenerateISerializeWrapImpl(
wrapperName!,
wrappedName!,
typeDecl,
context);
}

if (usage.HasFlag(SerdeUsage.Serialize))
{
SerializeImplRoslynGenerator.GenerateImpl(
usage,
new TypeDeclContext(typeDecl),
receiverType,
IdentifierName("value"),
context,
inProgress);

}

GenerateImpl(
usage,
new TypeDeclContext(typeDecl),
Expand All @@ -164,36 +138,8 @@ private static void GenerateEnumWrapper(
var typeDeclContext = new TypeDeclContext(typeDecl);
var typeName = typeDeclContext.Name;
var wrapperName = GetWrapperName(typeName);
var newType = SyntaxFactory.ParseMemberDeclaration($"""
readonly partial record struct {wrapperName}({typeName} Value);
""")!;
newType = typeDeclContext.WrapNewType(newType);
string fullWrapperName = string.Join(".", typeDeclContext.NamespaceNames
.Concat(typeDeclContext.ParentTypeInfo.Select(x => x.Name))
.Concat(new[] { wrapperName }));

var tree = CompilationUnit(
externs: default,
usings: default,
attributeLists: default,
members: List<MemberDeclarationSyntax>(new[] { newType }));
tree = tree.NormalizeWhitespace(eol: Environment.NewLine);

context.AddSource(fullWrapperName, Environment.NewLine + tree.ToFullString());
}

private static void GenerateISerializeWrapImpl(
string wrapperName,
string wrappedName,
BaseTypeDeclarationSyntax typeDecl,
GeneratorExecutionContext context)
{
var typeDeclContext = new TypeDeclContext(typeDecl);
var newType = SyntaxFactory.ParseMemberDeclaration($$"""
partial record struct {{wrapperName}} : Serde.ISerializeWrap<{{wrappedName}}, {{wrapperName}}>
{
static {{wrapperName}} Serde.ISerializeWrap<{{wrappedName}}, {{wrapperName}}>.Create({{wrappedName}} value) => new(value);
}
readonly partial struct {{wrapperName}} { }
""")!;
newType = typeDeclContext.WrapNewType(newType);
string fullWrapperName = string.Join(".", typeDeclContext.NamespaceNames
Expand All @@ -207,7 +153,7 @@ partial record struct {{wrapperName}} : Serde.ISerializeWrap<{{wrappedName}}, {{
members: List<MemberDeclarationSyntax>(new[] { newType }));
tree = tree.NormalizeWhitespace(eol: Environment.NewLine);

context.AddSource($"{fullWrapperName}.ISerializeWrap", Environment.NewLine + tree.ToFullString());
context.AddSource(fullWrapperName, Environment.NewLine + tree.ToFullString());
}

private static void GenerateImpl(
Expand All @@ -223,8 +169,8 @@ private static void GenerateImpl(
// Generate statements for the implementation
var (implMembers, baseList) = usage switch
{
SerdeUsage.Serialize => GenerateSerializeImpl(context, receiverType, receiverExpr, inProgress),
SerdeUsage.Deserialize => GenerateDeserializeImpl(context, receiverType, receiverExpr, inProgress),
SerdeUsage.Serialize => SerializeImplRoslynGenerator.GenerateSerializeGenericImpl(context, receiverType, receiverExpr, inProgress),
SerdeUsage.Deserialize => DeserializeImplGenerator.GenerateDeserializeImpl(context, receiverType, receiverExpr, inProgress),
_ => throw ExceptionUtilities.Unreachable
};

Expand All @@ -233,15 +179,12 @@ private static void GenerateImpl(
if (typeKind == SyntaxKind.EnumDeclaration)
{
var wrapperName = GetWrapperName(typeName);
newType = RecordDeclaration(
kind: SyntaxKind.RecordStructDeclaration,
newType = StructDeclaration(
attributeLists: default,
modifiers: TokenList(Token(SyntaxKind.PartialKeyword)),
keyword: Token(SyntaxKind.RecordKeyword),
classOrStructKeyword: Token(SyntaxKind.StructKeyword),
keyword: Token(SyntaxKind.StructKeyword),
identifier: Identifier(wrapperName),
typeParameterList: default,
parameterList: null,
baseList: baseList,
constraintClauses: default,
openBraceToken: Token(SyntaxKind.OpenBraceToken),
Expand Down Expand Up @@ -295,7 +238,7 @@ private static void GenerateImpl(
/// <summary>
/// Check to see if the type implements ISerialize or IDeserialize, depending on the WrapUsage.
/// </summary>
private static bool ImplementsSerde(ITypeSymbol memberType, GeneratorExecutionContext context, SerdeUsage usage)
internal static bool ImplementsSerde(ITypeSymbol memberType, GeneratorExecutionContext context, SerdeUsage usage)
{
// Nullable types are not considered as implementing the Serde interfaces -- they use wrappers to map to the underlying
if (memberType.NullableAnnotation == NullableAnnotation.Annotated ||
Expand Down
Loading

0 comments on commit 0e67e04

Please sign in to comment.