e22dd3bc6a
Previously, we added source generators for invoking/accessing methods, properties and fields in scripts. This freed us from the overhead of reflection. However, the generated code still used our dynamic marshaling functions, which do runtime type checking and box value types. This commit changes the bindings and source generators to include 'static' marshaling. Based on the types known at compile time, now we generate the appropriate marshaling call for each type.
256 lines
9.4 KiB
C#
256 lines
9.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
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);
|
|
|
|
public static bool AreGodotSourceGeneratorsDisabled(this GeneratorExecutionContext context)
|
|
=> context.TryGetGlobalAnalyzerProperty("GodotSourceGenerators", out string? toggle) &&
|
|
toggle != null &&
|
|
toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase);
|
|
|
|
public static bool IsGodotToolsProject(this GeneratorExecutionContext context)
|
|
=> context.TryGetGlobalAnalyzerProperty("IsGodotToolsProject", out string? toggle) &&
|
|
toggle != null &&
|
|
toggle.Equals("true", StringComparison.OrdinalIgnoreCase);
|
|
|
|
public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyName, string typeFullName)
|
|
{
|
|
while (symbol != null)
|
|
{
|
|
if (symbol.ContainingAssembly.Name == assemblyName &&
|
|
symbol.ToString() == typeFullName)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
symbol = symbol.BaseType;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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
|
|
|| !classTypeSymbol.BaseType.InheritsFrom("GodotSharp", GodotClasses.Object))
|
|
{
|
|
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!);
|
|
}
|
|
}
|
|
|
|
public static bool IsNested(this TypeDeclarationSyntax cds)
|
|
=> cds.Parent is TypeDeclarationSyntax;
|
|
|
|
public static bool IsPartial(this TypeDeclarationSyntax cds)
|
|
=> cds.Modifiers.Any(SyntaxKind.PartialKeyword);
|
|
|
|
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"
|
|
};
|
|
}
|
|
|
|
private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } =
|
|
SymbolDisplayFormat.FullyQualifiedFormat
|
|
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
|
|
|
|
public static string FullQualifiedName(this ITypeSymbol symbol)
|
|
=> symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal);
|
|
|
|
public static string NameWithTypeParameters(this INamedTypeSymbol symbol)
|
|
{
|
|
return symbol.IsGenericType ?
|
|
string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") :
|
|
symbol.Name;
|
|
}
|
|
|
|
public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol)
|
|
=> namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal);
|
|
|
|
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;
|
|
|
|
public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
|
|
=> symbol.ToString() == GodotClasses.GodotClassNameAttr;
|
|
|
|
public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
|
|
=> symbol.ToString() == GodotClasses.SystemFlagsAttr;
|
|
|
|
public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature(
|
|
this IEnumerable<IMethodSymbol> methods,
|
|
MarshalUtils.TypeCache typeCache
|
|
)
|
|
{
|
|
foreach (var method in methods)
|
|
{
|
|
if (method.IsGenericMethod)
|
|
continue;
|
|
|
|
var retSymbol = method.ReturnType;
|
|
var retType = method.ReturnsVoid ?
|
|
null :
|
|
MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache);
|
|
|
|
if (retType == null && !method.ReturnsVoid)
|
|
continue;
|
|
|
|
var parameters = method.Parameters;
|
|
|
|
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();
|
|
|
|
// If any parameter type was incompatible, it was discarded so the length won't match
|
|
if (parameters.Length > paramTypes.Length)
|
|
continue;
|
|
|
|
yield return new GodotMethodData(method, paramTypes, parameters
|
|
.Select(p => p.Type).ToImmutableArray(), retType, retSymbol);
|
|
}
|
|
}
|
|
|
|
public static IEnumerable<GodotPropertyData> WhereIsGodotCompatibleType(
|
|
this IEnumerable<IPropertySymbol> properties,
|
|
MarshalUtils.TypeCache typeCache
|
|
)
|
|
{
|
|
foreach (var property in properties)
|
|
{
|
|
// Ignore properties without a getter. Godot properties must be readable.
|
|
if (property.IsWriteOnly)
|
|
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)
|
|
{
|
|
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
|
|
|
|
if (marshalType == null)
|
|
continue;
|
|
|
|
yield return new GodotFieldData(field, marshalType.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|