diff --git a/EllieBot.sln b/EllieBot.sln
index 4b578fa..c015691 100644
--- a/EllieBot.sln
+++ b/EllieBot.sln
@@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EllieBot.Tests", "src\Ellie
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EllieBot.Coordinator", "src\EllieBot.Coordinator\EllieBot.Coordinator.csproj", "{A631DDF0-3AD1-4CB9-8458-314B1320868A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EllieBot.Generators", "src\EllieBot.Generators\EllieBot.Generators.csproj", "{CB1A5307-DD85-4795-8A8A-A25D36DADC51}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -45,6 +47,10 @@ Global
{A631DDF0-3AD1-4CB9-8458-314B1320868A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A631DDF0-3AD1-4CB9-8458-314B1320868A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A631DDF0-3AD1-4CB9-8458-314B1320868A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CB1A5307-DD85-4795-8A8A-A25D36DADC51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CB1A5307-DD85-4795-8A8A-A25D36DADC51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CB1A5307-DD85-4795-8A8A-A25D36DADC51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CB1A5307-DD85-4795-8A8A-A25D36DADC51}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -55,6 +61,7 @@ Global
{5AD2EFFB-7774-49B2-A791-3BAC4DAEE067} = {872A4C63-833C-4AE0-91AB-3CE348D3E6F8}
{179DF3B3-AD32-4335-8231-9818338DF3A2} = {B28FB883-9688-41EB-BF5A-945F4A4EB628}
{A631DDF0-3AD1-4CB9-8458-314B1320868A} = {B28FB883-9688-41EB-BF5A-945F4A4EB628}
+ {CB1A5307-DD85-4795-8A8A-A25D36DADC51} = {B28FB883-9688-41EB-BF5A-945F4A4EB628}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {79F61C2C-CDBB-4361-A234-91A0B334CFE4}
diff --git a/src/EllieBot.Generators/Cloneable/CloneableGenerator.cs b/src/EllieBot.Generators/Cloneable/CloneableGenerator.cs
new file mode 100644
index 0000000..6bd7da4
--- /dev/null
+++ b/src/EllieBot.Generators/Cloneable/CloneableGenerator.cs
@@ -0,0 +1,254 @@
+// Code temporarily yeeted from
+// https://github.com/mostmand/Cloneable/blob/master/Cloneable/CloneableGenerator.cs
+// because of NRT issue
+#nullable enable
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Cloneable
+{
+ [Generator]
+ public class CloneableGenerator : ISourceGenerator
+ {
+ private const string PREVENT_DEEP_COPY_KEY_STRING = "PreventDeepCopy";
+ private const string EXPLICIT_DECLARATION_KEY_STRING = "ExplicitDeclaration";
+
+ private const string CLONEABLE_NAMESPACE = "Cloneable";
+ private const string CLONEABLE_ATTRIBUTE_STRING = "CloneableAttribute";
+ private const string CLONE_ATTRIBUTE_STRING = "CloneAttribute";
+ private const string IGNORE_CLONE_ATTRIBUTE_STRING = "IgnoreCloneAttribute";
+
+ private const string CLONEABLE_ATTRIBUTE_TEXT = @"//
+using System;
+
+namespace " + CLONEABLE_NAMESPACE + @"
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
+ public sealed class " + CLONEABLE_ATTRIBUTE_STRING + @" : Attribute
+ {
+ public " + CLONEABLE_ATTRIBUTE_STRING + @"()
+ {
+ }
+
+ public bool " + EXPLICIT_DECLARATION_KEY_STRING + @" { get; set; }
+ }
+}
+";
+
+ private const string CLONE_PROPERTY_ATTRIBUTE_TEXT = @"//
+using System;
+
+namespace " + CLONEABLE_NAMESPACE + @"
+{
+ [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
+ public sealed class " + CLONE_ATTRIBUTE_STRING + @" : Attribute
+ {
+ public " + CLONE_ATTRIBUTE_STRING + @"()
+ {
+ }
+
+ public bool " + PREVENT_DEEP_COPY_KEY_STRING + @" { get; set; }
+ }
+}
+";
+
+ private const string IGNORE_CLONE_PROPERTY_ATTRIBUTE_TEXT = @"//
+using System;
+
+namespace " + CLONEABLE_NAMESPACE + @"
+{
+ [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
+ public sealed class " + IGNORE_CLONE_ATTRIBUTE_STRING + @" : Attribute
+ {
+ public " + IGNORE_CLONE_ATTRIBUTE_STRING + @"()
+ {
+ }
+ }
+}
+";
+
+ private INamedTypeSymbol? _cloneableAttribute;
+ private INamedTypeSymbol? _ignoreCloneAttribute;
+ private INamedTypeSymbol? _cloneAttribute;
+
+ public void Initialize(GeneratorInitializationContext context)
+ => context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ InjectCloneableAttributes(context);
+ GenerateCloneMethods(context);
+ }
+
+ private void GenerateCloneMethods(GeneratorExecutionContext context)
+ {
+ if (context.SyntaxReceiver is not SyntaxReceiver receiver)
+ return;
+
+ Compilation compilation = GetCompilation(context);
+
+ InitAttributes(compilation);
+
+ var classSymbols = GetClassSymbols(compilation, receiver);
+ foreach (var classSymbol in classSymbols)
+ {
+ if (!classSymbol.TryGetAttribute(_cloneableAttribute!, out var attributes))
+ continue;
+
+ var attribute = attributes.Single();
+ var isExplicit = (bool?)attribute.NamedArguments.FirstOrDefault(e => e.Key.Equals(EXPLICIT_DECLARATION_KEY_STRING)).Value.Value ?? false;
+ context.AddSource($"{classSymbol.Name}_cloneable.g.cs", SourceText.From(CreateCloneableCode(classSymbol, isExplicit), Encoding.UTF8));
+ }
+ }
+
+ private void InitAttributes(Compilation compilation)
+ {
+ _cloneableAttribute = compilation.GetTypeByMetadataName($"{CLONEABLE_NAMESPACE}.{CLONEABLE_ATTRIBUTE_STRING}")!;
+ _cloneAttribute = compilation.GetTypeByMetadataName($"{CLONEABLE_NAMESPACE}.{CLONE_ATTRIBUTE_STRING}")!;
+ _ignoreCloneAttribute = compilation.GetTypeByMetadataName($"{CLONEABLE_NAMESPACE}.{IGNORE_CLONE_ATTRIBUTE_STRING}")!;
+ }
+
+ private static Compilation GetCompilation(GeneratorExecutionContext context)
+ {
+ var options = context.Compilation.SyntaxTrees.First().Options as CSharpParseOptions;
+
+ var compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(CLONEABLE_ATTRIBUTE_TEXT, Encoding.UTF8), options)).
+ AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(CLONE_PROPERTY_ATTRIBUTE_TEXT, Encoding.UTF8), options)).
+ AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(IGNORE_CLONE_PROPERTY_ATTRIBUTE_TEXT, Encoding.UTF8), options));
+ return compilation;
+ }
+
+ private string CreateCloneableCode(INamedTypeSymbol classSymbol, bool isExplicit)
+ {
+ string namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
+ var fieldAssignmentsCode = GenerateFieldAssignmentsCode(classSymbol, isExplicit).ToList();
+ var fieldAssignmentsCodeSafe = fieldAssignmentsCode.Select(x =>
+ {
+ if (x.isCloneable)
+ return x.line + "Safe(referenceChain)";
+ return x.line;
+ });
+ var fieldAssignmentsCodeFast = fieldAssignmentsCode.Select(x =>
+ {
+ if (x.isCloneable)
+ return x.line + "()";
+ return x.line;
+ });
+
+ return $@"using System.Collections.Generic;
+
+namespace {namespaceName}
+{{
+ {GetAccessModifier(classSymbol)} partial class {classSymbol.Name}
+ {{
+ ///
+ /// Creates a copy of {classSymbol.Name} with NO circular reference checking. This method should be used if performance matters.
+ ///
+ /// Will occur on any object that has circular references in the hierarchy.
+ ///
+ public {classSymbol.Name} Clone()
+ {{
+ return new {classSymbol.Name}
+ {{
+{string.Join(",\n", fieldAssignmentsCodeFast)}
+ }};
+ }}
+
+ ///
+ /// Creates a copy of {classSymbol.Name} with circular reference checking. If a circular reference was detected, only a reference of the leaf object is passed instead of cloning it.
+ ///
+ /// Should only be provided if specific objects should not be cloned but passed by reference instead.
+ public {classSymbol.Name} CloneSafe(Stack