@@ -131,7 +131,18 @@ private static string FormatDisplayString(Type type, int genericTypeOffset, Read
131
131
genericTypeDefinition == typeof ( ValueTuple < , , , , , , > ) ||
132
132
genericTypeDefinition == typeof ( ValueTuple < , , , , , , , > ) )
133
133
{
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 ) ;
135
146
136
147
return $ "({ string . Join ( ", " , formattedTypes ) } )";
137
148
}
@@ -147,10 +158,19 @@ private static string FormatDisplayString(Type type, int genericTypeOffset, Read
147
158
int genericArgumentsCount = int . Parse ( tokens [ 1 ] ) ;
148
159
int typeArgumentsOffset = typeArguments . Length - genericTypeOffset - genericArgumentsCount ;
149
160
Type [ ] currentTypeArguments = typeArguments . Slice ( typeArgumentsOffset , genericArgumentsCount ) . ToArray ( ) ;
150
- IEnumerable < string > formattedTypes = FormatDisplayStringForAllTypes ( currentTypeArguments ) ;
151
161
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
+ }
154
174
155
175
// Track the current offset for the shared generic arguments list
156
176
genericTypeOffset += genericArgumentsCount ;
@@ -161,8 +181,10 @@ private static string FormatDisplayString(Type type, int genericTypeOffset, Read
161
181
displayName = type . Name ;
162
182
}
163
183
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 )
166
188
{
167
189
return $ "{ FormatDisplayString ( type . DeclaringType ! , genericTypeOffset , typeArguments ) } .{ displayName } ";
168
190
}
0 commit comments