@@ -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 }
0 commit comments