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

Add TypeAttributes to ISerdeInfo #185

Merged
merged 1 commit into from
Jul 28, 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
1 change: 1 addition & 0 deletions perf/bench/SampleTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public partial record LocationWrap : IDeserialize<Location>
{
public static ISerdeInfo SerdeInfo { get; } = Serde.SerdeInfo.MakeCustom(
"Location",
typeof(Location).GetCustomAttributesData(),
[
("id", StringWrap.SerdeInfo, typeof(Location).GetProperty("Id")!),
("address1", StringWrap.SerdeInfo, typeof(Location).GetProperty("Address1")!),
Expand Down
299 changes: 149 additions & 150 deletions src/generator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,184 +10,183 @@
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Serde.Diagnostics;

namespace Serde
namespace Serde;

/// <summary>
/// Recognizes the [GenerateSerialize] attribute on a type to generate an implementation
/// of Serde.ISerialize. The implementation generally looks like a call to SerializeType,
/// then successive calls to SerializeField.
/// </summary>
/// <example>
/// For a type like,
///
/// <code>
/// [GenerateSerialize]
/// partial struct Rgb { public byte Red; public byte Green; public byte Blue; }
/// </code>
///
/// The generated code would be,
///
/// <code>
/// using Serde;
///
/// partial struct Rgb : Serde.ISerialize
/// {
/// void Serde.ISerialize.Serialize&lt;TSerializer, TSerializeType, TSerializeEnumerable, TSerializeDictionary&gt;TSerializer serializer)
/// {
/// var type = serializer.SerializeType("Rgb", 3);
/// type.SerializeField("Red", new ByteWrap(Red));
/// type.SerializeField("Green", new ByteWrap(Green));
/// type.SerializeField("Blue", new ByteWrap(Blue));
/// type.End();
/// }
/// }
/// </code>
/// </example>
[Generator]
public partial class SerdeImplRoslynGenerator : IIncrementalGenerator
{
/// <summary>
/// Recognizes the [GenerateSerialize] attribute on a type to generate an implementation
/// of Serde.ISerialize. The implementation generally looks like a call to SerializeType,
/// then successive calls to SerializeField.
/// </summary>
/// <example>
/// For a type like,
///
/// <code>
/// [GenerateSerialize]
/// partial struct Rgb { public byte Red; public byte Green; public byte Blue; }
/// </code>
///
/// The generated code would be,
///
/// <code>
/// using Serde;
///
/// partial struct Rgb : Serde.ISerialize
/// {
/// void Serde.ISerialize.Serialize&lt;TSerializer, TSerializeType, TSerializeEnumerable, TSerializeDictionary&gt;TSerializer serializer)
/// {
/// var type = serializer.SerializeType("Rgb", 3);
/// type.SerializeField("Red", new ByteWrap(Red));
/// type.SerializeField("Green", new ByteWrap(Green));
/// type.SerializeField("Blue", new ByteWrap(Blue));
/// type.End();
/// }
/// }
/// </code>
/// </example>
[Generator]
public partial class SerdeImplRoslynGenerator : IIncrementalGenerator
/// <inheritdoc />
public void Initialize(IncrementalGeneratorInitializationContext context)
{
/// <inheritdoc />
public void Initialize(IncrementalGeneratorInitializationContext context)
static GenerationOutput GenerateForCtx(
SerdeUsage usage,
GeneratorAttributeSyntaxContext attrCtx,
CancellationToken cancelToken)
{
static GenerationOutput GenerateForCtx(
var generationContext = new GeneratorExecutionContext(attrCtx);
RunGeneration(usage, attrCtx, generationContext, cancelToken);
return generationContext.GetOutput();

static void RunGeneration(
SerdeUsage usage,
GeneratorAttributeSyntaxContext attrCtx,
GeneratorExecutionContext generationContext,
CancellationToken cancelToken)
{
var generationContext = new GeneratorExecutionContext(attrCtx);
RunGeneration(usage, attrCtx, generationContext, cancelToken);
return generationContext.GetOutput();

static void RunGeneration(
SerdeUsage usage,
GeneratorAttributeSyntaxContext attrCtx,
GeneratorExecutionContext generationContext,
CancellationToken cancelToken)
var typeDecl = (BaseTypeDeclarationSyntax)attrCtx.TargetNode;
var model = attrCtx.SemanticModel;
var typeSymbol = model.GetDeclaredSymbol(typeDecl);
if (typeSymbol is null)
{
return;
}

var attributeData = attrCtx.Attributes.Single();

INamedTypeSymbol receiverType = typeSymbol;
// If the Through property is set, then we are implementing a wrapper type
if (attributeData.NamedArguments is [(nameof(GenerateSerialize.ThroughMember), { Value: string memberName })])
{
var typeDecl = (BaseTypeDeclarationSyntax)attrCtx.TargetNode;
var model = attrCtx.SemanticModel;
var typeSymbol = model.GetDeclaredSymbol(typeDecl);
if (typeSymbol is null)
var members = model.LookupSymbols(typeDecl.SpanStart, typeSymbol, memberName);
if (members.Length != 1)
{
// TODO: Error about bad lookup
return;
}
receiverType = (INamedTypeSymbol)SymbolUtilities.GetSymbolType(members[0]);

var attributeData = attrCtx.Attributes.Single();

INamedTypeSymbol receiverType = typeSymbol;
// If the Through property is set, then we are implementing a wrapper type
if (attributeData.NamedArguments is [(nameof(GenerateSerialize.ThroughMember), { Value: string memberName })])
if (receiverType.SpecialType != SpecialType.None)
{
var members = model.LookupSymbols(typeDecl.SpanStart, typeSymbol, memberName);
if (members.Length != 1)
{
// TODO: Error about bad lookup
return;
}
receiverType = (INamedTypeSymbol)SymbolUtilities.GetSymbolType(members[0]);

if (receiverType.SpecialType != SpecialType.None)
{
generationContext.ReportDiagnostic(CreateDiagnostic(
DiagId.ERR_CantWrapSpecialType,
attributeData.ApplicationSyntaxReference!.GetSyntax().GetLocation(),
receiverType));
return;
}
generationContext.ReportDiagnostic(CreateDiagnostic(
DiagId.ERR_CantWrapSpecialType,
attributeData.ApplicationSyntaxReference!.GetSyntax().GetLocation(),
receiverType));
return;
}
else if (!typeDecl.IsKind(SyntaxKind.EnumDeclaration))
}
else if (!typeDecl.IsKind(SyntaxKind.EnumDeclaration))
{
if (!typeDecl.Modifiers.Any(tok => tok.IsKind(SyntaxKind.PartialKeyword)))
{
if (!typeDecl.Modifiers.Any(tok => tok.IsKind(SyntaxKind.PartialKeyword)))
{
// Type must be partial
generationContext.ReportDiagnostic(CreateDiagnostic(
DiagId.ERR_TypeNotPartial,
typeDecl.Identifier.GetLocation(),
typeDecl.Identifier.ValueText));
return;
}
// Type must be partial
generationContext.ReportDiagnostic(CreateDiagnostic(
DiagId.ERR_TypeNotPartial,
typeDecl.Identifier.GetLocation(),
typeDecl.Identifier.ValueText));
return;
}
}

if (typeDecl.IsKind(SyntaxKind.EnumDeclaration))
{
Wrappers.GenerateEnumWrapper(
typeDecl,
attrCtx.SemanticModel,
generationContext);
}
if (typeDecl.IsKind(SyntaxKind.EnumDeclaration))
{
Wrappers.GenerateEnumWrapper(
typeDecl,
attrCtx.SemanticModel,
generationContext);
}

var inProgress = ImmutableList.Create<ITypeSymbol>(receiverType);
var inProgress = ImmutableList.Create<ITypeSymbol>(receiverType);

SerdeInfoGenerator.GenerateSerdeInfo(
typeDecl,
SerdeInfoGenerator.GenerateSerdeInfo(
typeDecl,
receiverType,
generationContext,
usage.HasFlag(SerdeUsage.Serialize) ? SerdeUsage.Serialize : SerdeUsage.Deserialize,
inProgress);

if (usage.HasFlag(SerdeUsage.Serialize))
{
GenerateImpl(
SerdeUsage.Serialize,
new TypeDeclContext(typeDecl),
receiverType,
generationContext,
usage.HasFlag(SerdeUsage.Serialize) ? SerdeUsage.Serialize : SerdeUsage.Deserialize,
inProgress);
}

if (usage.HasFlag(SerdeUsage.Serialize))
{
GenerateImpl(
SerdeUsage.Serialize,
new TypeDeclContext(typeDecl),
receiverType,
generationContext,
inProgress);
}

if (usage.HasFlag(SerdeUsage.Deserialize))
{
GenerateImpl(
SerdeUsage.Deserialize,
new TypeDeclContext(typeDecl),
receiverType,
generationContext,
inProgress);
}
if (usage.HasFlag(SerdeUsage.Deserialize))
{
GenerateImpl(
SerdeUsage.Deserialize,
new TypeDeclContext(typeDecl),
receiverType,
generationContext,
inProgress);
}
}
}

var generateSerdeTypes = context.SyntaxProvider.ForAttributeWithMetadataName(
WellKnownAttribute.GenerateSerde.GetFqn(),
(_, _) => true,
(attrCtx, cancelToken) => GenerateForCtx(SerdeUsage.Both, attrCtx, cancelToken));

var generateSerializeTypes = context.SyntaxProvider.ForAttributeWithMetadataName(
WellKnownAttribute.GenerateSerialize.GetFqn(),
(_, _) => true,
(attrCtx, cancelToken) => GenerateForCtx(SerdeUsage.Serialize, attrCtx, cancelToken));

var generateDeserializeTypes = context.SyntaxProvider.ForAttributeWithMetadataName(
WellKnownAttribute.GenerateDeserialize.GetFqn(),
(_, _) => true,
(attrCtx, cancelToken) => GenerateForCtx(SerdeUsage.Deserialize, attrCtx, cancelToken));

// Combine GenerateSerialize and GenerateDeserialize in case they both need to generate an identical
// helper type.
var combined = generateSerializeTypes.Collect()
.Combine(generateDeserializeTypes.Collect())
.Select(static (values, _) =>
{
var (serialize, deserialize) = values;
return new GenerationOutput(
serialize.SelectMany(static o => o.Diagnostics).Concat(deserialize.SelectMany(static o => o.Diagnostics)),
serialize.SelectMany(static o => o.Sources).Concat(deserialize.SelectMany(static o => o.Sources)));
});
var generateSerdeTypes = context.SyntaxProvider.ForAttributeWithMetadataName(
WellKnownAttribute.GenerateSerde.GetFqn(),
(_, _) => true,
(attrCtx, cancelToken) => GenerateForCtx(SerdeUsage.Both, attrCtx, cancelToken));

var generateSerializeTypes = context.SyntaxProvider.ForAttributeWithMetadataName(
WellKnownAttribute.GenerateSerialize.GetFqn(),
(_, _) => true,
(attrCtx, cancelToken) => GenerateForCtx(SerdeUsage.Serialize, attrCtx, cancelToken));

var generateDeserializeTypes = context.SyntaxProvider.ForAttributeWithMetadataName(
WellKnownAttribute.GenerateDeserialize.GetFqn(),
(_, _) => true,
(attrCtx, cancelToken) => GenerateForCtx(SerdeUsage.Deserialize, attrCtx, cancelToken));

// Combine GenerateSerialize and GenerateDeserialize in case they both need to generate an identical
// helper type.
var combined = generateSerializeTypes.Collect()
.Combine(generateDeserializeTypes.Collect())
.Select(static (values, _) =>
{
var (serialize, deserialize) = values;
return new GenerationOutput(
serialize.SelectMany(static o => o.Diagnostics).Concat(deserialize.SelectMany(static o => o.Diagnostics)),
serialize.SelectMany(static o => o.Sources).Concat(deserialize.SelectMany(static o => o.Sources)));
});

var provideOutput = static (SourceProductionContext ctx, GenerationOutput output) =>
var provideOutput = static (SourceProductionContext ctx, GenerationOutput output) =>
{
foreach (var d in output.Diagnostics)
{
foreach (var d in output.Diagnostics)
{
ctx.ReportDiagnostic(d);
}
foreach (var (fileName, content) in output.Sources)
{
ctx.AddSource(fileName, content);
}
};
ctx.ReportDiagnostic(d);
}
foreach (var (fileName, content) in output.Sources)
{
ctx.AddSource(fileName, content);
}
};

context.RegisterSourceOutput(generateSerdeTypes, provideOutput);
context.RegisterSourceOutput(combined, provideOutput);
}
context.RegisterSourceOutput(generateSerdeTypes, provideOutput);
context.RegisterSourceOutput(combined, provideOutput);
}
}
13 changes: 8 additions & 5 deletions src/generator/SerdeInfoGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ internal static class SerdeInfoGenerator
/// <code>
/// partial {typeKind} {typeName}
/// {
/// internal static readonly SerdeInfo TypeInfo = TypeInfo.Create([
/// ("{{fieldName}}", typeof({typeName}).GetField("{fieldName}")!),
/// ...
/// ]);
/// internal static ISerdeInfo SerdeInfo { get; } = Serde.SerdeInfo.MakeCustom(
/// {typeName},
/// typeof({typeName}).GetCustomAttributesData(),
/// [
/// ("{{fieldName}}", SerdeInfoProvider.GetInfo&lt;{wrapperName}&gt;typeof({typeName}).GetField("{fieldName}")!),
/// ...
/// ]);
/// }
/// </code>
/// </summary>
Expand Down Expand Up @@ -57,7 +60,7 @@ public static void GenerateSerdeInfo(
var membersString = string.Join("," + Environment.NewLine,
SymbolUtilities.GetDataMembers(receiverType, SerdeUsage.Both).SelectNotNull(GetMemberEntry));

List<string> makeArgs = [ $"\"{receiverType.Name}\"" ];
List<string> makeArgs = [ $"\"{receiverType.Name}\"", $"typeof({typeString}).GetCustomAttributesData()" ];

if (isEnum)
{
Expand Down
7 changes: 7 additions & 0 deletions src/serde/ISerdeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ public interface ISerdeInfo
string TypeName { get; }
TypeKind Kind { get; }

/// <summary>
/// Get the attributes for the type. This list may be modified from the original set of attributes
/// in source code or metadata to reflect only the attributes that are relevant to serialization or
/// deserialization.
/// </summary>
IList<CustomAttributeData> TypeAttributes { get; }

/// <summary>
/// The number of serializable or deserializable fields or properties on the type.
/// </summary>
Expand Down
Loading
Loading