From e48b1dbcc671c66c6adacb91da4a857c092eb411 Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 17 Feb 2024 09:44:54 -0800 Subject: [PATCH] Handle ref struct --- .../GeneratorDiagnostics.cs | 6 +-- .../LogGenerator.cs | 50 ++++++++++++------- test/stereologue.test/TestTree.cs | 26 +++++----- 3 files changed, 48 insertions(+), 34 deletions(-) diff --git a/sourcegeneration/StereologueSourceGenerator/GeneratorDiagnostics.cs b/sourcegeneration/StereologueSourceGenerator/GeneratorDiagnostics.cs index 920d3735..8c314348 100644 --- a/sourcegeneration/StereologueSourceGenerator/GeneratorDiagnostics.cs +++ b/sourcegeneration/StereologueSourceGenerator/GeneratorDiagnostics.cs @@ -35,10 +35,10 @@ public class Ids Ids.LoggableTypeNotSupported, "", "", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, ""); public static readonly DiagnosticDescriptor GeneratedTypeIsInterface = new( - Ids.LoggableTypeNotSupported, "", "", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, ""); + Ids.LoggableTypeNotSupported, "Generated types cannot be ref", "", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, ""); public static readonly DiagnosticDescriptor GeneratedTypeIsRefStruct = new( - Ids.GeneratedTypeIsRefStruct, "", "", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, ""); + Ids.GeneratedTypeIsRefStruct, "Generated types cannot be ref struct", "HelloWorld", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, ""); public static readonly DiagnosticDescriptor LoggedMethodDoesntReturnVoid = new( Ids.LoggedMethodDoesntReturnVoid, "", "", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, ""); @@ -47,4 +47,4 @@ public class Ids Ids.LoggedMethodTakesArguments, "", "", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, ""); public static readonly DiagnosticDescriptor LoggedMemberTypeNotSupported = new( Ids.LoggedMemberTypeNotSupported, "", "", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, ""); -} \ No newline at end of file +} diff --git a/sourcegeneration/StereologueSourceGenerator/LogGenerator.cs b/sourcegeneration/StereologueSourceGenerator/LogGenerator.cs index 50a9c09c..d70b3131 100644 --- a/sourcegeneration/StereologueSourceGenerator/LogGenerator.cs +++ b/sourcegeneration/StereologueSourceGenerator/LogGenerator.cs @@ -20,7 +20,7 @@ internal record LogAttributeInfo(string Path, string LogLevel, string LogType, b internal record LogData(string GetOperation, string? Type, DeclarationType DecelType, LogAttributeInfo AttributeInfo); -internal record ClassData(EquatableArray LoggedItems, string Name, string ClassDeclaration, string? Namespace); +internal record ClassData(EquatableArray LoggedItems, string Name, string ClassDeclaration, bool IsRefStruct, string? Namespace); internal record ClassOrDiagnostic(ClassData? ValidClassData, EquatableArray Diagnostic); @@ -147,21 +147,47 @@ public class LogGenerator : IIncrementalGenerator } var fmt = new SymbolDisplayFormat(genericsOptions: SymbolDisplayGenericsOptions.None); - var fileName = $"{classSymbol.ContainingNamespace}{classSymbol.ToDisplayString(fmt)}{classSymbol.MetadataName}"; - - return new ClassOrDiagnostic(new ClassData(loggableMembers.ToImmutable(), $"{classSymbol.ContainingNamespace}{classSymbol.ToDisplayString(fmt)}{classSymbol.MetadataName}", typeBuilder.ToString(), ns), diagnosticList.ToImmutable()); + return new ClassOrDiagnostic(new ClassData(loggableMembers.ToImmutable(), $"{classSymbol.ContainingNamespace}{classSymbol.ToDisplayString(fmt)}{classSymbol.MetadataName}", typeBuilder.ToString(), classSymbol.IsRefLikeType, ns), diagnosticList.ToImmutable()); } private static LogData ComputeOperation(ITypeSymbol logType, string getOp, LogAttributeInfo attributeInfo) { + // If we know we're generating a loggable implementation if (logType.GetAttributes().Where(x => x.AttributeClass?.ToDisplayString() == "Stereologue.GenerateLogAttribute").Any()) { return new LogData(getOp, null, DeclarationType.Logged, attributeInfo); } + // If we know we already implement ILogged if (logType.AllInterfaces.Where(x => x.ToDisplayString() == "Stereologue.ILogged").Any()) { return new LogData(getOp, null, DeclarationType.Logged, attributeInfo); } + + // If we have an UpdateMonologue function + var members = logType.GetMembers("UpdateStereologue"); + foreach (var member in members) + { + // Must be a method + if (member is IMethodSymbol method) + { + // Must return void + if (!method.ReturnsVoid) + { + continue; + } + // Must have a string first parameter, and a Stereologue.Stereologuer second paramter + var parameters = method.Parameters; + if (parameters.Length != 2) + { + continue; + } + if (parameters[0].Type.SpecialType == SpecialType.System_String && parameters[1].Type.ToDisplayString() == "Stereologue.Stereologuer") + { + return new LogData(getOp, null, DeclarationType.Logged, attributeInfo); + } + } + } + var fmt = new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters); var fullTypeName = logType.ToDisplayString(fmt); var structName = $"WPIUtil.Serialization.Struct.IStructSerializable<{fullTypeName}>"; @@ -209,7 +235,7 @@ static void ConstructCall(LogData data, StringBuilder builder, SourceProductionC switch (data.DecelType) { case DeclarationType.Logged: - builder.AppendLine($"{data.GetOperation}?.UpdateStereologue($\"{{path}}/{data.AttributeInfo.Path}\", logger, {data.AttributeInfo.LogLevel});"); + builder.AppendLine($"{data.GetOperation}.UpdateStereologue($\"{{path}}/{data.AttributeInfo.Path}\", logger, {data.AttributeInfo.LogLevel});"); return; case DeclarationType.Struct: builder.AppendLine($"logger.LogStruct($\"{{path}}/{data.AttributeInfo.Path}\", {data.AttributeInfo.LogType}, {data.GetOperation}, {data.AttributeInfo.LogLevel});"); @@ -284,7 +310,7 @@ static void Execute(ClassOrDiagnostic? classData, SourceProductionContext contex builder.AppendLine($"namespace {value.Namespace};"); builder.AppendLine(); - builder.AppendLine(value.ClassDeclaration); + builder.AppendLine($"{value.ClassDeclaration}{(value.IsRefStruct ? "" : " : Stereologue.ILogged")}"); builder.AppendLine("{"); builder.AppendLine(" public void UpdateStereologue(string path, Stereologue.Stereologuer logger)"); builder.AppendLine(" {"); @@ -307,18 +333,6 @@ static void Execute(ClassOrDiagnostic? classData, SourceProductionContext contex return DiagnosticInfo.Create(GeneratorDiagnostics.GeneratedTypeNotPartial, syntax.Identifier.GetLocation(), [symbol.Name, nonPartialIdentifier]); } - // Ensure class doesn't implement ILogged - if (symbol.AllInterfaces.Where(x => x.ToDisplayString() == "Stereologue.ILogged").Any()) - { - return DiagnosticInfo.Create(GeneratorDiagnostics.GeneratedTypeImplementsILogged, syntax.Identifier.GetLocation(), [symbol.Name]); - } - - // Ensure implementation isn't ref struct - if (symbol.IsRefLikeType) - { - return DiagnosticInfo.Create(GeneratorDiagnostics.GeneratedTypeIsRefStruct, syntax.Identifier.GetLocation(), [symbol.Name]); - } - // Ensure implementation isn't interface if (symbol.TypeKind == TypeKind.Interface) { diff --git a/test/stereologue.test/TestTree.cs b/test/stereologue.test/TestTree.cs index 449e3637..b265c5b6 100644 --- a/test/stereologue.test/TestTree.cs +++ b/test/stereologue.test/TestTree.cs @@ -40,19 +40,19 @@ public readonly partial struct GenerateReadonlyStruct public int Variable { get; } } -// [GenerateLog] -// public ref partial struct GenerateRefStruct -// { -// [Log] -// public int Variable { get; } -// } +[GenerateLog] +public ref partial struct GenerateRefStruct +{ + [Log] + public int Variable { get; } +} -// [GenerateLog] -// public readonly ref partial struct GenerateReadonlyRefStruct -// { -// [Log] -// public int Variable { get; } -// } +[GenerateLog] +public readonly ref partial struct GenerateReadonlyRefStruct +{ + [Log] + public int Variable { get; } +} // [GenerateLog] // public partial interface GenerateInterface @@ -62,7 +62,7 @@ public readonly partial struct GenerateReadonlyStruct // } [GenerateLog] -public partial record GenerateRecord +public partial record GenerateRecord : ILogged { [Log] public int Variable { get; }