Merge pull request #88363 from Delsin-Yu/master
C#: Implement proper generic type name printing for Godot Editor
This commit is contained in:
commit
6bea41d68f
2 changed files with 181 additions and 28 deletions
|
@ -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, ¤tClassName, interopProperties, length);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue