Skip to content

Commit

Permalink
Move more pieces over to TypeInfo (#175)
Browse files Browse the repository at this point in the history
Reworks enum deserialization to go through TypeInfo. This simplifies the
code and makes the deserialization slightly faster.
  • Loading branch information
agocke authored Jul 2, 2024
1 parent baeef4c commit 53b8baf
Show file tree
Hide file tree
Showing 117 changed files with 894 additions and 1,300 deletions.
4 changes: 2 additions & 2 deletions samples/unions/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public void Serialize(BaseType value, ISerializer serializer)
serializeType.End();
}

[GenerateSerde(Through = nameof(Value))]
[GenerateSerde(ThroughMember = nameof(Value))]
private readonly partial record struct DerivedAWrap(DerivedA Value);

[GenerateSerde(Through = nameof(Value))]
[GenerateSerde(ThroughMember = nameof(Value))]
private readonly partial record struct DerivedBWrap(DerivedB Value);
}

Expand Down
2 changes: 2 additions & 0 deletions src/generator/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal enum DiagId
ERR_CantWrapSpecialType = 3,
ERR_CantFindConstructorSignature = 4,
ERR_CantFindNestedWrapper = 5,
ERR_WrapperDoesntImplementInterface = 6,
}

internal static class Diagnostics
Expand All @@ -27,6 +28,7 @@ internal static class Diagnostics
ERR_CantWrapSpecialType => nameof(ERR_CantWrapSpecialType),
ERR_CantFindConstructorSignature => nameof(ERR_CantFindConstructorSignature),
ERR_CantFindNestedWrapper => nameof(ERR_CantFindNestedWrapper),
ERR_WrapperDoesntImplementInterface => nameof(ERR_WrapperDoesntImplementInterface),
};

public static Diagnostic CreateDiagnostic(DiagId id, Location location, params object[] args)
Expand Down
404 changes: 60 additions & 344 deletions src/generator/Generator.Deserialize.cs

Large diffs are not rendered by default.

140 changes: 18 additions & 122 deletions src/generator/Generator.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,110 +57,9 @@ internal void AddSource(string fileName, string content)
partial class SerdeImplRoslynGenerator
{
internal static void GenerateImpl(
AttributeData attributeData,
SerdeUsage usage,
BaseTypeDeclarationSyntax typeDecl,
SemanticModel model,
GeneratorExecutionContext context,
ImmutableList<ITypeSymbol> inProgress)
{
var typeSymbol = model.GetDeclaredSymbol(typeDecl);
if (typeSymbol is null)
{
return;
}

ITypeSymbol receiverType;
ExpressionSyntax receiverExpr;
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 }) ])
{
var members = model.LookupSymbols(typeDecl.SpanStart, typeSymbol, memberName);
if (members.Length != 1)
{
// TODO: Error about bad lookup
return;
}
receiverType = SymbolUtilities.GetSymbolType(members[0]);
receiverExpr = IdentifierName(memberName);
wrapperName = typeDecl.Identifier.ValueText;
wrappedName = receiverType.ToDisplayString();
}
// Enums are also always wrapped, but the attribute is on the enum itself
else if (typeDecl.IsKind(SyntaxKind.EnumDeclaration))
{
receiverType = typeSymbol;
receiverExpr = IdentifierName("Value");
wrappedName = typeDecl.Identifier.ValueText;
wrapperName = GetWrapperName(wrappedName);
}
// Just a normal interface implementation
else
{
wrapperName = null;
wrappedName = null;
if (!typeDecl.Modifiers.Any(tok => tok.IsKind(SyntaxKind.PartialKeyword)))
{
// Type must be partial
context.ReportDiagnostic(CreateDiagnostic(
DiagId.ERR_TypeNotPartial,
typeDecl.Identifier.GetLocation(),
typeDecl.Identifier.ValueText));
return;
}
receiverType = typeSymbol;
receiverExpr = ThisExpression();
}

GenerateImpl(
usage,
new TypeDeclContext(typeDecl),
receiverType,
receiverExpr,
context,
inProgress);
}

