Skip to content
This repository has been archived by the owner on Nov 8, 2018. It is now read-only.

Treat ValueTask<TResult> as an asynchronous return type #70

Merged
merged 2 commits into from
Aug 6, 2018

Conversation

sharwell
Copy link
Member

@sharwell sharwell commented Aug 6, 2018

Fixes #68

@codecov
Copy link

codecov bot commented Aug 6, 2018

Codecov Report

Merging #70 into master will increase coverage by 0.28%.
The diff coverage is 89.77%.

@@            Coverage Diff             @@
##           master      #70      +/-   ##
==========================================
+ Coverage   81.26%   81.54%   +0.28%     
==========================================
  Files          38       38              
  Lines        2856     2943      +87     
  Branches      184      188       +4     
==========================================
+ Hits         2321     2400      +79     
- Misses        440      444       +4     
- Partials       95       99       +4
Impacted Files Coverage Δ
...ncUsageAnalyzers/Helpers/MethodSymbolExtensions.cs 77.19% <0%> (-1.38%) ⬇️
...geAnalyzers.Test/Naming/UseAsyncSuffixUnitTests.cs 100% <100%> (ø) ⬆️
...Analyzers.Test/Naming/AvoidAsyncSuffixUnitTests.cs 100% <100%> (ø) ⬆️
...cUsageAnalyzers.Test/Helpers/MetadataReferences.cs 70.58% <58.33%> (-29.42%) ⬇️
...nalyzers.Test/Helpers/DiagnosticVerifier.Helper.cs 81.29% <75%> (-0.35%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b5fb2d7...ca7bcad. Read the comment docs.

class ClassName
{
ValueTask<int> FirstMethod() { return new ValueTask<int>(3); }
ValueTask<int> SecondMethodAsync() { return new ValueTask<int>(3); }
Copy link

Choose a reason for hiding this comment

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

How about a test for the non-generic ValueTask type as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not available in the System.Threading.Tasks.Extensions package and requires the test to compile against .NET Core. The test project doesn't currently support this to my knowledge.

Copy link

Choose a reason for hiding this comment

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

The non-generic ValueTask struct certainly is available from that same package, and it does not require .NET Core as a target.
In my Nerdbank.Streams project I use ValueTask in many places. Hitting F12 on it reveals it is defined here:
.nuget\packages\system.threading.tasks.extensions\4.5.0\ref\netstandard1.0\System.Threading.Tasks.Extensions.dll

Copy link
Member Author

Choose a reason for hiding this comment

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

👍 I was going by these pages:

https://apisof.net/catalog/System.Threading.Tasks.ValueTask%3CTResult%3E
https://apisof.net/catalog/System.Threading.Tasks.ValueTask

Looks like it hasn't been updated for the 4.5.0 release of these packages.

Copy link

Choose a reason for hiding this comment

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

Ya, apisof.net has been the most accurate reference for this info (much better than msdn/docs). But it does appear to be outdated now. :(

@@ -25,7 +25,8 @@ public static bool HasAsyncSignature(this IMethodSymbol symbol, bool treatAsyncV
if (!symbol.IsAsync)
{
// This check conveniently covers Task and Task<T> by ignoring the `1 in Task<T>.
if (!string.Equals(nameof(Task), symbol.ReturnType?.Name, StringComparison.Ordinal))
if (!string.Equals(nameof(Task), symbol.ReturnType?.Name, StringComparison.Ordinal)
&& !string.Equals("ValueTask", symbol.ReturnType?.Name, StringComparison.Ordinal))
Copy link

Choose a reason for hiding this comment

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

Why not allow any type whose GetAwaitInfo suggests the type is awaitable?

Copy link
Member Author

Choose a reason for hiding this comment

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

❓ Is there an easy way to do this using existing Roslyn APIs? Otherwise I was planning to leave it as a follow-up change.

Copy link

Choose a reason for hiding this comment

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

I'm pretty sure it's been there since the earliest versions. Let me find it...

Copy link

Choose a reason for hiding this comment

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

Ok, never mind. The API I was thinking of is SemanticModel.GetAwaitExpressionInfo which accepts an await expression rather than a type that may be awaited on.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link

Choose a reason for hiding this comment

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

Rather than base it on whether a method's return type is compatible with the async method declaration modifier, why not just expect/require an Async suffix anytime the returned type is awaitable? For example, people may return custom awaitables from non-async methods that nevertheless dictate that the calling pattern for the method is to await the result. SwitchToMainThreadAsync is an example of this. We have several other custom awaitables returned from various Async-suffixed methods as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

I filed #73 and #74 to cover both cases.

Choose a reason for hiding this comment

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

@sharwell there is http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,882

Or you can roll your own:

internal static bool IsAwaitable(this ITypeSymbol type)
{
    return type.TryFindFirstMethod("GetAwaiter", x => x.Parameters.Length == 0, out var method) &&
           method.ReturnType is ITypeSymbol returnType &&
           returnType.TryFindFirstMethod("GetResult", x => x.Parameters.Length == 0, out _);
}

The above does not check for extension methods and there can be more checks on the awaiter type.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants