File: Completion\CompletionProviders\AwaitCompletionProviderTests.cs
Web Access
Project: src\src\EditorFeatures\CSharpTest\Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.EditorFeatures.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.
 
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Completion.Providers;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations;
 
/// <summary>
/// The <see cref="AwaitCompletionProvider"/> adds async modifier if the return type is Task or ValueTask. The tests
/// here are only checking whether the completion item is provided or not. Tests for checking adding async modifier are
/// in: src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb
/// </summary>
[Trait(Traits.Feature, Traits.Features.Completion)]
public sealed class AwaitCompletionProviderTests : AbstractCSharpCompletionProviderTests
{
    internal override Type GetCompletionProviderType() => typeof(AwaitCompletionProvider);
 
    private const string CompletionDisplayTextAwait = "await";
    private const string CompletionDisplayTextAwaitAndConfigureAwait = "awaitf";
 
    private async Task VerifyAbsenceAsync([StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code)
    {
        await VerifyItemIsAbsentAsync(code, CompletionDisplayTextAwait);
        await VerifyItemIsAbsentAsync(code, CompletionDisplayTextAwaitAndConfigureAwait);
    }
 
    private async Task VerifyAbsenceAsync(
        [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, LanguageVersion languageVersion = LanguageVersion.Default)
    {
        await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwait);
        await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwaitAndConfigureAwait);
    }
 
