Skip to content
Draft
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
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Blittable Struct は全ケースで offset table を持たない raw payload と

`[ZeroSerializer]` が付いていても Blittable Struct はネスト View 化しません。親が非 Blittable 型なら親の field offset table は存在しますが、Blittable Struct payload 内部には table を生成しません。

全フィールド型が Blittable 対応済みで、自身の `StructLayout(LayoutKind.Sequential, Pack = 1)` だけが不足する `[ZeroSerializer]` struct には、型名 identifier へ `ZEROS006` warning を出します。その struct が有効な `[ZeroSerializer]` ネスト型として使われている場合は、raw payload 化による性能改善を案内する `ZEROS007` info もネスト型の identifier へ1回だけ出します。`[ZeroSerializer]` がない間は親型に `ZEROS003` error が発生し、`ZEROS007` は生成エラーが解消されるまで出ません。
全フィールド型が Blittable 対応済みで、自身の `StructLayout(LayoutKind.Sequential, Pack = 1)` だけが不足する `[ZeroSerializer]` struct には、型名 identifier へ `ZEROS006` warning を出します。

ネストした class/struct にも `[ZeroSerializer]` が必要です。未修飾の型は `ZEROS003` error になります。

Expand Down
49 changes: 11 additions & 38 deletions src/ZeroSerializerGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ public sealed class ZeroSerializerGenerator : ISourceGenerator
DiagnosticSeverity.Warning,
isEnabledByDefault: true);

private static readonly DiagnosticDescriptor BlittableCompatibleNestedStruct = new(
private static readonly DiagnosticDescriptor UseFlagsEnumToReducePayloadSize = new(
"ZEROS007",
"Nested struct can use faster Blittable serialization",
"Nested struct '{0}' can use StructLayout(LayoutKind.Sequential, Pack = 1) to improve serialization performance with raw payload serialization",
"Use flags enum to reduce payload size",
"Property '{0}' uses bool type; consider using a flags enum (byte) to reduce payload size by combining up to 8 booleans into one byte",
SerializerName,
DiagnosticSeverity.Info,
isEnabledByDefault: true);
Expand Down Expand Up @@ -249,11 +249,6 @@ private static void ExecuteCore(
return;
}

ReportBlittableCompatibleNestedStructDiagnostics(
executionContext,
validModels,
generationModels);

var modelLookup = new Dictionary<INamedTypeSymbol, TypeGenerationModel>(SymbolEqualityComparer.Default);
// Each type owns one generated file and contributes one method to its namespace-local partial extension class.
foreach (TypeGenerationModel validModel in validModels)
Expand Down Expand Up @@ -373,6 +368,14 @@ private static TypeGenerationModel CreateGenerationModel(
continue;
}

if (serializableProperty.Type.SpecialType == SpecialType.System_Boolean)
{
executionContext.ReportDiagnostic(Diagnostic.Create(
UseFlagsEnumToReducePayloadSize,
GetPropertyTypeLocation(serializableProperty),
serializableProperty.Name));
}

generationModel.Fields.Add(propertyModel);
}

Expand Down Expand Up @@ -761,36 +764,6 @@ private static bool HasBlittableCompatibleFieldShape(INamedTypeSymbol candidateS
return true;
}

private static void ReportBlittableCompatibleNestedStructDiagnostics(
GeneratorExecutionContext executionContext,
IReadOnlyList<TypeGenerationModel> validGenerationModels,
IReadOnlyDictionary<INamedTypeSymbol, TypeGenerationModel> generationModels)
{
var reportedNestedStructs = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
foreach (TypeGenerationModel containingGenerationModel in validGenerationModels)
{
foreach (FieldGenerationModel nestedField in containingGenerationModel.Fields)
{
if (nestedField.Kind != FieldSerializationKind.Nested
|| nestedField.NestedSerializableType is not INamedTypeSymbol nestedSerializableStruct
|| nestedSerializableStruct.TypeKind != TypeKind.Struct
|| !generationModels.TryGetValue(nestedSerializableStruct, out TypeGenerationModel? nestedGenerationModel)
|| nestedGenerationModel.IsBlittableStruct
|| !HasBlittableCompatibleFieldShape(nestedSerializableStruct)
|| !reportedNestedStructs.Add(nestedSerializableStruct))
{
continue;
}

// Report only after dependency validation so this performance advice never replaces ZEROS003 or another generation error.
executionContext.ReportDiagnostic(Diagnostic.Create(
BlittableCompatibleNestedStruct,
GetTypeIdentifierLocation(nestedSerializableStruct),
nestedSerializableStruct.ToDisplayString()));
}
}
}

private static Location? GetTypeIdentifierLocation(INamedTypeSymbol declaredType)
{
foreach (SyntaxReference declaringSyntaxReference in declaredType.DeclaringSyntaxReferences)
Expand Down
29 changes: 6 additions & 23 deletions tests/Diagnostics/DiagnosticTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -507,54 +507,37 @@ await CSharpSourceGeneratorVerifier<ZeroSerializerGenerator>.VerifySourceGenerat
}

[Fact]
public async Task ZEROS007_Violation_BlittableCompatibleNestedStruct()
public async Task ZEROS007_Violation_BoolProperty()
{
string source = @"
using ZeroSerializer;

[ZeroSerializer]
public struct {|#0:NestedStruct|}
public class MyBoolClass
{
public int Value { get; set; }
}

[ZeroSerializer]
public class ParentClass
{
public NestedStruct Child { get; set; }
public {|#0:bool|} IsActive { get; set; }
}
";

await CSharpSourceGeneratorVerifier<ZeroSerializerGenerator>.VerifySourceGeneratorAsync(
source,
new DiagnosticResult("ZEROS006", DiagnosticSeverity.Warning)
.WithLocation(0)
.WithMessage("Struct 'NestedStruct' has a Blittable-compatible field shape; use StructLayout(LayoutKind.Sequential, Pack = 1) to enable raw payload serialization"),
new DiagnosticResult("ZEROS007", DiagnosticSeverity.Info)
.WithLocation(0)
.WithMessage("Nested struct 'NestedStruct' can use StructLayout(LayoutKind.Sequential, Pack = 1) to improve serialization performance with raw payload serialization")
.WithMessage("Property 'IsActive' uses bool type; consider using a flags enum (byte) to reduce payload size by combining up to 8 booleans into one byte")
);
}

[Fact]
public async Task ZEROS007_Compliant_BlittableNestedStructWithLayout()
public async Task ZEROS007_Compliant_NonBoolProperty()
{
string source = @"
using System.Runtime.InteropServices;
using ZeroSerializer;

[ZeroSerializer]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct NestedStructWithLayout
public class MyNonBoolClass
{
public int Value { get; set; }
}

[ZeroSerializer]
public class ParentClassWithBlittable
{
public NestedStructWithLayout Child { get; set; }
}
";

await CSharpSourceGeneratorVerifier<ZeroSerializerGenerator>.VerifySourceGeneratorAsync(
Expand Down
Loading