From 0fb017b93c20e6a113a94730c409efa60faf6ea7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 00:58:15 +0000 Subject: [PATCH 1/5] Change ZEROS007 to report on bool property types Update ZEROS007 to report an info diagnostic on bool property type syntax locations recommending flags enum (byte) to reduce payload size. Remove obsolete ZEROS007 check for blittable-compatible nested structs. --- README.ja.md | 2 +- src/ZeroSerializerGenerator.cs | 48 ++++++++++------------------ tests/Diagnostics/DiagnosticTests.cs | 31 ++++-------------- 3 files changed, 25 insertions(+), 56 deletions(-) diff --git a/README.ja.md b/README.ja.md index 99c031e..e2ac899 100644 --- a/README.ja.md +++ b/README.ja.md @@ -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 になります。 diff --git a/src/ZeroSerializerGenerator.cs b/src/ZeroSerializerGenerator.cs index 52628b7..6aab3d6 100644 --- a/src/ZeroSerializerGenerator.cs +++ b/src/ZeroSerializerGenerator.cs @@ -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", SerializerName, DiagnosticSeverity.Info, isEnabledByDefault: true); @@ -249,11 +249,6 @@ private static void ExecuteCore( return; } - ReportBlittableCompatibleNestedStructDiagnostics( - executionContext, - validModels, - generationModels); - var modelLookup = new Dictionary(SymbolEqualityComparer.Default); // Each type owns one generated file and contributes one method to its namespace-local partial extension class. foreach (TypeGenerationModel validModel in validModels) @@ -351,6 +346,14 @@ 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) { @@ -747,34 +750,17 @@ private static bool HasBlittableCompatibleFieldShape(INamedTypeSymbol candidateS return true; } - private static void ReportBlittableCompatibleNestedStructDiagnostics( - GeneratorExecutionContext executionContext, - IReadOnlyList validGenerationModels, - IReadOnlyDictionary generationModels) + private static Location? GetPropertyTypeLocation(IPropertySymbol property) { - var reportedNestedStructs = new HashSet(SymbolEqualityComparer.Default); - foreach (TypeGenerationModel containingGenerationModel in validGenerationModels) + foreach (SyntaxReference declaringSyntaxReference in property.DeclaringSyntaxReferences) { - foreach (FieldGenerationModel nestedField in containingGenerationModel.Fields) + if (declaringSyntaxReference.GetSyntax() is PropertyDeclarationSyntax propertyDeclaration) { - 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 propertyDeclaration.Type.GetLocation(); } } + + return property.Locations.IsDefaultOrEmpty ? null : property.Locations[0]; } private static Location? GetTypeIdentifierLocation(INamedTypeSymbol declaredType) diff --git a/tests/Diagnostics/DiagnosticTests.cs b/tests/Diagnostics/DiagnosticTests.cs index 54f16fd..fa6b05b 100644 --- a/tests/Diagnostics/DiagnosticTests.cs +++ b/tests/Diagnostics/DiagnosticTests.cs @@ -484,53 +484,36 @@ await CSharpSourceGeneratorVerifier.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.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") ); } [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; } } "; From 4a852226735aaf6a7498272143e4f874db6978b0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:29:28 +0000 Subject: [PATCH 2/5] Update ZEROS007 message to include payload reduction details Expand ZEROS007 diagnostic message to clarify payload size reduction by combining up to 8 booleans into one byte. --- src/ZeroSerializerGenerator.cs | 2 +- tests/Diagnostics/DiagnosticTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZeroSerializerGenerator.cs b/src/ZeroSerializerGenerator.cs index 6aab3d6..6c640f4 100644 --- a/src/ZeroSerializerGenerator.cs +++ b/src/ZeroSerializerGenerator.cs @@ -79,7 +79,7 @@ public sealed class ZeroSerializerGenerator : ISourceGenerator private static readonly DiagnosticDescriptor BoolPropertyTypeUseFlagsEnum = new( "ZEROS007", "Use flags enum to reduce payload size", - "Property '{0}' uses bool type; consider using a flags enum (byte) 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); diff --git a/tests/Diagnostics/DiagnosticTests.cs b/tests/Diagnostics/DiagnosticTests.cs index fa6b05b..56bd6a2 100644 --- a/tests/Diagnostics/DiagnosticTests.cs +++ b/tests/Diagnostics/DiagnosticTests.cs @@ -500,7 +500,7 @@ await CSharpSourceGeneratorVerifier.VerifySourceGenerat source, new DiagnosticResult("ZEROS007", DiagnosticSeverity.Info) .WithLocation(0) - .WithMessage("Property 'IsActive' uses bool type; consider using a flags enum (byte) to reduce payload size") + .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") ); } From e062c05246c8dac9867f2120c50dcbfe3ee81784 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:36:14 +0000 Subject: [PATCH 3/5] Update ZEROS007 message to include payload reduction details Expand ZEROS007 diagnostic message to clarify payload size reduction by combining up to 8 booleans into one byte. From 7bbff0542aada451b948c57a9c4a8bf7074d54ce Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:42:42 +0000 Subject: [PATCH 4/5] Remove duplicate GetPropertyTypeLocation method Clean up duplicate GetPropertyTypeLocation method in ZeroSerializerGenerator.cs that was introduced during rebase. --- .github/workflows/test-integration.yml | 4 +++- .github/workflows/test.yml | 4 +++- src/ZeroSerializerGenerator.cs | 24 ++++++++++++------------ tests/Diagnostics/DiagnosticTests.cs | 10 +++++----- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test-integration.yml b/.github/workflows/test-integration.yml index 8f60d49..1cef116 100644 --- a/.github/workflows/test-integration.yml +++ b/.github/workflows/test-integration.yml @@ -39,4 +39,6 @@ jobs: - if: failure() name: TEST FAILURE LOG - run: cat test-output.txt + run: | + cat test-output.txt + exit 1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d2ef75d..73cac0c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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) diff --git a/src/ZeroSerializerGenerator.cs b/src/ZeroSerializerGenerator.cs index 6c640f4..8147b99 100644 --- a/src/ZeroSerializerGenerator.cs +++ b/src/ZeroSerializerGenerator.cs @@ -360,7 +360,7 @@ private static TypeGenerationModel CreateGenerationModel( generationModel.IsValid = false; executionContext.ReportDiagnostic(Diagnostic.Create( UnsupportedSerializableField, - serializableProperty.Locations.IsDefaultOrEmpty ? null : serializableProperty.Locations[0], + GetPropertyTypeLocation(serializableProperty), serializableProperty.Name, serializableProperty.Type.ToDisplayString())); continue; @@ -371,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; } @@ -750,30 +750,30 @@ private static bool HasBlittableCompatibleFieldShape(INamedTypeSymbol candidateS return true; } - private static Location? GetPropertyTypeLocation(IPropertySymbol property) + private static Location? GetTypeIdentifierLocation(INamedTypeSymbol declaredType) { - foreach (SyntaxReference declaringSyntaxReference in property.DeclaringSyntaxReferences) + foreach (SyntaxReference declaringSyntaxReference in declaredType.DeclaringSyntaxReferences) { - if (declaringSyntaxReference.GetSyntax() is PropertyDeclarationSyntax propertyDeclaration) + if (declaringSyntaxReference.GetSyntax() is TypeDeclarationSyntax typeDeclaration) { - return propertyDeclaration.Type.GetLocation(); + return typeDeclaration.Identifier.GetLocation(); } } - return property.Locations.IsDefaultOrEmpty ? null : property.Locations[0]; + 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) diff --git a/tests/Diagnostics/DiagnosticTests.cs b/tests/Diagnostics/DiagnosticTests.cs index 56bd6a2..57df297 100644 --- a/tests/Diagnostics/DiagnosticTests.cs +++ b/tests/Diagnostics/DiagnosticTests.cs @@ -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; } } "; @@ -280,7 +280,7 @@ public class UnmarkedClass [ZeroSerializer] public class Container { - public UnmarkedClass {|#0:Value|} { get; set; } + public {|#0:UnmarkedClass|} Value { get; set; } } "; @@ -335,7 +335,7 @@ public struct PackedValue [ZeroSerializer] public class Container { - public PackedValue[] {|#0:Values|} { get; set; } + public {|#0:PackedValue[]|} Values { get; set; } } "; @@ -389,7 +389,7 @@ public struct PackedValue [ZeroSerializer] public class InvalidType { - public PackedValue {|#0:Value|} { get; set; } + public {|#0:PackedValue|} Value { get; set; } } [ZeroSerializer] From ba95d52a5de60b6f462d5b9018013d0916dab44c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 23:08:50 +0000 Subject: [PATCH 5/5] Update ZEROS007 to report on bool property types Update ZEROS007 to report an info diagnostic on bool property type syntax locations recommending flags enum (byte) to reduce payload size. Remove obsolete ZEROS007 check for blittable-compatible nested structs.