File: Semantics\AwaitExpressionTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Semantic\Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
using System.Linq;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
    /// <summary>
    /// Tests related to await expressions.
    /// </summary>
    public class AwaitExpressionTests : CompilingTestBase
    {
        [Fact]
        public void TestAwaitInfoExtensionMethod()
        {
            var text =
@"using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
 
static class App{
    public static async Task Main(){
        var x = new MyAwaitable();
        x.SetValue(42);
 
        Console.WriteLine(await x + ""!"");
    }
}
 
struct MyAwaitable
{
    private ValueTask<int> task;
    private TaskCompletionSource<int> source;
 
    private TaskCompletionSource<int> Source
    {
        get
        {
            if (source == null)
            {
                source = new TaskCompletionSource<int>();
                task = new ValueTask<int>(source.Task);
            }
            return source;
        }
    }
    internal ValueTask<int> Task
    {
        get
        {
            _ = Source;
            return task;
        }
    }
 
    public void SetValue(int i)
    {
        Source.SetResult(i);
    }
}
 
static class MyAwaitableExtension
{
    public static System.Runtime.CompilerServices.ValueTaskAwaiter<int> GetAwaiter(this MyAwaitable a)
    {
        return a.Task.GetAwaiter();
    }
}";
 
            var csCompilation = CreateCompilation(text, targetFramework: TargetFramework.NetCoreApp);
            var tree = csCompilation.SyntaxTrees.Single();
 
            var model = csCompilation.GetSemanticModel(tree);
            var awaitExpression = tree.GetRoot().DescendantNodes().OfType<AwaitExpressionSyntax>().First();
            Assert.Equal("await x", awaitExpression.ToString());
 
            var info = model.GetAwaitExpressionInfo(awaitExpression);
            Assert.Equal(
                "System.Runtime.CompilerServices.ValueTaskAwaiter<System.Int32> MyAwaitableExtension.GetAwaiter(this MyAwaitable a)",
                info.GetAwaiterMethod.ToTestDisplayString()
            );
            Assert.Equal(
                "System.Int32 System.Runtime.CompilerServices.ValueTaskAwaiter<System.Int32>.GetResult()",
                info.GetResultMethod.ToTestDisplayString()
            );
            Assert.Equal(
                "System.Boolean System.Runtime.CompilerServices.ValueTaskAwaiter<System.Int32>.IsCompleted { get; }",
                info.IsCompletedProperty.ToTestDisplayString()
            );
        }
 
        [Fact]
        [WorkItem(711413, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/711413")]
        public void TestAwaitInfo()
        {
            var text =
@"using System.Threading.Tasks;
 
class C
{
    async void Goo(Task<int> t)
    {
        int c = 1 + await t;
    }
}";
            var info = GetAwaitExpressionInfo(text);
            Assert.Equal("System.Runtime.CompilerServices.TaskAwaiter<System.Int32> System.Threading.Tasks.Task<System.Int32>.GetAwaiter()", info.GetAwaiterMethod.ToTestDisplayString());
            Assert.Equal("System.Int32 System.Runtime.CompilerServices.TaskAwaiter<System.Int32>.GetResult()", info.GetResultMethod.ToTestDisplayString());
            Assert.Equal("System.Boolean System.Runtime.CompilerServices.TaskAwaiter<System.Int32>.IsCompleted { get; }", info.IsCompletedProperty.ToTestDisplayString());
        }
 
        [Fact]
        [WorkItem(1084696, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1084696")]
        public void TestAwaitInfo2()
        {
            var text =
@"using System;
using System.Threading.Tasks;
public class C {
    public C(Task<int> t) {
        Func<Task> f = async() => await t;
    }
}";
            var info = GetAwaitExpressionInfo(text);
            Assert.Equal("System.Runtime.CompilerServices.TaskAwaiter<System.Int32> System.Threading.Tasks.Task<System.Int32>.GetAwaiter()", info.GetAwaiterMethod.ToTestDisplayString());
            Assert.Equal("System.Int32 System.Runtime.CompilerServices.TaskAwaiter<System.Int32>.GetResult()", info.GetResultMethod.ToTestDisplayString());
            Assert.Equal("System.Boolean System.Runtime.CompilerServices.TaskAwaiter<System.Int32>.IsCompleted { get; }", info.IsCompletedProperty.ToTestDisplayString());
        }
 
        [Fact]
        [WorkItem(744146, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/744146")]
        public void DefaultAwaitExpressionInfo()
        {
            AwaitExpressionInfo info = default;
            Assert.Null(info.GetAwaiterMethod);
            Assert.Null(info.GetResultMethod);
            Assert.Null(info.IsCompletedProperty);
            Assert.False(info.IsDynamic);
            Assert.Equal(0, info.GetHashCode());
        }
 
        private AwaitExpressionInfo GetAwaitExpressionInfo(string text, out CSharpCompilation compilation, params DiagnosticDescription[] diagnostics)
        {
            var tree = Parse(text, options: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp5));
            var comp = CreateCompilationWithMscorlib461(new SyntaxTree[] { tree }, new MetadataReference[] { SystemRef });
            comp.VerifyDiagnostics(diagnostics);
            compilation = comp;
            var syntaxNode = (AwaitExpressionSyntax)tree.FindNodeOrTokenByKind(SyntaxKind.AwaitExpression).AsNode();
            var treeModel = comp.GetSemanticModel(tree);
            return treeModel.GetAwaitExpressionInfo(syntaxNode);
        }
 
        private AwaitExpressionInfo GetAwaitExpressionInfo(string text, params DiagnosticDescription[] diagnostics)
        {
            CSharpCompilation temp;
            return GetAwaitExpressionInfo(text, out temp, diagnostics);
        }
 
        [Fact]
        [WorkItem(748533, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/748533")]
        public void Bug748533()
        {
            var text =
@"
using System;
using System.Threading;
using System.Threading.Tasks;
class A
{
    public async Task<T> GetVal<T>(T t)
    {
        await Task.Delay(10);
        return t;
    }
    public async void Run<T>(T t) where T : struct
    {
        int tests = 0;
        tests++;
        dynamic f = (await GetVal((Func<Task<int>>)(async () => 1)))();
        if (await f == 1)
            Driver.Count++;
        tests++;
        dynamic ff = new Func<Task<int>>((Func<Task<int>>)(async () => 1));
        if (await ff() == 1)
            Driver.Count++;
        Driver.Result = Driver.Count - tests;
        Driver.CompletedSignal.Set();
    }
}
class Driver
{
    public static int Result = -1;
    public static int Count = 0;
    public static AutoResetEvent CompletedSignal = new AutoResetEvent(false);
    static int Main()
    {
        var t = new A();
        t.Run(6);
        CompletedSignal.WaitOne();
        return Driver.Result;
    }
}
";
            var comp = CreateCompilationWithMscorlib461(text, options: TestOptions.ReleaseDll);
            comp.VerifyEmitDiagnostics(
                // (16,62): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
                //         dynamic f = (await GetVal((Func<Task<int>>)(async () => 1)))();
                Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "=>").WithLocation(16, 62),
                // (20,69): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
                //         dynamic ff = new Func<Task<int>>((Func<Task<int>>)(async () => 1));
                Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "=>").WithLocation(20, 69),
                // (17,13): error CS0656: Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'
                //         if (await f == 1)
                Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "await f").WithArguments("Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo", "Create"));
        }
 
        [Fact]
        [WorkItem(576316, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/576316")]
        public void Bug576316()
        {
            var text =
@"using System;
using System.Threading.Tasks;
 
class C
{
    static async Task Goo()
    {
        Console.WriteLine(new TypedReference().Equals(await Task.FromResult(0)));
    }
}";
            var comp = CreateCompilationWithMscorlib461(text, options: TestOptions.ReleaseDll);
            comp.VerifyEmitDiagnostics(
                // (8,27): error CS4007: Instance of type 'System.TypedReference' cannot be preserved across 'await' or 'yield' boundary.
                //         Console.WriteLine(new TypedReference().Equals(await Task.FromResult(0)));
                Diagnostic(ErrorCode.ERR_ByRefTypeAndAwait, "new TypedReference()").WithArguments("System.TypedReference").WithLocation(8, 27));
        }
 
        [Fact]
        [WorkItem(3951, "https://github.com/dotnet/roslyn/issues/3951")]
        public void TestAwaitInNonAsync()
        {
            var text =
@"using System.Threading.Tasks;
 
class C
{
    void Goo(Task<int> t)
    {
        var v = await t;
    }
}";
            CSharpCompilation compilation;
            var info = GetAwaitExpressionInfo(text, out compilation,
                // (7,21): error CS4033: The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
                //         int c = 1 + await t;
                Diagnostic(ErrorCode.ERR_BadAwaitWithoutVoidAsyncMethod, "await t").WithLocation(7, 17)
                );
            Assert.Equal("System.Runtime.CompilerServices.TaskAwaiter<System.Int32> System.Threading.Tasks.Task<System.Int32>.GetAwaiter()", info.GetAwaiterMethod.ToTestDisplayString());
            Assert.Equal("System.Int32 System.Runtime.CompilerServices.TaskAwaiter<System.Int32>.GetResult()", info.GetResultMethod.ToTestDisplayString());
            Assert.Equal("System.Boolean System.Runtime.CompilerServices.TaskAwaiter<System.Int32>.IsCompleted { get; }", info.IsCompletedProperty.ToTestDisplayString());
            var semanticModel = compilation.GetSemanticModel(compilation.SyntaxTrees[0]);
            var decl = compilation.SyntaxTrees[0].GetRoot().DescendantNodes().OfType<VariableDeclaratorSyntax>().AsSingleton();
            var symbolV = (ILocalSymbol)semanticModel.GetDeclaredSymbol(decl);
            Assert.Equal("System.Int32", symbolV.Type.ToTestDisplayString());
        }
 
        [Fact]
        public void Dynamic()
        {
            string source =
@"using System.Threading.Tasks;
class Program
{
    static async Task Main()
    {
        dynamic d = Task.CompletedTask;
        await d;
    }
}";
            var comp = CreateCompilation(source);
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
            var expr = (AwaitExpressionSyntax)tree.FindNodeOrTokenByKind(SyntaxKind.AwaitExpression).AsNode();
            var info = model.GetAwaitExpressionInfo(expr);
            Assert.True(info.IsDynamic);
            Assert.Null(info.GetAwaiterMethod);
            Assert.Null(info.IsCompletedProperty);
            Assert.Null(info.GetResultMethod);
        }
 
        [Fact]
        [WorkItem(52639, "https://github.com/dotnet/roslyn/issues/52639")]
        public void Issue52639_1()
        {
            var text =
@"
using System;
using System.Threading.Tasks;
 
class Test1
{
    public async Task<ActionResult> Test(MyBaseClass model)
    {
        switch (model)
        {
            case FirstImplementation firstImplementation:
                firstImplementation.MyString1 = await Task.FromResult(""test"");
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(model));
        }
 
        switch (model)
        {
            case FirstImplementation firstImplementation:
                await Task.FromResult(1);
                return PartialView(""View"", firstImplementation);
            default:
                throw new ArgumentOutOfRangeException(nameof(model));
        }
    }
 
    private ActionResult PartialView(string v, FirstImplementation firstImplementation)
    {
        return new ActionResult { F = firstImplementation };
    }
 
    static void Main()
    {
        var c = new Test1();
        var f = new FirstImplementation();
 
        if (c.Test(f).Result.F == f && f.MyString1 == ""test"")
        {
            System.Console.WriteLine(""Passed"");
        }
        else
        {
            System.Console.WriteLine(""Failed"");
        }
    }
}
 
internal class ActionResult
{
    public FirstImplementation F;
}
 
public abstract class MyBaseClass
{
    public string MyString { get; set; }
}
 
public class FirstImplementation : MyBaseClass
{
    public string MyString1 { get; set; }
}
 
public class SecondImplementation : MyBaseClass
{
    public string MyString2 { get; set; }
}
";
            CompileAndVerify(text, options: TestOptions.ReleaseExe, expectedOutput: "Passed").VerifyDiagnostics();
            CompileAndVerify(text, options: TestOptions.DebugExe, expectedOutput: "Passed").VerifyDiagnostics();
        }
 
        [Fact]
        [WorkItem(52639, "https://github.com/dotnet/roslyn/issues/52639")]
        public void Issue52639_2()
        {
            var text =
@"
using System.Threading.Tasks;
 
class C
{
    string F;
 
    async Task<C> Test(C c)
    {
        c.F = await Task.FromResult(""a"");
 
        switch (c)
        {
            case C c1:
                await Task.FromResult(1);
                return c1;
        }
 
        return null;
    }
 
    static void Main()
    {
        var c = new C();
        if (c.Test(c).Result == c && c.F == ""a"")
        {
            System.Console.WriteLine(""Passed"");
        }
        else
        {
            System.Console.WriteLine(""Failed"");
        }
    }
}
";
            CompileAndVerify(text, options: TestOptions.ReleaseExe, expectedOutput: "Passed").VerifyDiagnostics();
            CompileAndVerify(text, options: TestOptions.DebugExe, expectedOutput: "Passed").VerifyDiagnostics();
        }
    }
}