Skip to content
Closed
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: 3 additions & 1 deletion .github/workflows/test-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ jobs:

- if: failure()
name: TEST FAILURE LOG
run: cat test-output.txt
run: |
cat test-output.txt
exit 1
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ jobs:

- if: failure()
name: TEST FAILURE LOG
run: cat test-output.txt
run: |
cat test-output.txt
exit 1


# Place after test to make step summary order better (test -> code preview)
Expand Down
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 を出します。また、`bool` 型のプロパティーに対しては、flags enum (byte) の使用によるペイロードサイズの削減を促す `ZEROS007` info をプロパティーの型位置に出します

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

Expand Down
62 changes: 24 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 BoolPropertyTypeUseFlagsEnum = 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 @@ -351,13 +346,21 @@ private static TypeGenerationModel CreateGenerationModel(
continue;
}

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

FieldGenerationModel? propertyModel = CreatePropertyGenerationModel(serializableProperty, allSerializableTypes);
if (propertyModel is null)
{
generationModel.IsValid = false;
executionContext.ReportDiagnostic(Diagnostic.Create(
UnsupportedSerializableField,
serializableProperty.Locations.IsDefaultOrEmpty ? null : serializableProperty.Locations[0],
GetPropertyTypeLocation(serializableProperty),
serializableProperty.Name,
serializableProperty.Type.ToDisplayString()));
continue;
Expand All @@ -368,7 +371,7 @@ private static TypeGenerationModel CreateGenerationModel(
generationModel.IsValid = false;
executionContext.ReportDiagnostic(Diagnostic.Create(
InvalidBlittableArrayElement,
serializableProperty.Locations.IsDefaultOrEmpty ? null : serializableProperty.Locations[0],
GetPropertyTypeLocation(serializableProperty),
serializableProperty.Name));
continue;
}
Expand Down Expand Up @@ -747,47 +750,30 @@ private static bool HasBlittableCompatibleFieldShape(INamedTypeSymbol candidateS
return true;
}

private static void ReportBlittableCompatibleNestedStructDiagnostics(
GeneratorExecutionContext executionContext,
IReadOnlyList<TypeGenerationModel> validGenerationModels,
IReadOnlyDictionary<INamedTypeSymbol, TypeGenerationModel> generationModels)
private static Location? GetTypeIdentifierLocation(INamedTypeSymbol declaredType)
{
var reportedNestedStructs = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
foreach (TypeGenerationModel containingGenerationModel in validGenerationModels)
foreach (SyntaxReference declaringSyntaxReference in declaredType.DeclaringSyntaxReferences)
{
foreach (FieldGenerationModel nestedField in containingGenerationModel.Fields)
if (declaringSyntaxReference.GetSyntax() is TypeDeclarationSyntax typeDeclaration)
{
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()));
return typeDeclaration.Identifier.GetLocation();
}
}

return declaredType.Locations.IsDefaultOrEmpty ? null : declaredType.Locations[0];
}

private static Location? GetTypeIdentifierLocation(INamedTypeSymbol declaredType)
private static Location? GetPropertyTypeLocation(IPropertySymbol propertySymbol)
{
foreach (SyntaxReference declaringSyntaxReference in declaredType.DeclaringSyntaxReferences)
foreach (SyntaxReference declaringSyntaxReference in propertySymbol.DeclaringSyntaxReferences)
{
if (declaringSyntaxReference.GetSyntax() is TypeDeclarationSyntax typeDeclaration)
if (declaringSyntaxReference.GetSyntax() is PropertyDeclarationSyntax propertyDeclaration)
{
return typeDeclaration.Identifier.GetLocation();
return propertyDeclaration.Type.GetLocation();
}
}

return declaredType.Locations.IsDefaultOrEmpty ? null : declaredType.Locations[0];
return propertySymbol.Locations.IsDefaultOrEmpty ? null : propertySymbol.Locations[0];
}

private static bool TryGetPrimitiveByteCount(ITypeSymbol candidateType, out int byteCount)
Expand Down
41 changes: 12 additions & 29 deletions tests/Diagnostics/DiagnosticTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@ public struct PackedValue
[ZeroSerializer]
public class Container
{
public PackedValue {|#0:Value|} { get; set; }
public PackedValue? {|#1:OptionalValue|} { get; set; }
public {|#0:PackedValue|} Value { get; set; }
public {|#1:PackedValue?|} OptionalValue { get; set; }
}
";

Expand Down Expand Up @@ -280,7 +280,7 @@ public class UnmarkedClass
[ZeroSerializer]
public class Container
{
public UnmarkedClass {|#0:Value|} { get; set; }
public {|#0:UnmarkedClass|} Value { get; set; }
}
";

Expand Down Expand Up @@ -335,7 +335,7 @@ public struct PackedValue
[ZeroSerializer]
public class Container
{
public PackedValue[] {|#0:Values|} { get; set; }
public {|#0:PackedValue[]|} Values { get; set; }
}
";

Expand Down Expand Up @@ -389,7 +389,7 @@ public struct PackedValue
[ZeroSerializer]
public class InvalidType
{
public PackedValue {|#0:Value|} { get; set; }
public {|#0:PackedValue|} Value { get; set; }
}

[ZeroSerializer]
Expand Down Expand Up @@ -484,53 +484,36 @@ 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 BoolPropertyContainer
{
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 int Value { get; set; }
}

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

Expand Down
Loading