Skip to content
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

Add support for F# Async<'T> as an awaitable type in minimal APIs #46898

Merged
merged 25 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d818c5b
Remove unneeded unboxing/boxing
brianrourkeboll Feb 26, 2023
b36b82a
Add doc comment
brianrourkeboll Feb 26, 2023
8387746
Remove unused using
brianrourkeboll Feb 26, 2023
f2c23be
Convert F# Async<> to Task<> in request delegates
brianrourkeboll Feb 26, 2023
205c312
Add tests for F# async request delegate support
brianrourkeboll Feb 26, 2023
3b43ac2
F# async not supported by source generator
brianrourkeboll Feb 26, 2023
5a30f86
Actually remove extra convert call
brianrourkeboll Feb 27, 2023
2b42bae
Shrink diff
brianrourkeboll Feb 27, 2023
93dc5f1
Merge main
brianrourkeboll Mar 19, 2023
17bf70a
Use CoercedAwaitableInfo in OpenApi gen
brianrourkeboll Mar 19, 2023
25cb783
Use CoercedAwaitableInfo in EndpointMetadataApiDescriptionProvider
brianrourkeboll Mar 19, 2023
5242ba9
Add FSharpAsync tests for EndpointMetadataApiDescriptionProvider
brianrourkeboll Mar 19, 2023
7a0ac17
Add runtime creation test for FSharpAsync
brianrourkeboll Mar 19, 2023
be473f1
Convert unit awaitables to void awaitables
brianrourkeboll Mar 26, 2023
5904ea5
Add tests for unit → void awaitable conversions
brianrourkeboll Mar 26, 2023
03e61a7
Make consistent
brianrourkeboll Mar 26, 2023
dc353d4
Don't need
brianrourkeboll Mar 26, 2023
ee184d5
Merge main
brianrourkeboll Jun 10, 2023
3bbe1e7
Handle boxed IResult
brianrourkeboll Jun 11, 2023
3fea9df
Simplify
brianrourkeboll Jun 17, 2023
2111eb2
Add runtime tests back
brianrourkeboll Jun 17, 2023
3e31ac6
Merge main
brianrourkeboll Aug 11, 2023
3bee5c6
Merge main
brianrourkeboll Aug 16, 2023
801d429
Use affirmative form
brianrourkeboll Aug 16, 2023
1e60480
Merge branch 'main' into fsharp-async-minimal-apis
captainsafia Nov 1, 2023
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
108 changes: 66 additions & 42 deletions src/Http/Http.Extensions/src/RequestDelegateFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -501,34 +501,45 @@ private static Expression MapHandlerReturnTypeToValueTask(Expression methodCall,
{
return Expression.Block(methodCall, EmptyHttpResultValueTaskExpr);
}
else if (returnType == typeof(Task))
else if (CoercedAwaitableInfo.IsTypeAwaitable(returnType, out var coercedAwaitableInfo))
{
return Expression.Call(ExecuteTaskWithEmptyResultMethod, methodCall);
}
else if (returnType == typeof(ValueTask))
{
return Expression.Call(ExecuteValueTaskWithEmptyResultMethod, methodCall);
}
else if (returnType == typeof(ValueTask<object?>))
{
return methodCall;
}
else if (returnType.IsGenericType &&
returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
{
var typeArg = returnType.GetGenericArguments()[0];
return Expression.Call(ValueTaskOfTToValueTaskOfObjectMethod.MakeGenericMethod(typeArg), methodCall);
}
else if (returnType.IsGenericType &&
returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
var typeArg = returnType.GetGenericArguments()[0];
return Expression.Call(TaskOfTToValueTaskOfObjectMethod.MakeGenericMethod(typeArg), methodCall);
}
else
{
return Expression.Call(WrapObjectAsValueTaskMethod, methodCall);
if (coercedAwaitableInfo.CoercerResultType is { } coercedType)
{
returnType = coercedType;
}

if (coercedAwaitableInfo.CoercerExpression is { } coercerExpression)
{
methodCall = Expression.Invoke(coercerExpression, methodCall);
}

if (returnType == typeof(Task))
{
return Expression.Call(ExecuteTaskWithEmptyResultMethod, methodCall);
}
else if (returnType == typeof(ValueTask))
{
return Expression.Call(ExecuteValueTaskWithEmptyResultMethod, methodCall);
}
else if (returnType == typeof(ValueTask<object?>))
{
return methodCall;
}
else if (returnType.IsGenericType &&
returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
{
var typeArg = coercedAwaitableInfo.AwaitableInfo.ResultType;
return Expression.Call(ValueTaskOfTToValueTaskOfObjectMethod.MakeGenericMethod(typeArg), methodCall);
}
else if (returnType.IsGenericType &&
returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
var typeArg = coercedAwaitableInfo.AwaitableInfo.ResultType;
return Expression.Call(TaskOfTToValueTaskOfObjectMethod.MakeGenericMethod(typeArg), methodCall);
}
}

return Expression.Call(WrapObjectAsValueTaskMethod, methodCall);
}

private static ValueTask<object?> ValueTaskOfTToValueTaskOfObject<T>(ValueTask<T> valueTask)
Expand Down Expand Up @@ -978,22 +989,9 @@ private static void PopulateBuiltInResponseTypeMetadata(Type returnType, Endpoin
throw GetUnsupportedReturnTypeException(returnType);
}

if (returnType == typeof(Task) || returnType == typeof(ValueTask))
if (CoercedAwaitableInfo.IsTypeAwaitable(returnType, out var coercedAwaitableInfo))
{
returnType = typeof(void);
}
else if (AwaitableInfo.IsTypeAwaitable(returnType, out _))
{
var genericTypeDefinition = returnType.IsGenericType ? returnType.GetGenericTypeDefinition() : null;

if (genericTypeDefinition == typeof(Task<>) || genericTypeDefinition == typeof(ValueTask<>))
{
returnType = returnType.GetGenericArguments()[0];
}
else
{
throw GetUnsupportedReturnTypeException(returnType);
}
returnType = coercedAwaitableInfo.AwaitableInfo.ResultType;
}

// Skip void returns and IResults. IResults might implement IEndpointMetadataProvider but otherwise we don't know what it might do.
Expand Down Expand Up @@ -1041,8 +1039,18 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
HttpContextExpr,
Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
}
else if (AwaitableInfo.IsTypeAwaitable(returnType, out _))
else if (CoercedAwaitableInfo.IsTypeAwaitable(returnType, out var coercedAwaitableInfo))
{
if (coercedAwaitableInfo.CoercerResultType is { } coercedType)
{
returnType = coercedType;
}

if (coercedAwaitableInfo.CoercerExpression is { } coercerExpression)
{
methodCall = Expression.Invoke(coercerExpression, methodCall);
}

if (returnType == typeof(Task))
{
return methodCall;
Expand Down Expand Up @@ -1073,6 +1081,14 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
methodCall,
HttpContextExpr);
}
else if (typeArg == typeof(object))
{
return Expression.Call(
ExecuteTaskOfObjectMethod,
methodCall,
HttpContextExpr,
Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
}
else
{
var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeArg);
Expand Down Expand Up @@ -1113,6 +1129,14 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
methodCall,
HttpContextExpr);
}
else if (typeArg == typeof(object))
{
return Expression.Call(
ExecuteValueTaskOfObjectMethod,
methodCall,
HttpContextExpr,
Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
}
else
{
var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeArg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
</ItemGroup>

<ItemGroup>
<Reference Include="FSharp.Core" />
brianrourkeboll marked this conversation as resolved.
Show resolved Hide resolved
<Reference Include="Microsoft.AspNetCore" />
<Reference Include="Microsoft.AspNetCore.Http" />
<Reference Include="Microsoft.AspNetCore.Http.Results" />
Expand Down
Loading