diff --git a/src/ZeroSerializerGenerator.cs b/src/ZeroSerializerGenerator.cs index bf0ae53..4a3944d 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 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() {