2023-07-12 04:17:43 +00:00
// Code temporarily yeeted from
// https://github.com/mostmand/Cloneable/blob/master/Cloneable/CloneableGenerator.cs
// because of NRT issue
2023-07-15 10:37:02 +00:00
#nullable enable
2023-07-12 04:17:43 +00:00
using Microsoft.CodeAnalysis ;
using Microsoft.CodeAnalysis.CSharp ;
using Microsoft.CodeAnalysis.CSharp.Syntax ;
using Microsoft.CodeAnalysis.Text ;
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 = $ $"" "
// <AutoGenerated/>
using System ;
namespace { { CLONEABLE_NAMESPACE } }
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
internal 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 = $ $"" "
// <AutoGenerated/>
using System ;
namespace { { CLONEABLE_NAMESPACE } }
{
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
internal 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 = $ $"" "
// <AutoGenerated/>
using System ;
namespace { { CLONEABLE_NAMESPACE } }
{
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
internal 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 }
{ {
/// <summary>
/// Creates a copy of {classSymbol.Name} with NO circular reference checking. This method should be used if performance matters.
///
/// <exception cref=""StackOverflowException"">Will occur on any object that has circular references in the hierarchy.</exception>
/// </summary>
public { classSymbol . Name } Clone ( )
{ {
return new { classSymbol . Name }
{ {
{ string . Join ( ",\n" , fieldAssignmentsCodeFast ) }
} } ;
} }
/// <summary>
/// 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.
/// </summary>
/// <param name=""referenceChain"">Should only be provided if specific objects should not be cloned but passed by reference instead.</param>
public { classSymbol . Name } CloneSafe ( Stack < object > referenceChain = null )
{ {
if ( referenceChain ? . Contains ( this ) = = true )
return this ;
referenceChain ? ? = new Stack < object > ( ) ;
referenceChain . Push ( this ) ;
var result = new { classSymbol . Name }
{ {
{ string . Join ( $",\n" , fieldAssignmentsCodeSafe ) }
} } ;
referenceChain . Pop ( ) ;
return result ;
} }
} }
} } ";
}
2023-08-08 08:31:53 +00:00
private IEnumerable < ( string line , bool isCloneable ) > GenerateFieldAssignmentsCode ( INamedTypeSymbol classSymbol , bool isExplicit )
2023-07-12 04:17:43 +00:00
{
var fieldNames = GetCloneableProperties ( classSymbol , isExplicit ) ;
var fieldAssignments = fieldNames . Select ( field = > IsFieldCloneable ( field , classSymbol ) )
. OrderBy ( x = > x . isCloneable )
. Select ( x = > ( GenerateAssignmentCode ( x . item . Name , x . isCloneable ) , x . isCloneable ) ) ;
return fieldAssignments ;
}
private string GenerateAssignmentCode ( string name , bool isCloneable )
{
if ( isCloneable )
{
return $@" {name} = this.{name}?.Clone" ;
}
return $@" {name} = this.{name}" ;
}
private ( IPropertySymbol item , bool isCloneable ) IsFieldCloneable ( IPropertySymbol x , INamedTypeSymbol classSymbol )
{
if ( SymbolEqualityComparer . Default . Equals ( x . Type , classSymbol ) )
{
return ( x , false ) ;
}
if ( ! x . Type . TryGetAttribute ( _cloneableAttribute ! , out var attributes ) )
{
return ( x , false ) ;
}
var preventDeepCopy = ( bool? ) attributes . Single ( ) . NamedArguments . FirstOrDefault ( e = > e . Key . Equals ( PREVENT_DEEP_COPY_KEY_STRING ) ) . Value . Value ? ? false ;
return ( item : x , ! preventDeepCopy ) ;
}
private string GetAccessModifier ( INamedTypeSymbol classSymbol )
= > classSymbol . DeclaredAccessibility . ToString ( ) . ToLowerInvariant ( ) ;
private IEnumerable < IPropertySymbol > GetCloneableProperties ( ITypeSymbol classSymbol , bool isExplicit )
{
var targetSymbolMembers = classSymbol . GetMembers ( ) . OfType < IPropertySymbol > ( )
. Where ( x = > x . SetMethod is not null & &
x . CanBeReferencedByName ) ;
if ( isExplicit )
{
return targetSymbolMembers . Where ( x = > x . HasAttribute ( _cloneAttribute ! ) ) ;
}
else
{
return targetSymbolMembers . Where ( x = > ! x . HasAttribute ( _ignoreCloneAttribute ! ) ) ;
}
}
private static IEnumerable < INamedTypeSymbol > GetClassSymbols ( Compilation compilation , SyntaxReceiver receiver )
= > receiver . CandidateClasses . Select ( clazz = > GetClassSymbol ( compilation , clazz ) ) ;
private static INamedTypeSymbol GetClassSymbol ( Compilation compilation , ClassDeclarationSyntax clazz )
{
var model = compilation . GetSemanticModel ( clazz . SyntaxTree ) ;
var classSymbol = model . GetDeclaredSymbol ( clazz ) ! ;
return classSymbol ;
}
private static void InjectCloneableAttributes ( GeneratorExecutionContext context )
{
context . AddSource ( CLONEABLE_ATTRIBUTE_STRING , SourceText . From ( CLONEABLE_ATTRIBUTE_TEXT , Encoding . UTF8 ) ) ;
context . AddSource ( CLONE_ATTRIBUTE_STRING , SourceText . From ( CLONE_PROPERTY_ATTRIBUTE_TEXT , Encoding . UTF8 ) ) ;
context . AddSource ( IGNORE_CLONE_ATTRIBUTE_STRING , SourceText . From ( IGNORE_CLONE_PROPERTY_ATTRIBUTE_TEXT , Encoding . UTF8 ) ) ;
}
}
2023-07-15 10:37:02 +00:00
}