public void Execute(GeneratorExecutionContext context) { INamedTypeSymbol[] unmanagedCallbacksClasses = context .Compilation.SyntaxTrees .SelectMany(tree => tree.GetRoot().DescendantNodes() .OfType <ClassDeclarationSyntax>() .SelectUnmanagedCallbacksClasses(context.Compilation) // Report and skip non-partial classes .Where(x => { if (x.cds.IsPartial()) { if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) { Common.ReportNonPartialUnmanagedCallbacksOuterClass(context, typeMissingPartial !); return(false); } return(true); } Common.ReportNonPartialUnmanagedCallbacksClass(context, x.cds, x.symbol); return(false); }) .Select(x => x.symbol) ) .Distinct <INamedTypeSymbol>(SymbolEqualityComparer.Default) .ToArray(); foreach (var symbol in unmanagedCallbacksClasses) { var attr = symbol.GetGenerateUnmanagedCallbacksAttribute(); if (attr == null || attr.ConstructorArguments.Length != 1) { // TODO: Report error or throw exception, this is an invalid case and should never be reached System.Diagnostics.Debug.Fail("FAILED!"); continue; } var funcStructType = (INamedTypeSymbol?)attr.ConstructorArguments[0].Value; if (funcStructType == null) { // TODO: Report error or throw exception, this is an invalid case and should never be reached System.Diagnostics.Debug.Fail("FAILED!"); continue; } var data = new CallbacksData(symbol, funcStructType); GenerateInteropMethodImplementations(context, data); GenerateUnmanagedCallbacksStruct(context, data); } }
private void GenerateInteropMethodImplementations(GeneratorExecutionContext context, CallbacksData data) { var symbol = data.NativeTypeSymbol; INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? namespaceSymbol.FullQualifiedName() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; var source = new StringBuilder(); var methodSource = new StringBuilder(); var methodCallArguments = new StringBuilder(); var methodSourceAfterCall = new StringBuilder(); source.Append( @"using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Godot.Bridge; using Godot.NativeInterop; #pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores "); if (hasNamespace) { source.Append("namespace "); source.Append(classNs); source.Append("\n{\n"); } if (isInnerClass) { var containingType = symbol.ContainingType; while (containingType != null) { source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); containingType = containingType.ContainingType; } } source.Append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); source.Append($"unsafe partial class {symbol.Name}\n"); source.Append("{\n"); source.Append($" private static {data.FuncStructSymbol.FullQualifiedName()} _unmanagedCallbacks;\n\n"); foreach (var callback in data.Methods) { methodSource.Clear(); methodCallArguments.Clear(); methodSourceAfterCall.Clear(); source.Append(" [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]\n"); source.Append($" {SyntaxFacts.GetText(callback.DeclaredAccessibility)} "); if (callback.IsStatic) { source.Append("static "); } source.Append("partial "); source.Append(callback.ReturnType.FullQualifiedName()); source.Append(' '); source.Append(callback.Name); source.Append('('); for (int i = 0; i < callback.Parameters.Length; i++) { var parameter = callback.Parameters[i]; source.Append(parameter.ToDisplayString()); source.Append(' '); source.Append(parameter.Name); if (parameter.RefKind == RefKind.Out) { // Only assign default if the parameter won't be passed by-ref or copied later. if (IsGodotInteropStruct(parameter.Type)) { methodSource.Append($" {parameter.Name} = default;\n"); } } if (IsByRefParameter(parameter)) { if (IsGodotInteropStruct(parameter.Type)) { methodSource.Append(" "); AppendCustomUnsafeAsPointer(methodSource, parameter, out string varName); methodCallArguments.Append(varName); } else if (parameter.Type.IsValueType) { methodSource.Append(" "); AppendCopyToStackAndGetPointer(methodSource, parameter, out string varName); methodCallArguments.Append($"&{varName}"); if (parameter.RefKind is RefKind.Out or RefKind.Ref) { methodSourceAfterCall.Append($" {parameter.Name} = {varName};\n"); } } else { // If it's a by-ref param and we can't get the pointer // just pass it by-ref and let it be pinned. AppendRefKind(methodCallArguments, parameter.RefKind) .Append(' ') .Append(parameter.Name); } } else { methodCallArguments.Append(parameter.Name); } if (i < callback.Parameters.Length - 1) { source.Append(", "); methodCallArguments.Append(", "); } } source.Append(")\n"); source.Append(" {\n"); source.Append(methodSource); source.Append(" "); if (!callback.ReturnsVoid) { if (methodSourceAfterCall.Length != 0) { source.Append($"{callback.ReturnType.FullQualifiedName()} ret = "); } else { source.Append("return "); } } source.Append($"_unmanagedCallbacks.{callback.Name}("); source.Append(methodCallArguments); source.Append(");\n"); if (methodSourceAfterCall.Length != 0) { source.Append(methodSourceAfterCall); if (!callback.ReturnsVoid) { source.Append(" return ret;\n"); } } source.Append(" }\n\n"); } source.Append("}\n"); if (isInnerClass) { var containingType = symbol.ContainingType; while (containingType != null) { source.Append("}\n"); // outer class containingType = containingType.ContainingType; } } if (hasNamespace) { source.Append("\n}"); } source.Append("\n\n#pragma warning restore CA1707\n"); context.AddSource($"{data.NativeTypeSymbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", SourceText.From(source.ToString(), Encoding.UTF8)); }
private void GenerateUnmanagedCallbacksStruct(GeneratorExecutionContext context, CallbacksData data) { var symbol = data.FuncStructSymbol; INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? namespaceSymbol.FullQualifiedName() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; var source = new StringBuilder(); source.Append( @"using System.Runtime.InteropServices; using Godot.NativeInterop; #pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores "); if (hasNamespace) { source.Append("namespace "); source.Append(classNs); source.Append("\n{\n"); } if (isInnerClass) { var containingType = symbol.ContainingType; while (containingType != null) { source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); containingType = containingType.ContainingType; } } source.Append("[StructLayout(LayoutKind.Sequential)]\n"); source.Append($"unsafe partial struct {symbol.Name}\n{{\n"); foreach (var callback in data.Methods) { source.Append(" "); source.Append(callback.DeclaredAccessibility == Accessibility.Public ? "public " : "internal "); source.Append("delegate* unmanaged<"); foreach (var parameter in callback.Parameters) { if (IsByRefParameter(parameter)) { if (IsGodotInteropStruct(parameter.Type) || parameter.Type.IsValueType) { AppendPointerType(source, parameter.Type); } else { // If it's a by-ref param and we can't get the pointer // just pass it by-ref and let it be pinned. AppendRefKind(source, parameter.RefKind) .Append(' ') .Append(parameter.Type.FullQualifiedName()); } } else { source.Append(parameter.Type.FullQualifiedName()); } source.Append(", "); } source.Append(callback.ReturnType.FullQualifiedName()); source.Append($"> {callback.Name};\n"); } source.Append("}\n"); if (isInnerClass) { var containingType = symbol.ContainingType; while (containingType != null) { source.Append("}\n"); // outer class containingType = containingType.ContainingType; } } if (hasNamespace) { source.Append("}\n"); } source.Append("\n#pragma warning restore CA1707\n"); context.AddSource($"{symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", SourceText.From(source.ToString(), Encoding.UTF8)); }