From 2c381a8cb40bd69cdffe5c3bffa8c1cc5d0e6d4c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:45:56 +0000 Subject: [PATCH 1/2] Add ZEROS009 warning diagnostic for StructLayout on non-struct types Emit ZEROS009 warning when a type decorated with ZeroSerializer is not a struct and has a StructLayout attribute applied. Also include violation and compliant unit tests in DiagnosticTests.cs. --- src/ZeroSerializerGenerator.cs | 36 +++++++++++++--- tests/Diagnostics/DiagnosticTests.cs | 61 ++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/ZeroSerializerGenerator.cs b/src/ZeroSerializerGenerator.cs index bf0ae53..1997850 100644 --- a/src/ZeroSerializerGenerator.cs +++ b/src/ZeroSerializerGenerator.cs @@ -92,6 +92,14 @@ public sealed class ZeroSerializerGenerator : ISourceGenerator DiagnosticSeverity.Error, isEnabledByDefault: true); + private static readonly DiagnosticDescriptor InvalidClassStructLayoutAttribute = new( + "ZEROS009", + "StructLayout attribute has no effect on class", + "StructLayout attribute on class '{0}' has no effect", + SerializerName, + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + public void Initialize(GeneratorInitializationContext initializationContext) { // Unity is pinned to Roslyn 3.8, so discovery must stay on the classic syntax-receiver API. @@ -316,6 +324,20 @@ private static TypeGenerationModel CreateGenerationModel( return generationModel; } + if (serializableType.TypeKind != TypeKind.Struct) + { + var structLayoutAttr = GetStructLayoutAttribute(serializableType); + if (structLayoutAttr is not null) + { + Location? location = structLayoutAttr.ApplicationSyntaxReference?.GetSyntax().GetLocation() + ?? GetTypeIdentifierLocation(serializableType); + executionContext.ReportDiagnostic(Diagnostic.Create( + InvalidClassStructLayoutAttribute, + location, + serializableType.Name)); + } + } + if (serializableType.TypeKind == TypeKind.Struct && !isBlittableStruct) { var packOneAttr = GetSequentialPackOneAttribute(serializableType); @@ -631,10 +653,9 @@ private static bool TryGetBlittableStructByteCount(INamedTypeSymbol candidateTyp out byteCount); } - private static AttributeData? GetSequentialPackOneAttribute(INamedTypeSymbol structType) + private static AttributeData? GetStructLayoutAttribute(INamedTypeSymbol typeSymbol) { - AttributeData? structLayoutAttribute = null; - foreach (AttributeData candidateAttribute in structType.GetAttributes()) + foreach (AttributeData candidateAttribute in typeSymbol.GetAttributes()) { if (candidateAttribute.AttributeClass is INamedTypeSymbol { @@ -653,10 +674,15 @@ private static bool TryGetBlittableStructByteCount(INamedTypeSymbol candidateTyp } }) { - structLayoutAttribute = candidateAttribute; - break; + return candidateAttribute; } } + return null; + } + + private static AttributeData? GetSequentialPackOneAttribute(INamedTypeSymbol structType) + { + AttributeData? structLayoutAttribute = GetStructLayoutAttribute(structType); if (structLayoutAttribute is null || structLayoutAttribute.ConstructorArguments.Length != 1 || structLayoutAttribute.ConstructorArguments[0].Value is not int layoutKind diff --git a/tests/Diagnostics/DiagnosticTests.cs b/tests/Diagnostics/DiagnosticTests.cs index 649b83b..187707d 100644 --- a/tests/Diagnostics/DiagnosticTests.cs +++ b/tests/Diagnostics/DiagnosticTests.cs @@ -62,6 +62,67 @@ await CSharpSourceGeneratorVerifier.VerifySourceGenerat ); } + [Fact] + public async Task ZEROS009_Violation_ClassWithStructLayout() + { + string source = @" +using System.Runtime.InteropServices; +using ZeroSerializer; + +[{|#0:StructLayout(LayoutKind.Sequential, Pack = 1)|}] +[ZeroSerializer] +public class MyClassWithStructLayout +{ + public int Value { get; set; } +} +"; + + await CSharpSourceGeneratorVerifier.VerifySourceGeneratorAsync( + source, + new DiagnosticResult("ZEROS009", DiagnosticSeverity.Warning) + .WithLocation(0) + .WithMessage("StructLayout attribute on class 'MyClassWithStructLayout' has no effect") + ); + } + + [Fact] + public async Task ZEROS009_Compliant_StructWithAttributes() + { + string source = @" +using System.Runtime.InteropServices; +using ZeroSerializer; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +[ZeroSerializer] +public struct MyStructWithAttributes +{ + public int Value { get; set; } +} +"; + + await CSharpSourceGeneratorVerifier.VerifySourceGeneratorAsync( + source + ); + } + + [Fact] + public async Task ZEROS009_Compliant_ClassWithoutZeroSerializer() + { + string source = @" +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public class MyClassWithoutZeroSerializer +{ + public int Value { get; set; } +} +"; + + await CSharpSourceGeneratorVerifier.VerifySourceGeneratorAsync( + source + ); + } + [Fact] public async Task ZEROS002_Violation_NonBlittableStructWithLayout() { From 915f7797808d195aee9a061e172bcb57e188c8eb Mon Sep 17 00:00:00 2001 From: sator-imaging <16752340+sator-imaging@users.noreply.github.com> Date: Fri, 21 Aug 2026 06:47:30 +0900 Subject: [PATCH 2/2] Apply suggestion from @sator-imaging --- src/ZeroSerializerGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZeroSerializerGenerator.cs b/src/ZeroSerializerGenerator.cs index 1997850..4a3944d 100644 --- a/src/ZeroSerializerGenerator.cs +++ b/src/ZeroSerializerGenerator.cs @@ -94,7 +94,7 @@ public sealed class ZeroSerializerGenerator : ISourceGenerator private static readonly DiagnosticDescriptor InvalidClassStructLayoutAttribute = new( "ZEROS009", - "StructLayout attribute has no effect on class", + "StructLayout attribute on class", "StructLayout attribute on class '{0}' has no effect", SerializerName, DiagnosticSeverity.Warning,