using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Sandbox.Generator
{
internal class CodeGen
{
[Flags]
internal enum Flags
{
WrapProperyGet = 1,
WrapPropertySet = 2,
WrapMethod = 4,
Static = 8,
Instance = 16
}
///
/// Find anything marked with [CodeGen] and perform the appropriate code generation.
///
internal static void VisitMethod( ref MethodDeclarationSyntax node, IMethodSymbol symbol, Worker master )
{
// This will be true for abstract methods..
if ( (node.Body == null && node.ExpressionBody == null) || symbol.IsAbstract ) return;
bool hasTarget = false;
var attributesToWrite = new List();
var attributes = symbol.GetAttributes();
foreach ( var attribute in attributes )
{
foreach ( var cg in GetCodeGeneratorAttributes( attribute ) )
{
var type = (Flags)int.Parse( cg.GetArgumentValue( 0, "Type", "0" ) );
var callbackName = cg.GetArgumentValue( 1, "CallbackName", string.Empty );
if ( !type.Contains( Flags.WrapMethod ) ) continue;
hasTarget = HandleWrapCall( attribute, type, callbackName, ref node, symbol, master ) || hasTarget;
}
// include ALL the attributes when passing to the thing
AddAttributeString( attribute, attributesToWrite );
}
if ( hasTarget && attributesToWrite.Count > 0 )
{
var methodIdentity = MakeMethodIdentitySafe( GetUniqueMethodIdentity( symbol ) );
master.AddToCurrentClass( $"[global::Sandbox.SkipHotload] static readonly global::System.Attribute[] __{methodIdentity}__Attrs = new global::System.Attribute[] {{ {string.Join( ", ", attributesToWrite )} }};\n", false );
}
}
private struct PropertyWrapperData
{
public AttributeData Attribute { get; set; }
public string CallbackName { get; set; }
public int Priority { get; set; }
public Flags Type { get; set; }
}
internal static void VisitProperty( ref PropertyDeclarationSyntax node, IPropertySymbol symbol, Worker master )
{
var generateBackingField = false;
var attributesToWrite = new List();
var attributes = symbol.GetAttributes();
var originalNode = node;
var data = new List();
foreach ( var attribute in attributes )
{
foreach ( var cg in GetCodeGeneratorAttributes( attribute ) )
{
var type = (Flags)int.Parse( cg.GetArgumentValue( 0, "Type", "0" ) );
var callbackName = cg.GetArgumentValue( 1, "CallbackName", string.Empty );
var priority = int.Parse( cg.GetArgumentValue( 2, "Priority", "0" ) );
if ( type.Contains( Flags.WrapPropertySet ) || type.Contains( Flags.WrapProperyGet ) )
{
data.Add( new()
{
Attribute = attribute,
CallbackName = callbackName,
Priority = priority,
Type = type
} );
}
AddAttributeString( attribute, attributesToWrite );
}
}
data.Sort( ( a, b ) => b.Priority.CompareTo( a.Priority ) );
foreach ( var w in data )
{
if ( w.Type.Contains( Flags.WrapPropertySet ) )
{
if ( HandleWrapSet( w.Attribute, w.Type, w.CallbackName, ref node, symbol, master ) )
generateBackingField = true;
}
if ( w.Type.Contains( Flags.WrapProperyGet ) )
{
if ( HandleWrapGet( w.Attribute, w.Type, w.CallbackName, ref node, symbol, master ) )
generateBackingField = true;
}
}
if ( attributesToWrite.Count > 0 )
{
master.AddToCurrentClass( $"[global::Sandbox.SkipHotload] static readonly global::System.Attribute[] __{symbol.Name}__Attrs = new global::System.Attribute[] {{ {string.Join( ", ", attributesToWrite )} }};\n", false );
}
if ( !generateBackingField ) return;
var fieldName = originalNode.BackingFieldName();
var modifiers = originalNode.Modifiers.ToString();
var nodeType = originalNode.Type;
modifiers = modifiers.Replace( "public", "" ).Replace( "protected", "" ).Trim();
master.AddToCurrentClass( $"{modifiers} {nodeType} {fieldName}{originalNode.Initializer};\n", false );
}
private static void AddAttributeString( AttributeData attribute, List list )
{
var sn = attribute.ApplicationSyntaxReference?.GetSyntax() as AttributeSyntax;
if ( sn is null ) return;
var attributeClassName = attribute.AttributeClass.FullName();
var propertyArguments = new List<(string, string)>();
var regularArguments = new List();
if ( !attributeClassName.EndsWith( "Attribute" ) )
attributeClassName += "Attribute";
var arguments = sn.ArgumentList?.Arguments.ToArray() ?? Array.Empty();
if ( arguments.Length == 0 )
{
list.Add( $"new {attributeClassName}()" );
return;
}
foreach ( var syntax in arguments )
{
if ( syntax.NameColon is not null )
propertyArguments.Add( (syntax.NameColon.Name.ToString(), syntax.Expression.ToString()) );
else if ( syntax.NameEquals != null )
propertyArguments.Add( (syntax.NameEquals.Name.ToString(), syntax.Expression.ToString()) );
else
regularArguments.Add( syntax.Expression.ToString() );
}
var output = $"new {attributeClassName}( {string.Join( ",", regularArguments )} ) {{ ";
for ( var i = 0; i < propertyArguments.Count; i++ )
{
var (k, v) = propertyArguments[i];
output += $"{k} = {v}";
if ( i < propertyArguments.Count - 1 )
{
output += ", ";
}
}
list.Add( $"{output} }}" );
}
private static bool HandleWrapSet( AttributeData attribute, Flags type, string callbackName, ref PropertyDeclarationSyntax node, IPropertySymbol symbol, Worker master )
{
if ( symbol.IsStatic && !type.Contains( Flags.Static ) )
return false;
if ( !symbol.IsStatic && !type.Contains( Flags.Instance ) )
return false;
var typeToInvokeOn = symbol.ContainingType;
var methodToInvoke = callbackName;
var splitCallbackName = callbackName.Split( '.' );
var isStaticCallback = false;
if ( splitCallbackName.Length > 1 )
{
isStaticCallback = true;
methodToInvoke = splitCallbackName[splitCallbackName.Length - 1];
var typeToLookFor = string.Join( ".", splitCallbackName.Take( splitCallbackName.Length - 1 ) );
typeToInvokeOn = master.GetOrCreateTypeByMetadataName( typeToLookFor );
if ( typeToInvokeOn is null )
{
master.AddError( node.GetLocation(),
$"Unable to find {typeToLookFor} required for {attribute.AttributeClass?.Name}. Ensure that a fully qualified callback name is used." );
return false;
}
}
if ( typeToInvokeOn is null || !ValidateSetterCallback( symbol.ContainingType, typeToInvokeOn, methodToInvoke, isStaticCallback, symbol.Type ) )
{
master.AddError( node.GetLocation(),
$"A method {callbackName}( WrappedPropertySet ) is required on {typeToInvokeOn?.Name}." );
return false;
}
var propertyType = symbol.Type.FullName();
var usesBackingField = false;
var accessors = new List();
var get = node.AccessorList?.Accessors.FirstOrDefault( a => a.Kind() == SyntaxKind.GetAccessorDeclaration );
if ( get is not null && (get.Body is null && get.ExpressionBody is null) )
{
var defaultStatement = ParseStatement( $"return {node.BackingFieldName()};" );
usesBackingField = true;
get = AccessorDeclaration( SyntaxKind.GetAccessorDeclaration, Block( defaultStatement ) );
}
if ( get is not null )
{
accessors.Add( get );
}
{
var existingSetter = node.AccessorList?.Accessors.FirstOrDefault( a => a.Kind() == SyntaxKind.SetAccessorDeclaration );
string defaultStatement = $"{node.BackingFieldName()} = value;";
string getValue = $"{node.BackingFieldName()}";
if ( existingSetter is not null && (existingSetter.Body is not null || existingSetter.ExpressionBody is not null) )
{
if ( existingSetter.Body is not null )
defaultStatement = existingSetter.Body.ToString();
else
defaultStatement = $"{existingSetter.ExpressionBody?.Expression.ToString()};";
}
else
{
usesBackingField = true;
}
var memberIdentity = $"{symbol.ContainingType.GetFullMetadataName().Replace( "global::", "" )}.{symbol.Name}";
var parameterStruct = ParseStatement(
$"new global::Sandbox.WrappedPropertySet<{propertyType}> {{" +
$"Value = value," +
$"Object = {(symbol.IsStatic ? "null" : "this")}," +
$"Setter = ( v ) => {{{defaultStatement}}}," +
$"Getter = () => {symbol.Name}," +
$"IsStatic = {(symbol.IsStatic ? "true" : "false")}," +
$"TypeName = {symbol.ContainingType.FullName().Replace( "global::", "" ).QuoteSafe()}," +
$"PropertyName = {symbol.Name.QuoteSafe()}," +
$"MemberIdent = {memberIdentity.FastHash()}," +
$"Attributes = __{symbol.Name}__Attrs" +
$"}}" );
var statements = new[]
{
ParseStatement( $"{callbackName}( {parameterStruct} );" )
};
var set = AccessorDeclaration( SyntaxKind.SetAccessorDeclaration, Block( statements ) );
if ( existingSetter is not null )
set = set.WithModifiers( existingSetter.Modifiers );
accessors.Add( set );
node = node.WithAccessorList( AccessorList( List( accessors ) ) )
.WithInitializer( null )
.WithSemicolonToken( Token( SyntaxKind.None ) )
.NormalizeWhitespace();
}
return usesBackingField;
}
private static bool HandleWrapGet( AttributeData attribute, Flags type, string callbackName, ref PropertyDeclarationSyntax node, IPropertySymbol symbol, Worker master )
{
if ( symbol.IsStatic && !type.Contains( Flags.Static ) )
return false;
if ( !symbol.IsStatic && !type.Contains( Flags.Instance ) )
return false;
var typeToInvokeOn = symbol.ContainingType;
var methodToInvoke = callbackName;
var splitCallbackName = callbackName.Split( '.' );
var isStaticCallback = false;
if ( splitCallbackName.Length > 1 )
{
isStaticCallback = true;
methodToInvoke = splitCallbackName[splitCallbackName.Length - 1];
var typeToLookFor = string.Join( ".", splitCallbackName.Take( splitCallbackName.Length - 1 ) );
typeToInvokeOn = master.GetOrCreateTypeByMetadataName( typeToLookFor );
if ( typeToInvokeOn is null )
{
master.AddError( node.GetLocation(),
$"Unable to find {typeToLookFor} required for {attribute.AttributeClass?.Name}. Ensure that a fully qualified callback name is used." );
return false;
}
}
var propertyType = symbol.Type.FullName();
if ( typeToInvokeOn is null || !ValidateGetterCallback( symbol.ContainingType, typeToInvokeOn, methodToInvoke, isStaticCallback, symbol.Type ) )
{
master.AddError( node.GetLocation(),
$"A method {symbol.Type.Name} {methodToInvoke}( WrappedPropertyGet ) is required on {typeToInvokeOn?.Name}." );
return false;
}
var usesBackingField = false;
var accessors = new List();
var set = node.AccessorList?.Accessors.FirstOrDefault( a => a.Kind() == SyntaxKind.SetAccessorDeclaration );
if ( set is not null && (set.Body is null && set.ExpressionBody is null) )
{
var defaultStatement = ParseStatement( $"{node.BackingFieldName()} = value;" );
usesBackingField = true;
set = AccessorDeclaration( SyntaxKind.SetAccessorDeclaration, Block( defaultStatement ) );
}
if ( set is not null )
{
accessors.Add( set );
}
{
var existingGetter = node.AccessorList?.Accessors.FirstOrDefault( a => a.Kind() == SyntaxKind.GetAccessorDeclaration );
var defaultStatement = string.Empty;
var defaultValue = $"value";
if ( existingGetter is not null && (existingGetter.Body is not null || existingGetter.ExpressionBody is not null) )
{
if ( existingGetter.Body is not null )
{
defaultStatement = $"var getValue = () => {existingGetter.Body.ToString()};";
defaultValue = $"getValue()";
}
else
{
defaultValue = $"{existingGetter.ExpressionBody?.Expression.ToString()}";
}
}
else
{
defaultStatement = $"var value = {node.BackingFieldName()};";
usesBackingField = true;
}
var statements = new List();
if ( !string.IsNullOrEmpty( defaultStatement ) )
{
statements.Add( ParseStatement( defaultStatement ) );
}
var memberIdentity = $"{symbol.ContainingType.GetFullMetadataName().Replace( "global::", "" )}.{symbol.Name}";
var parameterStruct = ParseStatement(
$"new global::Sandbox.WrappedPropertyGet<{propertyType}> {{" +
$"Value = {defaultValue}," +
$"Object = {(symbol.IsStatic ? "null" : "this")}," +
$"IsStatic = {(symbol.IsStatic ? "true" : "false")}," +
$"TypeName = {symbol.ContainingType.FullName().Replace( "global::", "" ).QuoteSafe()}," +
$"PropertyName = {symbol.Name.QuoteSafe()}," +
$"MemberIdent = {memberIdentity.FastHash()}," +
$"Attributes = __{symbol.Name}__Attrs" +
$"}}" );
statements.Add(
ParseStatement( $"return ({propertyType}){callbackName}( {parameterStruct} );" ) );
var get = AccessorDeclaration( SyntaxKind.GetAccessorDeclaration, Block( statements ) );
if ( existingGetter is not null )
get = get.WithModifiers( existingGetter.Modifiers );
accessors.Add( get );
node = node.WithAccessorList( AccessorList( List( accessors ) ) )
.WithInitializer( null )
.WithSemicolonToken( Token( SyntaxKind.None ) )
.NormalizeWhitespace();
}
return usesBackingField;
}
private static readonly Dictionary TypeAliases = new()
{
["object"] = "System.Object",
["string"] = "System.String",
["bool"] = "System.Boolean",
["byte"] = "System.Byte",
["sbyte"] = "System.SByte",
["short"] = "System.Int16",
["ushort"] = "System.UInt16",
["int"] = "System.Int32",
["uint"] = "System.UInt32",
["long"] = "System.Int64",
["ulong"] = "System.UInt64",
["float"] = "System.Single",
["double"] = "System.Double",
["decimal"] = "System.Decimal",
["char"] = "System.Char"
};
private static string SanitizeTypeName( ITypeSymbol type, bool fullName = false )
{
if ( type is IArrayTypeSymbol a ) return $"{SanitizeTypeName( a.ElementType )}[]";
if ( !fullName )
{
return TypeAliases.TryGetValue( type.Name, out var alias ) ? alias : type.Name;
}
return type.FullName()
.Replace( "global::", "" )
.Split( '<' )
.FirstOrDefault();
}
private static bool HandleWrapCall( AttributeData attribute, Flags type, string callbackName, ref MethodDeclarationSyntax node, IMethodSymbol symbol, Worker master )
{
if ( node.Body == null && node.ExpressionBody == null ) return false;
var parameterCount = symbol.Parameters.Count();
var parameterList = string.Join( ", ", symbol.Parameters.Select( s => s.Name ) );
if ( symbol.IsStatic && !type.Contains( Flags.Static ) )
return false;
if ( !symbol.IsStatic && !type.Contains( Flags.Instance ) )
return false;
var typeToInvokeOn = symbol.ContainingType;
var methodToInvoke = callbackName;
var splitCallbackName = callbackName.Split( '.' );
var isStaticCallback = false;
if ( splitCallbackName.Length > 1 )
{
isStaticCallback = true;
methodToInvoke = splitCallbackName[splitCallbackName.Length - 1];
var typeToLookFor = string.Join( ".", splitCallbackName.Take( splitCallbackName.Length - 1 ) );
typeToInvokeOn = master.GetOrCreateTypeByMetadataName( typeToLookFor );
if ( typeToInvokeOn is null )
{
master.AddError( node.GetLocation(),
$"Unable to find {typeToLookFor} required for {attribute.AttributeClass?.Name}. Ensure that a fully qualified callback name is used." );
return false;
}
}
var success = false;
if ( typeToInvokeOn is not null )
{
success = ValidateMethodCallback( symbol.ContainingType, typeToInvokeOn, methodToInvoke,
isStaticCallback, !symbol.ReturnsVoid ? symbol.ReturnType : null, parameterCount );
}
if ( !success )
{
var returnType = symbol.ReturnsVoid ? string.Empty : $"{symbol.ReturnType.Name} ";
var paramsString = string.Join( ", ", Enumerable.Repeat( "Object", parameterCount ) );
if ( symbol.ReturnsVoid )
{
master.AddError( node.GetLocation(),
parameterCount > 0
? $"A method {returnType}{methodToInvoke}( WrappedMethod, {paramsString} ) is required on {typeToInvokeOn?.Name}."
: $"A method {returnType}{methodToInvoke}( WrappedMethod ) is required on {typeToInvokeOn?.Name}." );
}
else
{
master.AddError( node.GetLocation(),
parameterCount > 0
? $"A method {returnType}{methodToInvoke}( WrappedMethod<{symbol.ReturnType.Name}>, {paramsString} ) is required on {typeToInvokeOn?.Name}."
: $"A method {returnType}{methodToInvoke}( WrappedMethod<{symbol.ReturnType.Name}> ) is required on {typeToInvokeOn?.Name}." );
}
return false;
}
var parameterStructGenericType = string.Empty;
if ( !symbol.ReturnsVoid )
parameterStructGenericType = $"<{symbol.ReturnType.FullName()}>";
var resumeString = "{}";
if ( node.Body is not null )
{
if ( node.Body.Statements.Any() )
resumeString = $"{node.Body.ToFullString()}";
}
else if ( node.ExpressionBody is not null )
resumeString = node.ExpressionBody.Expression.ToFullString();
string resumeExpression;
if ( symbol.IsAsync )
resumeExpression = $"async () => {resumeString}";
else
resumeExpression = $"() => {resumeString}";
var methodIdentity = GetUniqueMethodIdentity( symbol );
var parameterStruct = ParseStatement(
$"new global::Sandbox.WrappedMethod{parameterStructGenericType} {{" +
$"Resume = {resumeExpression}," +
$"Object = {(symbol.IsStatic ? "null" : "this")}," +
$"MethodIdentity = {methodIdentity}," +
$"MethodName = {symbol.Name.QuoteSafe()}," +
$"TypeName = {symbol.ContainingType.FullName().Replace( "global::", "" ).QuoteSafe()}," +
$"IsStatic = {(symbol.IsStatic ? "true" : "false")}," +
$"Attributes = __{MakeMethodIdentitySafe( methodIdentity )}__Attrs" +
$"}}" );
var fullReturnType = symbol.ReturnType.FullName();
var isGenericTaskType = fullReturnType.StartsWith( "global::System.Threading.Tasks.Task<" );
var isTaskType = fullReturnType == "global::System.Threading.Tasks.Task";
var callbackCall = parameterCount > 0
? $"{callbackName}( {parameterStruct}, {parameterList} )"
: $"{callbackName}( {parameterStruct} )";
if ( node.ExpressionBody is null )
{
List statements;
if ( symbol.IsAsync )
{
if ( isGenericTaskType )
{
statements = new List
{
ParseStatement( $"return await {callbackCall};" )
};
}
else if ( isTaskType )
{
statements = new List
{
ParseStatement( $"await {callbackCall};" ),
ParseStatement( "return;" )
};
}
else if ( symbol.ReturnsVoid )
{
statements = new List
{
ParseStatement( $"{callbackCall};" )
};
}
else
{
statements = new List
{
ParseStatement( $"return {callbackCall};" )
};
}
}
else
{
var returnPrefix = symbol.ReturnsVoid ? "" : "return ";
statements = new List
{
ParseStatement( $"{returnPrefix}{callbackCall};" )
};
}
var block = Block( statements );
var newBody = block.WithCloseBraceToken(
block.CloseBraceToken.WithTrailingTrivia( SyntaxTriviaList.Empty )
);
node = node.WithBody( newBody );
}
else
{
if ( symbol.IsAsync && isTaskType )
{
var statements = new[]
{
ParseStatement( $"await {callbackCall};" ),
ParseStatement( "return;" )
};
node = node
.WithExpressionBody( null )
.WithSemicolonToken( Token( SyntaxKind.None ) )
.WithBody( Block( statements ) );
}
else
{
var expression = (symbol.IsAsync && isGenericTaskType)
? $"await {callbackCall}"
: callbackCall;
node = node.WithExpressionBody(
node.ExpressionBody.WithExpression( ParseExpression( expression ) )
);
}
}
return true;
}
private static string GetUniqueMethodIdentityString( IMethodSymbol method )
{
// Needs to keep in sync with Sandbox.MethodDescription.GetIdentityHashString()
// TODO: this will have conflicts for generic types with different numbers of type params
var returnTypeName = method.ReturnsVoid ? "Void" : SanitizeTypeName( method.ReturnType );
return $"{returnTypeName}.{SanitizeTypeName( method.ContainingType, true )}.{method.Name}.{string.Join( ",", method.Parameters.Select( p => SanitizeTypeName( p.Type ) ) )}";
}
private static int GetUniqueMethodIdentity( IMethodSymbol method )
{
return GetUniqueMethodIdentityString( method ).FastHash();
}
private static string MakeMethodIdentitySafe( int identity )
{
return identity.ToString().Replace( "-", "m_" );
}
private static IEnumerable FetchValidMethods( INamedTypeSymbol parent, string methodName, bool isStatic = false, bool isRootType = false )
{
var validMethods = parent.GetMembers().OfType()
.Where( s => (!isStatic || s.IsStatic) && s.Name == methodName )
.Where( s => s.DeclaredAccessibility != Accessibility.Private || isRootType );
foreach ( var symbol in validMethods )
{
yield return symbol;
}
// If our target method is static we shouldn't look at base types.
if ( isStatic )
yield break;
if ( parent.BaseType is null )
yield break;
foreach ( var symbol in FetchValidMethods( parent.BaseType, methodName ) )
{
yield return symbol;
}
}
private static bool ValidateMethodCallback( INamedTypeSymbol containingType, INamedTypeSymbol parent, string methodName, bool isStatic, ITypeSymbol returnType, int argCount )
{
var validMethods = FetchValidMethods( parent, methodName, isStatic, SymbolEqualityComparer.Default.Equals( containingType, parent ) );
foreach ( var method in validMethods )
{
var hasObjectParams = method.Parameters.Length > 1 && method.Parameters[1].IsParams && method.Parameters[1].Type.FullName() == "object[]";
if ( !hasObjectParams && method.Parameters.Length != argCount + 1 )
continue;
var firstParameterType = method.Parameters[0].Type;
var firstParameterName = firstParameterType.FullName();
if ( returnType is null )
{
if ( firstParameterName != "global::Sandbox.WrappedMethod" )
continue;
}
else
{
if ( !firstParameterName.StartsWith( "global::Sandbox.WrappedMethod<" ) )
continue;
var namedParam = firstParameterType as INamedTypeSymbol;
var wrappedArg = namedParam?.TypeArguments[0];
if ( wrappedArg is null )
continue;
if ( !SymbolEqualityComparer.Default.Equals( wrappedArg, returnType )
&& !IsTypeCompatible( wrappedArg, returnType ) )
{
continue;
}
var cbReturn = method.ReturnType;
if ( !SymbolEqualityComparer.Default.Equals( cbReturn, returnType )
&& !IsTypeCompatible( cbReturn, returnType )
&& cbReturn is not ITypeParameterSymbol )
{
continue;
}
}
return true;
}
return false;
}
private static bool IsTypeCompatible( ITypeSymbol candidate, ITypeSymbol target )
{
if ( candidate is ITypeParameterSymbol )
return true;
if ( candidate is not INamedTypeSymbol namedCandidate || target is not INamedTypeSymbol namedTarget )
return false;
if ( !SymbolEqualityComparer.Default.Equals( namedCandidate.OriginalDefinition, namedTarget.OriginalDefinition ) )
return false;
var candidateArgs = namedCandidate.TypeArguments;
var targetArgs = namedTarget.TypeArguments;
if ( candidateArgs.Length != targetArgs.Length )
return false;
for ( var i = 0; i < candidateArgs.Length; i++ )
{
var candidateArg = candidateArgs[i];
var targetArg = targetArgs[i];
if ( SymbolEqualityComparer.Default.Equals( candidateArg, targetArg ) )
continue;
if ( candidateArg is ITypeParameterSymbol )
continue;
if ( candidateArg is not INamedTypeSymbol candidateNamedArg || targetArg is not INamedTypeSymbol targetNamedArg )
return false;
if ( !IsTypeCompatible( candidateNamedArg, targetNamedArg ) )
return false;
}
return true;
}
private static bool ValidateSetterCallback( INamedTypeSymbol containingType, INamedTypeSymbol parent, string methodName, bool isStatic, ITypeSymbol propertyType )
{
var validMethods = FetchValidMethods( parent, methodName, isStatic, SymbolEqualityComparer.Default.Equals( containingType, parent ) );
foreach ( var method in validMethods )
{
if ( method.Parameters.Count() != 1 )
continue;
if ( !method.Parameters[0].Type.FullName().StartsWith( "global::Sandbox.WrappedPropertySet<" ) )
continue;
var namedParameterType = method.Parameters[0].Type as INamedTypeSymbol;
if ( !SymbolEqualityComparer.Default.Equals( namedParameterType?.TypeArguments[0], propertyType )
&& namedParameterType?.TypeArguments[0] is not ITypeParameterSymbol )
continue;
return true;
}
return false;
}
private static bool ValidateGetterCallback( INamedTypeSymbol containingType, INamedTypeSymbol parent, string methodName, bool isStatic, ITypeSymbol propertyType )
{
var validMethods = FetchValidMethods( parent, methodName, isStatic, SymbolEqualityComparer.Default.Equals( containingType, parent ) );
foreach ( var method in validMethods )
{
if ( method.Parameters.Count() != 1 )
continue;
if ( !method.Parameters[0].Type.FullName().StartsWith( "global::Sandbox.WrappedPropertyGet<" ) )
continue;
var namedParameterType = method.Parameters[0].Type as INamedTypeSymbol;
if ( !SymbolEqualityComparer.Default.Equals( namedParameterType?.TypeArguments[0], propertyType )
&& namedParameterType?.TypeArguments[0] is not ITypeParameterSymbol )
continue;
return true;
}
return false;
}
private static bool IsCodeGeneratorAttribute( AttributeData attribute )
{
return attribute.AttributeClass.FullName() == "global::Sandbox.CodeGeneratorAttribute";
}
private static IEnumerable GetCodeGeneratorAttributes( AttributeData parent )
{
return parent.AttributeClass?.GetAttributes().Where( IsCodeGeneratorAttribute );
}
}
}