virtualx-engine/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs

277 lines
10 KiB
C#
Raw Normal View History

using System;
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
2021-03-06 00:12:42 +01:00
using System.Collections.Generic;
using System.Collections.Immutable;
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
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);
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)
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
2021-03-06 00:12:42 +01:00
{
while (symbol != null)
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
2021-03-06 00:12:42 +01:00
{
if (symbol.ContainingAssembly.Name == assemblyName &&
symbol.ToString() == typeFullName)
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
2021-03-06 00:12:42 +01:00
{
return true;
}
symbol = symbol.BaseType;
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
2021-03-06 00:12:42 +01:00
}
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;
}
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
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
|| !classTypeSymbol.BaseType.InheritsFrom("GodotSharp", GodotClasses.Object))
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
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!);
}
}
public static bool IsNested(this TypeDeclarationSyntax cds)
=> cds.Parent is TypeDeclarationSyntax;
public static bool IsPartial(this TypeDeclarationSyntax cds)
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
2021-03-06 00:12:42 +01:00
=> 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"
};
}
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
2021-03-06 00:12:42 +01:00
private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } =
SymbolDisplayFormat.FullyQualifiedFormat
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
public static string FullQualifiedName(this ITypeSymbol symbol)
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
2021-03-06 00:12:42 +01:00
=> 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 IsGodotSignalAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.SignalAttr;
public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.GodotClassNameAttr;
public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.SystemFlagsAttr;
public static GodotMethodData? HasGodotCompatibleSignature(
this IMethodSymbol method,
MarshalUtils.TypeCache typeCache
)
{
if (method.IsGenericMethod)
return null;
var retSymbol = method.ReturnType;
var retType = method.ReturnsVoid ?
null :
MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache);
if (retType == null && !method.ReturnsVoid)
return null;
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)
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);
if (methodData != null)
yield return methodData.Value;
}
}
public static IEnumerable<GodotPropertyData> WhereIsGodotCompatibleType(
this IEnumerable<IPropertySymbol> properties,
MarshalUtils.TypeCache typeCache
)
{
foreach (var property in properties)
{
// 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)
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)
{
// 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;
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
if (marshalType == null)
continue;
yield return new GodotFieldData(field, marshalType.Value);
}
}
Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor.
2021-03-06 00:12:42 +01:00
}
}