@@ -17,6 +17,9 @@ namespace Microsoft.AspNetCore.Analyzers.Infrastructure;
17
17
18
18
internal static class ParsabilityHelper
19
19
{
20
+ private static readonly BoundedCacheWithFactory < ITypeSymbol , ( BindabilityMethod ? , IMethodSymbol ? ) > BindabilityCache = new ( ) ;
21
+ private static readonly BoundedCacheWithFactory < ITypeSymbol , ( Parsability , ParsabilityMethod ? ) > ParsabilityCache = new ( ) ;
22
+
20
23
private static bool IsTypeAlwaysParsable ( ITypeSymbol typeSymbol , WellKnownTypes wellKnownTypes , [ NotNullWhen ( true ) ] out ParsabilityMethod ? parsabilityMethod )
21
24
{
22
25
// Any enum is valid.
@@ -51,36 +54,41 @@ internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownType
51
54
52
55
internal static Parsability GetParsability ( ITypeSymbol typeSymbol , WellKnownTypes wellKnownTypes , [ NotNullWhen ( false ) ] out ParsabilityMethod ? parsabilityMethod )
53
56
{
54
- if ( IsTypeAlwaysParsable ( typeSymbol , wellKnownTypes , out parsabilityMethod ) )
55
- {
56
- return Parsability . Parsable ;
57
- }
57
+ var parsability = Parsability . NotParsable ;
58
+ parsabilityMethod = null ;
58
59
59
- // MyType : IParsable<MyType>()
60
- if ( IsParsableViaIParsable ( typeSymbol , wellKnownTypes ) )
60
+ ( parsability , parsabilityMethod ) = ParsabilityCache . GetOrCreateValue ( typeSymbol , ( typeSymbol ) =>
61
61
{
62
- parsabilityMethod = ParsabilityMethod . IParsable ;
63
- return Parsability . Parsable ;
64
- }
62
+ if ( IsTypeAlwaysParsable ( typeSymbol , wellKnownTypes , out var parsabilityMethod ) )
63
+ {
64
+ return ( Parsability . Parsable , parsabilityMethod ) ;
65
+ }
65
66
66
- // Check if the parameter type has a public static TryParse method.
67
- var tryParseMethods = typeSymbol . GetThisAndBaseTypes ( )
68
- . SelectMany ( t => t . GetMembers ( "TryParse" ) )
69
- . OfType < IMethodSymbol > ( ) ;
67
+ // MyType : IParsable<MyType>()
68
+ if ( IsParsableViaIParsable ( typeSymbol , wellKnownTypes ) )
69
+ {
70
+ return ( Parsability . Parsable , ParsabilityMethod . IParsable ) ;
71
+ }
70
72
71
- if ( tryParseMethods . Any ( m => IsTryParseWithFormat ( m , wellKnownTypes ) ) )
72
- {
73
- parsabilityMethod = ParsabilityMethod . TryParseWithFormatProvider ;
74
- return Parsability . Parsable ;
75
- }
73
+ // Check if the parameter type has a public static TryParse method.
74
+ var tryParseMethods = typeSymbol . GetThisAndBaseTypes ( )
75
+ . SelectMany ( t => t . GetMembers ( "TryParse" ) )
76
+ . OfType < IMethodSymbol > ( ) ;
76
77
77
- if ( tryParseMethods . Any ( IsTryParse ) )
78
- {
79
- parsabilityMethod = ParsabilityMethod . TryParse ;
80
- return Parsability . Parsable ;
81
- }
78
+ if ( tryParseMethods . Any ( m => IsTryParseWithFormat ( m , wellKnownTypes ) ) )
79
+ {
80
+ return ( Parsability . Parsable , ParsabilityMethod . TryParseWithFormatProvider ) ;
81
+ }
82
82
83
- return Parsability . NotParsable ;
83
+ if ( tryParseMethods . Any ( IsTryParse ) )
84
+ {
85
+ return ( Parsability . Parsable , ParsabilityMethod . TryParse ) ;
86
+ }
87
+
88
+ return ( Parsability . NotParsable , null ) ;
89
+ } ) ;
90
+
91
+ return parsability ;
84
92
}
85
93
86
94
private static bool IsTryParse ( IMethodSymbol methodSymbol )
@@ -155,46 +163,54 @@ internal static Bindability GetBindability(ITypeSymbol typeSymbol, WellKnownType
155
163
{
156
164
bindabilityMethod = null ;
157
165
bindMethodSymbol = null ;
166
+ IMethodSymbol ? bindAsyncMethod = null ;
158
167
159
- if ( IsBindableViaIBindableFromHttpContext ( typeSymbol , wellKnownTypes ) )
160
- {
161
- bindabilityMethod = BindabilityMethod . IBindableFromHttpContext ;
162
- return Bindability . Bindable ;
163
- }
164
-
165
- // TODO: Search interfaces too. See MyBindAsyncFromInterfaceRecord test as an example.
166
- // It's easy to find, but we need to flow the interface back to the emitter to call it.
167
- // With parent types, we can continue to pretend we're calling a method directly on the child.
168
- var bindAsyncMethods = typeSymbol . GetThisAndBaseTypes ( )
169
- . Concat ( typeSymbol . AllInterfaces )
170
- . SelectMany ( t => t . GetMembers ( "BindAsync" ) )
171
- . OfType < IMethodSymbol > ( ) ;
172
-
173
- foreach ( var methodSymbol in bindAsyncMethods )
168
+ ( bindabilityMethod , bindMethodSymbol ) = BindabilityCache . GetOrCreateValue ( typeSymbol , ( typeSymbol ) =>
174
169
{
175
- if ( IsBindAsyncWithParameter ( methodSymbol , typeSymbol , wellKnownTypes ) )
170
+ BindabilityMethod ? bindabilityMethod = null ;
171
+ IMethodSymbol ? bindMethodSymbol = null ;
172
+ if ( IsBindableViaIBindableFromHttpContext ( typeSymbol , wellKnownTypes ) )
176
173
{
177
- bindabilityMethod = BindabilityMethod . BindAsyncWithParameter ;
178
- bindMethodSymbol = methodSymbol ;
179
- break ;
174
+ return ( BindabilityMethod . IBindableFromHttpContext , null ) ;
180
175
}
181
- if ( IsBindAsync ( methodSymbol , typeSymbol , wellKnownTypes ) )
176
+
177
+ var searchCandidates = typeSymbol . GetThisAndBaseTypes ( )
178
+ . Concat ( typeSymbol . AllInterfaces ) ;
179
+
180
+ foreach ( var candidate in searchCandidates )
182
181
{
183
- bindabilityMethod = BindabilityMethod . BindAsync ;
184
- bindMethodSymbol = methodSymbol ;
182
+ var baseTypeBindAsyncMethods = candidate . GetMembers ( "BindAsync" ) ;
183
+ foreach ( var methodSymbolCandidate in baseTypeBindAsyncMethods )
184
+ {
185
+ if ( methodSymbolCandidate is IMethodSymbol methodSymbol )
186
+ {
187
+ bindAsyncMethod ??= methodSymbol ;
188
+ if ( IsBindAsyncWithParameter ( methodSymbol , typeSymbol , wellKnownTypes ) )
189
+ {
190
+ bindabilityMethod = BindabilityMethod . BindAsyncWithParameter ;
191
+ bindMethodSymbol = methodSymbol ;
192
+ break ;
193
+ }
194
+ if ( IsBindAsync ( methodSymbol , typeSymbol , wellKnownTypes ) )
195
+ {
196
+ bindabilityMethod = BindabilityMethod . BindAsync ;
197
+ bindMethodSymbol = methodSymbol ;
198
+ }
199
+ }
200
+ }
185
201
}
186
- }
202
+
203
+ return ( bindabilityMethod , bindAsyncMethod ) ;
204
+ } ) ;
187
205
188
206
if ( bindabilityMethod is not null )
189
207
{
190
208
return Bindability . Bindable ;
191
209
}
192
210
193
211
// See if we can give better guidance on why the BindAsync method is no good.
194
- if ( bindAsyncMethods . Count ( ) == 1 )
212
+ if ( bindAsyncMethod is not null )
195
213
{
196
- var bindAsyncMethod = bindAsyncMethods . Single ( ) ;
197
-
198
214
if ( bindAsyncMethod . ReturnType is INamedTypeSymbol returnType && ! IsReturningValueTaskOfTOrNullableT ( returnType , typeSymbol , wellKnownTypes ) )
199
215
{
200
216
return Bindability . InvalidReturnType ;
0 commit comments