Skip to content

Commit 6bea41d

Browse files
committed
Merge pull request #88363 from Delsin-Yu/master
C#: Implement proper generic type name printing for Godot Editor
2 parents 4254946 + b5cd06b commit 6bea41d

File tree

2 files changed

+181
-28
lines changed

2 files changed

+181
-28
lines changed

modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs

+2-28
Original file line numberDiff line numberDiff line change
@@ -660,15 +660,7 @@ private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_
660660
{
661661
Type native = GodotObject.InternalGetClassNativeBase(scriptType);
662662

663-
string typeName = scriptType.Name;
664-
if (scriptType.IsGenericType)
665-
{
666-
var sb = new StringBuilder();
667-
AppendTypeName(sb, scriptType);
668-
typeName = sb.ToString();
669-
}
670-
671-
godot_string className = Marshaling.ConvertStringToNative(typeName);
663+
godot_string className = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(scriptType));
672664

673665
bool isTool = scriptType.IsDefined(typeof(ToolAttribute), inherit: false);
674666

@@ -701,24 +693,6 @@ private static unsafe void GetScriptTypeInfo(Type scriptType, godot_csharp_type_
701693
outTypeInfo->IsGenericTypeDefinition = scriptType.IsGenericTypeDefinition.ToGodotBool();
702694
outTypeInfo->IsConstructedGenericType = scriptType.IsConstructedGenericType.ToGodotBool();
703695

704-
static void AppendTypeName(StringBuilder sb, Type type)
705-
{
706-
sb.Append(type.Name);
707-
if (type.IsGenericType)
708-
{
709-
sb.Append('<');
710-
for (int i = 0; i < type.GenericTypeArguments.Length; i++)
711-
{
712-
Type typeArg = type.GenericTypeArguments[i];
713-
AppendTypeName(sb, typeArg);
714-
if (i != type.GenericTypeArguments.Length - 1)
715-
{
716-
sb.Append(", ");
717-
}
718-
}
719-
sb.Append('>');
720-
}
721-
}
722696
}
723697

724698
[UnmanagedCallersOnly]
@@ -1032,7 +1006,7 @@ private static unsafe void GetPropertyInfoListForType(Type type, IntPtr scriptPt
10321006
interopProperties[i] = interopProperty;
10331007
}
10341008

1035-
using godot_string currentClassName = Marshaling.ConvertStringToNative(type.Name);
1009+
using godot_string currentClassName = Marshaling.ConvertStringToNative(ReflectionUtils.ConstructTypeName(type));
10361010

10371011
addPropInfoFunc(scriptPtr, &currentClassName, interopProperties, length);
10381012

Original file line numberDiff line numberDiff line change
@@ -1,16 +1,195 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
24
using System.Linq;
5+
using System.Text;
36

47
#nullable enable
58

69
namespace Godot;
710

811
internal class ReflectionUtils
912
{
13+
private static readonly HashSet<Type>? _tupleTypeSet;
14+
private static readonly Dictionary<Type, string>? _builtinTypeNameDictionary;
15+
private static readonly bool _isEditorHintCached;
16+
17+
static ReflectionUtils()
18+
{
19+
_isEditorHintCached = Engine.IsEditorHint();
20+
if (!_isEditorHintCached)
21+
{
22+
return;
23+
}
24+
25+
_tupleTypeSet = new HashSet<Type>
26+
{
27+
// ValueTuple with only one element should be treated as normal generic type.
28+
//typeof(ValueTuple<>),
29+
typeof(ValueTuple<,>),
30+
typeof(ValueTuple<,,>),
31+
typeof(ValueTuple<,,,>),
32+
typeof(ValueTuple<,,,,>),
33+
typeof(ValueTuple<,,,,,>),
34+
typeof(ValueTuple<,,,,,,>),
35+
typeof(ValueTuple<,,,,,,,>),
36+
};
37+
38+
_builtinTypeNameDictionary ??= new Dictionary<Type, string>
39+
{
40+
{ typeof(sbyte), "sbyte" },
41+
{ typeof(byte), "byte" },
42+
{ typeof(short), "short" },
43+
{ typeof(ushort), "ushort" },
44+
{ typeof(int), "int" },
45+
{ typeof(uint), "uint" },
46+
{ typeof(long), "long" },
47+
{ typeof(ulong), "ulong" },
48+
{ typeof(nint), "nint" },
49+
{ typeof(nuint), "nuint" },
50+
{ typeof(float), "float" },
51+
{ typeof(double), "double" },
52+
{ typeof(decimal), "decimal" },
53+
{ typeof(bool), "bool" },
54+
{ typeof(char), "char" },
55+
{ typeof(string), "string" },
56+
{ typeof(object), "object" },
57+
};
58+
}
59+
1060
public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName)
1161
{
1262
return AppDomain.CurrentDomain.GetAssemblies()
1363
.FirstOrDefault(a => a.GetName().Name == assemblyName)?
1464
.GetType(typeFullName);
1565
}
66+
67+
public static string ConstructTypeName(Type type)
68+
{
69+
if (!_isEditorHintCached)
70+
{
71+
return type.Name;
72+
}
73+
74+
if (type is { IsArray: false, IsGenericType: false })
75+
{
76+
return GetSimpleTypeName(type);
77+
}
78+
79+
var typeNameBuilder = new StringBuilder();
80+
AppendType(typeNameBuilder, type);
81+
return typeNameBuilder.ToString();
82+
83+
static void AppendType(StringBuilder sb, Type type)
84+
{
85+
if (type.IsArray)
86+
{
87+
AppendArray(sb, type);
88+
}
89+
else if (type.IsGenericType)
90+
{
91+
AppendGeneric(sb, type);
92+
}
93+
else
94+
{
95+
sb.Append(GetSimpleTypeName(type));
96+
}
97+
}
98+
99+
static void AppendArray(StringBuilder sb, Type type)
100+
{
101+
// Append inner most non-array element.
102+
var elementType = type.GetElementType()!;
103+
while (elementType.IsArray)
104+
{
105+
elementType = elementType.GetElementType()!;
106+
}
107+
108+
AppendType(sb, elementType);
109+
// Append brackets.
110+
AppendArrayBrackets(sb, type);
111+
112+
static void AppendArrayBrackets(StringBuilder sb, Type type)
113+
{
114+
while (type != null && type.IsArray)
115+
{
116+
int rank = type.GetArrayRank();
117+
sb.Append('[');
118+
sb.Append(',', rank - 1);
119+
sb.Append(']');
120+
type = type.GetElementType();
121+
}
122+
}
123+
}
124+
125+
static void AppendGeneric(StringBuilder sb, Type type)
126+
{
127+
var genericArgs = type.GenericTypeArguments;
128+
var genericDefinition = type.GetGenericTypeDefinition();
129+
130+
// Nullable<T>
131+
if (genericDefinition == typeof(Nullable<>))
132+
{
133+
AppendType(sb, genericArgs[0]);
134+
sb.Append('?');
135+
return;
136+
}
137+
138+
// ValueTuple
139+
Debug.Assert(_tupleTypeSet != null);
140+
if (_tupleTypeSet.Contains(genericDefinition))
141+
{
142+
sb.Append('(');
143+
while (true)
144+
{
145+
// We assume that ValueTuple has 1~8 elements.
146+
// And the 8th element (TRest) is always another ValueTuple.
147+
148+
// This is a hard coded tuple element length check.
149+
if (genericArgs.Length != 8)
150+
{
151+
AppendParamTypes(sb, genericArgs);
152+
break;
153+
}
154+
else
155+
{
156+
AppendParamTypes(sb, genericArgs.AsSpan(0, 7));
157+
sb.Append(", ");
158+
159+
// TRest should be a ValueTuple!
160+
var nextTuple = genericArgs[7];
161+
162+
genericArgs = nextTuple.GenericTypeArguments;
163+
}
164+
}
165+
sb.Append(')');
166+
return;
167+
}
168+
169+
// Normal generic
170+
var typeName = type.Name.AsSpan();
171+
sb.Append(typeName[..typeName.LastIndexOf('`')]);
172+
sb.Append('<');
173+
AppendParamTypes(sb, genericArgs);
174+
sb.Append('>');
175+
176+
static void AppendParamTypes(StringBuilder sb, ReadOnlySpan<Type> genericArgs)
177+
{
178+
int n = genericArgs.Length - 1;
179+
for (int i = 0; i < n; i += 1)
180+
{
181+
AppendType(sb, genericArgs[i]);
182+
sb.Append(", ");
183+
}
184+
185+
AppendType(sb, genericArgs[n]);
186+
}
187+
}
188+
189+
static string GetSimpleTypeName(Type type)
190+
{
191+
Debug.Assert(_builtinTypeNameDictionary != null);
192+
return _builtinTypeNameDictionary.TryGetValue(type, out string? name) ? name : type.Name;
193+
}
194+
}
16195
}

0 commit comments

Comments
 (0)