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

Likely unexpected breaking change introduced recently for await using #72819

Closed
AlekseyTs opened this issue Mar 30, 2024 · 3 comments · Fixed by #73850
Closed

Likely unexpected breaking change introduced recently for await using #72819

AlekseyTs opened this issue Mar 30, 2024 · 3 comments · Fixed by #73850

Comments

@AlekseyTs
Copy link
Contributor

The tests below reflects the current behavior:

        [Fact]
        public void Test()
        {
            var src = @"
using System;
using System.Threading.Tasks;

interface IMyAsyncDisposable1
{
    ValueTask DisposeAsync();
}

interface IMyAsyncDisposable2
{
    ValueTask DisposeAsync();
}

struct S2 : IMyAsyncDisposable1, IMyAsyncDisposable2, IAsyncDisposable
{
    ValueTask IMyAsyncDisposable1.DisposeAsync() => throw null;
    ValueTask IMyAsyncDisposable2.DisposeAsync() => throw null;

    public ValueTask DisposeAsync()
    {
        System.Console.Write('D');
        return ValueTask.CompletedTask;
    }
}

class C
{
    static async Task Main()
    {
        await Test<S2>();
    }

    static async Task Test<T>() where T : IMyAsyncDisposable1, IMyAsyncDisposable2, IAsyncDisposable, new()
    {
        await using (new T())
        {
            System.Console.Write(123);
        }
    }
}
";
            var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe);

            comp.VerifyDiagnostics(
                // (36,22): error CS0121: The call is ambiguous between the following methods or properties: 'IMyAsyncDisposable1.DisposeAsync()' and 'IMyAsyncDisposable2.DisposeAsync()'
                //         await using (new T())
                Diagnostic(ErrorCode.ERR_AmbigCall, "new T()").WithArguments("IMyAsyncDisposable1.DisposeAsync()", "IMyAsyncDisposable2.DisposeAsync()").WithLocation(36, 22)
                );
        }

The code used to compile successfully and was printing "123D". This is likely a regression from #72598.

Note, similar scenario in await foreach works the same way the await using used to work (i.e. it falls back to using IAsyncDisposable implementation).

using System;
using System.Threading;
using System.Threading.Tasks;

interface ICustomEnumerator
{
    public int Current {get;}

    public ValueTask<bool> MoveNextAsync();
}

interface IGetEnumerator<TEnumerator> where TEnumerator : ICustomEnumerator
{
    TEnumerator GetAsyncEnumerator(CancellationToken token = default);
}

struct S1 : IGetEnumerator<S2>
{
    public S2 GetAsyncEnumerator(CancellationToken token = default)
    {
        return new S2();
    }
}

interface IMyAsyncDisposable1
{
    ValueTask DisposeAsync();
}

interface IMyAsyncDisposable2
{
    ValueTask DisposeAsync();
}

struct S2 : ICustomEnumerator, IMyAsyncDisposable1, IMyAsyncDisposable2, IAsyncDisposable
{
    ValueTask IMyAsyncDisposable1.DisposeAsync() => throw null;
    ValueTask IMyAsyncDisposable2.DisposeAsync() => throw null;
    public ValueTask DisposeAsync()
    { 
        System.Console.Write("D");
        return ValueTask.CompletedTask;
    }

    public int Current => 123;
    public ValueTask<bool> MoveNextAsync()
    {
        return ValueTask.FromResult(false);
    }
}

class C
{
    static async Task Main()
    {
        await Test<S1, S2>();
    }

    static async Task Test<TEnumerable, TEnumerator>()
        where TEnumerable : IGetEnumerator<TEnumerator>
        where TEnumerator : ICustomEnumerator, IMyAsyncDisposable1, IMyAsyncDisposable2, IAsyncDisposable
    {
        await foreach (var i in default(TEnumerable))
        {
            System.Console.Write(i);
        }
    }
}
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label Mar 30, 2024
@AlekseyTs
Copy link
Contributor Author

CC @jcouv

@jcouv jcouv self-assigned this Apr 1, 2024
@jcouv jcouv added this to the 17.11 milestone Apr 1, 2024
@jaredpar jaredpar removed the untriaged Issues and PRs which have not yet been triaged by a lead label Apr 1, 2024
@jaredpar
Copy link
Member

jaredpar commented Apr 1, 2024

Keeping this in 17.11 for now as we don't have any user reports of this breaking them. If there are reports though we may need to go back and service 17.10 .

@MaceWindu
Copy link

MaceWindu commented Jun 9, 2024

Probably same issue with different error

public class Class1 : IAsyncDisposable
{
	ValueTask IAsyncDisposable.DisposeAsync() => default;

	protected virtual ValueTask DisposeAsync(bool disposing) => default;
}

public class Class2
{
	public static async void Test()
	{
                // error CS0122: 'Class1.DisposeAsync(bool)' is inaccessible due to its protection level
		await using var x = new Class1();
	}
}

Quite puzzling, as I wouldn't expect reference to DisposeAsync(bool) method here

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

Successfully merging a pull request may close this issue.

4 participants