@@ -17,6 +17,9 @@ namespace Microsoft.AspNetCore.Analyzers.Infrastructure;
1717
1818internal static class ParsabilityHelper
1919{
20+ private static readonly BoundedCacheWithFactory < ITypeSymbol , ( BindabilityMethod ? , IMethodSymbol ? ) > BindabilityCache = new ( ) ;
21+ private static readonly BoundedCacheWithFactory < ITypeSymbol , ( Parsability , ParsabilityMethod ? ) > ParsabilityCache = new ( ) ;
22+
2023 private static bool IsTypeAlwaysParsable ( ITypeSymbol typeSymbol , WellKnownTypes wellKnownTypes , [ NotNullWhen ( true ) ] out ParsabilityMethod ? parsabilityMethod )
2124 {
2225 // Any enum is valid.
@@ -51,36 +54,41 @@ internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownType
5154
5255 internal static Parsability GetParsability ( ITypeSymbol typeSymbol , WellKnownTypes wellKnownTypes , [ NotNullWhen ( false ) ] out ParsabilityMethod ? parsabilityMethod )
5356 {
54- if ( IsTypeAlwaysParsable ( typeSymbol , wellKnownTypes , out parsabilityMethod ) )
55- {
56- return Parsability . Parsable ;
57- }
57+ var parsability = Parsability . NotParsable ;
58+ parsabilityMethod = null ;
5859
59- // MyType : IParsable<MyType>()
60- if ( IsParsableViaIParsable ( typeSymbol , wellKnownTypes ) )
60+ ( parsability , parsabilityMethod ) = ParsabilityCache . GetOrCreateValue ( typeSymbol , ( typeSymbol ) =>
6161 {
62- parsabilityMethod = ParsabilityMethod . IParsable ;
63- return Parsability . Parsable ;
64- }
62+ if ( IsTypeAlwaysParsable ( typeSymbol , wellKnownTypes , out var parsabilityMethod ) )
63+ {
64+ return ( Parsability . Parsable , parsabilityMethod ) ;
65+ }
6566
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+ }
7072
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 > ( ) ;
7677
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+ }
8282
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 ;
8492 }
8593
8694 private static bool IsTryParse ( IMethodSymbol methodSymbol )
@@ -155,46 +163,54 @@ internal static Bindability GetBindability(ITypeSymbol typeSymbol, WellKnownType
155163 {
156164 bindabilityMethod = null ;
157165 bindMethodSymbol = null ;
166+ IMethodSymbol ? bindAsyncMethod = null ;
158167
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 ) =>
174169 {
175- if ( IsBindAsyncWithParameter ( methodSymbol , typeSymbol , wellKnownTypes ) )
170+ BindabilityMethod ? bindabilityMethod = null ;
171+ IMethodSymbol ? bindMethodSymbol = null ;
172+ if ( IsBindableViaIBindableFromHttpContext ( typeSymbol , wellKnownTypes ) )
176173 {
177- bindabilityMethod = BindabilityMethod . BindAsyncWithParameter ;
178- bindMethodSymbol = methodSymbol ;
179- break ;
174+ return ( BindabilityMethod . IBindableFromHttpContext , null ) ;
180175 }
181- if ( IsBindAsync ( methodSymbol , typeSymbol , wellKnownTypes ) )
176+
177+ var searchCandidates = typeSymbol . GetThisAndBaseTypes ( )
178+ . Concat ( typeSymbol . AllInterfaces ) ;
179+
180+ foreach ( var candidate in searchCandidates )
182181 {
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+ }
185201 }
186- }
202+
203+ return ( bindabilityMethod , bindAsyncMethod ) ;
204+ } ) ;
187205
188206 if ( bindabilityMethod is not null )
189207 {
190208 return Bindability . Bindable ;
191209 }
192210
193211 // 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 )
195213 {
196- var bindAsyncMethod = bindAsyncMethods . Single ( ) ;
197-
198214 if ( bindAsyncMethod . ReturnType is INamedTypeSymbol returnType && ! IsReturningValueTaskOfTOrNullableT ( returnType , typeSymbol , wellKnownTypes ) )
199215 {
200216 return Bindability . InvalidReturnType ;
0 commit comments