Skip to content

Cache parsable and bindable type info in RDG #50326

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 24, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 66 additions & 50 deletions src/Shared/RoslynUtils/ParsabilityHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ namespace Microsoft.AspNetCore.Analyzers.Infrastructure;

internal static class ParsabilityHelper
{
private static readonly BoundedCacheWithFactory<ITypeSymbol, (BindabilityMethod?, IMethodSymbol?)> BindabilityCache = new();
private static readonly BoundedCacheWithFactory<ITypeSymbol, (Parsability, ParsabilityMethod?)> ParsabilityCache = new();

private static bool IsTypeAlwaysParsable(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out ParsabilityMethod? parsabilityMethod)
{
// Any enum is valid.
Expand Down Expand Up @@ -51,36 +54,41 @@ internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownType

internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, [NotNullWhen(false)] out ParsabilityMethod? parsabilityMethod)
{
if (IsTypeAlwaysParsable(typeSymbol, wellKnownTypes, out parsabilityMethod))
{
return Parsability.Parsable;
}
var parsability = Parsability.NotParsable;
parsabilityMethod = null;

// MyType : IParsable<MyType>()
if (IsParsableViaIParsable(typeSymbol, wellKnownTypes))
(parsability, parsabilityMethod) = ParsabilityCache.GetOrCreateValue(typeSymbol, (typeSymbol) =>
{
parsabilityMethod = ParsabilityMethod.IParsable;
return Parsability.Parsable;
}
if (IsTypeAlwaysParsable(typeSymbol, wellKnownTypes, out var parsabilityMethod))
{
return (Parsability.Parsable, parsabilityMethod);
}

// Check if the parameter type has a public static TryParse method.
var tryParseMethods = typeSymbol.GetThisAndBaseTypes()
.SelectMany(t => t.GetMembers("TryParse"))
.OfType<IMethodSymbol>();
// MyType : IParsable<MyType>()
if (IsParsableViaIParsable(typeSymbol, wellKnownTypes))
{
return (Parsability.Parsable, ParsabilityMethod.IParsable);
}

if (tryParseMethods.Any(m => IsTryParseWithFormat(m, wellKnownTypes)))
{
parsabilityMethod = ParsabilityMethod.TryParseWithFormatProvider;
return Parsability.Parsable;
}
// Check if the parameter type has a public static TryParse method.
var tryParseMethods = typeSymbol.GetThisAndBaseTypes()
.SelectMany(t => t.GetMembers("TryParse"))
.OfType<IMethodSymbol>();

if (tryParseMethods.Any(IsTryParse))
{
parsabilityMethod = ParsabilityMethod.TryParse;
return Parsability.Parsable;
}
if (tryParseMethods.Any(m => IsTryParseWithFormat(m, wellKnownTypes)))
{
return (Parsability.Parsable, ParsabilityMethod.TryParseWithFormatProvider);
}

return Parsability.NotParsable;
if (tryParseMethods.Any(IsTryParse))
{
return (Parsability.Parsable, ParsabilityMethod.TryParse);
}

return (Parsability.NotParsable, null);
});

return parsability;
}

private static bool IsTryParse(IMethodSymbol methodSymbol)
Expand Down Expand Up @@ -155,46 +163,54 @@ internal static Bindability GetBindability(ITypeSymbol typeSymbol, WellKnownType
{
bindabilityMethod = null;
bindMethodSymbol = null;
IMethodSymbol? bindAsyncMethod = null;

if (IsBindableViaIBindableFromHttpContext(typeSymbol, wellKnownTypes))
{
bindabilityMethod = BindabilityMethod.IBindableFromHttpContext;
return Bindability.Bindable;
}

// TODO: Search interfaces too. See MyBindAsyncFromInterfaceRecord test as an example.
// It's easy to find, but we need to flow the interface back to the emitter to call it.
// With parent types, we can continue to pretend we're calling a method directly on the child.
var bindAsyncMethods = typeSymbol.GetThisAndBaseTypes()
.Concat(typeSymbol.AllInterfaces)
.SelectMany(t => t.GetMembers("BindAsync"))
.OfType<IMethodSymbol>();

foreach (var methodSymbol in bindAsyncMethods)
(bindabilityMethod, bindMethodSymbol) = BindabilityCache.GetOrCreateValue(typeSymbol, (typeSymbol) =>
{
if (IsBindAsyncWithParameter(methodSymbol, typeSymbol, wellKnownTypes))
BindabilityMethod? bindabilityMethod = null;
IMethodSymbol? bindMethodSymbol = null;
if (IsBindableViaIBindableFromHttpContext(typeSymbol, wellKnownTypes))
{
bindabilityMethod = BindabilityMethod.BindAsyncWithParameter;
bindMethodSymbol = methodSymbol;
break;
return (BindabilityMethod.IBindableFromHttpContext, null);
}
if (IsBindAsync(methodSymbol, typeSymbol, wellKnownTypes))

var searchCandidates = typeSymbol.GetThisAndBaseTypes()
.Concat(typeSymbol.AllInterfaces);

foreach (var candidate in searchCandidates)
{
bindabilityMethod = BindabilityMethod.BindAsync;
bindMethodSymbol = methodSymbol;
var baseTypeBindAsyncMethods = candidate.GetMembers("BindAsync");
foreach (var methodSymbolCandidate in baseTypeBindAsyncMethods)
{
if (methodSymbolCandidate is IMethodSymbol methodSymbol)
{
bindAsyncMethod ??= methodSymbol;
if (IsBindAsyncWithParameter(methodSymbol, typeSymbol, wellKnownTypes))
{
bindabilityMethod = BindabilityMethod.BindAsyncWithParameter;
bindMethodSymbol = methodSymbol;
break;
}
if (IsBindAsync(methodSymbol, typeSymbol, wellKnownTypes))
{
bindabilityMethod = BindabilityMethod.BindAsync;
bindMethodSymbol = methodSymbol;
}
}
}
}
}

return (bindabilityMethod, bindAsyncMethod);
});

if (bindabilityMethod is not null)
{
return Bindability.Bindable;
}

// See if we can give better guidance on why the BindAsync method is no good.
if (bindAsyncMethods.Count() == 1)
if (bindAsyncMethod is not null)
{
var bindAsyncMethod = bindAsyncMethods.Single();

if (bindAsyncMethod.ReturnType is INamedTypeSymbol returnType && !IsReturningValueTaskOfTOrNullableT(returnType, typeSymbol, wellKnownTypes))
{
return Bindability.InvalidReturnType;
Expand Down