- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 273
Closed
Labels
Description
Product and Version Used
VS extension version 4.12.5
Background
C# supports awaiting any expression whose type has a specific shape (C# spec).
Additionally, async methods can have their return type be any "task-like" type, which is not too complex to implement in user code (C# spec).
Currently, as far as I've been able to tell, analyzers like RCS1047 don't check for those shapes and thus end up misfiring (or not firing) on custom task types.
Example/reproduction code
using System;
using System.IO;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
#pragma warning disable CA1822 // Mark members as static
#pragma warning disable CA1050 // Declare types in namespaces
#pragma warning disable CS9113 // Parameter is unread.
#nullable disable
class CustomTask
{
    // class must have a (visible) instance or extension GetAwaiter method (returning an awaiter type) to be awaitable
    public Awaiter GetAwaiter() => default;
    public CustomTask DoSomething() => this; // expected RCS1046 (if enabled)
    public CustomTask DoSomethingAsync() => this; // expected no RCS1047
    public async Task ClassicallyAsync() => await DoSomethingAsync(); // expected RCS1090 (if enabled)
    public Task ExpectedlyAsync() => ClassicallyAsync(); // no RCS1047 as expected (since Task is known)
    // must return an awaitable type
    public ConfiguredAwaitable ConfigureAwait(bool continueOnCapturedContext) => new ConfiguredAwaitable(this);
}
interface ICustomTask
{
    CustomTask DoAsync(); // expected no RCS1047
}
#region Awaiter shape
public struct Awaiter : INotifyCompletion
{
    public bool IsCompleted => false;
    public void OnCompleted(Action continuation) { }
    public void GetResult() { }
}
public struct Awaiter<T> : INotifyCompletion
{
    public bool IsCompleted => false;
    public void OnCompleted(Action continuation) { }
    public T GetResult() => default;
}
#endregion
// This attribute is required for the type to be a valid return type for an async method
[AsyncMethodBuilder(typeof(CustomTask<>))]
class CustomTask<T>
{
    //public async CustomTask Nope() { } // CS1983
    public CustomTask(T t) { }
    public Awaiter<T> GetAwaiter() => default;
    public CustomTask<int> Foo()
    {
        using FileStream fs = default!; // expected RCS1261
        return new CustomTask<int>(1);
    }
    public async CustomTask<int> FooAsync()
    {
        using FileStream fs = default!; // get RCS1261 as expected (since it checks if method is async)
        if (fs.CanRead)
        {
            return await new CustomTask<int>(1).ConfigureAwait(false);
        }
        else
        {
            return 2;
        }
    }
    public async Task Bar()
    {
        await Foo(); // expected RCS1090 (if enabled)
    }
    public CustomTask<int> Baz() // expected RCS1229
    {
        using (default(IDisposable))
        {
            return new CustomTask<int>(1);
        }
    }
    #region Async method builder shape
    public static CustomTask<T> Create() => default!;
    public CustomTask<T> Task { get; set; } = null!;
    public void SetStateMachine(IAsyncStateMachine stateMachine) { }
    public void SetException(Exception ex) { }
    public void SetResult(T result) { }
    public void Start<TStateMachine>(ref TStateMachine stateMachine)
        where TStateMachine : IAsyncStateMachine
    { }
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine
    { }
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine
    { }
    #endregion
}
struct ConfiguredAwaitable
{
    public ConfiguredAwaitable(CustomTask task) { }
    public Awaiter GetAwaiter() => default;
}
static class Extensions
{
    public static CustomTask<T> ConfigureAwait<T>(this CustomTask<T> task, bool continueOnCapturedContext)
    {
        return task; // just needs to be awaitable
    }
}I have a branch ready with fixes, let me know if you're cool with a PR 👍