Merge pull request #88363 from Delsin-Yu/master

C#: Implement proper generic type name printing for Godot Editor
This commit is contained in:
Rémi Verschelde 2024-09-24 12:56:52 +02:00
commit 6bea41d68f
No known key found for this signature in database
GPG Key ID: C3336907360768E1
2 changed files with 181 additions and 28 deletions

View File

@ -660,15 +660,7 @@ namespace Godot.Bridge
{
Type native = GodotObject.InternalGetClassNativeBase(scriptType);
string typeName = scriptType.Name;
if (scriptType.IsGenericType)
{
var sb = new StringBuilder();
AppendTypeName(sb, scriptType);
typeName = sb.ToString();
}
godot_string className = Marshaling.ConvertStringToNative(typeName);
godot_string className = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(scriptType));
bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
@ -701,24 +693,6 @@ namespace Godot.Bridge
outTypeInfo->IsGenericTypeDefinition = scriptType.IsGenericTypeDefinition.ToGodotBool();
outTypeInfo->IsConstructedGenericType = scriptType.IsConstructedGenericType.ToGodotBool();
static void AppendTypeName(StringBuilder sb, Type type)
{
sb.Append(type.Name);
if (type.IsGenericType)
{
sb.Append('<');
for (int i = 0; i < type.GenericTypeArguments.Length; i++)
{
Type typeArg = type.GenericTypeArguments[i];
AppendTypeName(sb, typeArg);
if (i != type.GenericTypeArguments.Length - 1)
{
sb.Append(", ");
}
}
sb.Append('>');
}
}
}
[UnmanagedCallersOnly]
@ -1032,7 +1006,7 @@ namespace Godot.Bridge
interopProperties[i] = interopProperty;
}
using godot_string currentClassName = Marshaling.ConvertStringToNative(type.Name);
using godot_string currentClassName = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(type));
addPropInfoFunc(scriptPtr, &currentClassName, interopProperties, length);

View File

@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
#nullable enable
@ -7,10 +10,186 @@ namespace Godot;
internal class ReflectionUtils
{
private static readonly HashSet<Type>? _tupleTypeSet;
private static readonly Dictionary<Type, string>? _builtinTypeNameDictionary;
private static readonly bool _isEditorHintCached;
static ReflectionUtils()
{
_isEditorHintCached = Engine.IsEditorHint();
if (!_isEditorHintCached)
{
return;
}
_tupleTypeSet = new HashSet<Type>
{
// ValueTuple with only one element should be treated as normal generic type.
//typeof(ValueTuple<>),
typeof(ValueTuple<,>),
typeof(ValueTuple<,,>),
typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>),
typeof(ValueTuple<,,,,,>),
typeof(ValueTuple<,,,,,,>),
typeof(ValueTuple<,,,,,,,>),
};
_builtinTypeNameDictionary ??= new Dictionary<Type, string>
{
{ typeof(sbyte), "sbyte" },
{ typeof(byte), "byte" },
{ typeof(short), "short" },
{ typeof(ushort), "ushort" },
{ typeof(int), "int" },
{ typeof(uint), "uint" },
{ typeof(long), "long" },
{ typeof(ulong), "ulong" },
{ typeof(nint), "nint" },
{ typeof(nuint), "nuint" },
{ typeof(float), "float" },
{ typeof(double), "double" },
{ typeof(decimal), "decimal" },
{ typeof(bool), "bool" },
{ typeof(char), "char" },
{ typeof(string), "string" },
{ typeof(object), "object" },
};
}
public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName)
{
return AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == assemblyName)?
.GetType(typeFullName);
}
public static string ConstructTypeName(Type type)
{
if (!_isEditorHintCached)
{
return type.Name;
}
if (type is { IsArray: false, IsGenericType: false })
{
return GetSimpleTypeName(type);
}
var typeNameBuilder = new StringBuilder();
AppendType(typeNameBuilder, type);
return typeNameBuilder.ToString();
static void AppendType(StringBuilder sb, Type type)
{
if (type.IsArray)
{
AppendArray(sb, type);
}
else if (type.IsGenericType)
{
AppendGeneric(sb, type);
}
else
{
sb.Append(GetSimpleTypeName(type));
}
}
static void AppendArray(StringBuilder sb, Type type)
{
// Append inner most non-array element.
var elementType = type.GetElementType()!;
while (elementType.IsArray)
{
elementType = elementType.GetElementType()!;
}
AppendType(sb, elementType);
// Append brackets.
AppendArrayBrackets(sb, type);
static void AppendArrayBrackets(StringBuilder sb, Type type)
{
while (type != null && type.IsArray)
{
int rank = type.GetArrayRank();
sb.Append('[');
sb.Append(',', rank - 1);
sb.Append(']');
type = type.GetElementType();
}
}
}
static void AppendGeneric(StringBuilder sb, Type type)
{
var genericArgs = type.GenericTypeArguments;
var genericDefinition = type.GetGenericTypeDefinition();
// Nullable<T>
if (genericDefinition == typeof(Nullable<>))
{
AppendType(sb, genericArgs[0]);
sb.Append('?');
return;
}
// ValueTuple
Debug.Assert(_tupleTypeSet != null);
if (_tupleTypeSet.Contains(genericDefinition))
{
sb.Append('(');
while (true)
{
// We assume that ValueTuple has 1~8 elements.
// And the 8th element (TRest) is always another ValueTuple.
// This is a hard coded tuple element length check.
if (genericArgs.Length != 8)
{
AppendParamTypes(sb, genericArgs);
break;
}
else
{
AppendParamTypes(sb, genericArgs.AsSpan(0, 7));
sb.Append(", ");
// TRest should be a ValueTuple!
var nextTuple = genericArgs[7];
genericArgs = nextTuple.GenericTypeArguments;
}
}
sb.Append(')');
return;
}
// Normal generic
var typeName = type.Name.AsSpan();
sb.Append(typeName[..typeName.LastIndexOf('`')]);
sb.Append('<');
AppendParamTypes(sb, genericArgs);
sb.Append('>');
static void AppendParamTypes(StringBuilder sb, ReadOnlySpan<Type> genericArgs)
{
int n = genericArgs.Length - 1;
for (int i = 0; i < n; i += 1)
{
AppendType(sb, genericArgs[i]);
sb.Append(", ");
}
AppendType(sb, genericArgs[n]);
}
}
static string GetSimpleTypeName(Type type)
{
Debug.Assert(_builtinTypeNameDictionary != null);
return _builtinTypeNameDictionary.TryGetValue(type, out string? name) ? name : type.Name;
}
}
}