From f4f0c0da4b85e6477933cd0270c0b8386bc05064 Mon Sep 17 00:00:00 2001 From: MeltyPlayer Date: Mon, 15 Apr 2024 00:33:27 -0500 Subject: [PATCH] Got the const generator working, in some capacity. --- Schema Build Tests/const/Something.cs | 5 ++ Schema Build Tests/const/ValueType.cs | 9 +++ .../const/BasicConstGeneratorTests.cs | 48 ++++++++++++-- Schema Tests/const/ConstGeneratorTestUtil.cs | 9 ++- Schema/src/binary/BinarySchemaGenerator.cs | 2 - Schema/src/const/ConstTypeGenerator.cs | 26 +++++--- .../util/generators/BNamedTypeGenerator.cs | 37 ++++++----- .../BNamedTypeSecondaryGenerator.cs | 14 ++-- Schema/src/util/symbols/SymbolTypeUtil.cs | 65 +++++++++++++++---- 9 files changed, 152 insertions(+), 63 deletions(-) create mode 100644 Schema Build Tests/const/Something.cs create mode 100644 Schema Build Tests/const/ValueType.cs diff --git a/Schema Build Tests/const/Something.cs b/Schema Build Tests/const/Something.cs new file mode 100644 index 0000000..a237ca6 --- /dev/null +++ b/Schema Build Tests/const/Something.cs @@ -0,0 +1,5 @@ +namespace foo { + public class Something { + public IConstValueType Field { get; set; } + } +} \ No newline at end of file diff --git a/Schema Build Tests/const/ValueType.cs b/Schema Build Tests/const/ValueType.cs new file mode 100644 index 0000000..12f21da --- /dev/null +++ b/Schema Build Tests/const/ValueType.cs @@ -0,0 +1,9 @@ +using schema.@const; + + +namespace foo { + [GenerateConst] + public partial class ValueType { + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/Schema Tests/const/BasicConstGeneratorTests.cs b/Schema Tests/const/BasicConstGeneratorTests.cs index 488bbf8..8799af6 100644 --- a/Schema Tests/const/BasicConstGeneratorTests.cs +++ b/Schema Tests/const/BasicConstGeneratorTests.cs @@ -10,10 +10,9 @@ internal class BasicConstGeneratorTests { [TestCase("public bool field;")] [TestCase("public bool Field { set; }")] // Missing const attribute - [TestCase("public bool Field { get; set; }")] [TestCase("public bool Field();")] // Not accessible enough - [TestCase("[Const] private bool Field { get; set; }")] + [TestCase("private bool Field { get; set; }")] [TestCase("[Const] bool Field();")] public void TestEmpty(string emptySrc) { ConstGeneratorTestUtil.AssertGenerated( @@ -31,7 +30,7 @@ public partial class Empty { namespace foo.bar { public partial class Empty : IConstEmpty; - public partial class IConstEmpty { + public interface IConstEmpty { } } @@ -56,7 +55,7 @@ public T1 Foo(T1 t1, T2 t2, T3 t3, T4 t4) { } namespace foo.bar { public partial class SimpleGenerics : IConstSimpleGenerics; - public partial class IConstSimpleGenerics { + public interface IConstSimpleGenerics { public T1 Foo(T1 t1, T2 t2, T3 t3, T4 t4); } } @@ -88,7 +87,7 @@ public T Foo(T t, S s) where S : {{constraint}} { } namespace foo.bar { public partial class EachConstraint : IConstEachConstraint; - public partial class IConstEachConstraint where T : {{constraint}} { + public interface IConstEachConstraint where T : {{constraint}} { public T Foo(T t, S s) where S : {{constraint}}; } } @@ -107,6 +106,8 @@ namespace foo.bar { public partial class SubConstraint where T2 : T1 { [Const] public T1 Foo(S s) where S : T1 { } + + public T2 Bar { get; set; } } } """, @@ -114,8 +115,9 @@ public T1 Foo(S s) where S : T1 { } namespace foo.bar { public partial class SubConstraint : IConstSubConstraint; - public partial class IConstSubConstraint where T2 : T1 { + public interface IConstSubConstraint where T2 : T1 { public T1 Foo(S s) where S : T1; + public T2 Bar { get; } } } @@ -133,6 +135,8 @@ namespace foo.bar { public partial class SimpleAttributes where T1 : notnull, struct where T2 : unmanaged { [Const] public T1 Foo(T1 t1, T2 t2, T3 t3, T4 t4) where T3 : class where T4 : class? { } + + public T2 Bar { get; set; } } } """, @@ -140,8 +144,38 @@ public T1 Foo(T1 t1, T2 t2, T3 t3, T4 t4) where T3 : class where T4 : cl namespace foo.bar { public partial class SimpleAttributes : IConstSimpleAttributes; - public partial class IConstSimpleAttributes where T1 : notnull, struct where T2 : unmanaged { + public interface IConstSimpleAttributes where T1 : notnull, struct where T2 : unmanaged { public T1 Foo(T1 t1, T2 t2, T3 t3, T4 t4) where T3 : class where T4 : class?; + public T2 Bar { get; } + } + } + + """); + } + + [Test] + public void TestKeywords() { + ConstGeneratorTestUtil.AssertGenerated( + """ + using schema.@const; + + namespace @const { + [GenerateConst] + public partial class @void<@double> where @double : struct { + [Const] + public @void @int<@short>(@void @bool) where @short : @void { } + + public @void @float { get; } + } + } + """, + """ + namespace @const { + public partial class @void<@double> : IConstvoid<@double>; + + public interface IConstvoid<@double> where @double : struct { + public @void @int<@short>(@void @bool) where @short : @void; + public @void @float { get; } } } diff --git a/Schema Tests/const/ConstGeneratorTestUtil.cs b/Schema Tests/const/ConstGeneratorTestUtil.cs index 75cf9e9..3f6ce49 100644 --- a/Schema Tests/const/ConstGeneratorTestUtil.cs +++ b/Schema Tests/const/ConstGeneratorTestUtil.cs @@ -26,7 +26,7 @@ internal static class ConstGeneratorTestUtil { public static void AssertGenerated(string src, string expected) { var syntaxTree = CSharpSyntaxTree.ParseText(src); var compilation = BinarySchemaTestUtil.Compilation.Clone() - .AddSyntaxTrees(syntaxTree); + .AddSyntaxTrees(syntaxTree); var semanticModel = compilation.GetSemanticModel(syntaxTree); @@ -80,14 +80,13 @@ var symbol var namedTypeSymbol = symbol as INamedTypeSymbol; - return (typeNode, namedTypeSymbol); + return namedTypeSymbol; }) .ToArray(); - var (syntax, synbol) = structures.First(); + var synbol = structures.First(); - Assert.True( - new ConstTypeGenerator().Generate(syntax, synbol, out var actual)); + Assert.True(new ConstTypeGenerator().Generate(synbol, out var actual)); Assert.AreEqual(expected, actual.ReplaceLineEndings()); } diff --git a/Schema/src/binary/BinarySchemaGenerator.cs b/Schema/src/binary/BinarySchemaGenerator.cs index af42e81..138cbc2 100644 --- a/Schema/src/binary/BinarySchemaGenerator.cs +++ b/Schema/src/binary/BinarySchemaGenerator.cs @@ -3,7 +3,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; using schema.binary.attributes; using schema.binary.text; @@ -14,7 +13,6 @@ namespace schema.binary { [Generator(LanguageNames.CSharp)] - [DiagnosticAnalyzer(LanguageNames.CSharp)] internal class BinarySchemaGenerator : BNamedTypeSecondaryGenerator { private readonly BinarySchemaContainerParser parser_ = new(); diff --git a/Schema/src/const/ConstTypeGenerator.cs b/Schema/src/const/ConstTypeGenerator.cs index 72f3b45..df0c238 100644 --- a/Schema/src/const/ConstTypeGenerator.cs +++ b/Schema/src/const/ConstTypeGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Text; @@ -12,28 +13,27 @@ using schema.util.types; namespace schema.@const { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Method)] public class ConstAttribute : Attribute; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class GenerateConstAttribute : Attribute; + [Generator(LanguageNames.CSharp)] public class ConstTypeGenerator : BNamedTypeGenerator { public const string PREFIX = "IConst"; internal override void Generate( - TypeDeclarationSyntax syntax, INamedTypeSymbol typeSymbol, ISourceFileDictionary sourceFileDictionary) { var typeV2 = TypeV2.FromSymbol(typeSymbol); - if (this.Generate(syntax, typeSymbol, out var source)) { + if (this.Generate(typeSymbol, out var source)) { sourceFileDictionary.Add($"{typeV2.FullyQualifiedName}_const.g", source); } } - public bool Generate(TypeDeclarationSyntax syntax, - INamedTypeSymbol typeSymbol, + public bool Generate(INamedTypeSymbol typeSymbol, out string source) { var typeV2 = TypeV2.FromSymbol(typeSymbol); @@ -69,8 +69,13 @@ public bool Generate(TypeDeclarationSyntax syntax, // Interface { - cbsb.EnterBlock(typeSymbol.GetQualifiersAndNameAndGenericsFor(PREFIX) + - typeSymbol.TypeParameters.GetTypeConstraints(typeV2)); + cbsb.Write( + SymbolTypeUtil.AccessibilityToModifier( + typeSymbol.DeclaredAccessibility)); + cbsb.Write(" interface "); + cbsb.EnterBlock( + typeSymbol.GetNameAndGenericsFor(PREFIX) + + typeSymbol.TypeParameters.GetTypeConstraints(typeV2)); foreach (var parsedMember in new TypeInfoParser().ParseMembers( typeSymbol)) { @@ -94,7 +99,8 @@ public bool Generate(TypeDeclarationSyntax syntax, continue; } - if (!memberSymbol.HasAttribute()) { + if (memberSymbol is IMethodSymbol && + !memberSymbol.HasAttribute()) { continue; } @@ -113,7 +119,7 @@ public bool Generate(TypeDeclarationSyntax syntax, cbsb.Write(typeV2.GetQualifiedNameFromCurrentSymbol(memberTypeV2)); cbsb.Write(" "); - cbsb.Write(memberSymbol.Name); + cbsb.Write(memberSymbol.Name.EscapeKeyword()); switch (memberSymbol) { case IMethodSymbol methodSymbol: { @@ -130,7 +136,7 @@ public bool Generate(TypeDeclarationSyntax syntax, cbsb.Write( typeV2.GetQualifiedNameFromCurrentSymbol(parameterTypeV2)); cbsb.Write(" "); - cbsb.Write(parameterSymbol.Name); + cbsb.Write(parameterSymbol.Name.EscapeKeyword()); } cbsb.Write(")"); diff --git a/Schema/src/util/generators/BNamedTypeGenerator.cs b/Schema/src/util/generators/BNamedTypeGenerator.cs index 8165608..5f14cb6 100644 --- a/Schema/src/util/generators/BNamedTypeGenerator.cs +++ b/Schema/src/util/generators/BNamedTypeGenerator.cs @@ -1,27 +1,27 @@ -using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; + +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using schema.util.data; + namespace schema.util.generators { public abstract class BNamedTypeGenerator : ISourceGenerator { + private readonly Queue symbolQueue_ = new(); + private readonly SourceFileDictionary sourceFileDictionary_ = new(); - internal abstract void Generate( - TypeDeclarationSyntax syntax, - INamedTypeSymbol typeSymbol, - ISourceFileDictionary sourceFileDictionary); + internal abstract void Generate(INamedTypeSymbol typeSymbol, + ISourceFileDictionary sourceFileDictionary); public void Initialize(GeneratorInitializationContext context) => context.RegisterForSyntaxNotifications(() => new CustomReceiver(this)); - private class CustomReceiver : ISyntaxContextReceiver { - private readonly BNamedTypeGenerator g_; - - public CustomReceiver(BNamedTypeGenerator g) { - this.g_ = g; - } - + private class CustomReceiver(BNamedTypeGenerator g) + : ISyntaxContextReceiver { public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { TypeDeclarationSyntax syntax; ISymbol symbol; @@ -39,13 +39,16 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { return; } - this.g_.Generate(syntax, - namedTypeSymbol, - this.g_.sourceFileDictionary_); + g.symbolQueue_.Enqueue(namedTypeSymbol); } } - public void Execute(GeneratorExecutionContext context) - => this.sourceFileDictionary_.SetHandler(context.AddSource); + public void Execute(GeneratorExecutionContext context) { + while (this.symbolQueue_.TryDequeue(out var symbol)) { + this.Generate(symbol, this.sourceFileDictionary_); + } + + this.sourceFileDictionary_.SetHandler(context.AddSource); + } } } \ No newline at end of file diff --git a/Schema/src/util/generators/BNamedTypeSecondaryGenerator.cs b/Schema/src/util/generators/BNamedTypeSecondaryGenerator.cs index 6c397de..9db355a 100644 --- a/Schema/src/util/generators/BNamedTypeSecondaryGenerator.cs +++ b/Schema/src/util/generators/BNamedTypeSecondaryGenerator.cs @@ -9,7 +9,8 @@ namespace schema.util.generators { - internal abstract class BNamedTypeSecondaryGenerator : ISourceGenerator { + internal abstract class BNamedTypeSecondaryGenerator + : ISourceGenerator { private readonly Queue<(INamedTypeSymbol, TypeDeclarationSyntax)> symbolSyntaxQueue_ = new(); @@ -30,13 +31,8 @@ internal abstract void Generate( public void Initialize(GeneratorInitializationContext context) => context.RegisterForSyntaxNotifications(() => new CustomReceiver(this)); - private class CustomReceiver : ISyntaxContextReceiver { - private readonly BNamedTypeSecondaryGenerator g_; - - public CustomReceiver(BNamedTypeSecondaryGenerator g) { - this.g_ = g; - } - + private class CustomReceiver(BNamedTypeSecondaryGenerator g) + : ISyntaxContextReceiver { public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { TypeDeclarationSyntax syntax; ISymbol symbol; @@ -54,7 +50,7 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { return; } - this.g_.symbolSyntaxQueue_.Enqueue((namedTypeSymbol, syntax)); + g.symbolSyntaxQueue_.Enqueue((namedTypeSymbol, syntax)); } } diff --git a/Schema/src/util/symbols/SymbolTypeUtil.cs b/Schema/src/util/symbols/SymbolTypeUtil.cs index 1f20ad1..b82149b 100644 --- a/Schema/src/util/symbols/SymbolTypeUtil.cs +++ b/Schema/src/util/symbols/SymbolTypeUtil.cs @@ -1,4 +1,5 @@ using System; +using System.CodeDom.Compiler; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -87,7 +88,7 @@ private static IEnumerable GetContainingNamespacesReversed_( while (!namespaceSymbol?.IsGlobalNamespace ?? true) { if (namespaceSymbol.Name.Length > 0) { - yield return namespaceSymbol.Name; + yield return namespaceSymbol.Name.EscapeKeyword(); } namespaceSymbol = namespaceSymbol.ContainingNamespace; @@ -231,8 +232,13 @@ public static StringBuilder AppendNameAndGenericsFor( this StringBuilder sb, INamedTypeSymbol namedTypeSymbol, string namePrefix = "") { - sb.Append(namePrefix); - sb.Append(namedTypeSymbol.Name); + if (namePrefix != "") { + sb.Append(namePrefix); + sb.Append(namedTypeSymbol.Name); + } else { + sb.Append(namedTypeSymbol.Name.EscapeKeyword()); + } + sb.AppendGenericsFor(namedTypeSymbol); return sb; } @@ -264,7 +270,7 @@ public static StringBuilder AppendGenerics( } var typeParameter = typeParameters[i]; - sb.Append(typeParameter.Name); + sb.Append(typeParameter.Name.EscapeKeyword()); } sb.Append(">"); @@ -283,7 +289,11 @@ public static StringBuilder AppendSymbolQualifiers( .Append(" ") .Append(typeSymbol.IsAbstract ? "abstract " : "") .Append("partial ") - .Append(typeSymbol.TypeKind == TypeKind.Class ? "class" : "struct"); + .Append(typeSymbol.TypeKind switch { + TypeKind.Class => "class", + TypeKind.Struct => "struct", + TypeKind.Interface => "interface" + }); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string AccessibilityToModifier(Accessibility accessibility) @@ -325,11 +335,13 @@ public static string GetQualifiedNameFromCurrentSymbol( } if (referencedSymbol.IsGenericTypeParameter(out _)) { - return referencedSymbol.Name; + return referencedSymbol.Name.EscapeKeyword(); } - var currentNamespace = sourceSymbol.NamespaceParts.ToArray(); - var referencedNamespace = referencedSymbol.NamespaceParts.ToArray(); + var currentNamespace + = sourceSymbol.NamespaceParts.Select(EscapeKeyword).ToArray(); + var referencedNamespace = + referencedSymbol.NamespaceParts.Select(EscapeKeyword).ToArray(); string mergedNamespaceText; if (currentNamespace.Length == 0 && referencedNamespace.Length == 0) { @@ -359,11 +371,11 @@ public static string GetQualifiedNameFromCurrentSymbol( var mergedContainersText = ""; foreach (var container in referencedSymbol.DeclaringTypeNamesDownward) { - mergedContainersText += $"{container}."; + mergedContainersText += $"{container.EscapeKeyword()}."; } return - $"{mergedNamespaceText}{mergedContainersText}{referencedSymbol.Name}"; + $"{mergedNamespaceText}{mergedContainersText}{referencedSymbol.Name.EscapeKeyword()}"; } public static string GetTypeConstraints( @@ -379,7 +391,7 @@ var typeConstraintNames } sb.Append(" where "); - sb.Append(typeParameter.Name); + sb.Append(typeParameter.Name.EscapeKeyword()); sb.Append(" : "); for (var i = 0; i < typeConstraintNames.Length; ++i) { @@ -416,8 +428,8 @@ public static IEnumerable GetTypeConstraintNames( : "class"; } - if (typeParameter.HasValueTypeConstraint && - !typeParameter.HasUnmanagedTypeConstraint) { + if (typeParameter is + { HasValueTypeConstraint: true, HasUnmanagedTypeConstraint: false }) { yield return "struct"; } @@ -469,5 +481,32 @@ internal static void GetMemberRelativeToAnother( memberTypeSymbol = target.MemberTypeSymbol; memberTypeInfo = target.MemberTypeInfo; } + + public static string EscapeKeyword(this string text) + => !text.IsKeyword() ? text : $"@{text}"; + + public static bool IsKeyword(this string text) + => text is "as" + or "bool" + or "char" + or "class" + or "const" + or "double" + or "float" + or "for" + or "foreach" + or "in" + or "int" + or "internal" + or "public" + or "private" + or "protected" + or "short" + or "static" + or "string" + or "struct" + or "this" + or "unmanaged" + or "void"; } } \ No newline at end of file