From c082fbf014ff890ae1150ef14a22cfd3d52ef3c0 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Mon, 10 Jun 2024 22:47:22 +0900 Subject: [PATCH 1/2] Refactor generators and fix cs8619 --- .../VYaml.SourceGenerator.Roslyn3.csproj | 1 + .../VYamlSourceGenerator.cs | 2 +- VYaml.SourceGenerator/Emitter.cs | 597 ++++++++++++++++++ VYaml.SourceGenerator/MemberMeta.cs | 4 +- .../VYamlIncrementalSourceGenerator.cs | 590 +---------------- 5 files changed, 604 insertions(+), 590 deletions(-) create mode 100644 VYaml.SourceGenerator/Emitter.cs diff --git a/VYaml.SourceGenerator.Roslyn3/VYaml.SourceGenerator.Roslyn3.csproj b/VYaml.SourceGenerator.Roslyn3/VYaml.SourceGenerator.Roslyn3.csproj index f487b2a..a8bbf3a 100644 --- a/VYaml.SourceGenerator.Roslyn3/VYaml.SourceGenerator.Roslyn3.csproj +++ b/VYaml.SourceGenerator.Roslyn3/VYaml.SourceGenerator.Roslyn3.csproj @@ -5,6 +5,7 @@ enable enable VYaml.SourceGenerator + ROSLYN3 cs false diff --git a/VYaml.SourceGenerator.Roslyn3/VYamlSourceGenerator.cs b/VYaml.SourceGenerator.Roslyn3/VYamlSourceGenerator.cs index ad3eb5d..3066cec 100644 --- a/VYaml.SourceGenerator.Roslyn3/VYamlSourceGenerator.cs +++ b/VYaml.SourceGenerator.Roslyn3/VYamlSourceGenerator.cs @@ -634,4 +634,4 @@ static bool TryGetConstructor( constructedMembers = parameterMembers; return !error; } -} \ No newline at end of file +} diff --git a/VYaml.SourceGenerator/Emitter.cs b/VYaml.SourceGenerator/Emitter.cs new file mode 100644 index 0000000..96db559 --- /dev/null +++ b/VYaml.SourceGenerator/Emitter.cs @@ -0,0 +1,597 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +#if ROSLYN3 +using SourceProductionContext = Microsoft.CodeAnalysis.GeneratorExecutionContext; +#endif + +namespace VYaml.SourceGenerator; + +static class Emitter +{ + public static bool TryEmit( + TypeMeta typeMeta, + CodeWriter codeWriter, + ReferenceSymbols references, + in SourceProductionContext context) + { + try + { + var error = false; + + // verify is partial + if (!typeMeta.IsPartial()) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MustBePartial, + typeMeta.Syntax.Identifier.GetLocation(), + typeMeta.Symbol.Name)); + error = true; + } + + // nested is not allowed + if (typeMeta.IsNested()) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.NestedNotAllow, + typeMeta.Syntax.Identifier.GetLocation(), + typeMeta.Symbol.Name)); + error = true; + } + + // verify abstract/interface + if (typeMeta.Symbol.IsAbstract) + { + if (!typeMeta.IsUnion) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.AbstractMustUnion, + typeMeta.Syntax.Identifier.GetLocation(), + typeMeta.TypeName)); + error = true; + } + } + + // verify union + if (typeMeta.IsUnion) + { + if (!typeMeta.Symbol.IsAbstract) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.ConcreteTypeCantBeUnion, + typeMeta.Syntax.Identifier.GetLocation(), + typeMeta.TypeName)); + error = true; + } + + // verify tag duplication + foreach (var tagGroup in typeMeta.UnionMetas.GroupBy(x => x.SubTypeTag)) + { + if (tagGroup.Count() > 1) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UnionTagDuplicate, + typeMeta.Syntax.Identifier.GetLocation(), + tagGroup.Key)); + error = true; + } + } + + // verify interface impl + if (typeMeta.Symbol.TypeKind == TypeKind.Interface) + { + foreach (var unionMeta in typeMeta.UnionMetas) + { + // interface, check interfaces. + var check = unionMeta.SubTypeSymbol.IsGenericType + ? unionMeta.SubTypeSymbol.OriginalDefinition.AllInterfaces.Any(x => x.EqualsUnconstructedGenericType(typeMeta.Symbol)) + : unionMeta.SubTypeSymbol.AllInterfaces.Any(x => SymbolEqualityComparer.Default.Equals(x, typeMeta.Symbol)); + + if (!check) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UnionMemberTypeNotImplementBaseType, + typeMeta.Syntax.Identifier.GetLocation(), + typeMeta.TypeName, + unionMeta.SubTypeSymbol.Name)); + error = true; + } + } + } + // verify abstract inherit + else + { + foreach (var unionMeta in typeMeta.UnionMetas) + { + // abstract type, check base. + var check = unionMeta.SubTypeSymbol.IsGenericType + ? unionMeta.SubTypeSymbol.OriginalDefinition.GetAllBaseTypes().Any(x => x.EqualsUnconstructedGenericType(typeMeta.Symbol)) + : unionMeta.SubTypeSymbol.GetAllBaseTypes().Any(x => SymbolEqualityComparer.Default.Equals(x, typeMeta.Symbol)); + + if (!check) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UnionMemberTypeNotDerivedBaseType, + typeMeta.Syntax.Identifier.GetLocation(), + typeMeta.TypeName, + unionMeta.SubTypeSymbol.Name)); + error = true; + } + } + } + } + + if (error) + { + return false; + } + + codeWriter.AppendLine("// "); + codeWriter.AppendLine("#nullable enable"); + codeWriter.AppendLine("#pragma warning disable CS0162 // Unreachable code"); + codeWriter.AppendLine("#pragma warning disable CS0219 // Variable assigned but never used"); + codeWriter.AppendLine("#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type."); + codeWriter.AppendLine("#pragma warning disable CS8601 // Possible null reference assignment"); + codeWriter.AppendLine("#pragma warning disable CS8602 // Possible null return"); + codeWriter.AppendLine("#pragma warning disable CS8604 // Possible null reference argument for parameter"); + codeWriter.AppendLine("#pragma warning disable CS8619 // Possible null reference assignment fix"); + codeWriter.AppendLine("#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method"); + codeWriter.AppendLine(); + codeWriter.AppendLine("using System;"); + codeWriter.AppendLine("using VYaml.Annotations;"); + codeWriter.AppendLine("using VYaml.Parser;"); + codeWriter.AppendLine("using VYaml.Emitter;"); + codeWriter.AppendLine("using VYaml.Serialization;"); + codeWriter.AppendLine(); + + var ns = typeMeta.Symbol.ContainingNamespace; + if (!ns.IsGlobalNamespace) + { + codeWriter.AppendLine($"namespace {ns}"); + codeWriter.BeginBlock(); + } + + var typeDeclarationKeyword = (typeMeta.Symbol.IsRecord, typeMeta.Symbol.IsValueType) switch + { + (true, true) => "record struct", + (true, false) => "record", + (false, true) => "struct", + (false, false) => "class", + }; + if (typeMeta.IsUnion) + { + typeDeclarationKeyword = typeMeta.Symbol.IsRecord + ? "record" + : typeMeta.Symbol.TypeKind == TypeKind.Interface ? "interface" : "class"; + } + + using (codeWriter.BeginBlockScope($"partial {typeDeclarationKeyword} {typeMeta.TypeName}")) + { + // EmitCCtor(typeMeta, codeWriter, in context); + if (!TryEmitRegisterMethod(typeMeta, codeWriter, in context)) + { + return false; + } + if (!TryEmitFormatter(typeMeta, codeWriter, references, in context)) + { + return false; + } + } + + if (!ns.IsGlobalNamespace) + { + codeWriter.EndBlock(); + } + + codeWriter.AppendLine("#pragma warning restore CS0162 // Unreachable code"); + codeWriter.AppendLine("#pragma warning restore CS0219 // Variable assigned but never used"); + codeWriter.AppendLine("#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type."); + codeWriter.AppendLine("#pragma warning restore CS8601 // Possible null reference assignment"); + codeWriter.AppendLine("#pragma warning restore CS8602 // Possible null return"); + codeWriter.AppendLine("#pragma warning restore CS8604 // Possible null reference argument for parameter"); + codeWriter.AppendLine("#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method"); + return true; + } + catch (Exception ex) + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.UnexpectedErrorDescriptor, + Location.None, + ex.ToString())); + return false; + } + } + + static void EmitCCtor(TypeMeta typeMeta, CodeWriter codeWriter) + { + using var _ = codeWriter.BeginBlockScope($"static {typeMeta.TypeName}()"); + codeWriter.AppendLine($"__RegisterVYamlFormatter();"); + } + + static bool TryEmitRegisterMethod(TypeMeta typeMeta, CodeWriter codeWriter, in SourceProductionContext context) + { + codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); + using var _ = codeWriter.BeginBlockScope("public static void __RegisterVYamlFormatter()"); + codeWriter.AppendLine($"global::VYaml.Serialization.GeneratedResolver.Register(new {typeMeta.TypeName}GeneratedFormatter());"); + return true; + } + + static bool TryEmitFormatter( + TypeMeta typeMeta, + CodeWriter codeWriter, + ReferenceSymbols references, + in SourceProductionContext context) + { + var returnType = typeMeta.Symbol.IsValueType + ? typeMeta.FullTypeName + : $"{typeMeta.FullTypeName}?"; + + codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); + using var _ = codeWriter.BeginBlockScope($"public class {typeMeta.TypeName}GeneratedFormatter : IYamlFormatter<{returnType}>"); + + // Union + if (typeMeta.IsUnion) + { + return TryEmitSerializeMethodUnion(typeMeta, codeWriter, in context) && + TryEmitDeserializeMethodUnion(typeMeta, codeWriter, in context); + } + + // Default + foreach (var memberMeta in typeMeta.MemberMetas) + { + codeWriter.Append($"static readonly byte[] {memberMeta.Name}KeyUtf8Bytes = "); + codeWriter.AppendByteArrayString(memberMeta.KeyNameUtf8Bytes); + codeWriter.AppendLine($"; // {memberMeta.KeyName}", false); + codeWriter.AppendLine(); + } + + return TryEmitSerializeMethod(typeMeta, codeWriter, in context) && + TryEmitDeserializeMethod(typeMeta, codeWriter, references, in context); + } + + static bool TryEmitSerializeMethod(TypeMeta typeMeta, CodeWriter codeWriter, in SourceProductionContext context) + { + var memberMetas = typeMeta.MemberMetas; + var returnType = typeMeta.Symbol.IsValueType + ? typeMeta.FullTypeName + : $"{typeMeta.FullTypeName}?"; + + codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); + using var methodScope = codeWriter.BeginBlockScope( + $"public void Serialize(ref Utf8YamlEmitter emitter, {returnType} value, YamlSerializationContext context)"); + + if (!typeMeta.Symbol.IsValueType) + { + using (codeWriter.BeginBlockScope("if (value is null)")) + { + codeWriter.AppendLine("emitter.WriteNull();"); + codeWriter.AppendLine("return;"); + } + } + + codeWriter.AppendLine("emitter.BeginMapping();"); + foreach (var memberMeta in memberMetas) + { + if (memberMeta.HasKeyNameAlias) + { + codeWriter.AppendLine($"emitter.WriteString(\"{memberMeta.KeyName}\");"); + } + else + { + codeWriter.AppendLine($"emitter.WriteString(\"{memberMeta.KeyName}\", ScalarStyle.Plain);"); + } + codeWriter.AppendLine($"context.Serialize(ref emitter, value.{memberMeta.Name});"); + } + codeWriter.AppendLine("emitter.EndMapping();"); + + return true; + } + + static bool TryEmitSerializeMethodUnion(TypeMeta typeMeta, CodeWriter codeWriter, in SourceProductionContext context) + { + var returnType = typeMeta.Symbol.IsValueType + ? typeMeta.FullTypeName + : $"{typeMeta.FullTypeName}?"; + + codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); + using var methodScope = codeWriter.BeginBlockScope( + $"public void Serialize(ref Utf8YamlEmitter emitter, {returnType} value, YamlSerializationContext context)"); + + if (!typeMeta.Symbol.IsValueType) + { + using (codeWriter.BeginBlockScope("if (value is null)")) + { + codeWriter.AppendLine("emitter.WriteNull();"); + codeWriter.AppendLine("return;"); + } + } + + using (codeWriter.BeginBlockScope("switch (value)")) + { + foreach (var unionMeta in typeMeta.UnionMetas) + { + codeWriter.AppendLine($"case {unionMeta.FullTypeName} x:"); + codeWriter.AppendLine($" emitter.Tag(\"{unionMeta.SubTypeTag}\");"); + codeWriter.AppendLine($" context.Serialize(ref emitter, x);"); + codeWriter.AppendLine( " break;"); + } + } + return true; + } + + static bool TryEmitDeserializeMethod( + TypeMeta typeMeta, + CodeWriter codeWriter, + ReferenceSymbols references, + in SourceProductionContext context) + { + if (!TryGetConstructor(typeMeta, references, in context, + out var selectedConstructor, + out var constructedMembers)) + { + return false; + } + + var setterMembers = typeMeta.MemberMetas + .Where(x => + { + return constructedMembers.All(constructedMember => !SymbolEqualityComparer.Default.Equals(x.Symbol, constructedMember.Symbol)); + }) + .ToArray(); + + foreach (var setterMember in setterMembers) + { + switch (setterMember) + { + case { IsProperty: true, IsSettable: false }: + { + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.YamlMemberPropertyMustHaveSetter, + setterMember.GetLocation(typeMeta.Syntax), + typeMeta.TypeName, + setterMember.Name)); + return false; + } + case { IsField: true, IsSettable: false }: + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.YamlMemberFieldCannotBeReadonly, + setterMember.GetLocation(typeMeta.Syntax), + typeMeta.TypeName, + setterMember.Name)); + return false; + } + } + + var returnType = typeMeta.Symbol.IsValueType + ? typeMeta.FullTypeName + : $"{typeMeta.FullTypeName}?"; + codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); + using var methodScope = codeWriter.BeginBlockScope( + $"public {returnType} Deserialize(ref YamlParser parser, YamlDeserializationContext context)"); + + using (codeWriter.BeginBlockScope("if (parser.IsNullScalar())")) + { + codeWriter.AppendLine("parser.Read();"); + codeWriter.AppendLine("return default;"); + } + + if (typeMeta.MemberMetas.Count <= 0) + { + codeWriter.AppendLine("parser.SkipCurrentNode();"); + codeWriter.AppendLine($"return new {typeMeta.TypeName}();"); + return true; + } + + codeWriter.AppendLine("parser.ReadWithVerify(ParseEventType.MappingStart);"); + codeWriter.AppendLine(); + foreach (var memberMeta in typeMeta.MemberMetas) + { + codeWriter.Append($"var __{memberMeta.Name}__ = {memberMeta.EmitDefaultValue()};"); + } + + using (codeWriter.BeginBlockScope("while (!parser.End && parser.CurrentEventType != ParseEventType.MappingEnd)")) + { + using (codeWriter.BeginBlockScope("if (parser.CurrentEventType != ParseEventType.Scalar)")) + { + codeWriter.AppendLine("throw new YamlSerializerException(parser.CurrentMark, \"Custom type deserialization supports only string key\");"); + } + codeWriter.AppendLine(); + using (codeWriter.BeginBlockScope("if (!parser.TryGetScalarAsSpan(out var key))")) + { + codeWriter.AppendLine("throw new YamlSerializerException(parser.CurrentMark, \"Custom type deserialization supports only string key\");"); + } + codeWriter.AppendLine(); + using (codeWriter.BeginBlockScope("switch (key.Length)")) + { + var membersByNameLength = typeMeta.MemberMetas.GroupBy(x => x.KeyNameUtf8Bytes.Length); + foreach (var group in membersByNameLength) + { + using (codeWriter.BeginIndentScope($"case {group.Key}:")) + { + var branching = "if"; + foreach (var memberMeta in group) + { + using (codeWriter.BeginBlockScope($"{branching} (key.SequenceEqual({memberMeta.Name}KeyUtf8Bytes))")) + { + codeWriter.AppendLine("parser.Read(); // skip key"); + codeWriter.AppendLine( + $"__{memberMeta.Name}__ = context.DeserializeWithAlias<{memberMeta.FullTypeName}>(ref parser);"); + } + branching = "else if"; + } + using (codeWriter.BeginBlockScope("else")) + { + codeWriter.AppendLine("parser.Read(); // skip key"); + codeWriter.AppendLine("parser.SkipCurrentNode(); // skip value"); + } + codeWriter.AppendLine("continue;"); + } + } + + using (codeWriter.BeginIndentScope("default:")) + { + codeWriter.AppendLine("parser.Read(); // skip key"); + codeWriter.AppendLine("parser.SkipCurrentNode(); // skip value"); + codeWriter.AppendLine("continue;"); + } + } + } + codeWriter.AppendLine("parser.ReadWithVerify(ParseEventType.MappingEnd);"); + + codeWriter.Append("return new "); + if (selectedConstructor != null) + { + var parameters = string.Join(",", constructedMembers.Select(x => + { + if (x.HasExplicitDefaultValueFromConstructor) + { + return $"__{x.Name}__ = {x.EmitDefaultValue()}"; + } + return $"__{x.Name}__"; + })); + codeWriter.Append($"{typeMeta.TypeName}({parameters})", false); + } + else + { + codeWriter.Append($"{typeMeta.TypeName}", false); + } + + if (setterMembers.Length > 0) + { + using (codeWriter.BeginBlockScope()) + { + foreach (var setterMember in setterMembers) + { + if (!constructedMembers.Contains(setterMember)) + { + codeWriter.AppendLine($"{setterMember.Name} = __{setterMember.Name}__,"); + } + } + } + } + codeWriter.AppendLine(";"); + return true; + } + + static bool TryEmitDeserializeMethodUnion(TypeMeta typeMeta, CodeWriter codeWriter, in SourceProductionContext context) + { + var returnType = typeMeta.Symbol.IsValueType + ? typeMeta.FullTypeName + : $"{typeMeta.FullTypeName}?"; + + codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); + using var methodScope = codeWriter.BeginBlockScope( + $"public {returnType} Deserialize(ref YamlParser parser, YamlDeserializationContext context)"); + + using (codeWriter.BeginBlockScope("if (parser.IsNullScalar())")) + { + codeWriter.AppendLine("parser.Read();"); + codeWriter.AppendLine("return default;"); + } + + using (codeWriter.BeginBlockScope("if (!parser.TryGetCurrentTag(out var tag))")) + { + codeWriter.AppendLine("throw new YamlSerializerException(parser.CurrentMark, \"Cannot find any tag for union\");"); + } + + codeWriter.AppendLine(); + + var branch = "if"; + foreach (var unionMeta in typeMeta.UnionMetas) + { + using (codeWriter.BeginBlockScope($"{branch} (tag.Equals(\"{unionMeta.SubTypeTag}\")) ")) + { + codeWriter.AppendLine($"return context.DeserializeWithAlias<{unionMeta.FullTypeName}>(ref parser);"); + } + branch = "else if"; + } + using (codeWriter.BeginBlockScope("else")) + { + codeWriter.AppendLine("throw new YamlSerializerException(parser.CurrentMark, \"Cannot find any subtype tag for union\");"); + } + return true; + } + + static bool TryGetConstructor( + TypeMeta typeMeta, + ReferenceSymbols reference, + in SourceProductionContext context, + out IMethodSymbol? selectedConstructor, + out IReadOnlyList constructedMembers) + { + if (typeMeta.Constructors.Count <= 0) + { + selectedConstructor = null; + constructedMembers = Array.Empty(); + return true; + } + + if (typeMeta.Constructors.Count == 1) + { + selectedConstructor = typeMeta.Constructors[0]; + } + else + { + var ctorWithAttrs = typeMeta.Constructors + .Where(x => x.ContainsAttribute(reference.YamlConstructorAttribute)) + .ToArray(); + + switch (ctorWithAttrs.Length) + { + case 1: + selectedConstructor = ctorWithAttrs[0]; + break; + case > 1: + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MultipleConstructorAttribute, + typeMeta.Syntax.Identifier.GetLocation(), + typeMeta.Symbol.Name)); + selectedConstructor = null; + constructedMembers = Array.Empty(); + return false; + + default: + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MultipleConstructorWithoutAttribute, + typeMeta.Syntax.Identifier.GetLocation(), + typeMeta.Symbol.Name)); + selectedConstructor = null; + constructedMembers = Array.Empty(); + return false; + } + } + + var parameterMembers = new List(); + var error = false; + foreach (var parameter in selectedConstructor.Parameters) + { + var matchedMember = typeMeta.MemberMetas + .FirstOrDefault(member => parameter.Name.Equals(member.Name, StringComparison.OrdinalIgnoreCase)); + if (matchedMember != null) + { + matchedMember.IsConstructorParameter = true; + if (parameter.HasExplicitDefaultValue) + { + matchedMember.HasExplicitDefaultValueFromConstructor = true; + matchedMember.ExplicitDefaultValueFromConstructor = parameter.ExplicitDefaultValue; + } + parameterMembers.Add(matchedMember); + } + else + { + var location = selectedConstructor.Locations.FirstOrDefault() ?? + typeMeta.Syntax.Identifier.GetLocation(); + context.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.ConstructorHasNoMatchedParameter, + location, + typeMeta.Symbol.Name, + parameter.Name)); + constructedMembers = Array.Empty(); + error = true; + } + } + constructedMembers = parameterMembers; + return !error; + } +} diff --git a/VYaml.SourceGenerator/MemberMeta.cs b/VYaml.SourceGenerator/MemberMeta.cs index 86ac3ad..9b63689 100644 --- a/VYaml.SourceGenerator/MemberMeta.cs +++ b/VYaml.SourceGenerator/MemberMeta.cs @@ -83,7 +83,9 @@ public string EmitDefaultValue() { if (!HasExplicitDefaultValueFromConstructor) { - return $"default({FullTypeName})"; + return (MemberType is { IsReferenceType: true, NullableAnnotation: NullableAnnotation.Annotated }) + ? $"default({FullTypeName})!" + : $"default({FullTypeName})"; } return ExplicitDefaultValueFromConstructor switch diff --git a/VYaml.SourceGenerator/VYamlIncrementalSourceGenerator.cs b/VYaml.SourceGenerator/VYamlIncrementalSourceGenerator.cs index 3e0c97a..b6a9727 100644 --- a/VYaml.SourceGenerator/VYamlIncrementalSourceGenerator.cs +++ b/VYaml.SourceGenerator/VYamlIncrementalSourceGenerator.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; @@ -49,7 +48,7 @@ or RecordDeclarationSyntax x.Attributes.First(), references); - if (TryEmit(typeMeta, codeWriter, references, sourceProductionContext)) + if (Emitter.TryEmit(typeMeta, codeWriter, references, sourceProductionContext)) { var fullType = typeMeta.FullTypeName .Replace("global::", "") @@ -62,593 +61,8 @@ or RecordDeclarationSyntax } }); } - - static bool TryEmit( - TypeMeta typeMeta, - CodeWriter codeWriter, - ReferenceSymbols references, - in SourceProductionContext context) - { - try - { - var error = false; - - // verify is partial - if (!typeMeta.IsPartial()) - { - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.MustBePartial, - typeMeta.Syntax.Identifier.GetLocation(), - typeMeta.Symbol.Name)); - error = true; - } - - // nested is not allowed - if (typeMeta.IsNested()) - { - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.NestedNotAllow, - typeMeta.Syntax.Identifier.GetLocation(), - typeMeta.Symbol.Name)); - error = true; - } - - // verify abstract/interface - if (typeMeta.Symbol.IsAbstract) - { - if (!typeMeta.IsUnion) - { - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.AbstractMustUnion, - typeMeta.Syntax.Identifier.GetLocation(), - typeMeta.TypeName)); - error = true; - } - } - - // verify union - if (typeMeta.IsUnion) - { - if (!typeMeta.Symbol.IsAbstract) - { - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.ConcreteTypeCantBeUnion, - typeMeta.Syntax.Identifier.GetLocation(), - typeMeta.TypeName)); - error = true; - } - - // verify tag duplication - foreach (var tagGroup in typeMeta.UnionMetas.GroupBy(x => x.SubTypeTag)) - { - if (tagGroup.Count() > 1) - { - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.UnionTagDuplicate, - typeMeta.Syntax.Identifier.GetLocation(), - tagGroup.Key)); - error = true; - } - } - - // verify interface impl - if (typeMeta.Symbol.TypeKind == TypeKind.Interface) - { - foreach (var unionMeta in typeMeta.UnionMetas) - { - // interface, check interfaces. - var check = unionMeta.SubTypeSymbol.IsGenericType - ? unionMeta.SubTypeSymbol.OriginalDefinition.AllInterfaces.Any(x => x.EqualsUnconstructedGenericType(typeMeta.Symbol)) - : unionMeta.SubTypeSymbol.AllInterfaces.Any(x => SymbolEqualityComparer.Default.Equals(x, typeMeta.Symbol)); - - if (!check) - { - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.UnionMemberTypeNotImplementBaseType, - typeMeta.Syntax.Identifier.GetLocation(), - typeMeta.TypeName, - unionMeta.SubTypeSymbol.Name)); - error = true; - } - } - } - // verify abstract inherit - else - { - foreach (var unionMeta in typeMeta.UnionMetas) - { - // abstract type, check base. - var check = unionMeta.SubTypeSymbol.IsGenericType - ? unionMeta.SubTypeSymbol.OriginalDefinition.GetAllBaseTypes().Any(x => x.EqualsUnconstructedGenericType(typeMeta.Symbol)) - : unionMeta.SubTypeSymbol.GetAllBaseTypes().Any(x => SymbolEqualityComparer.Default.Equals(x, typeMeta.Symbol)); - - if (!check) - { - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.UnionMemberTypeNotDerivedBaseType, - typeMeta.Syntax.Identifier.GetLocation(), - typeMeta.TypeName, - unionMeta.SubTypeSymbol.Name)); - error = true; - } - } - } - } - - if (error) - { - return false; - } - - codeWriter.AppendLine("// "); - codeWriter.AppendLine("#nullable enable"); - codeWriter.AppendLine("#pragma warning disable CS0162 // Unreachable code"); - codeWriter.AppendLine("#pragma warning disable CS0219 // Variable assigned but never used"); - codeWriter.AppendLine("#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type."); - codeWriter.AppendLine("#pragma warning disable CS8601 // Possible null reference assignment"); - codeWriter.AppendLine("#pragma warning disable CS8602 // Possible null return"); - codeWriter.AppendLine("#pragma warning disable CS8604 // Possible null reference argument for parameter"); - codeWriter.AppendLine("#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method"); - codeWriter.AppendLine(); - codeWriter.AppendLine("using System;"); - codeWriter.AppendLine("using VYaml.Annotations;"); - codeWriter.AppendLine("using VYaml.Parser;"); - codeWriter.AppendLine("using VYaml.Emitter;"); - codeWriter.AppendLine("using VYaml.Serialization;"); - codeWriter.AppendLine(); - - var ns = typeMeta.Symbol.ContainingNamespace; - if (!ns.IsGlobalNamespace) - { - codeWriter.AppendLine($"namespace {ns}"); - codeWriter.BeginBlock(); - } - - var typeDeclarationKeyword = (typeMeta.Symbol.IsRecord, typeMeta.Symbol.IsValueType) switch - { - (true, true) => "record struct", - (true, false) => "record", - (false, true) => "struct", - (false, false) => "class", - }; - if (typeMeta.IsUnion) - { - typeDeclarationKeyword = typeMeta.Symbol.IsRecord - ? "record" - : typeMeta.Symbol.TypeKind == TypeKind.Interface ? "interface" : "class"; - } - - using (codeWriter.BeginBlockScope($"partial {typeDeclarationKeyword} {typeMeta.TypeName}")) - { - // EmitCCtor(typeMeta, codeWriter, in context); - if (!TryEmitRegisterMethod(typeMeta, codeWriter, in context)) - { - return false; - } - if (!TryEmitFormatter(typeMeta, codeWriter, references, in context)) - { - return false; - } - } - - if (!ns.IsGlobalNamespace) - { - codeWriter.EndBlock(); - } - - codeWriter.AppendLine("#pragma warning restore CS0162 // Unreachable code"); - codeWriter.AppendLine("#pragma warning restore CS0219 // Variable assigned but never used"); - codeWriter.AppendLine("#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type."); - codeWriter.AppendLine("#pragma warning restore CS8601 // Possible null reference assignment"); - codeWriter.AppendLine("#pragma warning restore CS8602 // Possible null return"); - codeWriter.AppendLine("#pragma warning restore CS8604 // Possible null reference argument for parameter"); - codeWriter.AppendLine("#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method"); - return true; - } - catch (Exception ex) - { - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.UnexpectedErrorDescriptor, - Location.None, - ex.ToString())); - return false; - } - } - - static void EmitCCtor(TypeMeta typeMeta, CodeWriter codeWriter) - { - using var _ = codeWriter.BeginBlockScope($"static {typeMeta.TypeName}()"); - codeWriter.AppendLine($"__RegisterVYamlFormatter();"); - } - - static bool TryEmitRegisterMethod(TypeMeta typeMeta, CodeWriter codeWriter, in SourceProductionContext context) - { - codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); - using var _ = codeWriter.BeginBlockScope("public static void __RegisterVYamlFormatter()"); - codeWriter.AppendLine($"global::VYaml.Serialization.GeneratedResolver.Register(new {typeMeta.TypeName}GeneratedFormatter());"); - return true; - } - - static bool TryEmitFormatter( - TypeMeta typeMeta, - CodeWriter codeWriter, - ReferenceSymbols references, - in SourceProductionContext context) - { - var returnType = typeMeta.Symbol.IsValueType - ? typeMeta.FullTypeName - : $"{typeMeta.FullTypeName}?"; - - codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); - using var _ = codeWriter.BeginBlockScope($"public class {typeMeta.TypeName}GeneratedFormatter : IYamlFormatter<{returnType}>"); - - // Union - if (typeMeta.IsUnion) - { - return TryEmitSerializeMethodUnion(typeMeta, codeWriter, in context) && - TryEmitDeserializeMethodUnion(typeMeta, codeWriter, in context); - } - - // Default - foreach (var memberMeta in typeMeta.MemberMetas) - { - codeWriter.Append($"static readonly byte[] {memberMeta.Name}KeyUtf8Bytes = "); - codeWriter.AppendByteArrayString(memberMeta.KeyNameUtf8Bytes); - codeWriter.AppendLine($"; // {memberMeta.KeyName}", false); - codeWriter.AppendLine(); - } - - return TryEmitSerializeMethod(typeMeta, codeWriter, in context) && - TryEmitDeserializeMethod(typeMeta, codeWriter, references, in context); - } - - static bool TryEmitSerializeMethod(TypeMeta typeMeta, CodeWriter codeWriter, in SourceProductionContext context) - { - var memberMetas = typeMeta.MemberMetas; - var returnType = typeMeta.Symbol.IsValueType - ? typeMeta.FullTypeName - : $"{typeMeta.FullTypeName}?"; - - codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); - using var methodScope = codeWriter.BeginBlockScope( - $"public void Serialize(ref Utf8YamlEmitter emitter, {returnType} value, YamlSerializationContext context)"); - - if (!typeMeta.Symbol.IsValueType) - { - using (codeWriter.BeginBlockScope("if (value is null)")) - { - codeWriter.AppendLine("emitter.WriteNull();"); - codeWriter.AppendLine("return;"); - } - } - - codeWriter.AppendLine("emitter.BeginMapping();"); - foreach (var memberMeta in memberMetas) - { - if (memberMeta.HasKeyNameAlias) - { - codeWriter.AppendLine($"emitter.WriteString(\"{memberMeta.KeyName}\");"); - } - else - { - codeWriter.AppendLine($"emitter.WriteString(\"{memberMeta.KeyName}\", ScalarStyle.Plain);"); - } - codeWriter.AppendLine($"context.Serialize(ref emitter, value.{memberMeta.Name});"); - } - codeWriter.AppendLine("emitter.EndMapping();"); - - return true; - } - - static bool TryEmitSerializeMethodUnion(TypeMeta typeMeta, CodeWriter codeWriter, in SourceProductionContext context) - { - var returnType = typeMeta.Symbol.IsValueType - ? typeMeta.FullTypeName - : $"{typeMeta.FullTypeName}?"; - - codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); - using var methodScope = codeWriter.BeginBlockScope( - $"public void Serialize(ref Utf8YamlEmitter emitter, {returnType} value, YamlSerializationContext context)"); - - if (!typeMeta.Symbol.IsValueType) - { - using (codeWriter.BeginBlockScope("if (value is null)")) - { - codeWriter.AppendLine("emitter.WriteNull();"); - codeWriter.AppendLine("return;"); - } - } - - using (codeWriter.BeginBlockScope("switch (value)")) - { - foreach (var unionMeta in typeMeta.UnionMetas) - { - codeWriter.AppendLine($"case {unionMeta.FullTypeName} x:"); - codeWriter.AppendLine($" emitter.Tag(\"{unionMeta.SubTypeTag}\");"); - codeWriter.AppendLine($" context.Serialize(ref emitter, x);"); - codeWriter.AppendLine( " break;"); - } - } - return true; - } - - static bool TryEmitDeserializeMethod( - TypeMeta typeMeta, - CodeWriter codeWriter, - ReferenceSymbols references, - in SourceProductionContext context) - { - if (!TryGetConstructor(typeMeta, references, in context, - out var selectedConstructor, - out var constructedMembers)) - { - return false; - } - - var setterMembers = typeMeta.MemberMetas - .Where(x => - { - return constructedMembers.All(constructedMember => !SymbolEqualityComparer.Default.Equals(x.Symbol, constructedMember.Symbol)); - }) - .ToArray(); - - foreach (var setterMember in setterMembers) - { - switch (setterMember) - { - case { IsProperty: true, IsSettable: false }: - { - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.YamlMemberPropertyMustHaveSetter, - setterMember.GetLocation(typeMeta.Syntax), - typeMeta.TypeName, - setterMember.Name)); - return false; - } - case { IsField: true, IsSettable: false }: - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.YamlMemberFieldCannotBeReadonly, - setterMember.GetLocation(typeMeta.Syntax), - typeMeta.TypeName, - setterMember.Name)); - return false; - } - } - - var returnType = typeMeta.Symbol.IsValueType - ? typeMeta.FullTypeName - : $"{typeMeta.FullTypeName}?"; - codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); - using var methodScope = codeWriter.BeginBlockScope( - $"public {returnType} Deserialize(ref YamlParser parser, YamlDeserializationContext context)"); - - using (codeWriter.BeginBlockScope("if (parser.IsNullScalar())")) - { - codeWriter.AppendLine("parser.Read();"); - codeWriter.AppendLine("return default;"); - } - - if (typeMeta.MemberMetas.Count <= 0) - { - codeWriter.AppendLine("parser.SkipCurrentNode();"); - codeWriter.AppendLine($"return new {typeMeta.TypeName}();"); - return true; - } - - codeWriter.AppendLine("parser.ReadWithVerify(ParseEventType.MappingStart);"); - codeWriter.AppendLine(); - foreach (var memberMeta in typeMeta.MemberMetas) - { - codeWriter.Append($"var __{memberMeta.Name}__ = {memberMeta.EmitDefaultValue()};"); - } - - using (codeWriter.BeginBlockScope("while (!parser.End && parser.CurrentEventType != ParseEventType.MappingEnd)")) - { - using (codeWriter.BeginBlockScope("if (parser.CurrentEventType != ParseEventType.Scalar)")) - { - codeWriter.AppendLine("throw new YamlSerializerException(parser.CurrentMark, \"Custom type deserialization supports only string key\");"); - } - codeWriter.AppendLine(); - using (codeWriter.BeginBlockScope("if (!parser.TryGetScalarAsSpan(out var key))")) - { - codeWriter.AppendLine("throw new YamlSerializerException(parser.CurrentMark, \"Custom type deserialization supports only string key\");"); - } - codeWriter.AppendLine(); - using (codeWriter.BeginBlockScope("switch (key.Length)")) - { - var membersByNameLength = typeMeta.MemberMetas.GroupBy(x => x.KeyNameUtf8Bytes.Length); - foreach (var group in membersByNameLength) - { - using (codeWriter.BeginIndentScope($"case {group.Key}:")) - { - var branching = "if"; - foreach (var memberMeta in group) - { - using (codeWriter.BeginBlockScope($"{branching} (key.SequenceEqual({memberMeta.Name}KeyUtf8Bytes))")) - { - codeWriter.AppendLine("parser.Read(); // skip key"); - codeWriter.AppendLine( - $"__{memberMeta.Name}__ = context.DeserializeWithAlias<{memberMeta.FullTypeName}>(ref parser);"); - } - branching = "else if"; - } - using (codeWriter.BeginBlockScope("else")) - { - codeWriter.AppendLine("parser.Read(); // skip key"); - codeWriter.AppendLine("parser.SkipCurrentNode(); // skip value"); - } - codeWriter.AppendLine("continue;"); - } - } - - using (codeWriter.BeginIndentScope("default:")) - { - codeWriter.AppendLine("parser.Read(); // skip key"); - codeWriter.AppendLine("parser.SkipCurrentNode(); // skip value"); - codeWriter.AppendLine("continue;"); - } - } - } - codeWriter.AppendLine("parser.ReadWithVerify(ParseEventType.MappingEnd);"); - - codeWriter.Append("return new "); - if (selectedConstructor != null) - { - var parameters = string.Join(",", constructedMembers.Select(x => - { - if (x.HasExplicitDefaultValueFromConstructor) - { - return $"__{x.Name}__ = {x.EmitDefaultValue()}"; - } - return $"__{x.Name}__"; - })); - codeWriter.Append($"{typeMeta.TypeName}({parameters})", false); - } - else - { - codeWriter.Append($"{typeMeta.TypeName}", false); - } - - if (setterMembers.Length > 0) - { - using (codeWriter.BeginBlockScope()) - { - foreach (var setterMember in setterMembers) - { - if (!constructedMembers.Contains(setterMember)) - { - codeWriter.AppendLine($"{setterMember.Name} = __{setterMember.Name}__,"); - } - } - } - } - codeWriter.AppendLine(";"); - return true; - } - - static bool TryEmitDeserializeMethodUnion(TypeMeta typeMeta, CodeWriter codeWriter, in SourceProductionContext context) - { - var returnType = typeMeta.Symbol.IsValueType - ? typeMeta.FullTypeName - : $"{typeMeta.FullTypeName}?"; - - codeWriter.AppendLine("[VYaml.Annotations.Preserve]"); - using var methodScope = codeWriter.BeginBlockScope( - $"public {returnType} Deserialize(ref YamlParser parser, YamlDeserializationContext context)"); - - using (codeWriter.BeginBlockScope("if (parser.IsNullScalar())")) - { - codeWriter.AppendLine("parser.Read();"); - codeWriter.AppendLine("return default;"); - } - - using (codeWriter.BeginBlockScope("if (!parser.TryGetCurrentTag(out var tag))")) - { - codeWriter.AppendLine("throw new YamlSerializerException(parser.CurrentMark, \"Cannot find any tag for union\");"); - } - - codeWriter.AppendLine(); - - var branch = "if"; - foreach (var unionMeta in typeMeta.UnionMetas) - { - using (codeWriter.BeginBlockScope($"{branch} (tag.Equals(\"{unionMeta.SubTypeTag}\")) ")) - { - codeWriter.AppendLine($"return context.DeserializeWithAlias<{unionMeta.FullTypeName}>(ref parser);"); - } - branch = "else if"; - } - using (codeWriter.BeginBlockScope("else")) - { - codeWriter.AppendLine("throw new YamlSerializerException(parser.CurrentMark, \"Cannot find any subtype tag for union\");"); - } - return true; - } - - static bool TryGetConstructor( - TypeMeta typeMeta, - ReferenceSymbols reference, - in SourceProductionContext context, - out IMethodSymbol? selectedConstructor, - out IReadOnlyList constructedMembers) - { - if (typeMeta.Constructors.Count <= 0) - { - selectedConstructor = null; - constructedMembers = Array.Empty(); - return true; - } - - if (typeMeta.Constructors.Count == 1) - { - selectedConstructor = typeMeta.Constructors[0]; - } - else - { - var ctorWithAttrs = typeMeta.Constructors - .Where(x => x.ContainsAttribute(reference.YamlConstructorAttribute)) - .ToArray(); - - switch (ctorWithAttrs.Length) - { - case 1: - selectedConstructor = ctorWithAttrs[0]; - break; - case > 1: - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.MultipleConstructorAttribute, - typeMeta.Syntax.Identifier.GetLocation(), - typeMeta.Symbol.Name)); - selectedConstructor = null; - constructedMembers = Array.Empty(); - return false; - - default: - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.MultipleConstructorWithoutAttribute, - typeMeta.Syntax.Identifier.GetLocation(), - typeMeta.Symbol.Name)); - selectedConstructor = null; - constructedMembers = Array.Empty(); - return false; - } - } - - var parameterMembers = new List(); - var error = false; - foreach (var parameter in selectedConstructor.Parameters) - { - var matchedMember = typeMeta.MemberMetas - .FirstOrDefault(member => parameter.Name.Equals(member.Name, StringComparison.OrdinalIgnoreCase)); - if (matchedMember != null) - { - matchedMember.IsConstructorParameter = true; - if (parameter.HasExplicitDefaultValue) - { - matchedMember.HasExplicitDefaultValueFromConstructor = true; - matchedMember.ExplicitDefaultValueFromConstructor = parameter.ExplicitDefaultValue; - } - parameterMembers.Add(matchedMember); - } - else - { - var location = selectedConstructor.Locations.FirstOrDefault() ?? - typeMeta.Syntax.Identifier.GetLocation(); - context.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.ConstructorHasNoMatchedParameter, - location, - typeMeta.Symbol.Name, - parameter.Name)); - constructedMembers = Array.Empty(); - error = true; - } - } - constructedMembers = parameterMembers; - return !error; - } } - class Comparer : IEqualityComparer<(GeneratorAttributeSyntaxContext, Compilation)> { public static readonly Comparer Instance = new(); @@ -662,4 +76,4 @@ public int GetHashCode((GeneratorAttributeSyntaxContext, Compilation) obj) { return obj.Item1.TargetNode.GetHashCode(); } -} \ No newline at end of file +} From 7e22c7529b17b774de1ec23528bb4bfea1721923 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Mon, 10 Jun 2024 22:53:01 +0900 Subject: [PATCH 2/2] Fix generated code indent --- VYaml.SourceGenerator/Emitter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VYaml.SourceGenerator/Emitter.cs b/VYaml.SourceGenerator/Emitter.cs index 96db559..24d68cf 100644 --- a/VYaml.SourceGenerator/Emitter.cs +++ b/VYaml.SourceGenerator/Emitter.cs @@ -387,7 +387,7 @@ static bool TryEmitDeserializeMethod( codeWriter.AppendLine(); foreach (var memberMeta in typeMeta.MemberMetas) { - codeWriter.Append($"var __{memberMeta.Name}__ = {memberMeta.EmitDefaultValue()};"); + codeWriter.AppendLine($"var __{memberMeta.Name}__ = {memberMeta.EmitDefaultValue()};"); } using (codeWriter.BeginBlockScope("while (!parser.End && parser.CurrentEventType != ParseEventType.MappingEnd)"))