diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs
index 1ef020bd47cd8..e6a96469724fe 100644
--- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs
+++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AbstractCSharpCompletionProviderTests.cs
@@ -32,7 +32,9 @@ protected static string GetMarkup(string source, LanguageVersion languageVersion
=> $@"
+
";
diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs
index 2f72a34b5aefa..9a807f476f1e9 100644
--- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs
+++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AwaitCompletionProviderTests.cs
@@ -24,19 +24,40 @@ public class AwaitCompletionProviderTests : AbstractCSharpCompletionProviderTest
{
internal override Type GetCompletionProviderType() => typeof(AwaitCompletionProvider);
+ private const string CompletionDisplayTextAwait = "await";
+ private const string CompletionDisplayTextAwaitAndConfigureAwait = "awaitf";
+
private async Task VerifyAbsenceAsync(string code)
{
- await VerifyItemIsAbsentAsync(code, "await");
+ await VerifyItemIsAbsentAsync(code, CompletionDisplayTextAwait);
+ await VerifyItemIsAbsentAsync(code, CompletionDisplayTextAwaitAndConfigureAwait);
}
- private async Task VerifyAbsenceAsync(string code, LanguageVersion languageVersion)
+ private async Task VerifyAbsenceAsync(string code, LanguageVersion languageVersion = LanguageVersion.Default)
{
- await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), "await");
+ await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwait);
+ await VerifyItemIsAbsentAsync(GetMarkup(code, languageVersion), CompletionDisplayTextAwaitAndConfigureAwait);
}
- private async Task VerifyKeywordAsync(string code, LanguageVersion languageVersion, string? inlineDescription = null)
+ private async Task VerifyKeywordAsync(string code, LanguageVersion languageVersion = LanguageVersion.Default, string? inlineDescription = null, bool dotAwait = false, bool dotAwaitf = false)
{
- await VerifyItemExistsAsync(GetMarkup(code, languageVersion), "await", glyph: (int)Glyph.Keyword, inlineDescription: inlineDescription);
+ 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]
@@ -58,7 +79,7 @@ class C
void F()
{
$$ }
-}", LanguageVersion.CSharp9, FeaturesResources.Make_containing_scope_async);
+}", LanguageVersion.CSharp9);
}
[Fact]
@@ -102,7 +123,7 @@ Task F()
{
var z = $$ }
}
-", LanguageVersion.CSharp9, FeaturesResources.Make_containing_scope_async);
+", LanguageVersion.CSharp9);
}
[Fact]
@@ -279,5 +300,602 @@ 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 someTask)
+ {
+ someTask.$$
+ }
+}
+", dotAwait: true, dotAwaitf: true);
+ }
+
+ [Fact]
+ public async Task TestDotAwaitSuggestAfterDotOnValueTask()
+ {
+ var valueTaskAssembly = typeof(ValueTask).Assembly.Location;
+ var markup = @$"
+
+
+ {valueTaskAssembly}
+
+using System.Threading.Tasks;
+
+class C
+{{
+ async Task F(ValueTask someTask)
+ {{
+ someTask.$$
+ }}
+}}
+
+
+
+";
+ 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 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 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);
+ }
+
+ [WorkItem(56245, "https://github.com/dotnet/roslyn/issues/56245")]
+ [Fact(Skip = "Fails because speculative binding can't figure out that local is a Task.")]
+ 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);
+ }
}
}
diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb
index 71ab0e7b76262..1f92cf4fc1764 100644
--- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb
+++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests_AwaitCompletion.vb
@@ -8,6 +8,44 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Public Class CSharpCompletionCommandHandlerTests_Await
+
+ Private Shared Function GetTestClassDocument(containerHasAsyncModifier As Boolean, testExpression As String) As XElement
+ Return _
+
+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<%= If(containerHasAsyncModifier, " async", "") %> Task Main(Task parameter)
+ {
+ Task local = Task.CompletedTask;
+ var c = new C();
+
+ <%= testExpression %>
+
+ Task LocalFunction() => Task.CompletedTask;
+ }
+}
+
+ End Function
+
Public Async Function AwaitCompletionAddsAsync_MethodDeclaration() As Task
Using state = TestStateFactory.CreateCSharpTestState(
@@ -24,7 +62,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -60,7 +98,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -96,7 +134,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -130,7 +168,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -164,7 +202,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -198,7 +236,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -232,7 +270,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -266,7 +304,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -301,7 +339,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -335,7 +373,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -368,7 +406,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -386,6 +424,431 @@ public class C
End Function
+ Public Async Function DotAwaitCompletionAddsAwaitInFrontOfExpression() As Task
+ Using state = TestStateFactory.CreateCSharpTestState(
+
+ )
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+using System.Threading.Tasks;
+
+public class C
+{
+ public static async Task Main()
+ {
+ await Task.CompletedTask
+ }
+}
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" await Task.CompletedTask", "")
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionAddsAwaitInFrontOfExpressionAndAsyncModifier() As Task
+ Using state = TestStateFactory.CreateCSharpTestState(
+
+ )
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+using System.Threading.Tasks;
+
+public class C
+{
+ public static async Task Main()
+ {
+ await Task.CompletedTask
+ }
+}
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" await Task.CompletedTask", "")
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionAddsAwaitInFrontOfExpressionAndAppendsConfigureAwait() As Task
+ Using state = TestStateFactory.CreateCSharpTestState(
+
+ )
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("await", "awaitf")
+ state.SendTypeChars("a")
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
+ state.SendTypeChars("f")
+ Await state.AssertSelectedCompletionItem(displayText:="awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+using System.Threading.Tasks;
+
+public class C
+{
+ public static async Task Main()
+ {
+ await Task.CompletedTask.ConfigureAwait(false)
+ }
+}
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" await Task.CompletedTask.ConfigureAwait(false)", "")
+ End Using
+ End Function
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Public Async Function DotAwaitCompletionAddsAwaitInFrontOfExpressionForDifferentExpressions(expression As String, committed As String) As Task
+ ' place await in front of expression
+ Using state = TestStateFactory.CreateCSharpTestState(GetTestClassDocument(containerHasAsyncModifier:=True, expression))
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal(GetTestClassDocument(containerHasAsyncModifier:=True, committed).Value.NormalizeLineEndings(), state.GetDocumentText().NormalizeLineEndings())
+ Await state.AssertLineTextAroundCaret($" {committed}", "")
+ End Using
+
+ ' place await in front of expression and make container async
+ Using state = TestStateFactory.CreateCSharpTestState(GetTestClassDocument(containerHasAsyncModifier:=False, expression))
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal(GetTestClassDocument(containerHasAsyncModifier:=True, committed).Value.NormalizeLineEndings(), state.GetDocumentText().NormalizeLineEndings())
+ Await state.AssertLineTextAroundCaret($" {committed}", "")
+ End Using
+
+ ' ConfigureAwait(false) starts here
+ committed += ".ConfigureAwait(false)"
+ ' place await in front of expression and append ConfigureAwait(false)
+ Using state = TestStateFactory.CreateCSharpTestState(GetTestClassDocument(containerHasAsyncModifier:=True, expression))
+ state.SendTypeChars("af")
+ Await state.AssertSelectedCompletionItem(displayText:="awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal(GetTestClassDocument(containerHasAsyncModifier:=True, committed).Value.NormalizeLineEndings(), state.GetDocumentText().NormalizeLineEndings())
+ Await state.AssertLineTextAroundCaret($" {committed}", "")
+ End Using
+
+ ' place await in front of expression, append ConfigureAwait(false) and make container async
+ Using state = TestStateFactory.CreateCSharpTestState(GetTestClassDocument(containerHasAsyncModifier:=False, expression))
+ state.SendTypeChars("af")
+ Await state.AssertSelectedCompletionItem(displayText:="awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal(GetTestClassDocument(containerHasAsyncModifier:=True, committed).Value.NormalizeLineEndings(), state.GetDocumentText().NormalizeLineEndings())
+ Await state.AssertLineTextAroundCaret($" {committed}", "")
+ End Using
+ End Function
+
+
+ Task.CompletedTask.$$",
+ "await Task.Run(async () => await Task.CompletedTask$$")>
+ Task.CompletedTask.$$",
+ "await Task.Run(async () => await Task.CompletedTask$$")>
+ Task.CompletedTask.aw$$",
+ "await Task.Run(async () => await Task.CompletedTask$$")>
+ Task.CompletedTask.aw$$",
+ "await Task.Run(async () => await Task.CompletedTask$$")>
+ someTask.$$",
+ "await Task.Run(async () => await someTask$$")>
+ someTask.$$",
+ "await Task.Run(async () => await someTask$$")>
+ someTask.$$);",
+ "await Task.Run(async () => await someTask$$);")>
+ someTask.$$);",
+ "await Task.Run(async () => await someTask$$);")>
+ someTask.aw$$);",
+ "await Task.Run(async () => await someTask$$);")>
+ someTask.aw$$);",
+ "await Task.Run(async () => await someTask$$);")>
+ {someTask.$$}",
+ "await Task.Run(async () => {await someTask$$}")>
+ {someTask.$$}",
+ "await Task.Run(async () => {await someTask$$}")>
+ {someTask.$$});",
+ "await Task.Run(async () => {await someTask$$});")>
+ {someTask.$$});",
+ "await Task.Run(async () => {await someTask$$});")>
+ someTask. $$ );",
+ "await Task.Run(async () => await someTask$$ );")>
+ someTask. $$ );",
+ "await Task.Run(async () => await someTask$$ );")>
+ someTask . $$ );",
+ "await Task.Run(async () => await someTask $$ );")>
+ someTask . $$ );",
+ "await Task.Run(async () => await someTask $$ );")>
+ someTask.$$.);",
+ "await Task.Run(async () => await someTask$$.);")>
+ someTask.$$.);",
+ "await Task.Run(async () => await someTask$$.);")>
+ await someTask).$$",
+ "await Task.Run(async () => await someTask)$$")>
+ Public Async Function DotAwaitCompletionAddsAwaitInFrontOfExpressionInLambdas(expression As String, committed As String) As Task
+
+ Dim document As XElement =
+using System.Threading.Tasks;
+
+static class Program
+{
+ static async Task Main()
+ {
+ var someTask = Task.CompletedTask;
+ <%= expression %>
+ }
+}
+
+ ' Test await completion
+ Using state = TestStateFactory.CreateCSharpTestState(document)
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("await", "awaitf")
+ state.SendTypeChars("a")
+ state.SendTypeChars("w")
+ Await state.AssertSelectedCompletionItem("await")
+
+ state.SendTab()
+ Dim committedAwait = committed
+ Dim committedCursorPosition = committedAwait.IndexOf("$$")
+ committedAwait = committedAwait.Replace("$$", "")
+ Assert.Equal($"
+using System.Threading.Tasks;
+
+static class Program
+{{
+ static async Task Main()
+ {{
+ var someTask = Task.CompletedTask;
+ {committedAwait}
+ }}
+}}
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret($" {committedAwait.Substring(0, committedCursorPosition)}", committedAwait.Substring(committedCursorPosition))
+ End Using
+
+ ' Test awaitf completion
+ Using state = TestStateFactory.CreateCSharpTestState(document)
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("await", "awaitf")
+ state.SendTypeChars("a")
+ state.SendTypeChars("f")
+ Await state.AssertSelectedCompletionItem("awaitf")
+
+ state.SendTab()
+ Dim committedAwaitf = committed
+ Dim committedCursorPosition = committedAwaitf.IndexOf("$$")
+ committedAwaitf = committedAwaitf.Replace("$$", "")
+ Dim committedAwaitfBeforeCursor = committedAwaitf.Substring(0, committedCursorPosition)
+ Dim committedAwaitfAfterCursor = committedAwaitf.Substring(committedCursorPosition)
+ Assert.Equal($"
+using System.Threading.Tasks;
+
+static class Program
+{{
+ static async Task Main()
+ {{
+ var someTask = Task.CompletedTask;
+ {committedAwaitfBeforeCursor}.ConfigureAwait(false){committedAwaitfAfterCursor}
+ }}
+}}
+", state.GetDocumentText())
+ ' the expected cursor position is right after the inserted .ConfigureAwait(false)
+ Await state.AssertLineTextAroundCaret($" {committedAwaitfBeforeCursor}.ConfigureAwait(false)", committedAwaitfAfterCursor)
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionOffersAwaitAfterConfigureAwaitInvocation() As Task
+ Using state = TestStateFactory.CreateCSharpTestState(
+
+ )
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("await")
+ Await state.AssertCompletionItemsDoNotContainAny("awaitf")
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+using System.Threading.Tasks;
+
+public class C
+{
+ public static async Task Main()
+ {
+ await Task.CompletedTask.ConfigureAwait(false)
+ }
+}
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" await Task.CompletedTask.ConfigureAwait(false)", "")
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionOffersAwaitBeforeConfigureAwaitInvocation() As Task
+ Using state = TestStateFactory.CreateCSharpTestState(
+
+ )
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("await", "awaitf")
+ state.SendTypeChars("af")
+ Await state.AssertSelectedCompletionItem(displayText:="awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+using System.Threading.Tasks;
+
+public class C
+{
+ public static async Task Main()
+ {
+ await Task.CompletedTask.ConfigureAwait(false)ConfigureAwait(false);
+ }
+}
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" await Task.CompletedTask.ConfigureAwait(false)", "ConfigureAwait(false);")
+ End Using
+ End Function
+
Public Async Function SyntaxIsLikeLocalFunction() As Task
Using state = TestStateFactory.CreateCSharpTestState(
@@ -402,7 +865,7 @@ public class C
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
state.SendTab()
@@ -419,5 +882,144 @@ public class C
", state.GetDocumentText())
End Using
End Function
+
+ Public Async Function DotAwaitCompletionInQueryInFirstFromClause() As Task
+ Using state = TestStateFactory.CreateCSharpTestState(
+
+ )
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("await", "awaitf")
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+using System.Linq;
+using System.Threading.Tasks;
+
+class C
+{
+ async Task F()
+ {
+ var arrayTask2 = Task.FromResult(new int[0]);
+ var z = from i1 in await arrayTask2
+ select i1;
+ }
+}
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" var z = from i1 in await arrayTask2", "")
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionInQueryInFirstFromClauseConfigureAwait() As Task
+ Using state = TestStateFactory.CreateCSharpTestState(
+
+ )
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("await", "awaitf")
+ state.SendTypeChars("af")
+ Await state.AssertSelectedCompletionItem(displayText:="awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+using System.Linq;
+using System.Threading.Tasks;
+
+class C
+{
+ async Task F()
+ {
+ var arrayTask2 = Task.FromResult(new int[0]);
+ var z = from i1 in await arrayTask2.ConfigureAwait(false)
+ select i1;
+ }
+}
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" var z = from i1 in await arrayTask2.ConfigureAwait(false)", "")
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionNullForgivingOperatorIsKept() As Task
+ Using state = TestStateFactory.CreateCSharpTestState(
+ 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;
+ c!.SomeTask!.$$;
+ }
+}
+]]>
+ )
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("await", "awaitf")
+ state.SendTypeChars("af")
+ Await state.AssertSelectedCompletionItem(displayText:="awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+#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;
+ await c!.SomeTask!.ConfigureAwait(false);
+ }
+}
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" await c!.SomeTask!.ConfigureAwait(false)", ";")
+ End Using
+ End Function
End Class
End Namespace
diff --git a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_AwaitCompletion.vb b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_AwaitCompletion.vb
index bedc2c7c497e9..93c42335f48d7 100644
--- a/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_AwaitCompletion.vb
+++ b/src/EditorFeatures/Test2/IntelliSense/VisualBasicCompletionCommandHandlerTests_AwaitCompletion.vb
@@ -8,6 +8,66 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
Public Class VisualBasicCompletionCommandHandlerTests_Await
+
+ Private Shared Function GetTestClassDocument(containerHasAsyncModifier As Boolean, testExpression As String) As XElement
+ Return _
+
+Imports System
+Imports System.Threading.Tasks
+
+Class C
+ Public ReadOnly Property Self As C
+ Get
+ Return Me
+ End Get
+ End Property
+
+ Public Field As Task = Task.CompletedTask
+
+ Public Function Method() As Task
+ Return Task.CompletedTask
+ End Function
+
+ Public ReadOnly Property [Property] As Task
+ Get
+ Return Task.CompletedTask
+ End Get
+ End Property
+
+ Default Public ReadOnly Property Item(ByVal i As Integer) As Task
+ Get
+ Return Task.CompletedTask
+ End Get
+ End Property
+
+ Public Function Func() As Func(Of Task)
+ Return Function() Task.CompletedTask
+ End Function
+End Class
+
+Module Program
+ Shared StaticField As Task = Task.CompletedTask
+
+ Private Shared ReadOnly Property StaticProperty As Task
+ Get
+ Return Task.CompletedTask
+ End Get
+ End Property
+
+ Private Function StaticMethod() As Task
+ Return Task.CompletedTask
+ End Function
+
+ Private<%= If(containerHasAsyncModifier, " Async", "") %> Function Main(ByVal parameter As Task) As Task
+ Dim local As Task = Task.CompletedTask
+ Dim c = New C()
+
+ <%= testExpression %>
+ End Function
+End Module
+
+ End Function
+
Public Async Function AwaitCompletionAddsAsync_FunctionDeclaration() As Task
Using state = TestStateFactory.CreateVisualBasicTestState(
@@ -22,7 +82,7 @@ End Class
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -49,7 +109,7 @@ End Class
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -78,7 +138,7 @@ End Class
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -111,7 +171,7 @@ End Class
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -142,7 +202,7 @@ End Class
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -171,7 +231,7 @@ End Class
]]>
)
state.SendTypeChars("aw")
- Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True, inlineDescription:=FeaturesResources.Make_containing_scope_async)
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
state.SendTab()
Assert.Equal("
@@ -363,5 +423,276 @@ End Class
", state.GetDocumentText())
End Using
End Function
+
+
+ Public Async Function DotAwaitCompletionAddsAwaitInFrontOfExpression() As Task
+ Using state = TestStateFactory.CreateVisualBasicTestState(
+
+ )
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+Imports System.Threading.Tasks
+
+Public Class C
+ Public Shared Async Function Main() As Task
+ Await Task.CompletedTask
+ End Function
+End Class
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" Await Task.CompletedTask", "")
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionAddsAwaitInFrontOfExpressionAndAsyncModifier() As Task
+ Using state = TestStateFactory.CreateVisualBasicTestState(
+
+ )
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+Imports System.Threading.Tasks
+
+Public Class C
+ Public Shared Async Function Main() As Task
+ Await Task.CompletedTask
+ End Function
+End Class
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" Await Task.CompletedTask", "")
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionAddsAwaitInFrontOfExpressionAndAppendsConfigureAwait() As Task
+ Using state = TestStateFactory.CreateVisualBasicTestState(
+
+ )
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("Await", "Awaitf")
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
+ state.SendTypeChars("f")
+ Await state.AssertSelectedCompletionItem(displayText:="Awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+Imports System.Threading.Tasks
+
+Public Class C
+ Public Shared Async Function Main() As Task
+ Await Task.CompletedTask.ConfigureAwait(False)
+ End Function
+End Class
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" Await Task.CompletedTask.ConfigureAwait(False)", "")
+ End Using
+ End Function
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Public Async Function DotAwaitCompletionAddsAwaitInFrontOfExpressionForDifferentExpressions(expression As String, committed As String) As Task
+ ' place await in front of expression
+ Using state = TestStateFactory.CreateVisualBasicTestState(GetTestClassDocument(containerHasAsyncModifier:=True, expression))
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal(GetTestClassDocument(containerHasAsyncModifier:=True, committed).Value.NormalizeLineEndings(), state.GetDocumentText().NormalizeLineEndings())
+ Await state.AssertLineTextAroundCaret($" {committed}", "")
+ End Using
+
+ ' place await in front of expression and make container async
+ Using state = TestStateFactory.CreateVisualBasicTestState(GetTestClassDocument(containerHasAsyncModifier:=False, expression))
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal(GetTestClassDocument(containerHasAsyncModifier:=True, committed).Value.NormalizeLineEndings(), state.GetDocumentText().NormalizeLineEndings())
+ Await state.AssertLineTextAroundCaret($" {committed}", "")
+ End Using
+
+ ' ConfigureAwait(false) starts here
+ committed += ".ConfigureAwait(False)"
+ ' place await in front of expression and append ConfigureAwait(false)
+ Using state = TestStateFactory.CreateVisualBasicTestState(GetTestClassDocument(containerHasAsyncModifier:=True, expression))
+ state.SendTypeChars("awf")
+ Await state.AssertSelectedCompletionItem(displayText:="Awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal(GetTestClassDocument(containerHasAsyncModifier:=True, committed).Value.NormalizeLineEndings(), state.GetDocumentText().NormalizeLineEndings())
+ Await state.AssertLineTextAroundCaret($" {committed}", "")
+ End Using
+
+ ' place await in front of expression, append ConfigureAwait(false) and make container async
+ Using state = TestStateFactory.CreateVisualBasicTestState(GetTestClassDocument(containerHasAsyncModifier:=False, expression))
+ state.SendTypeChars("awf")
+ Await state.AssertSelectedCompletionItem(displayText:="Awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal(GetTestClassDocument(containerHasAsyncModifier:=True, committed).Value.NormalizeLineEndings(), state.GetDocumentText().NormalizeLineEndings())
+ Await state.AssertLineTextAroundCaret($" {committed}", "")
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionInQueryInFirstFromClause() As Task
+ Using state = TestStateFactory.CreateVisualBasicTestState(
+
+ )
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("Await", "Awaitf")
+ state.SendTypeChars("aw")
+ Await state.AssertSelectedCompletionItem(displayText:="Await", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim arrayTask1 = Task.FromResult(new Integer() {})
+ Dim qry = From i in Await arrayTask1
+ End Function
+End Class
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" Dim qry = From i in Await arrayTask1", "")
+ End Using
+ End Function
+
+
+ Public Async Function DotAwaitCompletionInQueryInFirstFromClauseConfigureAwait() As Task
+ Using state = TestStateFactory.CreateVisualBasicTestState(
+
+ )
+ state.SendInvokeCompletionList()
+ Await state.AssertCompletionItemsContainAll("Await", "Awaitf")
+ state.SendTypeChars("af")
+ Await state.AssertSelectedCompletionItem(displayText:="Awaitf", isHardSelected:=True)
+
+ state.SendTab()
+ Assert.Equal("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim arrayTask1 = Task.FromResult(new Integer() {})
+ Dim qry = From i in Await arrayTask1.ConfigureAwait(False)
+ End Function
+End Class
+", state.GetDocumentText())
+ Await state.AssertLineTextAroundCaret(" Dim qry = From i in Await arrayTask1.ConfigureAwait(False)", "")
+ End Using
+ End Function
End Class
End Namespace
diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AwaitCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AwaitCompletionProviderTests.vb
index bcc90954bac5e..7465f4196ca46 100644
--- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AwaitCompletionProviderTests.vb
+++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/AwaitCompletionProviderTests.vb
@@ -12,42 +12,57 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.Complet
Return GetType(AwaitCompletionProvider)
End Function
+ Protected Async Function VerifyAwaitKeyword(markup As String, Optional dotAwait As Boolean = False, Optional dotAwaitf As Boolean = False) As Task
+ Dim expectedDescription = If(dotAwait, GetDescription("Await", FeaturesResources.Await_the_preceding_expression), GetDescription("Await", FeaturesResources.Asynchronously_waits_for_the_task_to_finish))
+ Await VerifyItemExistsAsync(markup, "Await", expectedDescriptionOrNull:=expectedDescription)
+ If dotAwaitf Then
+ expectedDescription = String.Format(FeaturesResources.Await_the_preceding_expression_and_add_ConfigureAwait_0, "False")
+ Await VerifyItemExistsAsync(markup, "Awaitf", expectedDescriptionOrNull:=expectedDescription)
+ Else
+ Await VerifyItemIsAbsentAsync(markup, "Awaitf")
+ End If
+ End Function
+
+ Private Shared Function GetDescription(ByVal keyword As String, ByVal tooltip As String) As String
+ Return $"{String.Format(FeaturesResources._0_Keyword, keyword)}{vbCrLf}{tooltip}"
+ End Function
+
- Public Sub InSynchronousMethodTest()
- VerifyItemExistsAsync("
+ Public Async Function InSynchronousMethodTest() As Task
+ Await VerifyAwaitKeyword("
Class C
Sub Goo()
Dim z = $$
End Sub
End Class
-", "Await")
- End Sub
+")
+ End Function
- Public Sub InMethodStatementTest()
- VerifyItemExistsAsync("
+ Public Async Function InMethodStatementTest() As Task
+ Await VerifyAwaitKeyword("
Class C
Async Sub Goo()
$$
End Sub
End Class
-", "Await")
- End Sub
+")
+ End Function
- Public Sub InMethodExpressionTest()
- VerifyItemExistsAsync("
+ Public Async Function InMethodExpressionTest() As Task
+ Await VerifyAwaitKeyword("
Class C
Async Sub Goo()
Dim z = $$
End Sub
End Class
-", "Await")
- End Sub
+")
+ End Function
- Public Sub NotInCatchTest()
- VerifyItemExistsAsync("
+ Public Async Function NotInCatchTest() As Task
+ Await VerifyNoItemsExistAsync("
Class C
Async Sub Goo()
Try
@@ -57,12 +72,12 @@ Class C
End Sub
End Class
-", "Await")
- End Sub
+")
+ End Function
- Public Sub NotInCatchExceptionFilterTest()
- VerifyNoItemsExistAsync("
+ Public Async Function NotInCatchExceptionFilterTest() As Task
+ Await VerifyNoItemsExistAsync("
Class C
Async Sub Goo()
Try
@@ -72,11 +87,11 @@ Class C
End Sub
End Class
")
- End Sub
+ End Function
- Public Sub InCatchNestedDelegateTest()
- VerifyItemExistsAsync("
+ Public Async Function InCatchNestedDelegateTest() As Task
+ Await VerifyAwaitKeyword("
Class C
Async Sub Goo()
Try
@@ -86,12 +101,12 @@ Class C
End Sub
End Class
-", "Await")
- End Sub
+")
+ End Function
- Public Sub NotInFinallyTest()
- VerifyItemExistsAsync("
+ Public Async Function NotInFinallyTest() As Task
+ Await VerifyNoItemsExistAsync("
Class C
Async Sub Goo()
Try
@@ -101,12 +116,12 @@ Class C
End Sub
End Class
-", "Await")
- End Sub
+")
+ End Function
- Public Sub NotInSyncLockTest()
- VerifyItemExistsAsync("
+ Public Async Function NotInSyncLockTest() As Task
+ Await VerifyNoItemsExistAsync("
Class C
Async Sub Goo()
SyncLock True
@@ -114,7 +129,471 @@ Class C
End SyncLock
End Sub
End Class
-", "Await")
- End Sub
+")
+ End Function
+
+
+ Public Async Function DotAwaitInAsyncSub() As Task
+ Await VerifyAwaitKeyword("
+Imports System.Threading.Tasks
+
+Class C
+ Async Sub Goo()
+ Task.CompletedTask.$$
+ End Sub
+End Class
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+ Public Async Function DotAwaitSuggestAfterDotOnTaskOfT() As Task
+ Await VerifyAwaitKeyword("
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F(ByVal someTask As Task(Of Integer)) As Task
+ someTask.$$
+ End Function
+End Class
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+ Public Async Function DotAwaitSuggestAfterDotOnValueTask() As Task
+ Dim valueTaskAssembly = GetType(ValueTask).Assembly.Location
+
+ Await VerifyAwaitKeyword($"
+
+
+ {valueTaskAssembly}
+
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F(ByVal someTask As ValueTask) As Task
+ someTask.$$
+ End Function
+End Class
+
+
+
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+ Public Async Function DotAwaitSuggestAfterDotOnCustomAwaitable() As Task
+ Await VerifyAwaitKeyword("
+Imports System
+Imports System.Runtime.CompilerServices
+Imports System.Threading.Tasks
+
+Public Class DummyAwaiter
+ Implements INotifyCompletion
+
+ Public ReadOnly Property IsCompleted As Boolean
+ Get
+ Return True
+ End Get
+ End Property
+
+ Public Sub OnCompleted(ByVal continuation As Action)
+ Return continuation()
+ End Sub
+
+ Public Sub GetResult()
+ End Sub
+End Class
+
+Public Class CustomAwaitable
+ Public Function GetAwaiter() As DummyAwaiter
+ Return New DummyAwaiter()
+ End Function
+End Class
+
+Module Program
+ Private Async Function Main() As Task
+ Dim awaitable = New CustomAwaitable()
+ awaitable.$$
+ End Function
+End Module
+", dotAwait:=True)
+ End Function
+
+
+ Public Async Function DotAwaitSuggestAfterDotBeforeType() As Task
+ Await VerifyAwaitKeyword("
+Imports System
+Imports System.Threading.Tasks
+
+Module Program
+ Private Async Function Main(ByVal someTask As Task) As Task
+ someTask.$$
+ Dim i As Int32 = 0
+ End Function
+End Module
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+ Public Async Function DotAwaitSuggestAfterDotBeforeAnotherAwait() As Task
+ Await VerifyAwaitKeyword("
+Imports System
+Imports System.Threading.Tasks
+
+Module Program
+ Private Async Function Main(ByVal someTask As Task) As Task
+ someTask.$$
+ Await Test()
+ End Function
+
+ Private Async Function Test() As Task
+ End Function
+End Module
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Public Async Function DotAwaitSuggestAfterDifferentExpressions(ByVal expression As String) As Task
+ Dim t = If(True, expression, expression).Length
+ Await VerifyAwaitKeyword($"
+Imports System
+Imports System.Threading.Tasks
+
+Class C
+ Public ReadOnly Property Self As C
+ Get
+ Return Me
+ End Get
+ End Property
+
+ Public Field As Task = Task.CompletedTask
+
+ Public Function Method() As Task
+ Return Task.CompletedTask
+ End Function
+
+ Public ReadOnly Property [Property] As Task
+ Get
+ Return Task.CompletedTask
+ End Get
+ End Property
+
+ Default Public ReadOnly Property Item(ByVal i As Integer) As Task
+ Get
+ Return Task.CompletedTask
+ End Get
+ End Property
+
+ Public Function Func() As Func(Of Task)
+ Return Function() Task.CompletedTask
+ End Function
+End Class
+
+Module Program
+ Shared StaticField As Task = Task.CompletedTask
+
+ Private Shared ReadOnly Property StaticProperty As Task
+ Get
+ Return Task.CompletedTask
+ End Get
+ End Property
+
+ Private Function StaticMethod() As Task
+ Return Task.CompletedTask
+ End Function
+
+ Private Async Function Main(ByVal parameter As Task) As Task
+ Dim local As Task = Task.CompletedTask
+ Dim c = New C()
+
+ {expression}
+
+ End Function
+End Module
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+
+
+
+
+
+
+ Public Async Function DotAwaitSuggestInLambdas(lambda As String) As Task
+ Await VerifyAwaitKeyword($"
+Imports System.Threading.Tasks
+
+Module Program
+ Private Async Function Main() As Task
+ Dim someTask = Task.CompletedTask
+
+ {lambda}
+ End Function
+End Module
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+ Public Async Function DotAwaitNotAfterDotOnTaskIfAlreadyAwaited() As Task
+ Await VerifyNoItemsExistAsync("
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F(ByVal someTask As Task) As Task
+ Await someTask.$$
+ End Function
+End Class
+")
+ End Function
+
+
+ Public Async Function DotAwaitNotAfterTaskType() As Task
+ Await VerifyNoItemsExistAsync("
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Task.$$
+ End Function
+End Class
+")
+ End Function
+
+
+ Public Async Function DotAwaitNotInLock() As Task
+ Await VerifyNoItemsExistAsync("
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F(ByVal someTask As Task) As Task
+ SyncLock Me
+ someTask.$$
+ End SyncLock
+ End Function
+End Class
+")
+ End Function
+
+
+ Public Async Function DotAwaitQueryNotInSelect() As Task
+ Await VerifyNoItemsExistAsync("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim z = From t In {Task.CompletedTask} Select t.$$
+ End Function
+End Class
+")
+ End Function
+
+
+ Public Async Function DotAwaitQueryInFirstFromClause() As Task
+ Await VerifyAwaitKeyword("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim arrayTask1 = Task.FromResult(new Integer() {})
+ Dim qry = From i In arrayTask1.$$
+ End Function
+End Class
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+ Public Async Function DotAwaitQueryNotInSecondFromClause_1() As Task
+ Await VerifyNoItemsExistAsync("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim array1 = new Integer() {}
+ Dim arrayTask2 = Task.FromResult(new Integer() {})
+
+ Dim qry = From i1 In array1
+ From i2 In arrayTask2.$$
+ End Function
+End Class
+")
+ End Function
+
+
+ Public Async Function DotAwaitQueryNotInSecondFromClause_2() As Task
+ Await VerifyNoItemsExistAsync("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim array1 = new Integer() {}
+ Dim arrayTask2 = Task.FromResult(new Integer() {})
+
+ Dim qry = From i1 In array1, i2 In arrayTask2.$$
+ End Function
+End Class
+")
+ End Function
+
+
+ Public Async Function DotAwaitQueryNotInSecondFromClause_3() As Task
+ Await VerifyNoItemsExistAsync("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim array1 = new Integer() {}
+ Dim arrayTask2 = Task.FromResult(new Integer() {})
+
+ Dim qry = From i1 In array1
+ From i2 In array1, i2 In arrayTask2.$$
+ End Function
+End Class
+")
+ End Function
+
+
+ Public Async Function DotAwaitQueryNotInSecondFromClause_4() As Task
+ Await VerifyNoItemsExistAsync("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim array1 = new Integer() {}
+ Dim arrayTask2 = Task.FromResult(new Integer() {})
+
+ Dim qry = From i1 In array1, i2 In array1
+ From i3 In arrayTask2.$$
+ End Function
+End Class
+")
+ End Function
+
+
+ Public Async Function DotAwaitQueryInJoinClause() As Task
+ Await VerifyAwaitKeyword("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim array1 = new Integer() {}
+ Dim arrayTask2 = Task.FromResult(new Integer() {})
+
+ Dim qry = From i1 In array1
+ Join i2 In arrayTask2.$$
+ On i1 Equals i2
+ End Function
+End Class
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+ Public Async Function DotAwaitQueryInGroupJoinClause() As Task
+ Await VerifyAwaitKeyword("
+Imports System.Linq
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F() As Task
+ Dim array1 = new Integer() {}
+ Dim arrayTask2 = Task.FromResult(new Integer() {})
+
+ Dim qry = From i1 In array1
+ Group Join i2 In arrayTask2.$$
+ On i1 Equals i2 Into g = Group
+ End Function
+End Class
+", dotAwait:=True, dotAwaitf:=True)
+ End Function
+
+
+ Public Async Function DotAwaitNotAfterConditionalAccessOfTaskMembers() As Task
+ Await VerifyNoItemsExistAsync("
+Imports System.Threading.Tasks
+
+Class C
+ Private Async Function F(ByVal someTask As Task) As Task
+ someTask?.$$
+ End Function
+End Class
+")
+ End Function
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Public Async Function DotAwaitNotAfterDotInConditionalAccessChain(ByVal conditionalAccess As String) As Task
+ Await VerifyNoItemsExistAsync($"
+Imports System.Threading.Tasks
+
+Public Class C
+ Public ReadOnly Property SomeTask As Task
+ Get
+ Return Task.CompletedTask
+ End Get
+ End Property
+
+ Public ReadOnly Property Pro As C
+ Get
+ Return Me
+ End Get
+ End Property
+
+ Public Function M() As C
+ Return Me
+ End Function
+End Class
+
+Module Program
+ Async Function Main() As Task
+ Dim c = New C()
+
+ If True Then
+ {conditionalAccess}
+ End If
+ End Function
+End Module
+")
+ End Function
End Class
End Namespace
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs
index 4825d121acdfa..8b295ea792da8 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/AwaitCompletionProvider.cs
@@ -5,12 +5,16 @@
using System;
using System.Collections.Immutable;
using System.Composition;
+using System.Linq;
+using System.Threading;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Extensions;
+using Microsoft.CodeAnalysis.CSharp.LanguageServices;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
@@ -23,6 +27,7 @@ internal sealed class AwaitCompletionProvider : AbstractAwaitCompletionProvider
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public AwaitCompletionProvider()
+ : base(CSharpSyntaxFacts.Instance)
{
}
@@ -31,7 +36,7 @@ public AwaitCompletionProvider()
///
/// Gets the span start where async keyword should go.
///
- private protected override int GetSpanStart(SyntaxNode declaration)
+ protected override int GetSpanStart(SyntaxNode declaration)
{
return declaration switch
{
@@ -47,22 +52,65 @@ private protected override int GetSpanStart(SyntaxNode declaration)
};
}
- private protected override SyntaxNode? GetAsyncSupportingDeclaration(SyntaxToken token)
+ protected override SyntaxNode? GetAsyncSupportingDeclaration(SyntaxToken token)
{
- var node = token.GetAncestor(node => node.IsAsyncSupportingFunctionSyntax());
- // For local functions, make sure we either have arrow token or open brace token.
- // Consider the following scenario:
- // void Method()
- // {
- // aw$$ AnotherMethodCall(); // NOTE: Here, the compiler produces LocalFunctionStatementSyntax.
- // }
- // For this case, we're interested in putting async in front of Method()
- if (node is LocalFunctionStatementSyntax { ExpressionBody: null, Body: null })
+ // In a case like
+ // someTask.$$
+ // await Test();
+ // someTask.await Test() is parsed as a local function statement.
+ // We skip this and look further up in the hierarchy.
+ var parent = token.Parent;
+ if (parent == null)
+ return null;
+
+ if (parent is QualifiedNameSyntax { Parent: LocalFunctionStatementSyntax localFunction } qualifiedName &&
+ localFunction.ReturnType == qualifiedName)
{
- return node.Parent?.FirstAncestorOrSelf(node => node.IsAsyncSupportingFunctionSyntax());
+ parent = localFunction;
}
- return node;
+ return parent.Ancestors().FirstOrDefault(node => node.IsAsyncSupportingFunctionSyntax());
+ }
+
+ protected override SyntaxNode? GetExpressionToPlaceAwaitInFrontOf(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
+ {
+ var dotToken = GetDotTokenLeftOfPosition(syntaxTree, position, cancellationToken);
+ return dotToken?.Parent switch
+ {
+ // Don't support conditional access someTask?.$$ or c?.TaskReturning().$$ because there is no good completion until
+ // await? is supported by the language https://github.com/dotnet/csharplang/issues/35
+ MemberAccessExpressionSyntax memberAccess => memberAccess.GetParentConditionalAccessExpression() is null ? memberAccess : null,
+ // someTask.$$.
+ RangeExpressionSyntax range => range.LeftOperand,
+ // special cases, where parsing is misleading. Such cases are handled in GetTypeSymbolOfExpression.
+ QualifiedNameSyntax qualifiedName => qualifiedName.Left,
+ _ => null,
+ };
+ }
+
+ protected override SyntaxToken? GetDotTokenLeftOfPosition(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
+ => CompletionUtilities.GetDotTokenLeftOfPosition(syntaxTree, position, cancellationToken);
+
+ protected override ITypeSymbol? GetTypeSymbolOfExpression(SemanticModel semanticModel, SyntaxNode potentialAwaitableExpression, CancellationToken cancellationToken)
+ {
+ if (potentialAwaitableExpression is MemberAccessExpressionSyntax memberAccess)
+ {
+ var memberAccessExpression = memberAccess.Expression.WalkDownParentheses();
+ // In cases like Task.$$ semanticModel.GetTypeInfo returns Task, but
+ // we don't want to suggest await here. We look up the symbol of the "Task" part
+ // and return null if it is a NamedType.
+ var symbol = semanticModel.GetSymbolInfo(memberAccessExpression, cancellationToken).Symbol;
+ return symbol is ITypeSymbol ? null : semanticModel.GetTypeInfo(memberAccessExpression, cancellationToken).Type;
+ }
+ else if (potentialAwaitableExpression is ExpressionSyntax expression &&
+ expression.ShouldNameExpressionBeTreatedAsExpressionInsteadOfType(semanticModel, out _, out var container))
+ {
+ return container;
+ }
+ else
+ {
+ return semanticModel.GetTypeInfo(potentialAwaitableExpression, cancellationToken).Type;
+ }
}
}
}
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs
index eadd8f3634996..bb28b5def1de1 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs
@@ -5,6 +5,7 @@
#nullable disable
using System.Collections.Immutable;
+using System.Threading;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -41,6 +42,22 @@ public static bool TreatAsDot(SyntaxToken token, int characterPosition)
return false;
}
+ public static SyntaxToken? GetDotTokenLeftOfPosition(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
+ {
+ var tokenOnLeft = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken, includeSkipped: true);
+ var dotToken = tokenOnLeft.GetPreviousTokenIfTouchingWord(position);
+
+ // Has to be a . or a .. token
+ if (!TreatAsDot(dotToken, position - 1))
+ return null;
+
+ // don't want to trigger after a number. All other cases after dot are ok.
+ if (dotToken.GetPreviousToken().Kind() == SyntaxKind.NumericLiteralToken)
+ return null;
+
+ return dotToken;
+ }
+
internal static bool IsTriggerCharacter(SourceText text, int characterPosition, OptionSet options)
{
var ch = text[characterPosition];
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs
index 9446f42e90ddb..31f93219fdee4 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider.cs
@@ -73,17 +73,9 @@ private static string SortText(int sortingGroupIndex, string sortTextSymbolPart)
///
/// Gets the dot-like token we're after, and also the start of the expression we'd want to place any text before.
///
- private static (SyntaxToken dotLikeToken, int expressionStart) GetDotAndExpressionStart(SyntaxNode root, int position)
+ private static (SyntaxToken dotLikeToken, int expressionStart) GetDotAndExpressionStart(SyntaxNode root, int position, CancellationToken cancellationToken)
{
- var tokenOnLeft = root.FindTokenOnLeftOfPosition(position, includeSkipped: true);
- var dotToken = tokenOnLeft.GetPreviousTokenIfTouchingWord(position);
-
- // Has to be a . or a .. token
- if (!CompletionUtilities.TreatAsDot(dotToken, position - 1))
- return default;
-
- // don't want to trigger after a number. All other cases after dot are ok.
- if (dotToken.GetPreviousToken().Kind() == SyntaxKind.NumericLiteralToken)
+ if (CompletionUtilities.GetDotTokenLeftOfPosition(root.SyntaxTree, position, cancellationToken) is not SyntaxToken dotToken)
return default;
// if we have `.Name`, we want to get the parent member-access of that to find the starting position.
@@ -111,7 +103,7 @@ public override async Task ProvideCompletionsAsync(CompletionContext context)
return;
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
- var dotAndExprStart = GetDotAndExpressionStart(root, position);
+ var dotAndExprStart = GetDotAndExpressionStart(root, position, cancellationToken);
if (dotAndExprStart == default)
return;
@@ -190,7 +182,7 @@ private static async Task ReplaceTextAfterOperatorAsync(
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var position = SymbolCompletionItem.GetContextPosition(item);
- var (dotToken, _) = GetDotAndExpressionStart(root, position);
+ var (dotToken, _) = GetDotAndExpressionStart(root, position, cancellationToken);
var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken
? dotToken.GetPreviousToken()
: (SyntaxToken?)null;
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs
index cdb6162ec51f2..4301a851975bc 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Conversions.cs
@@ -72,7 +72,7 @@ private static async Task GetConversionChangeAsync(
var position = SymbolCompletionItem.GetContextPosition(item);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
- var (dotToken, _) = GetDotAndExpressionStart(root, position);
+ var (dotToken, _) = GetDotAndExpressionStart(root, position, cancellationToken);
var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken
? dotToken.GetPreviousToken()
diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs
index 4008c39cec955..9691ebaae991c 100644
--- a/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs
+++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/OperatorsAndIndexer/UnnamedSymbolCompletionProvider_Operators.cs
@@ -133,7 +133,7 @@ private async Task GetOperatorChangeAsync(
{
var position = SymbolCompletionItem.GetContextPosition(item);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
- var (dotLikeToken, expressionStart) = GetDotAndExpressionStart(root, position);
+ var (dotLikeToken, expressionStart) = GetDotAndExpressionStart(root, position, cancellationToken);
// Place the new operator before the expression, and delete the dot.
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs
index 5f76e2d450eb1..f31fd9ee332f5 100644
--- a/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs
+++ b/src/Features/Core/Portable/Completion/Providers/AbstractAwaitCompletionProvider.cs
@@ -2,6 +2,8 @@
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
@@ -12,6 +14,7 @@
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
@@ -21,12 +24,56 @@ namespace Microsoft.CodeAnalysis.Completion.Providers
///
internal abstract class AbstractAwaitCompletionProvider : LSPCompletionProvider
{
+ private const string AwaitCompletionTargetTokenPosition = nameof(AwaitCompletionTargetTokenPosition);
+ private const string AppendConfigureAwait = nameof(AppendConfigureAwait);
+ private const string MakeContainerAsync = nameof(MakeContainerAsync);
+
+ ///
+ /// If 'await' should be placed at the current position. If not present, it means to add 'await' prior
+ /// to the preceding expression.
+ ///
+ private const string AddAwaitAtCurrentPosition = nameof(AddAwaitAtCurrentPosition);
+
+ protected enum DotAwaitContext
+ {
+ None,
+ AwaitOnly,
+ AwaitAndConfigureAwait,
+ }
+
+ private readonly string _awaitKeyword;
+ private readonly string _awaitfDisplayText;
+ private readonly string _awaitfFilterText;
+ private readonly string _falseKeyword;
+
+ protected AbstractAwaitCompletionProvider(ISyntaxFacts syntaxFacts)
+ {
+ _falseKeyword = syntaxFacts.GetText(syntaxFacts.SyntaxKinds.FalseKeyword);
+ _awaitKeyword = syntaxFacts.GetText(syntaxFacts.SyntaxKinds.AwaitKeyword);
+ _awaitfDisplayText = $"{_awaitKeyword}f";
+ _awaitfFilterText = $"{_awaitKeyword}F"; // Uppercase F to select "awaitf" if "af" is written.
+ }
+
///
/// Gets the span start where async keyword should go.
///
- private protected abstract int GetSpanStart(SyntaxNode declaration);
+ protected abstract int GetSpanStart(SyntaxNode declaration);
- private protected abstract SyntaxNode? GetAsyncSupportingDeclaration(SyntaxToken token);
+ protected abstract SyntaxNode? GetAsyncSupportingDeclaration(SyntaxToken token);
+
+ protected abstract ITypeSymbol? GetTypeSymbolOfExpression(SemanticModel semanticModel, SyntaxNode potentialAwaitableExpression, CancellationToken cancellationToken);
+ protected abstract SyntaxNode? GetExpressionToPlaceAwaitInFrontOf(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken);
+ protected abstract SyntaxToken? GetDotTokenLeftOfPosition(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken);
+
+ private static bool IsConfigureAwaitable(Compilation compilation, ITypeSymbol symbol)
+ {
+ var originalDefinition = symbol.OriginalDefinition;
+ return
+ originalDefinition.Equals(compilation.TaskOfTType()) ||
+ originalDefinition.Equals(compilation.TaskType()) ||
+ originalDefinition.Equals(compilation.ValueTaskOfTType()) ||
+ originalDefinition.Equals(compilation.ValueTaskType());
+ }
public sealed override async Task ProvideCompletionsAsync(CompletionContext context)
{
@@ -34,75 +81,181 @@ public sealed override async Task ProvideCompletionsAsync(CompletionContext cont
var position = context.Position;
var cancellationToken = context.CancellationToken;
var syntaxFacts = document.GetRequiredLanguageService();
- var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
+ var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
if (syntaxFacts.IsInNonUserCode(syntaxTree, position, cancellationToken))
- {
return;
- }
var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false);
var syntaxContext = document.GetRequiredLanguageService().CreateContext(document, semanticModel, position, cancellationToken);
- if (!syntaxContext.IsAwaitKeywordContext())
- {
+
+ var isAwaitKeywordContext = syntaxContext.IsAwaitKeywordContext();
+ var dotAwaitContext = GetDotAwaitKeywordContext(syntaxContext, cancellationToken);
+ if (!isAwaitKeywordContext && dotAwaitContext == DotAwaitContext.None)
return;
+
+ var token = syntaxContext.TargetToken;
+ var declaration = GetAsyncSupportingDeclaration(token);
+
+ var properties = ImmutableDictionary.Empty
+ .Add(AwaitCompletionTargetTokenPosition, token.SpanStart.ToString());
+
+ var makeContainerAsync = declaration is not null && !SyntaxGenerator.GetGenerator(document).GetModifiers(declaration).IsAsync;
+ if (makeContainerAsync)
+ properties = properties.Add(MakeContainerAsync, string.Empty);
+
+ if (isAwaitKeywordContext)
+ {
+ properties = properties.Add(AddAwaitAtCurrentPosition, string.Empty);
+ context.AddItem(CreateCompletionItem(
+ properties, _awaitKeyword, _awaitKeyword,
+ FeaturesResources.Asynchronously_waits_for_the_task_to_finish,
+ isComplexTextEdit: makeContainerAsync));
+ }
+ else
+ {
+ Contract.ThrowIfTrue(dotAwaitContext == DotAwaitContext.None);
+
+ // add the `await` option that will remove the dot and add `await` to the start of the expression.
+ context.AddItem(CreateCompletionItem(
+ properties, _awaitKeyword, _awaitKeyword,
+ FeaturesResources.Await_the_preceding_expression,
+ isComplexTextEdit: true));
+
+ if (dotAwaitContext == DotAwaitContext.AwaitAndConfigureAwait)
+ {
+ // add the `awaitf` option to do the same, but also add .ConfigureAwait(false);
+ properties = properties.Add(AppendConfigureAwait, string.Empty);
+ context.AddItem(CreateCompletionItem(
+ properties, _awaitfDisplayText, _awaitfFilterText,
+ string.Format(FeaturesResources.Await_the_preceding_expression_and_add_ConfigureAwait_0, _falseKeyword),
+ isComplexTextEdit: true));
+ }
}
- var generator = SyntaxGenerator.GetGenerator(document);
- var syntaxKinds = document.GetRequiredLanguageService();
- var completionItem = GetCompletionItem(syntaxContext.TargetToken, generator, syntaxKinds, syntaxFacts);
- context.AddItem(completionItem);
+ return;
+
+ static CompletionItem CreateCompletionItem(
+ ImmutableDictionary completionProperties, string displayText, string filterText, string tooltip, bool isComplexTextEdit)
+ {
+ var appendConfigureAwait = completionProperties.ContainsKey(AppendConfigureAwait);
+
+ var description = appendConfigureAwait
+ ? ImmutableArray.Create(new SymbolDisplayPart(SymbolDisplayPartKind.Text, null, tooltip))
+ : RecommendedKeyword.CreateDisplayParts(displayText, tooltip);
+
+ return CommonCompletionItem.Create(
+ displayText: displayText,
+ displayTextSuffix: "",
+ filterText: filterText,
+ rules: CompletionItemRules.Default,
+ glyph: Glyph.Keyword,
+ description: description,
+ isComplexTextEdit: isComplexTextEdit,
+ properties: completionProperties);
+ }
}
- public sealed override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey = null, CancellationToken cancellationToken = default)
+ public sealed override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken)
{
- // IsComplexTextEdit is true when we want to add async to the container.
+ // IsComplexTextEdit is true when we want to add async to the container or place await in front of the expression.
if (!item.IsComplexTextEdit)
- {
return await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false);
+
+ using var _ = ArrayBuilder.GetInstance(out var builder);
+
+ var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
+ var syntaxFacts = document.GetRequiredLanguageService();
+ var syntaxKinds = syntaxFacts.SyntaxKinds;
+ var properties = item.Properties;
+
+ if (properties.ContainsKey(MakeContainerAsync))
+ {
+ var root = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
+ var tokenPosition = int.Parse(properties[AwaitCompletionTargetTokenPosition]);
+ var declaration = GetAsyncSupportingDeclaration(root.FindToken(tokenPosition));
+ if (declaration is null)
+ {
+ // IsComplexTextEdit should only be true when GetAsyncSupportingDeclaration returns non-null.
+ // This is ensured by the ShouldMakeContainerAsync overrides.
+ Debug.Fail("Expected non-null value for declaration.");
+ return await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false);
+ }
+
+ builder.Add(new TextChange(new TextSpan(GetSpanStart(declaration), 0), syntaxFacts.GetText(syntaxKinds.AsyncKeyword) + " "));
}
- var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
- var declaration = GetAsyncSupportingDeclaration(root.FindToken(item.Span.Start));
- if (declaration is null)
+ if (properties.ContainsKey(AddAwaitAtCurrentPosition))
{
- // IsComplexTextEdit should only be true when GetAsyncSupportingDeclaration returns non-null.
- // This is ensured by the ShouldMakeContainerAsync overrides.
- Debug.Assert(false, "Expected non-null value for declaration.");
- return await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false);
+ builder.Add(new TextChange(item.Span, _awaitKeyword));
}
+ else
+ {
+ var position = item.Span.Start;
+ var dotToken = GetDotTokenLeftOfPosition(syntaxTree, position, cancellationToken);
+ var expr = GetExpressionToPlaceAwaitInFrontOf(syntaxTree, position, cancellationToken);
- var syntaxFacts = document.GetRequiredLanguageService();
- var syntaxKinds = document.GetRequiredLanguageService();
+ Contract.ThrowIfFalse(dotToken.HasValue);
+ Contract.ThrowIfNull(expr);
- using var _ = ArrayBuilder.GetInstance(out var builder);
- builder.Add(new TextChange(new TextSpan(GetSpanStart(declaration), 0), syntaxFacts.GetText(syntaxKinds.AsyncKeyword) + " "));
- builder.Add(new TextChange(item.Span, item.DisplayText));
+ // place "await" in front of expr
+ builder.Add(new TextChange(new TextSpan(expr.SpanStart, 0), _awaitKeyword + " "));
+
+ // remove any text after dot, including the dot token and optionally append .ConfigureAwait(false)
+ var replacementText = properties.ContainsKey(AppendConfigureAwait)
+ ? $".{nameof(Task.ConfigureAwait)}({_falseKeyword})"
+ : "";
+
+ builder.Add(new TextChange(TextSpan.FromBounds(dotToken.Value.SpanStart, item.Span.End), replacementText));
+ }
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var newText = text.WithChanges(builder);
return CompletionChange.Create(Utilities.Collapse(newText, builder.ToImmutableArray()));
}
-#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods
- private protected bool ShouldMakeContainerAsync(SyntaxToken token, SyntaxGenerator generator)
-#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods
+ ///
+ /// Should be offered, if left of the dot at position is an awaitable expression?
+ ///
+ /// someTask.$$ // Suggest await completion
+ /// await someTask.$$ // Don't suggest await completion
+ ///
+ ///
+ ///
+ /// , if await can not be suggested for the expression left of the dot.
+ /// , if await should be suggested for the expression left of the dot, but ConfigureAwait(false) not.
+ /// , if await should be suggested for the expression left of the dot and ConfigureAwait(false).
+ ///
+ private DotAwaitContext GetDotAwaitKeywordContext(SyntaxContext syntaxContext, CancellationToken cancellationToken)
{
- var declaration = GetAsyncSupportingDeclaration(token);
- return declaration is not null && !generator.GetModifiers(declaration).IsAsync;
- }
+ var position = syntaxContext.Position;
+ var syntaxTree = syntaxContext.SyntaxTree;
+ var potentialAwaitableExpression = GetExpressionToPlaceAwaitInFrontOf(syntaxTree, position, cancellationToken);
+ if (potentialAwaitableExpression is not null)
+ {
+ var parentOfAwaitable = potentialAwaitableExpression.Parent;
+ var document = syntaxContext.Document;
+ var syntaxFacts = document.GetRequiredLanguageService();
+ if (!syntaxFacts.IsAwaitExpression(parentOfAwaitable))
+ {
+ var semanticModel = syntaxContext.SemanticModel;
+ var symbol = GetTypeSymbolOfExpression(semanticModel, potentialAwaitableExpression, cancellationToken);
+ if (symbol.IsAwaitableNonDynamic(semanticModel, position))
+ {
+ // We have a awaitable type left of the dot, that is not yet awaited.
+ // We need to check if await is valid at the insertion position.
+ var syntaxContextAtInsertationPosition = syntaxContext.GetLanguageService().CreateContext(
+ document, syntaxContext.SemanticModel, potentialAwaitableExpression.SpanStart, cancellationToken);
+ if (syntaxContextAtInsertationPosition.IsAwaitKeywordContext())
+ {
+ return IsConfigureAwaitable(syntaxContext.SemanticModel.Compilation, symbol)
+ ? DotAwaitContext.AwaitAndConfigureAwait
+ : DotAwaitContext.AwaitOnly;
+ }
+ }
+ }
+ }
- private CompletionItem GetCompletionItem(SyntaxToken token, SyntaxGenerator generator, ISyntaxKindsService syntaxKinds, ISyntaxFactsService syntaxFacts)
- {
- var shouldMakeContainerAsync = ShouldMakeContainerAsync(token, generator);
- var text = syntaxFacts.GetText(syntaxKinds.AwaitKeyword);
- return CommonCompletionItem.Create(
- displayText: text,
- displayTextSuffix: "",
- rules: CompletionItemRules.Default,
- Glyph.Keyword,
- description: RecommendedKeyword.CreateDisplayParts(text, FeaturesResources.Asynchronously_waits_for_the_task_to_finish),
- inlineDescription: shouldMakeContainerAsync ? FeaturesResources.Make_containing_scope_async : null,
- isComplexTextEdit: shouldMakeContainerAsync);
+ return DotAwaitContext.None;
}
}
}
diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx
index b9553cd31fb3a..f96d0ebd7b71b 100644
--- a/src/Features/Core/Portable/FeaturesResources.resx
+++ b/src/Features/Core/Portable/FeaturesResources.resx
@@ -965,11 +965,12 @@ This version used in: {2}
Asynchronously waits for the task to finish.
-
- Make containing scope async
+
+ Await the preceding expression
-
- Make containing scope async (return Task)
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.(Unknown)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
index 94e3b152f5413..54594cfb92b36 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
@@ -240,6 +240,16 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn
Asynchronně čeká na dokončení úlohy.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ Očekávaná úloha vrací {0}.
@@ -4175,16 +4185,6 @@ Tato verze se používá zde: {2}.
Přidat chybějící uzly parametrů
-
-
- Převést obsažený obor na asynchronní
-
-
-
-
- Převést obsažený obor na asynchronní (návratová hodnota Task)
-
- (neznámé)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
index 70eceec3ba9d8..f139a0035f8d9 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
@@ -240,6 +240,16 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d
Wartet asynchron darauf, dass die Aufgabe fertig ist.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ Erwartete Aufgabe gibt "{0}" zurück
@@ -4175,16 +4185,6 @@ Diese Version wird verwendet in: {2}
Fehlende Parameterknoten hinzufügen
-
-
- Enthaltenden Bereich als asynchron definieren
-
-
-
-
- Enthaltenden Bereich als asynchron definieren (Task zurückgeben)
-
- (Unbekannt)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
index e43c0d9087606..1119fbbfc4f04 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
@@ -240,6 +240,16 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa
Espera de forma asincrónica a que termine la tarea.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ La tarea esperada devuelve "{0}".
@@ -4175,16 +4185,6 @@ Esta versión se utiliza en: {2}
Agregar nodos de parámetros que faltan
-
-
- Convertir el ámbito contenedor en asincrónico
-
-
-
-
- Convertir el ámbito contenedor en asincrónico (devolver Task)
-
- (Desconocido)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf
index b1774a1c29b57..08aa244b01c6e 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf
@@ -240,6 +240,16 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai
Attend de façon asynchrone que la tâche soit terminée.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ La tâche attendue retourne '{0}'
@@ -4175,16 +4185,6 @@ Version utilisée dans : {2}
Ajouter les nœuds de paramètre manquants
-
-
- Rendre la portée contenante async
-
-
-
-
- Rendre la portée contenante async (retourner Task)
-
- (Inconnu)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf
index e0487be1a9e7d..4b65ef9dfc22c 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf
@@ -240,6 +240,16 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa
Attende in modalità asincrona il completamento dell'attività.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ L'attività attesa restituisce '{0}'
@@ -4175,16 +4185,6 @@ Questa versione è usata {2}
Aggiungi nodi di parametro mancanti
-
-
- Rendi asincrono l'ambito contenitore
-
-
-
-
- Rendi asincrono l'ambito contenitore (restituisci Task)
-
- (Sconosciuto)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf
index 3736a7fd7ea8c..8eece4fa99595 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf
@@ -240,6 +240,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma
タスクの完了を非同期に待機します。
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ 待機中のタスクから '{0}' が返されました
@@ -4175,16 +4185,6 @@ This version used in: {2}
欠落している Param ノードを追加する
-
-
- 含まれているスコープを非同期にします
-
-
-
-
- 含まれているスコープを非同期にします (Task を返します)
-
- (不明)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf
index 108ade860a862..5e6184be8a065 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf
@@ -240,6 +240,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma
작업이 완료될 때까지 비동기적으로 기다립니다.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ 대기된 작업에서 '{0}'이(가) 반환됨
@@ -4175,16 +4185,6 @@ This version used in: {2}
누락된 매개 변수 노드 추가
-
-
- 포함 범위를 비동기로 설정
-
-
-
-
- 포함 범위를 비동기로 설정(Task 반환)
-
- (알 수 없음)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf
index 8f0cc37c0a3ee..1190c5ace986f 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf
@@ -240,6 +240,16 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k
Asynchronicznie oczekuje na zakończenie zadania.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ Zadanie, na które oczekiwano, zwraca wartość „{0}”
@@ -4175,16 +4185,6 @@ Ta wersja jest używana wersja: {2}
Dodaj brakujące węzły parametrów
-
-
- Ustaw zawierający zakres jako asynchroniczny
-
-
-
-
- Ustaw zawierający zakres jako asynchroniczny (zwróć typ Task)
-
- (Nieznany)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf
index cbc7450451337..c39320711f3e9 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf
@@ -240,6 +240,16 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess
Assincronamente aguarda a conclusão da tarefa.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ A tarefa esperada retorna '{0}'
@@ -4175,16 +4185,6 @@ Essa versão é usada no: {2}
Adicionar nós de parâmetro ausentes
-
-
- Tornar o escopo contentor assíncrono
-
-
-
-
- Tornar o escopo contentor assíncrono (retornar Task)
-
- (Desconhecido)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf
index 471724a8d8396..667fc3ff18c60 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf
@@ -240,6 +240,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma
Асинхронно ожидает окончания задачи.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ Ожидаемая задача возвращает "{0}".
@@ -4175,16 +4185,6 @@ This version used in: {2}
Добавить отсутствующие узлы параметров
-
-
- Преобразовать содержащую область в асинхронную
-
-
-
-
- Преобразовать содержащую область в асинхронную (задача возврата)
-
- (Неизвестно)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf
index 25eb5f90cdb81..ea8331e11fbc7 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf
@@ -240,6 +240,16 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be
Zaman uyumsuz olarak görevin tamamlanmasını bekler.
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ Beklenen görev '{0}' döndürüyor
@@ -4175,16 +4185,6 @@ Bu sürüm şurada kullanılır: {2}
Eksik parametre düğümlerini ekle
-
-
- İçeren kapsamı asenkron yap
-
-
-
-
- İçeren kapsamı asenkron yap (Görevi döndür)
-
- (Bilinmiyor)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf
index bcb2e26309ace..cbf7746ad9acd 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf
@@ -240,6 +240,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma
异步等待任务完成。
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ 等待任务返回“{0}”
@@ -4175,16 +4185,6 @@ This version used in: {2}
添加缺少的参数节点
-
-
- 将包含范围改为 Async
-
-
-
-
- 将包含范围改为 Async (返回“任务”)
-
- (未知)
diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf
index 3f3400f1f66a9..914552bf82edf 100644
--- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf
+++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf
@@ -240,6 +240,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma
以非同步方式等候工作完成。
+
+
+ Await the preceding expression
+
+
+
+
+ Await the preceding expression and add ConfigureAwait({0}).
+ {Locked="ConfigureAwait"} "ConfigureAwait" is an api name and should not be localized. {0} is a placeholder for the language specific keyword 'false'.
+ 等待的工作會傳回 '{0}'
@@ -4175,16 +4185,6 @@ This version used in: {2}
新增遺失的參數節點
-
-
- 讓包含範圍非同步
-
-
-
-
- 讓包含範圍非同步 (傳回工作)
-
- (未知)
diff --git a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/AwaitCompletionProvider.vb b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/AwaitCompletionProvider.vb
index 8e9300425ff0a..a1beadfd99fd0 100644
--- a/src/Features/VisualBasic/Portable/Completion/CompletionProviders/AwaitCompletionProvider.vb
+++ b/src/Features/VisualBasic/Portable/Completion/CompletionProviders/AwaitCompletionProvider.vb
@@ -4,10 +4,11 @@
Imports System.Collections.Immutable
Imports System.Composition
+Imports System.Threading
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Completion.Providers
Imports Microsoft.CodeAnalysis.Host.Mef
-Imports Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
+Imports Microsoft.CodeAnalysis.VisualBasic.LanguageServices
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
@@ -20,11 +21,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Public Sub New()
+ MyBase.New(VisualBasicSyntaxFacts.Instance)
End Sub
Public Overrides ReadOnly Property TriggerCharacters As ImmutableHashSet(Of Char) = CommonTriggerChars
- Private Protected Overrides Function GetSpanStart(declaration As SyntaxNode) As Integer
+ Protected Overrides Function GetSpanStart(declaration As SyntaxNode) As Integer
Select Case declaration.Kind()
Case SyntaxKind.FunctionBlock,
SyntaxKind.SubBlock
@@ -39,8 +41,50 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Throw ExceptionUtilities.Unreachable
End Function
- Private Protected Overrides Function GetAsyncSupportingDeclaration(token As SyntaxToken) As SyntaxNode
+ Protected Overrides Function GetAsyncSupportingDeclaration(token As SyntaxToken) As SyntaxNode
Return token.GetAncestor(Function(node) node.IsAsyncSupportedFunctionSyntax())
End Function
+
+ Protected Overrides Function GetTypeSymbolOfExpression(semanticModel As SemanticModel, potentialAwaitableExpression As SyntaxNode, cancellationToken As CancellationToken) As ITypeSymbol
+ Dim memberAccessExpression = TryCast(potentialAwaitableExpression, MemberAccessExpressionSyntax)?.Expression
+ If memberAccessExpression Is Nothing Then
+ Return Nothing
+ End If
+
+ Dim symbol = semanticModel.GetSymbolInfo(memberAccessExpression.WalkDownParentheses(), cancellationToken).Symbol
+ Return If(TypeOf symbol Is ITypeSymbol, Nothing, semanticModel.GetTypeInfo(memberAccessExpression, cancellationToken).Type)
+ End Function
+
+ Protected Overrides Function GetExpressionToPlaceAwaitInFrontOf(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As SyntaxNode
+ Dim dotToken = GetDotTokenLeftOfPosition(syntaxTree, position, cancellationToken)
+ If Not dotToken.HasValue Then
+ Return Nothing
+ End If
+
+ Dim memberAccess = TryCast(dotToken.Value.Parent, MemberAccessExpressionSyntax)
+ If memberAccess Is Nothing Then
+ Return Nothing
+ End If
+
+ If memberAccess.Expression.GetParentConditionalAccessExpression() IsNot Nothing Then
+ Return Nothing
+ End If
+
+ Return memberAccess
+ End Function
+
+ Protected Overrides Function GetDotTokenLeftOfPosition(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As SyntaxToken?
+ Dim tokenOnLeft = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken)
+ Dim dotToken = tokenOnLeft.GetPreviousTokenIfTouchingWord(position)
+ If Not dotToken.IsKind(SyntaxKind.DotToken) Then
+ Return Nothing
+ End If
+
+ If dotToken.GetPreviousToken().IsKind(SyntaxKind.IntegerLiteralToken, SyntaxKind.FloatingLiteralToken, SyntaxKind.DecimalLiteralToken, SyntaxKind.DateLiteralToken) Then
+ Return Nothing
+ End If
+
+ Return dotToken
+ End Function
End Class
End Namespace
diff --git a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs
index d7d2d3ec3ecb0..f3ea5dcf6182b 100644
--- a/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs
+++ b/src/Workspaces/CSharp/Portable/Recommendations/CSharpRecommendationServiceRunner.cs
@@ -253,7 +253,7 @@ private RecommendedSymbols GetSymbolsOffOfName(NameSyntax name)
if (_context.IsEnumTypeMemberAccessContext)
return GetSymbolsOffOfExpression(name);
- if (ShouldBeTreatedAsTypeInsteadOfExpression(name, out var nameBinding, out var container))
+ if (name.ShouldNameExpressionBeTreatedAsExpressionInsteadOfType(_context.SemanticModel, out var nameBinding, out var container))
return GetSymbolsOffOfBoundExpression(name, name, nameBinding, container, unwrapNullable: false);
// We're in a name-only context, since if we were an expression we'd be a
@@ -293,46 +293,6 @@ private RecommendedSymbols GetSymbolsOffOfName(NameSyntax name)
return new RecommendedSymbols(symbols);
}
- ///
- /// DeterminesCheck if we're in an interesting situation like this:
- ///
- /// int i = 5;
- /// i. // -- here
- /// List ml = new List();
- ///
- /// The problem is that "i.List" gets parsed as a type. In this case we need to try binding again as if "i" is
- /// an expression and not a type. In order to do that, we need to speculate as to what 'i' meant if it wasn't
- /// part of a local declaration's type.
- ///
- /// Another interesting case is something like:
- ///
- /// stringList.
- /// await Test2();
- ///
- /// Here "stringList.await" is thought of as the return type of a local function.
- ///
- private bool ShouldBeTreatedAsTypeInsteadOfExpression(
- ExpressionSyntax name,
- out SymbolInfo leftHandBinding,
- out ITypeSymbol? container)
- {
- if (name.IsFoundUnder(d => d.ReturnType) ||
- name.IsFoundUnder(d => d.Declaration.Type) ||
- name.IsFoundUnder(d => d.Declaration.Type))
- {
- leftHandBinding = _context.SemanticModel.GetSpeculativeSymbolInfo(
- name.SpanStart, name, SpeculativeBindingOption.BindAsExpression);
-
- container = _context.SemanticModel.GetSpeculativeTypeInfo(
- name.SpanStart, name, SpeculativeBindingOption.BindAsExpression).Type;
- return true;
- }
-
- leftHandBinding = default;
- container = null;
- return false;
- }
-
private RecommendedSymbols GetSymbolsOffOfExpression(ExpressionSyntax? originalExpression)
{
if (originalExpression == null)
@@ -527,7 +487,7 @@ private ImmutableArray GetUnnamedSymbols(ExpressionSyntax originalExpre
private ITypeSymbol? GetContainerForUnnamedSymbols(SemanticModel semanticModel, ExpressionSyntax originalExpression)
{
- return ShouldBeTreatedAsTypeInsteadOfExpression(originalExpression, out _, out var container)
+ return originalExpression.ShouldNameExpressionBeTreatedAsExpressionInsteadOfType(_context.SemanticModel, out _, out var container)
? container
: semanticModel.GetTypeInfo(originalExpression, _cancellationToken).Type;
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs
index 13402a07976e5..66bab6009ff2d 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs
@@ -38,6 +38,8 @@ public TSyntaxKind Convert(int kind) where TSyntaxKind : struct
public int StringLiteralToken => (int)SyntaxKind.StringLiteralToken;
public int IfKeyword => (int)SyntaxKind.IfKeyword;
+ public int TrueKeyword => (int)SyntaxKind.TrueKeyword;
+ public int FalseKeyword => (int)SyntaxKind.FalseKeyword;
public int GenericName => (int)SyntaxKind.GenericName;
public int IdentifierName => (int)SyntaxKind.IdentifierName;
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs
index ef64a33e9d064..b66e46deda84d 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs
@@ -43,6 +43,8 @@ internal interface ISyntaxKinds
int GlobalKeyword { get; }
int IfKeyword { get; }
int? GlobalStatement { get; }
+ int TrueKeyword { get; }
+ int FalseKeyword { get; }
#endregion
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb
index a6d25bf9a8ab3..94ab0cd2a79dd 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb
@@ -39,6 +39,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices
Public ReadOnly Property StringLiteralToken As Integer = SyntaxKind.StringLiteralToken Implements ISyntaxKinds.StringLiteralToken
Public ReadOnly Property IfKeyword As Integer = SyntaxKind.IfKeyword Implements ISyntaxKinds.IfKeyword
+ Public ReadOnly Property TrueKeyword As Integer = SyntaxKind.TrueKeyword Implements ISyntaxKinds.TrueKeyword
+ Public ReadOnly Property FalseKeyword As Integer = SyntaxKind.FalseKeyword Implements ISyntaxKinds.FalseKeyword
Public ReadOnly Property GenericName As Integer = SyntaxKind.GenericName Implements ISyntaxKinds.GenericName
Public ReadOnly Property IdentifierName As Integer = SyntaxKind.IdentifierName Implements ISyntaxKinds.IdentifierName
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs
index dd7709b500df0..a7024465f96ed 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs
@@ -427,6 +427,18 @@ internal override bool IsAwaitKeywordContext()
if (node.IsKind(SyntaxKind.QueryExpression))
{
+ // There are some cases where "await" is allowed in a query context. See error CS1995 for details:
+ // error CS1995: The 'await' operator may only be used in a query expression within the first collection expression of the initial 'from' clause or within the collection expression of a 'join' clause
+ if (TargetToken.IsKind(SyntaxKind.InKeyword))
+ {
+ return TargetToken.Parent switch
+ {
+ FromClauseSyntax { Parent: QueryExpressionSyntax queryExpression } fromClause => queryExpression.FromClause == fromClause,
+ JoinClauseSyntax => true,
+ _ => false,
+ };
+ }
+
return false;
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ExpressionSyntaxExtensions.cs
index 9ee7a79a5eaf3..ae0ce54503def 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ExpressionSyntaxExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ExpressionSyntaxExtensions.cs
@@ -143,6 +143,46 @@ public static ExpressionSyntax CastIfPossible(
return castExpression;
}
+ ///
+ /// DeterminesCheck if we're in an interesting situation like this:
+ ///
+ /// int i = 5;
+ /// i. // -- here
+ /// List ml = new List();
+ ///
+ /// The problem is that "i.List" gets parsed as a type. In this case we need to try binding again as if "i" is
+ /// an expression and not a type. In order to do that, we need to speculate as to what 'i' meant if it wasn't
+ /// part of a local declaration's type.
+ ///
+ /// Another interesting case is something like:
+ ///
+ /// stringList.
+ /// await Test2();
+ ///
+ /// Here "stringList.await" is thought of as the return type of a local function.
+ ///
+ public static bool ShouldNameExpressionBeTreatedAsExpressionInsteadOfType(
+ this ExpressionSyntax name,
+ SemanticModel semanticModel,
+ out SymbolInfo leftHandBinding,
+ out ITypeSymbol? container)
+ {
+ if (name.IsFoundUnder(d => d.ReturnType) ||
+ name.IsFoundUnder(d => d.Declaration.Type) ||
+ name.IsFoundUnder(d => d.Declaration.Type))
+ {
+ leftHandBinding = semanticModel.GetSpeculativeSymbolInfo(
+ name.SpanStart, name, SpeculativeBindingOption.BindAsExpression);
+
+ container = semanticModel.GetSpeculativeTypeInfo(
+ name.SpanStart, name, SpeculativeBindingOption.BindAsExpression).Type;
+ return true;
+ }
+
+ leftHandBinding = default;
+ container = null;
+ return false;
+ }
}
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Extensions/ContextQuery/VisualBasicSyntaxContext.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Extensions/ContextQuery/VisualBasicSyntaxContext.vb
index f0c97f08c2d36..ef924d640c569 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Extensions/ContextQuery/VisualBasicSyntaxContext.vb
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Extensions/ContextQuery/VisualBasicSyntaxContext.vb
@@ -170,9 +170,33 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
Friend Overrides Function IsAwaitKeywordContext() As Boolean
If IsAnyExpressionContext OrElse IsSingleLineStatementContext Then
+ If IsInQuery Then
+ ' There are some places where Await is allowed:
+ ' BC36929: 'Await' may only be used in a query expression within the first collection expression of the initial 'From' clause or within the collection expression of a 'Join' clause.
+ If TargetToken.Kind = SyntaxKind.InKeyword Then
+ Dim collectionRange = TryCast(TargetToken.Parent, CollectionRangeVariableSyntax)
+ If collectionRange IsNot Nothing Then
+ If TypeOf collectionRange.Parent Is FromClauseSyntax AndAlso TypeOf collectionRange.Parent.Parent Is QueryExpressionSyntax Then
+ Dim fromClause = DirectCast(collectionRange.Parent, FromClauseSyntax)
+ Dim queryExpression = DirectCast(collectionRange.Parent.Parent, QueryExpressionSyntax)
+ ' Await is only allowed for the first collection in a from clause. There are two forms to consider here:
+ ' 1. From x In xs From y In ys
+ ' 2. From x In xs, y In ys
+ ' 1. and 2. can be combined, but in any combination, Await is only allowed on the very first collection
+ If fromClause.Variables.FirstOrDefault() Is collectionRange AndAlso queryExpression.Clauses.FirstOrDefault() Is collectionRange.Parent Then
+ Return True
+ End If
+ ElseIf TypeOf collectionRange.Parent Is SimpleJoinClauseSyntax OrElse TypeOf collectionRange.Parent Is GroupJoinClauseSyntax Then
+ Return True
+ End If
+ End If
+ End If
+
+ Return False
+ End If
For Each node In TargetToken.GetAncestors(Of SyntaxNode)()
If node.IsKind(SyntaxKind.SingleLineSubLambdaExpression, SyntaxKind.SingleLineFunctionLambdaExpression,
- SyntaxKind.MultiLineSubLambdaExpression, SyntaxKind.MultiLineFunctionLambdaExpression) Then
+ SyntaxKind.MultiLineSubLambdaExpression, SyntaxKind.MultiLineFunctionLambdaExpression) Then
Return True
End If