C#: Implement proper generic type name printing for Godot Editor
Co-authored-by: Raul Santos <raulsntos@gmail.com>
This commit is contained in:
parent
1d47561319
commit
b5cd06b9ae
|
@ -725,15 +725,7 @@ namespace Godot.Bridge
|
||||||
{
|
{
|
||||||
Type native = GodotObject.InternalGetClassNativeBase(scriptType);
|
Type native = GodotObject.InternalGetClassNativeBase(scriptType);
|
||||||
|
|
||||||
string typeName = scriptType.Name;
|
godot_string className = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(scriptType));
|
||||||
if (scriptType.IsGenericType)
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
AppendTypeName(sb, scriptType);
|
|
||||||
typeName = sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
godot_string className = Marshaling.ConvertStringToNative(typeName);
|
|
||||||
|
|
||||||
bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
|
bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
|
||||||
|
|
||||||
|
@ -766,24 +758,6 @@ namespace Godot.Bridge
|
||||||
outTypeInfo->IsGenericTypeDefinition = scriptType.IsGenericTypeDefinition.ToGodotBool();
|
outTypeInfo->IsGenericTypeDefinition = scriptType.IsGenericTypeDefinition.ToGodotBool();
|
||||||
outTypeInfo->IsConstructedGenericType = scriptType.IsConstructedGenericType.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]
|
[UnmanagedCallersOnly]
|
||||||
|
@ -1097,7 +1071,7 @@ namespace Godot.Bridge
|
||||||
interopProperties[i] = interopProperty;
|
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);
|
addPropInfoFunc(scriptPtr, ¤tClassName, interopProperties, length);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
@ -7,10 +10,186 @@ namespace Godot;
|
||||||
|
|
||||||
internal class ReflectionUtils
|
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)
|
public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName)
|
||||||
{
|
{
|
||||||
return AppDomain.CurrentDomain.GetAssemblies()
|
return AppDomain.CurrentDomain.GetAssemblies()
|
||||||
.FirstOrDefault(a => a.GetName().Name == assemblyName)?
|
.FirstOrDefault(a => a.GetName().Name == assemblyName)?
|
||||||
.GetType(typeFullName);
|
.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 New Issue