diff --git a/src/Kafa.Generators/Kafa.Generators.csproj b/src/Kafa.Generators/Kafa.Generators.csproj index f558ae9..705186e 100644 --- a/src/Kafa.Generators/Kafa.Generators.csproj +++ b/src/Kafa.Generators/Kafa.Generators.csproj @@ -1,9 +1,18 @@ - netstandard2.0 + net7.0;netstandard2.0 + 11.0 + + true + false + true + true + true + + all diff --git a/src/Kafa.Generators/KafaSourceGenerator.Attribute.cs b/src/Kafa.Generators/KafaSourceGenerator.Attribute.cs new file mode 100644 index 0000000..6c43e39 --- /dev/null +++ b/src/Kafa.Generators/KafaSourceGenerator.Attribute.cs @@ -0,0 +1,27 @@ +namespace nyingi.Kafa.Generators; + +internal sealed partial class KafaSourceGenerator +{ + private static readonly string GeneratedAttribute = $@"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{typeof(KafaSourceGenerator).Assembly.GetName().Name}"", ""0.1.0"")]"; + + //lang=c# + private static readonly string KafaSerializableAttribute = $@" +// + +#nullable enable + +namespace nyingi.Kafa.Generators; + +{GeneratedAttribute} +[global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple =true, Inherited = false)] +internal sealed class KafaSerializableAttribute : global::System.Attribute +{{ + public global::System.Type Type {{ get; }} + + public KafaSerializableAttribute(global::System.Type type) + {{ + Type = type; + }} +}} +"; +} \ No newline at end of file diff --git a/src/Kafa.Generators/KafaSourceGenerator.Context.cs b/src/Kafa.Generators/KafaSourceGenerator.Context.cs new file mode 100644 index 0000000..6de4900 --- /dev/null +++ b/src/Kafa.Generators/KafaSourceGenerator.Context.cs @@ -0,0 +1,17 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace nyingi.Kafa.Generators +{ + internal sealed partial class KafaSourceGenerator + { + private record struct KafaGeneratorContext(string? actualnamespace, string name, TypeContext typeContext); + + private record struct TypeContext(bool isReferenceType, string name, ImmutableArray propertyContext); + + private record struct PropertyContext(string type, string name); + + private static readonly SymbolDisplayFormat Format = SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.ExpandNullable | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + + } +} \ No newline at end of file diff --git a/src/Kafa.Generators/KafaSourceGenerator.Emitter.cs b/src/Kafa.Generators/KafaSourceGenerator.Emitter.cs deleted file mode 100644 index d63f7c3..0000000 --- a/src/Kafa.Generators/KafaSourceGenerator.Emitter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Kafa.Generators -{ - - [Generator] - public class KafaSourceGenerator : ISourceGenerator - { - public void Execute(GeneratorExecutionContext context) - { - throw new System.NotImplementedException(); - } - - public void Initialize(GeneratorInitializationContext context) - { - throw new System.NotImplementedException(); - } - } -} diff --git a/src/Kafa.Generators/KafaSourceGenerator.cs b/src/Kafa.Generators/KafaSourceGenerator.cs new file mode 100644 index 0000000..3f97d29 --- /dev/null +++ b/src/Kafa.Generators/KafaSourceGenerator.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace nyingi.Kafa.Generators +{ + + [Generator] + internal sealed partial class KafaSourceGenerator : IIncrementalGenerator + { + private const string _kafaAttributeName = "nyingi.Kafa.Generators.KafaSerializableAttribute"; + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput(PostInitializationCallback); + + + IncrementalValuesProvider provider = context.SyntaxProvider + .CreateSyntaxProvider(SyntacticPredicate, SemanticTransformer) + .Where(static ((INamedTypeSymbol, INamedTypeSymbol)? types) => types.HasValue) + .Select(static ((INamedTypeSymbol, INamedTypeSymbol)? types, CancellationToken cancellationToken) => + TransformType(types!.Value)); + + } + + private static void PostInitializationCallback(IncrementalGeneratorPostInitializationContext context) + { + context.AddSource("KafaSerializableAttribute.g.cs", SourceText.From(KafaSerializableAttribute, Encoding.UTF8)); + } + private static bool SyntacticPredicate(SyntaxNode syntaxNode, CancellationToken cancellationToken) + { + return syntaxNode is ClassDeclarationSyntax + { + AttributeLists.Count: > 0, + } candidate + && candidate.Modifiers.Any(SyntaxKind.PartialKeyword) + && !candidate.Modifiers.Any(SyntaxKind.StaticKeyword); + } + + private static (INamedTypeSymbol, INamedTypeSymbol)? SemanticTransformer(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; + + INamedTypeSymbol? namedTypeSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken); + + + if (namedTypeSymbol is not null + && TryGetAttribute(classDeclarationSyntax, _kafaAttributeName, context.SemanticModel, cancellationToken, + out AttributeSyntax? attributeSyntax) + && TryGetType(attributeSyntax, context.SemanticModel, cancellationToken, + out INamedTypeSymbol? typeSymbol)) + { + return (namedTypeSymbol, typeSymbol); + } + + return null; + } + + + private static bool TryGetAttribute(ClassDeclarationSyntax classDeclarationSyntax, string attributeName, + SemanticModel semanticModel, CancellationToken cancellationToken, + out AttributeSyntax? value) + { + foreach (AttributeListSyntax attributeList in classDeclarationSyntax.AttributeLists) + { + foreach (AttributeSyntax attributeSyntax in attributeList.Attributes) + { + SymbolInfo info = semanticModel.GetSymbolInfo(attributeSyntax, cancellationToken); + ISymbol? symbol = info.Symbol; + + if (symbol is IMethodSymbol methodSymbol + && methodSymbol.ContainingType.ToDisplayString() + .Equals(attributeName, StringComparison.Ordinal)) + { + value = attributeSyntax; + return true; + } + } + } + + value = null; + return false; + } + + + private static bool TryGetType(AttributeSyntax attributeSyntax, SemanticModel semanticModel, + CancellationToken cancellationToken, out INamedTypeSymbol? namedTypeSymbol) + { + var argumentList = attributeSyntax.ArgumentList; + if (argumentList!.Arguments.Count > 1) ; + { + AttributeArgumentSyntax argumentSyntax = argumentList.Arguments[0]; + + if (argumentSyntax.Expression is TypeOfExpressionSyntax typeOf) + { + SymbolInfo info = semanticModel.GetSymbolInfo(typeOf.Type, cancellationToken); + ISymbol? symbol = info.Symbol; + + if (symbol is INamedTypeSymbol typeSymbol) + { + namedTypeSymbol = typeSymbol; + return true; + } + } + } + + namedTypeSymbol = null; + return false; + } + + private static KafaGeneratorContext TransformType((INamedTypeSymbol kafaPartialGeneratorType, INamedTypeSymbol targetType) types) + { + string? @namespace = types.kafaPartialGeneratorType.ContainingNamespace.IsGlobalNamespace + ? null + : types.kafaPartialGeneratorType.ContainingNamespace.ToDisplayString(); + + string name = types.kafaPartialGeneratorType.Name; + + bool isReferenceType = types.kafaPartialGeneratorType.IsReferenceType; + string targetTypeName = types.targetType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + bool areInternalSymbolsAccessible = + types.targetType.ContainingAssembly.GivesAccessTo((types.kafaPartialGeneratorType.ContainingAssembly)); + + ImmutableArray propertyContexts = types.targetType.GetTypeAndSubtypes() + .Reverse() + .SelectMany(static type => type.GetMembers()) + .Where(member => FilterProperty(member, areInternalSymbolsAccessible)) + .Select(static member => TransformProperty(member)) + .Distinct() + .ToImmutableArray(); + + + return new KafaGeneratorContext(@namespace, name, + new TypeContext(isReferenceType, targetTypeName, propertyContexts)); + } + + + private static bool FilterProperty(ISymbol member, bool areInernalSymbolsAccessible) + { + if (!member.IsStatic && member.Kind == SymbolKind.Property) + { + var property = (IPropertySymbol)member; + + return property.GetMethod is { } get + && (get.DeclaredAccessibility is Accessibility.Public + || (areInernalSymbolsAccessible && + (get.DeclaredAccessibility is Accessibility.Internal + or Accessibility.ProtectedOrInternal))); + } + + return false; + } + + private static PropertyContext TransformProperty(ISymbol member) + { + var property = (IPropertySymbol)member; + string type = property.Type.ToDisplayString(Format); + string name = property.Name; + + return new PropertyContext(type, name); + } + } +} diff --git a/src/Kafa.Generators/NameTypeSymbolExtensions.cs b/src/Kafa.Generators/NameTypeSymbolExtensions.cs new file mode 100644 index 0000000..a3c5587 --- /dev/null +++ b/src/Kafa.Generators/NameTypeSymbolExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace nyingi.Kafa.Generators; + +internal static class NameTypeSymbolExtensions +{ + + public static IEnumerable GetTypeAndSubtypes(this INamedTypeSymbol? type) + { + INamedTypeSymbol? current = type; + while (current is not null) + { + yield return current; + current = current.BaseType; + } + } + +} \ No newline at end of file diff --git a/src/Kafa.sln b/src/Kafa.sln index ae10311..19fa62a 100644 --- a/src/Kafa.sln +++ b/src/Kafa.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kafa", "Kafa\Kafa.csproj", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KafaTests", "KafaTests\KafaTests.csproj", "{BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kafa.Generators", "Kafa.Generators\Kafa.Generators.csproj", "{65208E1E-7289-451E-8C66-578B98F4CC90}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB863E7D-755B-4626-90C3-6AAA1A8B4FAD}.Release|Any CPU.Build.0 = Release|Any CPU + {65208E1E-7289-451E-8C66-578B98F4CC90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65208E1E-7289-451E-8C66-578B98F4CC90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65208E1E-7289-451E-8C66-578B98F4CC90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65208E1E-7289-451E-8C66-578B98F4CC90}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Kafa/IKafaFormatter.cs b/src/Kafa/IKafaFormatter.cs new file mode 100644 index 0000000..0142cb2 --- /dev/null +++ b/src/Kafa/IKafaFormatter.cs @@ -0,0 +1,10 @@ +using nyingi.Kafa.Reader; +using nyingi.Kafa.Writer; + +namespace nyingi.Kafa; + +public interface IKafaFormatter +{ + public static abstract void Serialize(ref KafaWriter writer, scoped ref T? entity); + public static abstract void Deserialize(ref KafaReader reader, scoped ref T? entity); +} \ No newline at end of file diff --git a/src/Kafa/Kafa.csproj b/src/Kafa/Kafa.csproj index 5cba791..68c1d16 100644 --- a/src/Kafa/Kafa.csproj +++ b/src/Kafa/Kafa.csproj @@ -5,6 +5,11 @@ enable enable true - Generated + + + + + diff --git a/src/Kafa/Writer/KafaWriter.cs b/src/Kafa/Writer/KafaWriter.cs index 0523b68..d9263c8 100644 --- a/src/Kafa/Writer/KafaWriter.cs +++ b/src/Kafa/Writer/KafaWriter.cs @@ -12,7 +12,7 @@ public class KafaWriter : IDisposable private readonly byte[] _winNewLine = new byte[2] {(byte)'\r',(byte)'\n'}; private readonly KafaOptions _options; - + private readonly byte[] _cachedNewLine; private byte[] _separator = new byte[1]; public KafaWriter(in IBufferWriter bufferWriter, KafaOptions options) @@ -20,12 +20,14 @@ public KafaWriter(in IBufferWriter bufferWriter, KafaOptions options) _bufferWriter = bufferWriter ?? throw new NullReferenceException(nameof(bufferWriter)); _options = options; _separator[0] = (byte)_options.Separator; + _cachedNewLine = Environment.OSVersion.Platform == PlatformID.Unix ? _unixNewLine : _winNewLine; } public KafaWriter(in Stream stream, KafaOptions options) { _stream = stream; _options = options; _kafaPooledWriter = new KafaPooledWriter(); // use the default 65k + _cachedNewLine = Environment.OSVersion.Platform == PlatformID.Unix ? _unixNewLine : _winNewLine; } public void WriteSeparator() @@ -35,8 +37,7 @@ public void WriteSeparator() public void WriteLine() { - var newLine = Environment.OSVersion.Platform == PlatformID.Unix ? _unixNewLine : _winNewLine; - Write(newLine); + Write(_cachedNewLine); } public void Write(string str)