From 0c6cf65718f0f638d9593a66d13d28052e6130fa Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 5 Oct 2023 06:55:01 +0200 Subject: [PATCH 1/3] Added support for including types which are defined in another assembly in static code generation. --- .../ClassSyntaxReceiver.cs | 121 ++++++++++-------- .../TypeFactoryGenerator.cs | 1 + .../ExternalModel.cs | 6 + ...amlDotNet.Core7AoTCompileTest.Model.csproj | 11 ++ YamlDotNet.Core7AoTCompileTest/Program.cs | 10 +- .../StaticAoTContext.cs | 2 + .../YamlDotNet.Core7AoTCompileTest.csproj | 1 + YamlDotNet.sln | 6 + YamlDotNet/Serialization/YamlSerializable.cs | 19 ++- 9 files changed, 121 insertions(+), 56 deletions(-) create mode 100644 YamlDotNet.Core7AoTCompileTest.Model/ExternalModel.cs create mode 100644 YamlDotNet.Core7AoTCompileTest.Model/YamlDotNet.Core7AoTCompileTest.Model.csproj diff --git a/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs b/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs index fe654dd0..e53ade43 100644 --- a/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs +++ b/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs @@ -44,69 +44,86 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) if (attributes.Any(attribute => attribute.AttributeClass?.ToDisplayString() == "YamlDotNet.Serialization.YamlStaticContextAttribute")) { YamlStaticContextType = classSymbol; + + var types = + attributes.Where(attribute => attribute.AttributeClass?.ToDisplayString() == "YamlDotNet.Serialization.YamlSerializableAttribute" + && attribute.ConstructorArguments.Any(argument => argument.Type?.ToDisplayString() == "System.Type")) + .Select(attribute => attribute.ConstructorArguments.First().Value) + .ToArray(); + + foreach (var type in types.OfType()) + { + AddSerializableClass(type); + } } - if (classSymbol.GetAttributes().Any(attribute => attribute.AttributeClass?.ToDisplayString() == "YamlDotNet.Serialization.YamlSerializableAttribute")) + if (classSymbol.GetAttributes().Any(attribute => attribute.AttributeClass?.ToDisplayString() == "YamlDotNet.Serialization.YamlSerializableAttribute" + && attribute.ConstructorArguments.Length == 0)) { - ClassObject classObject; - var className = SanitizeName(classSymbol.GetFullName()); - if (Classes.ContainsKey(className)) + AddSerializableClass(classSymbol); + } + } + } + } + + private void AddSerializableClass(INamedTypeSymbol classSymbol) + { + ClassObject classObject; + var className = SanitizeName(classSymbol.GetFullName()); + if (Classes.ContainsKey(className)) + { + classObject = Classes[className]; + } + else + { + classObject = new ClassObject(className, classSymbol); + Classes[className] = classObject; + } + while (classSymbol != null) + { + var members = classSymbol.GetMembers(); + foreach (var member in members) + { + if (member.IsStatic || + (member.DeclaredAccessibility != Accessibility.Public && + member.DeclaredAccessibility != Accessibility.Internal) || + member.GetAttributes().Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.YamlIgnoreAttribute")) + { + continue; + } + + if (member is IPropertySymbol propertySymbol) + { + classObject.PropertySymbols.Add(propertySymbol); + CheckForSupportedGeneric(propertySymbol.Type); + } + else if (member is IFieldSymbol fieldSymbol) + { + classObject.FieldSymbols.Add(fieldSymbol); + CheckForSupportedGeneric(fieldSymbol.Type); + } + else if (member is IMethodSymbol methodSymbol) + { + var methodAttributes = methodSymbol.GetAttributes(); + if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializedAttribute")) { - classObject = Classes[className]; + classObject.OnDeserializedMethods.Add(methodSymbol); } - else + if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializingAttribute")) { - classObject = new ClassObject(className, classSymbol); - Classes[className] = classObject; + classObject.OnDeserializingMethods.Add(methodSymbol); } - while (classSymbol != null) + if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializedAttribute")) { - var members = classSymbol.GetMembers(); - foreach (var member in members) - { - if (member.IsStatic || - (member.DeclaredAccessibility != Accessibility.Public && - member.DeclaredAccessibility != Accessibility.Internal) || - member.GetAttributes().Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.YamlIgnoreAttribute")) - { - continue; - } - - if (member is IPropertySymbol propertySymbol) - { - classObject.PropertySymbols.Add(propertySymbol); - CheckForSupportedGeneric(propertySymbol.Type); - } - else if (member is IFieldSymbol fieldSymbol) - { - classObject.FieldSymbols.Add(fieldSymbol); - CheckForSupportedGeneric(fieldSymbol.Type); - } - else if (member is IMethodSymbol methodSymbol) - { - var methodAttributes = methodSymbol.GetAttributes(); - if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializedAttribute")) - { - classObject.OnDeserializedMethods.Add(methodSymbol); - } - if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnDeserializingAttribute")) - { - classObject.OnDeserializingMethods.Add(methodSymbol); - } - if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializedAttribute")) - { - classObject.OnSerializedMethods.Add(methodSymbol); - } - if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializingAttribute")) - { - classObject.OnSerializingMethods.Add(methodSymbol); - } - } - } - classSymbol = classSymbol.BaseType; + classObject.OnSerializedMethods.Add(methodSymbol); + } + if (methodAttributes.Any(x => x.AttributeClass!.ToDisplayString() == "YamlDotNet.Serialization.Callbacks.OnSerializingAttribute")) + { + classObject.OnSerializingMethods.Add(methodSymbol); } } } + classSymbol = classSymbol.BaseType; } } diff --git a/YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs b/YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs index 0a95a796..7420cceb 100644 --- a/YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs +++ b/YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs @@ -20,6 +20,7 @@ // SOFTWARE. using System; +using System.Diagnostics; using System.Text; using System.Xml; using Microsoft.CodeAnalysis; diff --git a/YamlDotNet.Core7AoTCompileTest.Model/ExternalModel.cs b/YamlDotNet.Core7AoTCompileTest.Model/ExternalModel.cs new file mode 100644 index 00000000..a882325d --- /dev/null +++ b/YamlDotNet.Core7AoTCompileTest.Model/ExternalModel.cs @@ -0,0 +1,6 @@ +namespace YamlDotNet.Core7AoTCompileTest.Model; + +public class ExternalModel +{ + public string? Text { get; set; } +} diff --git a/YamlDotNet.Core7AoTCompileTest.Model/YamlDotNet.Core7AoTCompileTest.Model.csproj b/YamlDotNet.Core7AoTCompileTest.Model/YamlDotNet.Core7AoTCompileTest.Model.csproj new file mode 100644 index 00000000..2dfd6d2c --- /dev/null +++ b/YamlDotNet.Core7AoTCompileTest.Model/YamlDotNet.Core7AoTCompileTest.Model.csproj @@ -0,0 +1,11 @@ + + + + net70 + enable + enable + + + + + diff --git a/YamlDotNet.Core7AoTCompileTest/Program.cs b/YamlDotNet.Core7AoTCompileTest/Program.cs index 87d5de1d..9e8a4123 100644 --- a/YamlDotNet.Core7AoTCompileTest/Program.cs +++ b/YamlDotNet.Core7AoTCompileTest/Program.cs @@ -24,12 +24,14 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using YamlDotNet.Core; +using YamlDotNet.Core7AoTCompileTest.Model; using YamlDotNet.Serialization; using YamlDotNet.Serialization.Callbacks; -string yaml = $@"MyBool: true +string yaml = string.Create(CultureInfo.InvariantCulture, $@"MyBool: true hi: 1 MyChar: h MyDateTime: {DateTime.Now} @@ -65,7 +67,9 @@ Inherited: Inherited: hello NotInherited: world -"; +External: + Text: hello +"); var input = new StringReader(yaml); @@ -91,6 +95,7 @@ Console.WriteLine("MyUInt64: <{0}>", x.MyUInt64); Console.WriteLine("Inner == null: <{0}>", x.Inner == null); Console.WriteLine("Inner.Text: <{0}>", x.Inner?.Text); +Console.WriteLine("External.Text: <{0}>", x.External?.Text); foreach (var inner in x.InnerArray) { Console.WriteLine("InnerArray.Text: <{0}>", inner.Text); @@ -188,6 +193,7 @@ public class PrimitiveTypes public Dictionary? MyDictionary { get; set; } public List? MyList { get; set; } public Inherited Inherited { get; set; } + public ExternalModel External { get; set; } } public class InheritedBase diff --git a/YamlDotNet.Core7AoTCompileTest/StaticAoTContext.cs b/YamlDotNet.Core7AoTCompileTest/StaticAoTContext.cs index b455ce0c..f661c96a 100644 --- a/YamlDotNet.Core7AoTCompileTest/StaticAoTContext.cs +++ b/YamlDotNet.Core7AoTCompileTest/StaticAoTContext.cs @@ -19,12 +19,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using YamlDotNet.Core7AoTCompileTest.Model; using YamlDotNet.Serialization; namespace YamlDotNet.Core7AoTCompileTest { // The rest of this partial class gets generated at build time [YamlStaticContext] + [YamlSerializable(typeof(ExternalModel))] public partial class StaticContext : YamlDotNet.Serialization.StaticContext { } diff --git a/YamlDotNet.Core7AoTCompileTest/YamlDotNet.Core7AoTCompileTest.csproj b/YamlDotNet.Core7AoTCompileTest/YamlDotNet.Core7AoTCompileTest.csproj index dbd640dc..5867c74b 100644 --- a/YamlDotNet.Core7AoTCompileTest/YamlDotNet.Core7AoTCompileTest.csproj +++ b/YamlDotNet.Core7AoTCompileTest/YamlDotNet.Core7AoTCompileTest.csproj @@ -25,6 +25,7 @@ + diff --git a/YamlDotNet.sln b/YamlDotNet.sln index 2951c871..85ca3975 100644 --- a/YamlDotNet.sln +++ b/YamlDotNet.sln @@ -31,6 +31,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YamlDotNet.Core7AoTCompileT EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "YamlDotNet.Samples.Fsharp", "YamlDotNet.Samples.Fsharp\YamlDotNet.Samples.Fsharp.fsproj", "{C047392D-6B20-47CD-9FE6-D0FA326FD262}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YamlDotNet.Core7AoTCompileTest.Model", "YamlDotNet.Core7AoTCompileTest.Model\YamlDotNet.Core7AoTCompileTest.Model.csproj", "{BFE15564-7C2C-47DA-8302-9BCB39B6864B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +71,10 @@ Global {C047392D-6B20-47CD-9FE6-D0FA326FD262}.Debug|Any CPU.Build.0 = Debug|Any CPU {C047392D-6B20-47CD-9FE6-D0FA326FD262}.Release|Any CPU.ActiveCfg = Release|Any CPU {C047392D-6B20-47CD-9FE6-D0FA326FD262}.Release|Any CPU.Build.0 = Release|Any CPU + {BFE15564-7C2C-47DA-8302-9BCB39B6864B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFE15564-7C2C-47DA-8302-9BCB39B6864B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFE15564-7C2C-47DA-8302-9BCB39B6864B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFE15564-7C2C-47DA-8302-9BCB39B6864B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/YamlDotNet/Serialization/YamlSerializable.cs b/YamlDotNet/Serialization/YamlSerializable.cs index e6198017..49fc5dcb 100644 --- a/YamlDotNet/Serialization/YamlSerializable.cs +++ b/YamlDotNet/Serialization/YamlSerializable.cs @@ -24,10 +24,25 @@ namespace YamlDotNet.Serialization { /// - /// Put this attribute on classes that you want the static analyzer to detect and use. + /// Put this attribute either on serializable types or on the that you want + /// the static analyzer to detect and use. /// - [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class YamlSerializableAttribute : Attribute { + /// + /// Use this constructor if the attribute is placed on a serializable class. + /// + public YamlSerializableAttribute() + { + } + + /// + /// Use this constructor if the attribute is placed on the . + /// + /// The type for which to include static code generation. + public YamlSerializableAttribute(Type serializableType) + { + } } } From 5dcf05d76aa8125879a48e8d351dcbd4a03364e5 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 5 Oct 2023 06:56:31 +0200 Subject: [PATCH 2/3] Removed unused using directives. --- YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs b/YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs index 7420cceb..954cf4b0 100644 --- a/YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs +++ b/YamlDotNet.Analyzers.StaticGenerator/TypeFactoryGenerator.cs @@ -20,9 +20,7 @@ // SOFTWARE. using System; -using System.Diagnostics; using System.Text; -using System.Xml; using Microsoft.CodeAnalysis; namespace YamlDotNet.Analyzers.StaticGenerator From d63f33ff86243cd17eb924118a0bba9bd9bf3715 Mon Sep 17 00:00:00 2001 From: Christian Prochnow Date: Thu, 5 Oct 2023 08:06:49 +0200 Subject: [PATCH 3/3] Fixed nullable warnings. --- YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs b/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs index e53ade43..8a527c58 100644 --- a/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs +++ b/YamlDotNet.Analyzers.StaticGenerator/ClassSyntaxReceiver.cs @@ -66,17 +66,17 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) } } - private void AddSerializableClass(INamedTypeSymbol classSymbol) + private void AddSerializableClass(INamedTypeSymbol? classSymbol) { ClassObject classObject; - var className = SanitizeName(classSymbol.GetFullName()); + var className = SanitizeName(classSymbol!.GetFullName()); if (Classes.ContainsKey(className)) { classObject = Classes[className]; } else { - classObject = new ClassObject(className, classSymbol); + classObject = new ClassObject(className, classSymbol!); Classes[className] = classObject; } while (classSymbol != null)