2021-12-28 23:25:16 +01:00
using System ;
2021-03-06 00:12:42 +01:00
using System.Collections.Generic ;
2022-02-27 21:57:30 +01:00
using System.Collections.Immutable ;
2021-03-06 00:12:42 +01:00
using System.Linq ;
using Microsoft.CodeAnalysis ;
using Microsoft.CodeAnalysis.CSharp ;
using Microsoft.CodeAnalysis.CSharp.Syntax ;
namespace Godot.SourceGenerators
{
static class ExtensionMethods
{
public static bool TryGetGlobalAnalyzerProperty (
this GeneratorExecutionContext context , string property , out string? value
) = > context . AnalyzerConfigOptions . GlobalOptions
. TryGetValue ( "build_property." + property , out value ) ;
2021-12-28 23:25:16 +01:00
public static bool AreGodotSourceGeneratorsDisabled ( this GeneratorExecutionContext context )
= > context . TryGetGlobalAnalyzerProperty ( "GodotSourceGenerators" , out string? toggle ) & &
toggle ! = null & &
toggle . Equals ( "disabled" , StringComparison . OrdinalIgnoreCase ) ;
2021-12-28 23:25:16 +01:00
public static bool IsGodotToolsProject ( this GeneratorExecutionContext context )
= > context . TryGetGlobalAnalyzerProperty ( "IsGodotToolsProject" , out string? toggle ) & &
toggle ! = null & &
toggle . Equals ( "true" , StringComparison . OrdinalIgnoreCase ) ;
2022-02-27 21:57:30 +01:00
public static bool InheritsFrom ( this INamedTypeSymbol ? symbol , string assemblyName , string typeFullName )
2021-03-06 00:12:42 +01:00
{
2022-02-27 21:57:30 +01:00
while ( symbol ! = null )
2021-03-06 00:12:42 +01:00
{
2022-02-27 21:57:30 +01:00
if ( symbol . ContainingAssembly . Name = = assemblyName & &
symbol . ToString ( ) = = typeFullName )
2021-03-06 00:12:42 +01:00
{
return true ;
}
2022-02-27 21:57:30 +01:00
symbol = symbol . BaseType ;
2021-03-06 00:12:42 +01:00
}
return false ;
}
2022-02-27 21:57:30 +01:00
public static INamedTypeSymbol ? GetGodotScriptNativeClass ( this INamedTypeSymbol classTypeSymbol )
{
var symbol = classTypeSymbol ;
while ( symbol ! = null )
{
if ( symbol . ContainingAssembly . Name = = "GodotSharp" )
return symbol ;
symbol = symbol . BaseType ;
}
return null ;
}
public static string? GetGodotScriptNativeClassName ( this INamedTypeSymbol classTypeSymbol )
{
var nativeType = classTypeSymbol . GetGodotScriptNativeClass ( ) ;
if ( nativeType = = null )
return null ;
var godotClassNameAttr = nativeType . GetAttributes ( )
. FirstOrDefault ( a = > a . AttributeClass ? . IsGodotClassNameAttribute ( ) ? ? false ) ;
string? godotClassName = null ;
if ( godotClassNameAttr is { ConstructorArguments : { Length : > 0 } } )
godotClassName = godotClassNameAttr . ConstructorArguments [ 0 ] . Value ? . ToString ( ) ;
return godotClassName ? ? nativeType . Name ;
}
2021-03-06 00:12:42 +01:00
private static bool IsGodotScriptClass (
this ClassDeclarationSyntax cds , Compilation compilation ,
out INamedTypeSymbol ? symbol
)
{
var sm = compilation . GetSemanticModel ( cds . SyntaxTree ) ;
var classTypeSymbol = sm . GetDeclaredSymbol ( cds ) ;
if ( classTypeSymbol ? . BaseType = = null
2022-02-27 21:57:30 +01:00
| | ! classTypeSymbol . BaseType . InheritsFrom ( "GodotSharp" , GodotClasses . Object ) )
2021-03-06 00:12:42 +01:00
{
symbol = null ;
return false ;
}
symbol = classTypeSymbol ;
return true ;
}
public static IEnumerable < ( ClassDeclarationSyntax cds , INamedTypeSymbol symbol ) > SelectGodotScriptClasses (
this IEnumerable < ClassDeclarationSyntax > source ,
Compilation compilation
)
{
foreach ( var cds in source )
{
if ( cds . IsGodotScriptClass ( compilation , out var symbol ) )
yield return ( cds , symbol ! ) ;
}
}
2021-12-28 23:25:16 +01:00
public static bool IsNested ( this TypeDeclarationSyntax cds )
= > cds . Parent is TypeDeclarationSyntax ;
public static bool IsPartial ( this TypeDeclarationSyntax cds )
2021-03-06 00:12:42 +01:00
= > cds . Modifiers . Any ( SyntaxKind . PartialKeyword ) ;
2021-12-28 23:25:16 +01:00
public static bool AreAllOuterTypesPartial (
this TypeDeclarationSyntax cds ,
out TypeDeclarationSyntax ? typeMissingPartial
)
{
SyntaxNode ? outerSyntaxNode = cds . Parent ;
while ( outerSyntaxNode is TypeDeclarationSyntax outerTypeDeclSyntax )
{
if ( ! outerTypeDeclSyntax . IsPartial ( ) )
{
typeMissingPartial = outerTypeDeclSyntax ;
return false ;
}
outerSyntaxNode = outerSyntaxNode . Parent ;
}
typeMissingPartial = null ;
return true ;
}
public static string GetDeclarationKeyword ( this INamedTypeSymbol namedTypeSymbol )
{
string? keyword = namedTypeSymbol . DeclaringSyntaxReferences
. OfType < TypeDeclarationSyntax > ( ) . FirstOrDefault ( ) ?
. Keyword . Text ;
return keyword ? ? namedTypeSymbol . TypeKind switch
{
TypeKind . Interface = > "interface" ,
TypeKind . Struct = > "struct" ,
_ = > "class"
} ;
}
2021-03-06 00:12:42 +01:00
private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get ; } =
SymbolDisplayFormat . FullyQualifiedFormat
. WithGlobalNamespaceStyle ( SymbolDisplayGlobalNamespaceStyle . Omitted ) ;
2021-12-28 23:25:16 +01:00
public static string FullQualifiedName ( this ITypeSymbol symbol )
2021-03-06 00:12:42 +01:00
= > symbol . ToDisplayString ( NullableFlowState . NotNull , FullyQualifiedFormatOmitGlobal ) ;
2021-03-13 01:04:55 +01:00
2022-02-27 21:57:30 +01:00
public static string NameWithTypeParameters ( this INamedTypeSymbol symbol )
{
return symbol . IsGenericType ?
string . Concat ( symbol . Name , "<" , string . Join ( ", " , symbol . TypeParameters ) , ">" ) :
symbol . Name ;
}
2021-03-13 01:04:55 +01:00
public static string FullQualifiedName ( this INamespaceSymbol namespaceSymbol )
= > namespaceSymbol . ToDisplayString ( FullyQualifiedFormatOmitGlobal ) ;
2022-02-27 21:57:30 +01:00
public static string SanitizeQualifiedNameForUniqueHint ( this string qualifiedName )
= > qualifiedName
// AddSource() doesn't support angle brackets
. Replace ( "<" , "(Of " )
. Replace ( ">" , ")" ) ;
public static bool IsGodotExportAttribute ( this INamedTypeSymbol symbol )
= > symbol . ToString ( ) = = GodotClasses . ExportAttr ;
2022-07-28 17:41:47 +02:00
public static bool IsGodotSignalAttribute ( this INamedTypeSymbol symbol )
= > symbol . ToString ( ) = = GodotClasses . SignalAttr ;
2022-02-27 21:57:30 +01:00
public static bool IsGodotClassNameAttribute ( this INamedTypeSymbol symbol )
= > symbol . ToString ( ) = = GodotClasses . GodotClassNameAttr ;
public static bool IsSystemFlagsAttribute ( this INamedTypeSymbol symbol )
= > symbol . ToString ( ) = = GodotClasses . SystemFlagsAttr ;
2022-07-28 17:41:47 +02:00
public static GodotMethodData ? HasGodotCompatibleSignature (
this IMethodSymbol method ,
2022-02-27 21:57:30 +01:00
MarshalUtils . TypeCache typeCache
)
{
2022-07-28 17:41:47 +02:00
if ( method . IsGenericMethod )
return null ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
var retSymbol = method . ReturnType ;
var retType = method . ReturnsVoid ?
null :
MarshalUtils . ConvertManagedTypeToMarshalType ( method . ReturnType , typeCache ) ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
if ( retType = = null & & ! method . ReturnsVoid )
return null ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
var parameters = method . Parameters ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
var paramTypes = parameters
// Currently we don't support `ref`, `out`, `in`, `ref readonly` parameters (and we never may)
. Where ( p = > p . RefKind = = RefKind . None )
// Attempt to determine the variant type
. Select ( p = > MarshalUtils . ConvertManagedTypeToMarshalType ( p . Type , typeCache ) )
// Discard parameter types that couldn't be determined (null entries)
. Where ( t = > t ! = null ) . Cast < MarshalType > ( ) . ToImmutableArray ( ) ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
// If any parameter type was incompatible, it was discarded so the length won't match
if ( parameters . Length > paramTypes . Length )
return null ; // Ignore incompatible method
return new GodotMethodData ( method , paramTypes , parameters
. Select ( p = > p . Type ) . ToImmutableArray ( ) , retType , retSymbol ) ;
}
public static IEnumerable < GodotMethodData > WhereHasGodotCompatibleSignature (
this IEnumerable < IMethodSymbol > methods ,
MarshalUtils . TypeCache typeCache
)
{
foreach ( var method in methods )
{
var methodData = HasGodotCompatibleSignature ( method , typeCache ) ;
2022-02-27 21:57:30 +01:00
2022-07-28 17:41:47 +02:00
if ( methodData ! = null )
yield return methodData . Value ;
2022-02-27 21:57:30 +01:00
}
}
public static IEnumerable < GodotPropertyData > WhereIsGodotCompatibleType (
this IEnumerable < IPropertySymbol > properties ,
MarshalUtils . TypeCache typeCache
)
{
foreach ( var property in properties )
{
2022-05-28 04:56:46 +02:00
// TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
// Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
if ( property . IsWriteOnly | | property . IsReadOnly )
2022-02-27 21:57:30 +01:00
continue ;
var marshalType = MarshalUtils . ConvertManagedTypeToMarshalType ( property . Type , typeCache ) ;
if ( marshalType = = null )
continue ;
yield return new GodotPropertyData ( property , marshalType . Value ) ;
}
}
public static IEnumerable < GodotFieldData > WhereIsGodotCompatibleType (
this IEnumerable < IFieldSymbol > fields ,
MarshalUtils . TypeCache typeCache
)
{
foreach ( var field in fields )
{
2022-05-28 04:56:46 +02:00
// TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
// Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
if ( field . IsReadOnly )
continue ;
2022-02-27 21:57:30 +01:00
var marshalType = MarshalUtils . ConvertManagedTypeToMarshalType ( field . Type , typeCache ) ;
if ( marshalType = = null )
continue ;
yield return new GodotFieldData ( field , marshalType . Value ) ;
}
}
2021-03-06 00:12:42 +01:00
}
}