Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move more pieces over to TypeInfo #175

Merged
merged 2 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading