Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: introduce source generator for enum code generation #585

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f09b927
feature: introduce source generator for enum code generation
Meir017 Aug 11, 2024
8dee215
feature: introduce source generator for enum code generation
Meir017 Aug 11, 2024
150a665
feature: introduce source generator for enum code generation
Meir017 Aug 11, 2024
03caf87
feature: introduce source generator for enum code generation
Meir017 Aug 11, 2024
cceb42f
feature: introduce source generator for enum code generation
Meir017 Aug 11, 2024
9e288b3
feature: introduce source generator for enum code generation
Meir017 Aug 11, 2024
0ecb009
feature: introduce source generator for enum code generation
Meir017 Aug 11, 2024
1da53f7
feature: introduce source generator for enum code generation
Meir017 Aug 12, 2024
69d506b
feature: introduce source generator for enum code generation
Meir017 Aug 12, 2024
245492a
feature: introduce source generator for enum code generation
Meir017 Aug 12, 2024
39e5b5d
feature: introduce source generator for enum code generation
Meir017 Aug 12, 2024
82e9faf
feature: introduce source generator for enum code generation
Meir017 Aug 12, 2024
6ae0c75
Merge branch 'main' into feature/enum-source-generator
Meir017 Aug 13, 2024
e81058c
migrate to IIncrementalGenerator
Meir017 Aug 13, 2024
c0a1af5
Merge branch 'feature/enum-source-generator' of https://github.com/Me…
Meir017 Aug 13, 2024
5bea62d
Merge branch 'main' into feature/enum-source-generator
Meir017 Aug 14, 2024
c64790d
feature: introduce source generator for enum code generation
Meir017 Aug 14, 2024
bdde751
feature: introduce source generator for enum code generation
Meir017 Aug 14, 2024
bf44e78
feature: introduce source generator for enum code generation
Meir017 Aug 14, 2024
3b70c2f
Merge branch 'main' into feature/enum-source-generator
Meir017 Aug 15, 2024
7c09212
Merge branch 'main' into feature/enum-source-generator
Meir017 Aug 15, 2024
52befc0
Merge branch 'main' into feature/enum-source-generator
TalZaccai Aug 15, 2024
123ecc8
change tfm to netstandard
Meir017 Aug 25, 2024
943283d
Merge remote-tracking branch 'origin/main' into feature/enum-source-g…
Meir017 Aug 25, 2024
c7dd327
merge
Meir017 Aug 25, 2024
b018d62
merge
Meir017 Aug 25, 2024
ff931da
Merge branch 'main' into feature/enum-source-generator
Meir017 Aug 26, 2024
abf98c9
Merge branch 'main' into feature/enum-source-generator
Meir017 Aug 28, 2024
26a758d
Merge branch 'main' into feature/enum-source-generator
Meir017 Aug 30, 2024
3a65347
Merge branch 'main' into feature/enum-source-generator
Meir017 Sep 12, 2024
4157234
Merge remote-tracking branch 'origin/main' into feature/enum-source-g…
Meir017 Oct 20, 2024
63b7e2e
Merge branch 'feature/enum-source-generator' of https://github.com/me…
Meir017 Oct 20, 2024
bd88fe0
feature: introduce source generator for enum code generation
Meir017 Oct 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.10.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Configuration" Version="8.0.0" />
Expand All @@ -23,5 +25,6 @@
<PackageVersion Include="StackExchange.Redis" Version="2.7.33" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
<PackageVersion Include="System.Interactive.Async" Version="6.0.1" />

</ItemGroup>
</Project>
13 changes: 12 additions & 1 deletion Garnet.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31808.319
MinimumVisualStudioVersion = 10.0.40219.1
Expand Down Expand Up @@ -96,6 +96,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommandInfoUpdater", "playg
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleModule", "playground\SampleModule\SampleModule.csproj", "{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Garnet.gen", "libs\gen\Garnet.gen.csproj", "{E248012E-1D19-4114-924F-EFC9E859C4CD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -280,6 +282,14 @@ Global
{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F}.Release|Any CPU.Build.0 = Release|Any CPU
{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F}.Release|x64.ActiveCfg = Release|Any CPU
{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F}.Release|x64.Build.0 = Release|Any CPU
{E248012E-1D19-4114-924F-EFC9E859C4CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E248012E-1D19-4114-924F-EFC9E859C4CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E248012E-1D19-4114-924F-EFC9E859C4CD}.Debug|x64.ActiveCfg = Debug|Any CPU
{E248012E-1D19-4114-924F-EFC9E859C4CD}.Debug|x64.Build.0 = Debug|Any CPU
{E248012E-1D19-4114-924F-EFC9E859C4CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E248012E-1D19-4114-924F-EFC9E859C4CD}.Release|Any CPU.Build.0 = Release|Any CPU
{E248012E-1D19-4114-924F-EFC9E859C4CD}.Release|x64.ActiveCfg = Release|Any CPU
{E248012E-1D19-4114-924F-EFC9E859C4CD}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -309,6 +319,7 @@ Global
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD} = {346A5A53-51E4-4A75-B7E6-491D950382CE}
{9BE474A2-1547-43AC-B4F2-FB48A01FA995} = {69A71E2C-00E3-42F3-854E-BE157A24834E}
{A8CA619E-8F13-4EF8-943F-2D5E3FEBFB3F} = {69A71E2C-00E3-42F3-854E-BE157A24834E}
{E248012E-1D19-4114-924F-EFC9E859C4CD} = {147FCE31-EC09-4C90-8E4D-37CA87ED18C3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2C02C405-4798-41CA-AF98-61EDFEF6772E}
Expand Down
113 changes: 0 additions & 113 deletions libs/common/EnumUtils.cs

This file was deleted.

12 changes: 12 additions & 0 deletions libs/common/GenerateEnumUtilsAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System;

namespace Garnet.common
{
[AttributeUsage(AttributeTargets.Enum)]
public sealed class GenerateEnumUtilsAttribute : Attribute
{
}
}
151 changes: 151 additions & 0 deletions libs/gen/EnumsSourceGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Garnet;

[Generator]
public class EnumsSourceGenerator : ISourceGenerator
Meir017 marked this conversation as resolved.
Show resolved Hide resolved
{
const string GeneratedClassName = "EnumUtils";
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new EnumsSyntaxReceiver());
}

public void Execute(GeneratorExecutionContext context)
{
var syntaxReceiver = context.SyntaxReceiver as EnumsSyntaxReceiver;
if (syntaxReceiver is null) return;

foreach (var enumDeclaration in syntaxReceiver.Enums)
{
var namespaceDeclaration = enumDeclaration.FirstAncestorOrSelf<NamespaceDeclarationSyntax>();
var enumName = enumDeclaration.Identifier.Text;

var values = enumDeclaration.Members
.Select(m => (
m.Identifier.Text,
Value: m.EqualsValue?.Value?.ToString(),
Description: m.AttributeLists
.SelectMany(al => al.Attributes).Where(a => a.Name.ToString() == "Description")
.SingleOrDefault()
))
.ToList();

var classBuilder = new StringBuilder();
classBuilder.AppendLine("// <auto-generated>");
classBuilder.AppendLine($"// This code was generated by the {nameof(EnumsSourceGenerator)} source generator.");
classBuilder.AppendLine("// </auto-generated>");

classBuilder.AppendLine("using System;");
classBuilder.AppendLine("using System.ComponentModel;");
classBuilder.AppendLine("using System.Numerics;");
classBuilder.AppendLine($"using {namespaceDeclaration!.Name};");
classBuilder.AppendLine();
classBuilder.AppendLine("namespace Garnet.common;");
classBuilder.AppendLine();
classBuilder.AppendLine("/// <summary>");
classBuilder.AppendLine($"/// Utility methods for enums.");
classBuilder.AppendLine("/// </summary>");
classBuilder.AppendLine($"public static partial class {GeneratedClassName}");
classBuilder.AppendLine("{");
classBuilder.AppendLine(GenerateTryParseEnumFromDescriptionMethod(enumName, values));
classBuilder.AppendLine();
classBuilder.AppendLine(GenerateGetEnumDescriptionsMethod(enumName, values));
classBuilder.AppendLine("}");
var classSource = classBuilder.ToString();
context.AddSource($"{GeneratedClassName}.{enumName}.g.cs", classSource);
}
}

private static string GenerateTryParseEnumFromDescriptionMethod(string enumName, List<(string Name, string? Value, AttributeSyntax Description)> values)
{
var method = new StringBuilder();
method.AppendLine($" /// <summary>");
method.AppendLine($" /// Tries to parse the enum value from the description.");
method.AppendLine($" /// </summary>");
method.AppendLine($" /// <param name=\"description\">Enum description.</param>");
method.AppendLine($" /// <param name=\"result\">Enum value.</param>");
method.AppendLine($" /// <returns>True if successful.</returns>");
method.AppendLine($" public static bool TryParse{enumName}FromDescription(string description, out {enumName} result)");
method.AppendLine(" {");
method.AppendLine(" result = default;");
method.AppendLine(" switch (description)");
method.AppendLine(" {");
foreach (var (name, _, description) in values)
{
bool hasDescription = false;
var descriptionValue = description?.ArgumentList?.Arguments.FirstOrDefault()?.ToString();
if (descriptionValue is not null)
{
hasDescription = true;
method.AppendLine($" case {descriptionValue}:");
}

if (!hasDescription) continue;
method.AppendLine($" result = {enumName}.{name};");
method.AppendLine(" return true;");
}
method.AppendLine(" }");
method.AppendLine();
method.AppendLine(" return false;");
method.AppendLine(" }");
method.AppendLine();

return method.ToString();
}
private static string GenerateGetEnumDescriptionsMethod(string enumName, List<(string Name, string? Value, AttributeSyntax Description)> values)
{
var method = new StringBuilder();
method.AppendLine($" /// <summary>");
method.AppendLine($" /// Gets the descriptions of the set flags. Assumes the enum is a flags enum.");
Meir017 marked this conversation as resolved.
Show resolved Hide resolved
method.AppendLine($" /// If no description exists, returns the ToString() value of the input value.");
method.AppendLine($" /// </summary>");
method.AppendLine($" /// <param name=\"value\">Enum value.</param>");
method.AppendLine($" /// <returns>Array of descriptions.</returns>");
method.AppendLine($" public static string[] Get{enumName}Descriptions({enumName} value)");
method.AppendLine(" {");
method.AppendLine(" var setFlags = BitOperations.PopCount((uint)value);");
method.AppendLine(" if (setFlags == 0) return Array.Empty<string>();");
method.AppendLine(" if (setFlags == 1)");
method.AppendLine(" {");
method.AppendLine(" return value switch");
method.AppendLine(" {");
foreach (var (name, _, description) in values)
{
if (description is null) continue;
method.AppendLine($" {enumName}.{name} => [{description?.ArgumentList?.Arguments.FirstOrDefault()?.ToString()}],");
}
method.AppendLine($" _ => [value.ToString()],");
method.AppendLine(" };");
method.AppendLine(" }");
method.AppendLine();
method.AppendLine(" var descriptions = new string[setFlags];");
method.AppendLine(" var index = 0;");
foreach (var (name, _, description) in values)
{
if (description is null) continue;
method.AppendLine($" if ((value & {enumName}.{name}) != 0) descriptions[index++] = {description.ArgumentList?.Arguments.FirstOrDefault()?.ToString()};");
}
method.AppendLine();
method.AppendLine(" return descriptions;");
method.AppendLine(" }");

return method.ToString();
}

private class EnumsSyntaxReceiver : ISyntaxReceiver
{
public List<EnumDeclarationSyntax> Enums { get; } = new();

public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is EnumDeclarationSyntax enumDeclarationSyntax
&& enumDeclarationSyntax.AttributeLists.Any(al => al.Attributes.Any(a => a.Name.ToString() == "GenerateEnumUtils")))
{
Enums.Add(enumDeclarationSyntax);
}
}
}
}
22 changes: 22 additions & 0 deletions libs/gen/Garnet.gen.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>Latest</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>

<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Project>
6 changes: 6 additions & 0 deletions libs/server/Garnet.server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<AssemblyOriginatorKeyFile>../../Garnet.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

<ItemGroup>
Expand All @@ -26,4 +27,9 @@
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\gen\Garnet.gen.csproj" />
<Analyzer Include="..\gen\bin\Debug\netstandard2.0\Garnet.gen.dll" />
</ItemGroup>

</Project>
Loading
Loading