    private async Task VerifyKeywordAsync(
        [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string code, LanguageVersion languageVersion = LanguageVersion.Default, string? inlineDescription = null, bool dotAwait = false, bool dotAwaitf = false)
    {
        var expectedDescription = dotAwait
            ? GetDescription(CompletionDisplayTextAwait, FeaturesResources.Await_the_preceding_expression)
            : GetDescription(CompletionDisplayTextAwait, FeaturesResources.Asynchronously_waits_for_the_task_to_finish);
        await VerifyItemExistsAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwait, glyph: (int)Glyph.Keyword, expectedDescriptionOrNull: expectedDescription, inlineDescription: inlineDescription);
 
        if (dotAwaitf)
        {
            expectedDescription = string.Format(FeaturesResources.Await_the_preceding_expression_and_add_ConfigureAwait_0, "false");
            await VerifyItemExistsAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwaitAndConfigureAwait, glyph: (int)Glyph.Keyword, expectedDescriptionOrNull: expectedDescription, inlineDescription: inlineDescription);
        }
        else
        {
            await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwaitAndConfigureAwait);
        }
 
        static string GetDescription(string keyword, string tooltip)
            => $"{string.Format(FeaturesResources._0_Keyword, keyword)}\r\n{tooltip}";
    }
 
    [Fact]
    public async Task TestNotInTypeContext()
    {
        await VerifyAbsenceAsync("""
            class Program
            {
                $$
            }
            """);
    }
 
    [Fact]
    public async Task TestStatementInMethod()
    {
        await VerifyKeywordAsync("""
            class C
            {
              void F()
              {
                $$  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestStatementInMethod_Async()
    {
        await VerifyKeywordAsync("""
            class C
            {
              async Task F()
              {
                $$  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestStatementInMethod_TopLevel()
    {
        await VerifyKeywordAsync("$$", LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestExpressionInAsyncMethod()
    {
        await VerifyKeywordAsync("""
            class C
            {
              async Task F()
              {
                var z = $$  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestExpressionInNonAsyncMethodWithTaskReturn()
    {
        await VerifyKeywordAsync("""
            class C
            {
              Task F()
              {
                var z = $$  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestExpressionInAsyncMethod_TopLevel()
    {
        await VerifyKeywordAsync("var z = $$", LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestUsingStatement()
    {
        await VerifyAbsenceAsync("""
            class C
            {
              async Task F()
              {
                using $$  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestUsingStatement_TopLevel()
    {
        await VerifyAbsenceAsync("using $$", LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestUsingDirective()
        => await VerifyAbsenceAsync("using $$");
 
    [Fact]
    public async Task TestGlobalUsingDirective()
        => await VerifyAbsenceAsync("global using $$");
 
    [Fact]
    public async Task TestForeachStatement()
    {
        await VerifyAbsenceAsync("""
            class C
            {
              async Task F()
              {
                foreach $$  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestForeachStatement_TopLevel()
    {
        await VerifyAbsenceAsync("foreach $$", LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestNotInQuery()
    {
        await VerifyAbsenceAsync("""
            class C
            {
              async Task F()
              {
                var z = from a in "char"
                      select $$  }
                }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestNotInQuery_TopLevel()
    {
        await VerifyAbsenceAsync(
            """
            var z = from a in "char"
                      select $$
            """, LanguageVersion.CSharp9);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")]
    public async Task TestInFinally()
    {
        await VerifyKeywordAsync("""
            class C
            {
              async Task F()
              {
                try { }
            finally { $$ }  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")]
    public async Task TestInFinally_TopLevel()
    {
        await VerifyKeywordAsync(
            """
            try { }
            finally { $$ }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")]
    public async Task TestInCatch()
    {
        await VerifyKeywordAsync("""
            class C
            {
              async Task F()
              {
                try { }
            catch { $$ }  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/907052")]
    public async Task TestInCatch_TopLevel()
    {
        await VerifyKeywordAsync(
            """
            try { }
            catch { $$ }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestNotInLock()
    {
        await VerifyAbsenceAsync("""
            class C
            {
              async Task F()
              {
                lock(this) { $$ }  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestNotInLock_TopLevel()
    {
        await VerifyAbsenceAsync("lock (this) { $$ }", LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestInAsyncLambdaInCatch()
    {
        await VerifyKeywordAsync("""
            class C
            {
              async Task F()
              {
                try { }
            catch { var z = async () => $$ }  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestInAsyncLambdaInCatch_TopLevel()
    {
        await VerifyKeywordAsync(
            """
            try { }
            catch { var z = async () => $$ }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestAwaitInLock()
    {
        await VerifyKeywordAsync("""
            class C
            {
              async Task F()
              {
                lock($$  }
            }
            """, LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestAwaitInLock_TopLevel()
    {
        await VerifyKeywordAsync("lock($$", LanguageVersion.CSharp9);
    }
 
    [Fact]
    public async Task TestDotAwaitSuggestAfterDotOnTask()
    {
        await VerifyKeywordAsync("""
            using System.Threading.Tasks;
 
            class C
            {
              async Task F(Task someTask)
              {
                someTask.$$
              }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Fact]
    public async Task TestDotAwaitSuggestAfterDotOnTaskOfT()
    {
        await VerifyKeywordAsync("""
            using System.Threading.Tasks;
 
            class C
            {
              async Task F(Task<int> someTask)
              {
                someTask.$$
              }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Fact]
    public async Task TestDotAwaitSuggestAfterDotOnValueTask()
    {
        var valueTaskAssembly = typeof(ValueTask).Assembly.Location;
        var markup = $$"""
            <Workspace>
                <Project Language="C#" AssemblyName="Assembly1" CommonReferences="true">
                    <MetadataReference>{{valueTaskAssembly}}</MetadataReference>
                    <Document FilePath="Test2.cs">
            using System.Threading.Tasks;
 
            class C
            {
              async Task F(ValueTask someTask)
              {
                someTask.$$
              }
            }
                    </Document>
                </Project>
            </Workspace>
            """;
        await VerifyItemExistsAsync(markup, "await");
        await VerifyItemExistsAsync(markup, "awaitf");
    }
 
    [Fact]
    public async Task TestDotAwaitSuggestAfterDotOnCustomAwaitable()
    {
        await VerifyKeywordAsync("""
            using System;
            using System.Runtime.CompilerServices;
            using System.Threading.Tasks;
 
            public class DummyAwaiter: INotifyCompletion {
                public bool IsCompleted => true;
                public void OnCompleted(Action continuation) => continuation();
                public void GetResult() {}
            }
 
            public class CustomAwaitable
            {
                public DummyAwaiter GetAwaiter() => new DummyAwaiter();
            }
 
            static class Program
            {
                static async Task Main()
                {
                    var awaitable = new CustomAwaitable();
                    awaitable.$$;
                }
            }
            """, dotAwait: true);
    }
 
    [Fact]
    public async Task TestDotAwaitSuggestAfterDotOnCustomAwaitableButNotConfigureAwaitEvenIfPresent()
    {
        await VerifyKeywordAsync("""
            using System;
            using System.Runtime.CompilerServices;
            using System.Threading.Tasks;
 
            public class DummyAwaiter: INotifyCompletion {
                public bool IsCompleted => true;
                public void OnCompleted(Action continuation) => continuation();
                public void GetResult() {}
            }
 
            public class CustomAwaitable
            {
                public DummyAwaiter GetAwaiter() => new DummyAwaiter();
                public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => default;
            }
 
            static class Program
            {
                static async Task Main()
                {
                    var awaitable = new CustomAwaitable();
                    awaitable.$$;
                }
            }
            """, dotAwait: true, dotAwaitf: false);
    }
 
    [Fact]
    public async Task TestDotAwaitSuggestAfterDotDot()
    {
        await VerifyKeywordAsync("""
            using System.Threading.Tasks;
 
            static class Program
            {
                static async Task Main(Task someTask)
                {
                    someTask.$$.;
                }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Fact]
    public async Task TestDotAwaitSuggestAfterDotBeforeType()
    {
        await VerifyKeywordAsync("""
            using System;
            using System.Threading.Tasks;
 
            static class Program
            {
                static async Task Main(Task someTask)
                {
                    someTask.$$
                    Int32 i = 0;
                }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Fact]
    public async Task TestDotAwaitSuggestAfterDotBeforeAnotherAwait()
    {
        await VerifyKeywordAsync("""
            using System;
            using System.Threading.Tasks;
 
            static class Program
            {
                static async Task Main(Task someTask)
                {
                    someTask.$$
                    await Test();
                }
 
                async Task Test() { }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("X.Y Test();")]
    [InlineData("var x;")]
    [InlineData("int x;")]
    [InlineData("System.Int32 x;")]
    [InlineData("if (true) { }")]
    [InlineData("System.Int32 Test() => 0;")]
    [InlineData("async Task<System.Int32> Test() => await Task.FromResult(1);")]
    public async Task TestDotAwaitSuggestAfterDotBeforeDifferentStatements(string statement)
    {
        await VerifyKeywordAsync($$"""
            using System;
            using System.Threading.Tasks;
 
            static class Program
            {
                static async Task Main(Task someTask)
                {
                    someTask.$$
                    {{statement}}
                }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Theory]
    // static
    [InlineData("StaticField.$$")]
    [InlineData("StaticProperty.$$")]
    [InlineData("StaticMethod().$$")]
 
    // parameters, locals and local function
    [InlineData("local.$$")]
    [InlineData("parameter.$$")]
    [InlineData("LocalFunction().$$")]
 
    // members
    [InlineData("c.Field.$$")]
    [InlineData("c.Property.$$")]
    [InlineData("c.Method().$$")]
    [InlineData("c.Self.Field.$$")]
    [InlineData("c.Self.Property.$$")]
    [InlineData("c.Self.Method().$$")]
    [InlineData("c.Function()().$$")]
 
    // indexer, operator, conversion
    [InlineData("c[0].$$")]
    [InlineData("(c + c).$$")]
    [InlineData("((Task)c).$$")]
    [InlineData("(c as Task).$$")]
 
    // parenthesized
    [InlineData("(parameter).$$")]
    [InlineData("((parameter)).$$")]
    [InlineData("(true ? parameter : parameter).$$")]
    [InlineData("(null ?? Task.CompletedTask).$$")]
    public async Task TestDotAwaitSuggestAfterDifferentExpressions(string expression)
    {
        await VerifyKeywordAsync($$"""
            using System;
            using System.Threading.Tasks;
 
            class C
            {
                public C Self => this;
                public Task Field = Task.CompletedTask;
                public Task Method() => Task.CompletedTask;
                public Task Property => Task.CompletedTask;
                public Task this[int i] => Task.CompletedTask;
                public Func<Task> Function() => () => Task.CompletedTask;
                public static Task operator +(C left, C right) => Task.CompletedTask;
                public static explicit operator Task(C c) => Task.CompletedTask;
            }
 
            static class Program
            {
                static Task StaticField = Task.CompletedTask;
                static Task StaticProperty => Task.CompletedTask;
                static Task StaticMethod() => Task.CompletedTask;
 
                static async Task Main(Task parameter)
                {
                    Task local = Task.CompletedTask;
                    var c = new C();
 
                    {{expression}}
 
                    Task LocalFunction() => Task.CompletedTask;
                }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Fact(Skip = "Fails because speculative binding can't figure out that local is a Task.")]
    [WorkItem("https://github.com/dotnet/roslyn/issues/56245")]
    public async Task TestDotAwaitSuggestBeforeLocalFunction()
    {
        // Speculative binding a local as expression finds the local as ILocalSymbol, but the type is ErrorType.
        // This is only the case when
        // * await is partially written (local.a),
        // * only for locals (e.g. IParameterSymbols are fine) which
        //   * are declared with var
        //   * The return type of the local function is used as first name in a MemberAccess in the declarator
        await VerifyKeywordAsync("""
            using System.Threading.Tasks;
 
            static class Program
            {
                static async Task Main()
                {
                    var local = Task.CompletedTask;
                    local.a$$
 
                    Task LocalFunction() => Task.CompletedTask;
                }
            }
            """);
    }
 
    [Theory]
    [InlineData("await Task.Run(async () => Task.CompletedTask.$$")]
    [InlineData("await Task.Run(async () => someTask.$$")]
    [InlineData("await Task.Run(async () => someTask.$$);")]
    [InlineData("await Task.Run(async () => { someTask.$$ }")]
    [InlineData("await Task.Run(async () => { someTask.$$ });")]
 
    [InlineData("Task.Run(async () => await someTask).$$")]
 
    [InlineData("await Task.Run(() => someTask.$$")]
    public async Task TestDotAwaitSuggestInLambdas(string lambda)
    {
        await VerifyKeywordAsync($$"""
            using System.Threading.Tasks;
 
            static class Program
            {
                static async Task Main()
                {
                    var someTask = Task.CompletedTask;
                    {{lambda}}
                }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Fact]
    public async Task TestDotAwaitNotAfterDotOnTaskIfAlreadyAwaited()
    {
        await VerifyAbsenceAsync("""
            using System.Threading.Tasks;
 
            class C
            {
              async Task F(Task someTask)
              {
                await someTask.$$
              }
            }
            """);
    }
 
    [Fact]
    public async Task TestDotAwaitNotAfterTaskType()
    {
        await VerifyAbsenceAsync("""
            using System.Threading.Tasks;
 
            class C
            {
              async Task F()
              {
                Task.$$
              }
            }
            """);
    }
 
    [Fact]
    public async Task TestDotAwaitNotInLock()
    {
        await VerifyAbsenceAsync("""
            using System.Threading.Tasks;
 
            class C
            {
              async Task F(Task someTask)
              {
                lock(this) { someTask.$$ }
              }
            }
            """);
    }
 
    [Fact]
    public async Task TestDotAwaitNotInLock_TopLevel()
    {
        await VerifyAbsenceAsync("""
            using System.Threading.Tasks;
 
            lock(this) { Task.CompletedTask.$$ }
            """);
    }
 
    [Fact]
    public async Task TestDotAwaitQueryNotInSelect()
    {
        await VerifyAbsenceAsync("""
            using System.Linq;
            using System.Threading.Tasks;
 
            class C
            {
              async Task F()
              {
                var z = from t in new[] { Task.CompletedTask }
                        select t.$$
              }
            }
            """);
    }
 
    [Fact]
    public async Task TestDotAwaitQueryInFirstFromClause()
    {
        await VerifyKeywordAsync("""
            using System.Linq;
            using System.Threading.Tasks;
 
            class C
            {
                async Task F()
                {
                    var arrayTask2 = Task.FromResult(new int[0]);
                    var z = from t in arrayTask2.$$
                            select t;
                }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Fact]
    public async Task TestDotAwaitQueryNotInSecondFromClause()
    {
        await VerifyNoItemsExistAsync("""
            using System.Linq;
            using System.Threading.Tasks;
 
            class C
            {
                async Task F()
                {
                    var array1 = new int[0];
                    var arrayTask2 = Task.FromResult(new int[0]);
                    var z = from i1 in array1
                            from i2 in arrayTask2.$$
                            select i2;
                }
            }
            """);
    }
 
    [Fact]
    public async Task TestDotAwaitQueryNotInContinuation()
    {
        await VerifyNoItemsExistAsync("""
            using System.Linq;
            using System.Threading.Tasks;
 
            class C
            {
                async Task F()
                {
                    var array1 = new int[0];
                    var arrayTask2 = Task.FromResult(new int[0]);
                    var z = from i1 in array1
                            select i1 into c
                            from i2 in arrayTask2.$$
                            select i2;
                }
            }
            """);
    }
 
    [Fact]
    public async Task TestDotAwaitQueryInJoinClause()
    {
        await VerifyKeywordAsync("""
            using System.Linq;
            using System.Threading.Tasks;
 
            class C
            {
                async Task F()
                {
                    var array1 = new int[0];
                    var arrayTask2 = Task.FromResult(new int[0]);
                    var z = from i1 in array1
                            join i2 in arrayTask2.$$ on i1 equals i2
                            select i1;
                }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Fact]
    public async Task TestDotAwaitQueryInJoinIntoClause()
    {
        await VerifyKeywordAsync("""
            using System.Linq;
            using System.Threading.Tasks;
 
            class C
            {
                async Task F()
                {
                    var array1 = new int[0];
                    var arrayTask2 = Task.FromResult(new int[0]);
                    var z = from i1 in array1
                            join i2 in arrayTask2.$$ on i1 equals i2 into g
                            select g;
                }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Fact]
    public async Task TestDotAwaitNotAfterConditionalAccessOfTaskMembers()
    {
        // The conditional access suggests, that someTask can be null.
        // await on null throws at runtime, so the user should do
        // if (someTask is not null) await someTask;
        // or
        // await (someTask ?? Task.CompletedTask)
        // Completion should not offer await, because the patterns above would change to much code.
        // This decision should be revised after https://github.com/dotnet/csharplang/issues/35 
        // is implemented.
        await VerifyAbsenceAsync("""
            using System.Threading.Tasks;
 
            class C
            {
              async Task F(Task someTask)
              {
                someTask?.$$
              }
            }
            """);
    }
 
    [Theory]
    [InlineData("c?.SomeTask.$$")]
 
    [InlineData("c.M()?.SomeTask.$$")]
    [InlineData("c.Pro?.SomeTask.$$")]
 
    [InlineData("c?.M().SomeTask.$$")]
    [InlineData("c?.Pro.SomeTask.$$")]
 
    [InlineData("c?.M()?.SomeTask.$$")]
    [InlineData("c?.Pro?.SomeTask.$$")]
 
    [InlineData("c.M()?.Pro.SomeTask.$$")]
    [InlineData("c.Pro?.M().SomeTask.$$")]
 
    [InlineData("c.M()?.M().M()?.M().SomeTask.$$")]
    [InlineData("new C().M()?.Pro.M()?.M().SomeTask.$$")]
    public async Task TestDotAwaitNotAfterDotInConditionalAccessChain(string conditionalAccess)
    {
        await VerifyAbsenceAsync($$"""
            using System.Threading.Tasks;
            public class C
            {
                public Task SomeTask => Task.CompletedTask;
 
                public C Pro => this;
                public C M() => this;
            }
 
            static class Program
            {
                public static async Task Main()
                {
                    var c = new C();
                    {{conditionalAccess}}
                }
            }
            """);
    }
 
    [Theory]
    [InlineData("c!.SomeTask.$$")]
    [InlineData("c.SomeTask!.$$")]
 
    [InlineData("c.M()!.SomeTask.$$")]
    [InlineData("c.Pro!.SomeTask.$$")]
 
    [InlineData("c!.M().SomeTask.$$")]
    [InlineData("c!.Pro.SomeTask.$$")]
 
    [InlineData("c!.M()!.SomeTask.$$")]
    [InlineData("c!.Pro!.SomeTask.$$")]
 
    [InlineData("c.M()!.Pro.SomeTask.$$")]
    [InlineData("c.Pro!.M().SomeTask.$$")]
 
    [InlineData("c.M()!.M().M()!.M().SomeTask.$$")]
    [InlineData("new C().M()!.Pro.M()!.M().SomeTask.$$")]
    public async Task TestDotAwaitAfterNullForgivingOperatorAccessChain(string nullForgivingAccess)
    {
        await VerifyKeywordAsync($$"""
            #nullable enable
 
            using System.Threading.Tasks;
            public class C
            {
                public Task? SomeTask => Task.CompletedTask;
 
                public C? Pro => this;
                public C? M() => this;
            }
 
            static class Program
            {
                public static async Task Main(params string[] args)
                {
                    var c =  args[1] == string.Empty ? new C() : null;
                    {{nullForgivingAccess}}
                }
            }
            """, dotAwait: true, dotAwaitf: true);
    }
 
    [Theory, CombinatorialData]
    [WorkItem("https://github.com/dotnet/roslyn/issues/58921")]
    public async Task TestInCastExpressionThatMightBeParenthesizedExpression(bool hasNewline)
    {
        var code = $$"""
            class C
            {
                void M()
                {
                    var data = (n$$) {{(hasNewline ? Environment.NewLine : string.Empty)}} M();
                }
            }
            """;
        if (hasNewline)
            await VerifyKeywordAsync(code);
        else
            await VerifyAbsenceAsync(code);
    }
}