private static void GenerateEnumWrapper(
BaseTypeDeclarationSyntax typeDecl,
SemanticModel semanticModel,
GeneratorExecutionContext context)
{
var receiverType = semanticModel.GetDeclaredSymbol(typeDecl);
if (receiverType is null)
{
return;
}

// Generate enum wrapper stub
var typeDeclContext = new TypeDeclContext(typeDecl);
var typeName = typeDeclContext.Name;
var wrapperName = GetWrapperName(typeName);
var newType = SyntaxFactory.ParseMemberDeclaration($$"""
readonly partial struct {{wrapperName}} { }
""")!;
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 GenerateImpl(
SerdeUsage usage,
TypeDeclContext typeDeclContext,
ITypeSymbol receiverType,
ExpressionSyntax receiverExpr,
GeneratorExecutionContext context,
ImmutableList<ITypeSymbol> inProgress)
{
Expand All @@ -169,8 +68,8 @@ private static void GenerateImpl(
// Generate statements for the implementation
var (implMembers, baseList) = usage switch
{
SerdeUsage.Serialize => SerializeImplRoslynGenerator.GenerateSerializeGenericImpl(context, receiverType, receiverExpr, inProgress),
SerdeUsage.Deserialize => DeserializeImplGenerator.GenerateDeserializeImpl(context, receiverType, receiverExpr, inProgress),
SerdeUsage.Serialize => SerializeImplRoslynGenerator.GenerateSerializeGenericImpl(context, receiverType, inProgress),
SerdeUsage.Deserialize => DeserializeImplGenerator.GenerateDeserializeImpl(context, receiverType, inProgress),
_ => throw ExceptionUtilities.Unreachable
};

Expand Down Expand Up @@ -236,36 +135,34 @@ private static void GenerateImpl(
}

/// <summary>
/// Check to see if the type implements ISerialize or IDeserialize, depending on the WrapUsage.
/// Check to see if the <paramref name="targetType"/> implements ISerialize{<paramref
/// name="argType"/>} or IDeserialize{<paramref name="argType"/>}, depending on the WrapUsage.
/// </summary>
internal static bool ImplementsSerde(ITypeSymbol memberType, GeneratorExecutionContext context, SerdeUsage usage)
internal static bool ImplementsSerde(ITypeSymbol targetType, ITypeSymbol argType, 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 ||
memberType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
if (argType.NullableAnnotation == NullableAnnotation.Annotated ||
argType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
{
return false;
}

// Check if the type either has the GenerateSerialize attribute, or directly implements ISerialize
// (If the type has the GenerateSerialize attribute then the generator will implement the interface)
if (memberType.TypeKind is not TypeKind.Enum && HasGenerateAttribute(memberType, usage))
if (argType.TypeKind is not TypeKind.Enum && HasGenerateAttribute(argType, usage))
{
return true;
}

INamedTypeSymbol? serdeSymbol;
if (usage == SerdeUsage.Serialize)
{
serdeSymbol = context.Compilation.GetTypeByMetadataName("Serde.ISerialize");
}
else
{
var deserialize = context.Compilation.GetTypeByMetadataName("Serde.IDeserialize`1");
serdeSymbol = deserialize?.Construct(memberType);
}
if (serdeSymbol is not null && memberType.Interfaces.Contains(serdeSymbol, SymbolEqualityComparer.Default)
|| (memberType is ITypeParameterSymbol param && param.ConstraintTypes.Contains(serdeSymbol, SymbolEqualityComparer.Default)))
var mdName = usage switch {
SerdeUsage.Serialize => "Serde.ISerialize`1",
SerdeUsage.Deserialize => "Serde.IDeserialize`1",
_ => throw new ArgumentException("Invalid SerdeUsage", nameof(usage))
};
var serdeSymbol = context.Compilation.GetTypeByMetadataName(mdName)?.Construct(argType);

if (serdeSymbol is not null && targetType.AllInterfaces.Contains(serdeSymbol, SymbolEqualityComparer.Default)
|| (targetType is ITypeParameterSymbol param && param.ConstraintTypes.Contains(serdeSymbol, SymbolEqualityComparer.Default)))
{
return true;
}
Expand Down Expand Up @@ -298,8 +195,7 @@ internal static bool ImplementsSerde(ITypeSymbol memberType, GeneratorExecutionC
return SyntaxFactory.ParseTypeName(wrapperFqn);
}

private static string GetWrapperName(string typeName) => typeName + "Wrap";

internal static string GetWrapperName(string typeName) => typeName + "Wrap";

internal static bool HasGenerateAttribute(ITypeSymbol memberType, SerdeUsage usage)
{
Expand Down
64 changes: 1 addition & 63 deletions src/generator/Generator.SerdeTypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,71 +25,11 @@ internal static class SerdeTypeInfoGenerator
/// }
/// </code>
/// </summary>
public static void GenerateTypeInfo(
AttributeData attributeData,
BaseTypeDeclarationSyntax typeDecl,
SemanticModel model,
GeneratorExecutionContext context)
{
var typeSymbol = model.GetDeclaredSymbol(typeDecl);
if (typeSymbol is null)
{
return;
}

INamedTypeSymbol receiverType;
ExpressionSyntax receiverExpr;
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 }) ])
{
var members = model.LookupSymbols(typeDecl.SpanStart, typeSymbol, memberName);
if (members.Length != 1)
{
// TODO: Error about bad lookup
return;
}
receiverType = (INamedTypeSymbol)SymbolUtilities.GetSymbolType(members[0]);
receiverExpr = IdentifierName(memberName);
wrapperName = typeDecl.Identifier.ValueText;
wrappedName = receiverType.ToDisplayString();
}
// Enums are also always wrapped, but the attribute is on the enum itself
else if (typeDecl.IsKind(SyntaxKind.EnumDeclaration))
{
receiverType = typeSymbol;
receiverExpr = IdentifierName("Value");
wrappedName = typeDecl.Identifier.ValueText;
wrapperName = GetWrapperName(wrappedName);
}
// Just a normal interface implementation
else
{
wrapperName = null;
wrappedName = null;
if (!typeDecl.Modifiers.Any(tok => tok.IsKind(SyntaxKind.PartialKeyword)))
{
// Type must be partial
context.ReportDiagnostic(CreateDiagnostic(
DiagId.ERR_TypeNotPartial,
typeDecl.Identifier.GetLocation(),
typeDecl.Identifier.ValueText));
return;
}
receiverType = typeSymbol;
receiverExpr = ThisExpression();
}

GenerateTypeInfo(typeDecl, receiverType, context);
}

public static void GenerateTypeInfo(
BaseTypeDeclarationSyntax typeDecl,
INamedTypeSymbol receiverType,
GeneratorExecutionContext context)
{

var statements = new List<StatementSyntax>();
var fieldsAndProps = SymbolUtilities.GetDataMembers(receiverType, SerdeUsage.Both);
var typeDeclContext = new TypeDeclContext(typeDecl);
Expand All @@ -105,7 +45,7 @@ internal static class {{typeName}}SerdeTypeInfo
{
internal static readonly Serde.TypeInfo TypeInfo = Serde.TypeInfo.Create(
"{{typeName}}",
Serde.TypeInfo.TypeKind.CustomType,
Serde.TypeInfo.TypeKind.{{(receiverType.TypeKind == TypeKind.Enum ? "Enum" : "CustomType")}},
new (string, System.Reflection.MemberInfo)[] {
{{string.Join("," + Environment.NewLine,
fieldsAndProps.Select(x => $@"(""{x.GetFormattedName()}"", typeof({typeString}).Get{(x.Symbol.Kind == SymbolKind.Field ? "Field" : "Property")}(""{x.Name}"")!)"))}}
Expand All @@ -120,6 +60,4 @@ internal static class {{typeName}}SerdeTypeInfo

context.AddSource(fullTypeName, newType);
}

private static string GetWrapperName(string typeName) => typeName + "Wrap";
}
Loading

0 comments on commit 53b8baf

Please sign in to comment.