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

Allow async methods to return Task-like types #12434

Closed
wants to merge 3 commits into from

Conversation

cston
Copy link
Member

@cston cston commented Jul 11, 2016

Allow async methods to return types other than void, Task, or Task<T>. Specifically allow Task-like types (and AsyncMethodBuilder types) if the types contain an expected set of members.

Next: Compare full signature including type constraints when looking up members. (Currently, lookup is by name only.) And add more tests.

[Thanks to Lucian for prototyping this feature.]

@jaredpar
Copy link
Member

CC @roslyn-compiler, @ljw1004

@@ -660,6 +660,7 @@ Microsoft.CodeAnalysis.Semantics.UnaryOperationKind.UnsignedPrefixIncrement = 77
abstract Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.OperationKind> operationKinds) -> void
abstract Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.RegisterOperationBlockEndAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext> action) -> void
abstract Microsoft.CodeAnalysis.SemanticModel.GetOperationCore(Microsoft.CodeAnalysis.SyntaxNode node, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.IOperation
const Microsoft.CodeAnalysis.WellKnownMemberNames.CreateAsyncMethodBuilder = "CreateAsyncMethodBuilder" -> string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we mean for this to be public?

@cston cston force-pushed the tasklike branch 2 times, most recently from dd9e4fd to 451eb45 Compare July 13, 2016 17:00
@cston
Copy link
Member Author

cston commented Jul 13, 2016

@ljw1004, @dotnet/roslyn-compiler The PR has been updated with the changes to overload resolution and tests ported from Lucian's features/async-return branch. There are still a number of // PROTOTYPE() comments that I am working on now.

@cston cston force-pushed the tasklike branch 2 times, most recently from a6fe2ac to 9f8f875 Compare July 13, 2016 22:57
@@ -1252,9 +1252,12 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re
out okToDowngradeToNeither);
}

var type1Normalized = type1.NormalizeTaskTypes(Compilation);
var type2Normalized = type2.NormalizeTaskTypes(Compilation);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note NormalizeTaskTypes() is called in OverloadResolution.BetterFunctionMember<> whenever comparing parameter types to ignore differences in Task types. And NormalizeTaskTypes() walks the type calling GetAsyncMethodBuilderType() on each type part, and substituting System.Threading.Tasks.Task or System.Threading.Tasks.Tasks<T> for any Task-like types. However, the overhead should by low since GetAsyncMethodBuilderType() is essentially a dictionary lookup, and substitution only occurs if Task-like types are found.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens in the case where Task and Task<T> are not available? Does it break our ability to do overload resolution on other task-like types?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. If Task and Task<T> are not available, we are currently substituting an error type. I've changed NormalizeTaskTypes to skip substitution in that case but that means we will not consider two different task-like types as equivalent if the well known types are missing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's funny! Even if we fix this particular problem (to consider two different task-like types as equivalent even in the absence of System.Threading.Tasks.Task/Task), the compiler is still hard-coded to prefer Task in certain situations e.g.

void f<T>(Func<T> lambda)
f(async () => 3);

I think it's a mistake that the compiler does this inference, but we're stuck with it. Imagine if we came up with a set of libraries which removed Task completely, and only used other tasks, as a way to force the compiler never to do these dangerous inferences!!!

Still it's a far-removed way-out scenario. Task has found its way into the deepest part of the BCL and will never be removed.

@cston
Copy link
Member Author

cston commented Jul 14, 2016

@ljw1004, @dotnet/roslyn-compiler, ping, please review.

}
else if (type2IsGenericTask)
{
// A shortcut, Task<T> type cannot satisfy other rules.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can only keep this shortcut if a stronger invariant holds, that NO TASKLIKE can satisfy the other rules. I'm not sure what are the "other rules" mentioned by this comment. Could you check?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remaining rules are for delegate types and integral types.

@@ -132,10 +146,25 @@ private static TypeSymbol InferReturnType(BoundBlock block, Binder binder, bool

// Async:

NamedTypeSymbol taskType = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you comment what this chunk of code is doing? It's not obvious. I think it says:
// This lambda has an "async" modifier so we need to figure out what tasklike the lambda returns.
// The default assumption is it will return Task or Task (depending on whether it has returns or not).
// But if the lambda is being matched against a delegate type (i.e. not a generic type parameter, and
// not a non-delegate type), and if the delegate type has a return type, and if that return type is
// tasklike (as identified by the presence of a createBuilderMethod), then the delegate's return type
// indicates which tasklike the lambda should return. It might be Task, or Task, or Task,
// or Tasklike, or Tasklike, or Tasklike.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment.

@cston
Copy link
Member Author

cston commented Jul 19, 2016

Updated PR checked in to dotnet:dev15-preview-4. See #12518.

@cston cston closed this Jul 19, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants