Skip to content

Commit b11467e

Browse files
authored
Cache parsable and bindable type info in RDG (#50326)
1 parent 8c7a342 commit b11467e

File tree

1 file changed

+66
-50
lines changed

1 file changed

+66
-50
lines changed

src/Shared/RoslynUtils/ParsabilityHelper.cs

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ namespace Microsoft.AspNetCore.Analyzers.Infrastructure;
1717

1818
internal 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

Comments
 (0)