@@ -86,104 +86,123 @@ public static string ToDisplayString(
8686 this IParameterSymbol symbol ,
8787 SymbolDisplayFormat displayFormat ,
8888 List < string > generatedInterfaceNames
89- )
90- {
91- var parameterDisplayString = symbol . ToDisplayString ( displayFormat ) ;
92-
93- var parameterTypeDisplayString = symbol . Type . ToDisplayString (
94- displayFormat ,
95- generatedInterfaceNames
96- ) ;
97-
98- // Replace the type part of the parameter definition - we don't try to generate the whole parameter definition
99- // as it's quite complex - we leave that to Roslyn.
100- return ParameterTypeMatcher . Replace ( parameterDisplayString , parameterTypeDisplayString ) ;
101- }
89+ ) => ToDisplayString ( ( ISymbol ) symbol , displayFormat , generatedInterfaceNames ) ;
10290
103- /// <summary>
104- /// Matches the type part of a parameter definition (Type name[ = defaultValue])
105- /// </summary>
106- private static readonly Regex ParameterTypeMatcher =
107- new ( @"[^\s=]+(?=\s\S+(\s?=\s?\S+)?$)" , RegexOptions . Compiled ) ;
91+ public static string ToDisplayString (
92+ this ITypeSymbol symbol ,
93+ SymbolDisplayFormat displayFormat ,
94+ List < string > generatedInterfaceNames
95+ ) => ToDisplayString ( ( ISymbol ) symbol , displayFormat , generatedInterfaceNames ) ;
10896
10997 /// <summary>
11098 /// Wraps <see cref="ITypeSymbol.ToDisplayString(Microsoft.CodeAnalysis.SymbolDisplayFormat?)" /> with custom resolution for generated types
11199 /// </summary>
112- /// <returns></returns>
113- public static string ToDisplayString (
114- this ITypeSymbol symbol ,
100+ private static string ToDisplayString (
101+ this ISymbol symbol ,
115102 SymbolDisplayFormat displayFormat ,
116103 List < string > generatedInterfaceNames
117104 )
118105 {
119- var builder = new StringBuilder ( ) ;
106+ var displayStringBuilder = new StringBuilder ( ) ;
120107
121- AppendTypeSymbolDisplayString ( symbol , displayFormat , generatedInterfaceNames , builder ) ;
108+ var displayParts = GetDisplayParts ( symbol , displayFormat ) ;
109+
110+ foreach ( var part in displayParts )
111+ {
112+ if ( part . Kind == SymbolDisplayPartKind . ErrorTypeName )
113+ {
114+ var unrecognisedName = part . ToString ( ) ;
115+
116+ var inferredName = ReplaceWithInferredInterfaceName (
117+ unrecognisedName ,
118+ generatedInterfaceNames
119+ ) ;
120+
121+ displayStringBuilder . Append ( inferredName ) ;
122+ }
123+ else
124+ {
125+ displayStringBuilder . Append ( part ) ;
126+ }
127+ }
122128
123- return builder . ToString ( ) ;
129+ return displayStringBuilder . ToString ( ) ;
124130 }
125131
126- private static void AppendTypeSymbolDisplayString (
127- ITypeSymbol typeSymbol ,
128- SymbolDisplayFormat displayFormat ,
129- List < string > generatedInterfaceNames ,
130- StringBuilder builder
132+ /// <summary>
133+ /// The same as <see cref="ISymbol.ToDisplayParts"/> but with adjacent SymbolDisplayParts merged into qualified type references, e.g. [Parent, ., Child] => Parent.Child
134+ /// </summary>
135+ private static IEnumerable < SymbolDisplayPart > GetDisplayParts (
136+ ISymbol symbol ,
137+ SymbolDisplayFormat displayFormat
131138 )
132139 {
133- if ( typeSymbol is not IErrorTypeSymbol errorTypeSymbol )
140+ var cache = new List < SymbolDisplayPart > ( ) ;
141+
142+ foreach ( var part in symbol . ToDisplayParts ( displayFormat ) )
134143 {
135- // This symbol contains no unresolved types. Fall back to the default generation provided by Roslyn
136- builder . Append ( typeSymbol . ToDisplayString ( displayFormat ) ) ;
137- return ;
138- }
144+ if ( cache . Count == 0 )
145+ {
146+ cache . Add ( part ) ;
147+ continue ;
148+ }
139149
140- var symbolName =
141- InferGeneratedInterfaceName ( errorTypeSymbol , generatedInterfaceNames )
142- ?? errorTypeSymbol . Name ;
150+ var previousPart = cache . Last ( ) ;
143151
144- builder . Append ( symbolName ) ;
152+ if (
153+ IsPartQualificationPunctuation ( previousPart )
154+ ^ IsPartQualificationPunctuation ( part )
155+ )
156+ {
157+ cache . Add ( part ) ;
158+ }
159+ else
160+ {
161+ yield return CombineQualifiedTypeParts ( cache ) ;
162+ cache . Clear ( ) ;
163+ cache . Add ( part ) ;
164+ }
165+ }
145166
146- if ( errorTypeSymbol . IsGenericType )
167+ if ( cache . Count > 0 )
147168 {
148- builder . Append ( '<' ) ;
169+ yield return CombineQualifiedTypeParts ( cache ) ;
170+ }
149171
150- bool isFirstTypeArgument = true ;
151- foreach ( var typeArgument in errorTypeSymbol . TypeArguments )
152- {
153- if ( ! isFirstTypeArgument )
154- {
155- builder . Append ( ", " ) ;
156- }
157-
158- AppendTypeSymbolDisplayString (
159- typeArgument ,
160- displayFormat ,
161- generatedInterfaceNames ,
162- builder
172+ static SymbolDisplayPart CombineQualifiedTypeParts (
173+ ICollection < SymbolDisplayPart > qualifiedTypeParts
174+ )
175+ {
176+ var qualifiedType = qualifiedTypeParts . Last ( ) ;
177+
178+ return qualifiedTypeParts . Count == 1
179+ ? qualifiedType
180+ : new SymbolDisplayPart (
181+ qualifiedType . Kind ,
182+ qualifiedType . Symbol ,
183+ string . Join ( "" , qualifiedTypeParts )
163184 ) ;
164-
165- isFirstTypeArgument = false ;
166- }
167-
168- builder . Append ( '>' ) ;
169185 }
186+
187+ static bool IsPartQualificationPunctuation ( SymbolDisplayPart part ) =>
188+ part . ToString ( ) is "." or "::" ;
170189 }
171190
172- private static string ? InferGeneratedInterfaceName (
173- IErrorTypeSymbol unrecognisedSymbol ,
191+ private static string ReplaceWithInferredInterfaceName (
192+ string unrecognisedName ,
174193 List < string > generatedInterfaceNames
175194 )
176195 {
177196 var matches = generatedInterfaceNames
178- . Where ( name => Regex . IsMatch ( name , $ "[.:]{ unrecognisedSymbol . Name } $") )
197+ . Where ( name => Regex . IsMatch ( name , $ "[.:]{ Regex . Escape ( unrecognisedName ) } $") )
179198 . ToList ( ) ;
180199
181200 if ( matches . Count != 1 )
182201 {
183202 // Either there's no match or an ambiguous match - we can't safely infer the interface name.
184203 // This is very much a "best effort" approach - if there are two interfaces with the same name,
185204 // there's no obvious way to work out which one the symbol is referring to.
186- return null ;
205+ return unrecognisedName ;
187206 }
188207
189208 return matches [ 0 ] ;
0 commit comments