mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-30 21:21:10 +00:00 
			
		
		
		
	 6468f9b37c
			
		
	
	
		6468f9b37c
		
			
		
	
	
	
	
		
			
			- MustBeVariant attribute can be used to enforce that generic types must be a marshable from/to Variant. - Also renames all diagnostic ids to be valid unicode identifiers.
		
			
				
	
	
		
			279 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			279 lines
		
	
	
	
		
			11 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 IsGodotSignalAttribute(this INamedTypeSymbol symbol)
 | |
|             => symbol.ToString() == GodotClasses.SignalAttr;
 | |
| 
 | |
|         public static bool IsGodotMustBeVariantAttribute(this INamedTypeSymbol symbol)
 | |
|             => symbol.ToString() == GodotClasses.MustBeVariantAttr;
 | |
| 
 | |
|         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);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |