Skip to content

Commit 24388b8

Browse files
authored
Merge pull request #639 from GabrieleMessina/fix-TypeExtensions-ToTypeString
Fix TypeExtensions.ToTypeString method Exception.
2 parents 1537e98 + 551921d commit 24388b8

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

src/CommunityToolkit.Diagnostics/Extensions/TypeExtensions.cs

+28-6
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,18 @@ private static string FormatDisplayString(Type type, int genericTypeOffset, Read
131131
genericTypeDefinition == typeof(ValueTuple<,,,,,,>) ||
132132
genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
133133
{
134-
IEnumerable<string> formattedTypes = FormatDisplayStringForAllTypes(type.GetGenericArguments());
134+
Type[] tupleArguments = type.GetGenericArguments();
135+
136+
// If the tuple is using open generics, format in the form (,,,) to match other generic type
137+
// definitions. Note that it's not possible to have a mix of generic type parameters and
138+
// concrete type arguments, so we just need to check the first one to know what to do here.
139+
if (tupleArguments[0].IsGenericParameter)
140+
{
141+
return $"({new string(',', tupleArguments.Length - 1)})";
142+
}
143+
144+
// If the tuple type is constructed, format it normally in the (T1, T2, ..., TN) format
145+
IEnumerable<string> formattedTypes = FormatDisplayStringForAllTypes(tupleArguments);
135146

136147
return $"({string.Join(", ", formattedTypes)})";
137148
}
@@ -147,10 +158,19 @@ private static string FormatDisplayString(Type type, int genericTypeOffset, Read
147158
int genericArgumentsCount = int.Parse(tokens[1]);
148159
int typeArgumentsOffset = typeArguments.Length - genericTypeOffset - genericArgumentsCount;
149160
Type[] currentTypeArguments = typeArguments.Slice(typeArgumentsOffset, genericArgumentsCount).ToArray();
150-
IEnumerable<string> formattedTypes = FormatDisplayStringForAllTypes(currentTypeArguments);
151161

152-
// Standard generic types are displayed as Foo<T>
153-
displayName = $"{tokens[0]}<{string.Join(", ", formattedTypes)}>";
162+
// Special case generic type parameters (same as with tuples)
163+
if (currentTypeArguments[0].IsGenericParameter)
164+
{
165+
displayName = $"{tokens[0]}<{new string(',', currentTypeArguments.Length - 1)}>";
166+
}
167+
else
168+
{
169+
IEnumerable<string> formattedTypes = FormatDisplayStringForAllTypes(currentTypeArguments);
170+
171+
// Standard generic types are displayed as Foo<T>
172+
displayName = $"{tokens[0]}<{string.Join(", ", formattedTypes)}>";
173+
}
154174

155175
// Track the current offset for the shared generic arguments list
156176
genericTypeOffset += genericArgumentsCount;
@@ -161,8 +181,10 @@ private static string FormatDisplayString(Type type, int genericTypeOffset, Read
161181
displayName = type.Name;
162182
}
163183

164-
// If the type is nested, recursively format the hierarchy as well
165-
if (type.IsNested)
184+
// If the type is nested, recursively format the hierarchy as well, unless the type is a generic type parameter. In that case,
185+
// the declaring type would return the parent class that defined the generic type parameter. However, the current invocation of
186+
// FormatDisplayString has already been invoked recursively while trying to format the parent class, so we need to stop here.
187+
if (type.IsNested && !type.IsGenericParameter)
166188
{
167189
return $"{FormatDisplayString(type.DeclaringType!, genericTypeOffset, typeArguments)}.{displayName}";
168190
}

tests/CommunityToolkit.Diagnostics.UnitTests/Extensions/Test_TypeExtensions.cs

+14
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,26 @@ public void Test_TypeExtensions_GenericTypes(string name, Type type)
4141
Assert.AreEqual(name, type.ToTypeString());
4242
}
4343

44+
[TestMethod]
45+
[DataRow("System.Span<>", typeof(Span<>))]
46+
[DataRow("System.Collections.Generic.List<>", typeof(List<>))]
47+
[DataRow("System.Collections.Generic.Dictionary<,>", typeof(Dictionary<,>))]
48+
[DataRow("(,)", typeof(ValueTuple<,>))]
49+
[DataRow("(,,,,,)", typeof(ValueTuple<,,,,,>))]
50+
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<>.Foo<>", typeof(Animal.Rabbit<>.Foo<>))]
51+
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Llama<,>.Foo<>", typeof(Animal.Llama<,>.Foo<>))]
52+
public void Test_TypeExtensions_OpenGenericTypes(string name, Type type)
53+
{
54+
Assert.AreEqual(name, type.ToTypeString());
55+
}
56+
4457
[TestMethod]
4558
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal", typeof(Animal))]
4659
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Cat", typeof(Animal.Cat))]
4760
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Dog", typeof(Animal.Dog))]
4861
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<int?>", typeof(Animal.Rabbit<int?>))]
4962
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<string>", typeof(Animal.Rabbit<string>))]
63+
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Dog>", typeof(Animal.Rabbit<Animal.Dog>))]
5064
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<int>.Foo", typeof(Animal.Rabbit<int>.Foo))]
5165
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<(string, int)?>.Foo", typeof(Animal.Rabbit<(string, int)?>.Foo))]
5266
[DataRow("CommunityToolkit.Diagnostics.UnitTests.Extensions.Test_TypeExtensions.Animal.Rabbit<int>.Foo<string>", typeof(Animal.Rabbit<int>.Foo<string>))]

0 commit comments

Comments
 (0)