diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index c602e8c9c62d3..c374754a9ca8f 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -9418,7 +9418,7 @@ static IEnumerable F() var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "yield return 1;", CSharpFeaturesResources.yield_return_statement)); + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "static IEnumerable F()")); } [Fact] @@ -9512,7 +9512,7 @@ static async Task F() var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "await", CSharpFeaturesResources.await_expression)); + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "static async Task F()")); } [Fact] @@ -9544,7 +9544,7 @@ static async Task F() var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "await foreach (var x in AsyncIter())", CSharpFeaturesResources.asynchronous_foreach_statement)); + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "static async Task F()")); } [Fact] @@ -9576,8 +9576,7 @@ static async Task F() var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "x = new AsyncDisposable()", CSharpFeaturesResources.asynchronous_using_declaration), - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "y = new AsyncDisposable()", CSharpFeaturesResources.asynchronous_using_declaration)); + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "static async Task F()")); } [Fact] @@ -9825,7 +9824,7 @@ static void F() var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "await", CSharpFeaturesResources.await_expression)); + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "()")); } [Fact] @@ -9922,7 +9921,7 @@ async Task f() var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "await", CSharpFeaturesResources.await_expression)); + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "f")); } [Fact] @@ -9951,7 +9950,7 @@ static void F() var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "await", CSharpFeaturesResources.await_expression)); + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "f")); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 1a507057e1f1d..84013717b48c2 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -9206,9 +9206,7 @@ static IEnumerable F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingStateMachineShape, "yield break;", CSharpFeaturesResources.yield_return_statement, CSharpFeaturesResources.yield_break_statement), - Diagnostic(RudeEditKind.ChangingStateMachineShape, "yield return 4;", CSharpFeaturesResources.yield_break_statement, CSharpFeaturesResources.yield_return_statement)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -9256,8 +9254,7 @@ static IEnumerable F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "{", CSharpFeaturesResources.yield_return_statement)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -9308,9 +9305,7 @@ static IEnumerable F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "yield return 4;", CSharpFeaturesResources.yield_return_statement), - Diagnostic(RudeEditKind.Insert, "yield return 2;", CSharpFeaturesResources.yield_return_statement)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -9387,32 +9382,31 @@ static IEnumerable F() /// /// Tests spilling detection logic of . /// - [Fact] - public void AwaitSpilling_OK() + [Theory] + [InlineData("await F(1);", "await F(2);")] + [InlineData("if (await F(1)) { Console.WriteLine(1); }", "if (await F(1)) { Console.WriteLine(2); }")] + [InlineData("if (await F(1)) { Console.WriteLine(1); }", "if (await F(2)) { Console.WriteLine(1); }")] + [InlineData("if (F(1, await F(1))) { Console.WriteLine(1); }", "if (F(1, await F(1))) { Console.WriteLine(2); }")] + [InlineData("if (await F(1)) { Console.WriteLine(1); }", "while (await F(1)) { Console.WriteLine(1); }")] + [InlineData("do { Console.WriteLine(1); } while (await F(1));", "do { Console.WriteLine(2); } while (await F(2));")] + [InlineData("for (var x = await F(1); await G(1); await H(1)) { Console.WriteLine(1); }", "for (var x = await F(2); await G(2); await H(2)) { Console.WriteLine(2); }")] + [InlineData("foreach (var x in await F(1)) { Console.WriteLine(1); }", "foreach (var x in await F(2)) { Console.WriteLine(2); }")] + [InlineData("using (var x = await F(1)) { Console.WriteLine(1); }", "using (var x = await F(2)) { Console.WriteLine(1); }")] + [InlineData("lock (await F(1)) { Console.WriteLine(1); }", "lock (await F(2)) { Console.WriteLine(2); }")] + [InlineData("lock (a = await F(1)) { Console.WriteLine(1); }", "lock (a = await F(2)) { Console.WriteLine(2); }")] + [InlineData("var a = await F(1), b = await G(1);", "var a = await F(2), b = await G(2);")] + [InlineData("a = await F(1);", "b = await F(2);")] + [InlineData("switch (await F(2)) { case 1: return b = await F(1); }", "switch (await F(2)) { case 1: return b = await F(2); }")] + [InlineData("return await F(1);", "return await F(2);")] + public void AwaitSpilling_OK(string oldStatement, string newStatement) { var src1 = @" class C { static async Task F() { - await F(1); - if (await F(1)) { Console.WriteLine(1); } - if (await F(1)) { Console.WriteLine(1); } - if (F(1, await F(1))) { Console.WriteLine(1); } - if (await F(1)) { Console.WriteLine(1); } - do { Console.WriteLine(1); } while (await F(1)); - for (var x = await F(1); await G(1); await H(1)) { Console.WriteLine(1); } - foreach (var x in await F(1)) { Console.WriteLine(1); } - using (var x = await F(1)) { Console.WriteLine(1); } - lock (await F(1)) { Console.WriteLine(1); } - lock (a = await F(1)) { Console.WriteLine(1); } - var a = await F(1), b = await G(1); - a = await F(1); - switch (await F(2)) { case 1: return b = await F(1); } - return await F(1); + " + oldStatement + @" } - - static async Task G() => await F(1); } "; var src2 = @" @@ -9420,23 +9414,26 @@ class C { static async Task F() { - await F(2); - if (await F(1)) { Console.WriteLine(2); } - if (await F(2)) { Console.WriteLine(1); } - if (F(1, await F(1))) { Console.WriteLine(2); } - while (await F(1)) { Console.WriteLine(1); } - do { Console.WriteLine(2); } while (await F(2)); - for (var x = await F(2); await G(2); await H(2)) { Console.WriteLine(2); } - foreach (var x in await F(2)) { Console.WriteLine(2); } - using (var x = await F(2)) { Console.WriteLine(1); } - lock (await F(2)) { Console.WriteLine(2); } - lock (a = await F(2)) { Console.WriteLine(2); } - var a = await F(2), b = await G(2); - b = await F(2); - switch (await F(2)) { case 1: return b = await F(2); } - return await F(2); + " + newStatement + @" } +} +"; + var edits = GetTopEdits(src1, src2); + edits.VerifySemanticDiagnostics(); + } + [Fact] + public void AwaitSpilling_ExpressionBody() + { + var src1 = @" +class C +{ + static async Task G() => await F(1); +} +"; + var src2 = @" +class C +{ static async Task G() => await F(2); } "; @@ -9447,22 +9444,25 @@ static async Task F() /// /// Tests spilling detection logic of . /// - [Fact] - public void AwaitSpilling_Errors() + [Theory] + [InlineData("F(1, await F(1));", "F(2, await F(1));")] + [InlineData("F(1, await F(1));", "F(1, await F(2));")] + [InlineData("F(await F(1));", "F(await F(2));")] + [InlineData("await F(await F(1));", "await F(await F(2));")] + [InlineData("if (F(1, await F(1))) { Console.WriteLine(1); }", "if (F(2, await F(1))) { Console.WriteLine(1); }", + new[] { "F(2, await F(1))" })] + [InlineData("var a = F(1, await F(1)), b = F(1, await G(1));", "var a = F(1, await F(2)), b = F(1, await G(2));", + new[] { "var a = F(1, await F(2)), b = F(1, await G(2));", "var a = F(1, await F(2)), b = F(1, await G(2));" })] + [InlineData("b = F(1, await F(1));", "b = F(1, await F(2));")] + [InlineData("b += await F(1);", "b += await F(2);")] + public void AwaitSpilling_Errors(string oldStatement, string newStatement, string[] errorMessages = null) { var src1 = @" class C { static async Task F() { - F(1, await F(1)); - F(1, await F(1)); - F(await F(1)); - await F(await F(1)); - if (F(1, await F(1))) { Console.WriteLine(1); } - var a = F(1, await F(1)), b = F(1, await G(1)); - b = F(1, await F(1)); - b += await F(1); + " + oldStatement + @" } } "; @@ -9471,30 +9471,17 @@ class C { static async Task F() { - F(2, await F(1)); - F(1, await F(2)); - F(await F(2)); - await F(await F(2)); - if (F(2, await F(1))) { Console.WriteLine(1); } - var a = F(1, await F(2)), b = F(1, await G(2)); - b = F(1, await F(2)); - b += await F(2); + " + newStatement + @" } } "; var edits = GetTopEdits(src1, src2); // consider: these edits can be allowed if we get more sophisticated - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.AwaitStatementUpdate, "F(2, await F(1));"), - Diagnostic(RudeEditKind.AwaitStatementUpdate, "F(1, await F(2));"), - Diagnostic(RudeEditKind.AwaitStatementUpdate, "F(await F(2));"), - Diagnostic(RudeEditKind.AwaitStatementUpdate, "await F(await F(2));"), - Diagnostic(RudeEditKind.AwaitStatementUpdate, "F(2, await F(1))"), - Diagnostic(RudeEditKind.AwaitStatementUpdate, "var a = F(1, await F(2)), b = F(1, await G(2));"), - Diagnostic(RudeEditKind.AwaitStatementUpdate, "var a = F(1, await F(2)), b = F(1, await G(2));"), - Diagnostic(RudeEditKind.AwaitStatementUpdate, "b = F(1, await F(2));"), - Diagnostic(RudeEditKind.AwaitStatementUpdate, "b += await F(2);")); + var expectedDiagnostics = from errorMessage in errorMessages ?? new[] { newStatement } + select Diagnostic(RudeEditKind.AwaitStatementUpdate, errorMessage); + + edits.VerifySemanticDiagnostics(expectedDiagnostics.ToArray()); } [Fact] @@ -9548,8 +9535,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "F(2);", CSharpFeaturesResources.await_expression)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -9575,8 +9561,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "await F(1);", CSharpFeaturesResources.await_expression)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -9596,8 +9581,7 @@ class C "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "static async Task F()", CSharpFeaturesResources.await_expression)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -9617,8 +9601,7 @@ class C "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "static async Task F()", CSharpFeaturesResources.await_expression)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: false)); } [Fact] @@ -9644,8 +9627,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(ActiveStatementsDescription.Empty, - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "foreach (var x in G())", CSharpFeaturesResources.foreach_statement)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: false)); } [Fact] @@ -9671,8 +9653,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(ActiveStatementsDescription.Empty, - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "foreach (var (x, y) in G())", CSharpFeaturesResources.foreach_statement)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: false)); } [Fact] @@ -9697,8 +9678,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(ActiveStatementsDescription.Empty, - Diagnostic(RudeEditKind.Delete, "{", CSharpFeaturesResources.asynchronous_foreach_statement)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: false)); } [Fact] @@ -9724,8 +9704,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(ActiveStatementsDescription.Empty, - Diagnostic(RudeEditKind.Delete, "await using", CSharpFeaturesResources.asynchronous_using_declaration)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -9751,8 +9730,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(ActiveStatementsDescription.Empty, - Diagnostic(RudeEditKind.Delete, "await using", CSharpFeaturesResources.asynchronous_using_declaration)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -9777,9 +9755,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(ActiveStatementsDescription.Empty, - Diagnostic(RudeEditKind.Delete, "{", CSharpFeaturesResources.asynchronous_using_declaration), - Diagnostic(RudeEditKind.Delete, "{", CSharpFeaturesResources.asynchronous_using_declaration)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: false)); } [Fact] @@ -9805,9 +9781,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(ActiveStatementsDescription.Empty, - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "using", CSharpFeaturesResources.using_declaration), - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "using", CSharpFeaturesResources.using_declaration)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: false)); } [Fact] @@ -9860,9 +9834,7 @@ static async IEnumerable F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "await", CSharpFeaturesResources.await_expression), - Diagnostic(RudeEditKind.Insert, "await", CSharpFeaturesResources.await_expression)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -9891,10 +9863,8 @@ static async IEnumerable F() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "await", CSharpFeaturesResources.await_expression), - Diagnostic(RudeEditKind.Insert, "await", CSharpFeaturesResources.await_expression), - Diagnostic(RudeEditKind.Insert, "await", CSharpFeaturesResources.await_expression), - Diagnostic(RudeEditKind.Insert, "await", CSharpFeaturesResources.await_expression)); + Diagnostic(RudeEditKind.AwaitStatementUpdate, "await F(await F(1));"), + Diagnostic(RudeEditKind.AwaitStatementUpdate, "await F(await F(2));")); } [Fact] @@ -9915,7 +9885,7 @@ class C var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "await", CSharpFeaturesResources.await_expression)); + Diagnostic(RudeEditKind.AwaitStatementUpdate, "await F(await F(1))")); } [Fact] @@ -9994,8 +9964,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "await", "await")); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -10021,8 +9990,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "y = new D()", CSharpFeaturesResources.asynchronous_using_declaration)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -10050,8 +10018,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "await", "await")); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -10088,13 +10055,7 @@ static async IAsyncEnumerable F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingStateMachineShape, "await foreach (var x in G()) { }", CSharpFeaturesResources.await_expression, CSharpFeaturesResources.asynchronous_foreach_statement), - Diagnostic(RudeEditKind.ChangingStateMachineShape, "x = new D()", CSharpFeaturesResources.await_expression, CSharpFeaturesResources.asynchronous_using_declaration), - Diagnostic(RudeEditKind.ChangingStateMachineShape, "y = new D()", CSharpFeaturesResources.await_expression, CSharpFeaturesResources.asynchronous_using_declaration), - Diagnostic(RudeEditKind.ChangingStateMachineShape, "await Task.FromResult(1)", CSharpFeaturesResources.yield_return_statement, CSharpFeaturesResources.await_expression), - Diagnostic(RudeEditKind.ChangingStateMachineShape, "await Task.FromResult(1)", CSharpFeaturesResources.yield_break_statement, CSharpFeaturesResources.await_expression), - Diagnostic(RudeEditKind.ChangingStateMachineShape, "yield return 1;", CSharpFeaturesResources.yield_break_statement, CSharpFeaturesResources.yield_return_statement)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs index 7a097a377a7c5..06be7c08d2b2c 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs @@ -1472,6 +1472,88 @@ public void Queries5() public void Yields() { var src1 = @" +yield return 0; +yield return 1; +"; + var src2 = @" +yield break; +yield return 1; +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Iterator); + var actual = ToMatchingPairs(match); + + // yield return should not match yield break + var expected = new MatchingPairs() + { + { "yield return 1;", "yield return 1;" } + }; + + expected.AssertEqual(actual); + } + + [Fact] + public void YieldReturn_Add() + { + var src1 = @" +yield return /*1*/ 1; +yield return /*2*/ 2; +"; + var src2 = @" +yield return /*3*/ 3; +yield return /*1*/ 1; +yield return /*2*/ 2; +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Iterator); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs + { + { "yield return /*1*/ 1;", "yield return /*1*/ 1;" }, + { "yield return /*2*/ 2;", "yield return /*2*/ 2;" } + }; + + expected.AssertEqual(actual); + } + + [Fact] + public void YieldReturn_Swap1() + { + var src1 = @" +A(); +yield return /*1*/ 1; +B(); +yield return /*2*/ 2; +C(); +"; + var src2 = @" +B(); +yield return /*2*/ 2; +A(); +yield return /*1*/ 1; +C(); +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Iterator); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs + { + { "A();", "A();" }, + { "yield return /*1*/ 1;", "yield return /*1*/ 1;" }, + { "B();", "B();" }, + { "yield return /*2*/ 2;", "yield return /*2*/ 2;" }, + { "C();", "C();" } + }; + + expected.AssertEqual(actual); + } + + [Fact] + public void YieldReturn_Swap2() + { + var src1 = @" yield return /*1*/ 1; { @@ -1489,14 +1571,267 @@ public void Yields() var match = GetMethodMatches(src1, src2, kind: MethodKind.Iterator); var actual = ToMatchingPairs(match); - // note that yield returns are matched in source order, regardless of the yielded expressions: var expected = new MatchingPairs { { "yield return /*1*/ 1;", "yield return /*1*/ 1;" }, { "{ yield return /*2*/ 2; }", "{ yield return /*3*/ 2; }" }, - { "yield return /*2*/ 2;", "yield return /*2*/ 3;" }, - { "foreach (var x in y) { yield return /*3*/ 3; }", "foreach (var x in y) { yield return /*3*/ 2; }" }, - { "yield return /*3*/ 3;", "yield return /*3*/ 2;" }, + { "yield return /*2*/ 2;", "yield return /*3*/ 2;" }, + { "foreach (var x in y) { yield return /*3*/ 3; }", "foreach (var x in y) { yield return /*3*/ 2; }" } + }; + + expected.AssertEqual(actual); + } + + #endregion + + #region Async + + [Fact] + public void Awaits() + { + var src1 = @" +await x; +await using (expr) {} +await using (D y = new D()) {} +await using D y = new D(); +await foreach (var z in w) {} +await foreach (var (u, v) in w) {} +"; + var src2 = @" +await foreach (var (u, v) in w) {} +await foreach (var z in w) {} +await using D y = new D(); +await using (D y = new D()) {} +await using (expr) {} +await x; +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs + { + { "await x;", "await x;" }, + { "await x", "await x" }, + { "await using (expr) {}", "await using (expr) {}" }, + { "{}", "{}" }, + { "await using (D y = new D()) {}", "await using (D y = new D()) {}" }, + { "D y = new D()", "D y = new D()" }, + { "y = new D()", "y = new D()" }, + { "{}", "{}" }, + { "await using D y = new D();", "await using D y = new D();" }, + { "D y = new D()", "D y = new D()" }, + { "y = new D()", "y = new D()" }, + { "await foreach (var z in w) {}", "await foreach (var z in w) {}" }, + { "{}", "{}" }, + { "await foreach (var (u, v) in w) {}", "await foreach (var (u, v) in w) {}" }, + { "u", "u" }, + { "v", "v" }, + { "{}", "{}" } + }; + + expected.AssertEqual(actual); + } + + [Fact] + public void Await_To_AwaitUsingExpression() + { + var src1 = @" +await x; +"; + var src2 = @" +await using (expr) {} +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs(); + + expected.AssertEqual(actual); + } + + [Fact] + public void Await_To_AwaitUsingDecl() + { + var src1 = @" +await x; +"; + var src2 = @" +await using D y = new D(); +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + // empty - treated as different awaits as the await using awaits at the end of the scope + var expected = new MatchingPairs(); + + expected.AssertEqual(actual); + } + + [Fact] + public void AwaitUsingDecl_To_AwaitUsingStatement() + { + var src1 = @" +await using D y = new D(); +"; + var src2 = @" +await using (D y = new D()) { } +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs(); + + expected.AssertEqual(actual); + } + + [Fact] + public void AwaitUsingExpression_To_AwaitUsingStatementWithSingleVariable() + { + var src1 = @" +await using (y = new D()) { } +"; + var src2 = @" +await using (D y = new D()) { } +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + // Using with a single variable could match using with an expression because they both generate a single try-finally block, + // but to simplify logic we do not match them currently. + var expected = new MatchingPairs + { + { "{ }", "{ }" } + }; + + expected.AssertEqual(actual); + } + + [Fact] + public void AwaitUsingExpression_To_AwaitUsingStatementWithMultipleVariables() + { + var src1 = @" +await using (y = new D()) { } +"; + var src2 = @" +await using (D y = new D(), z = new D()) { } +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + // Using with multiple variables should not match using with an expression because they generate different number of try-finally blocks. + var expected = new MatchingPairs + { + { "{ }", "{ }" } + }; + + expected.AssertEqual(actual); + } + + [Fact] + public void AwaitUsingMatchesUSing() + { + var src1 = "await x;foreach (T x in y) {}"; + var src2 = "await x;await foreach (T x in y) {}"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs() + { + { "await x;", "await x;" }, + { "await x", "await x" }, + { "foreach (T x in y) {}", "await foreach (T x in y) {}" }, + { "{}", "{}" } + }; + + expected.AssertEqual(actual); + } + + [Fact] + public void Await_To_AwaitForeach() + { + var src1 = @" +await x; +"; + var src2 = @" +await foreach (var x in y) {} +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + // empty - treated as different awaits as the foreach awaits in each loop iteration + var expected = new MatchingPairs(); + + expected.AssertEqual(actual); + } + + [Fact] + public void Await_To_AwaitForeachVar() + { + var src1 = @" +await x; +"; + var src2 = @" +await foreach (var (x, y) in z) {} +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + // empty - treated as different awaits as the foreach awaits in each loop iteration + var expected = new MatchingPairs(); + + expected.AssertEqual(actual); + } + + [Fact] + public void AwaitForeach_To_AwaitForeachVar() + { + var src1 = @" +await foreach (var x in y) {} +"; + var src2 = @" +await foreach (var (u, v) in y) {} +"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs + { + { "await foreach (var x in y) {}", "await foreach (var (u, v) in y) {}" }, + { "{}", "{}" } + }; + + expected.AssertEqual(actual); + } + + [Fact] + public void AwaitForeachMatchesForeach() + { + var src1 = "await x;foreach (T x in y) {}"; + var src2 = "await x;await foreach (T x in y) {}"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); + var actual = ToMatchingPairs(match); + + // We do match await foreach to foreach, even though the latter does not represent state machine. + // This is ok since the previous version of the method won't have state machine state associated with the + // foreach statement and thus matching state machine state for the await foreach won't succeed even though + // the syntax nodes match. + var expected = new MatchingPairs() + { + { "await x;", "await x;" }, + { "await x", "await x" }, + { "foreach (T x in y) {}", "await foreach (T x in y) {}" }, + { "{}", "{}" } }; expected.AssertEqual(actual); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxUtilitiesTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxUtilitiesTests.cs index fdc244e3f609e..e3532b92300ea 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxUtilitiesTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/SyntaxUtilitiesTests.cs @@ -260,7 +260,6 @@ IAsyncEnumerable f() AssertEx.Equal(new[] { "yield return 1;", - "yield break;", "await Task.FromResult(1)", "await foreach (var x in F()) { }", "await foreach (var (x, y) in F()) { }", diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 7a607b4a4153e..cdd27bad1149d 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -6472,9 +6472,9 @@ partial class C new[] { DocumentResults(), - DocumentResults(diagnostics: new[] + DocumentResults(semanticEdits: new[] { - Diagnostic(RudeEditKind.Insert, "yield return 2;", CSharpFeaturesResources.yield_return_statement) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true) }) }); } @@ -8499,7 +8499,7 @@ public void MethodUpdate_Iterator_YieldBreak() edits.VerifySemanticDiagnostics(); - VerifyPreserveLocalVariables(edits, preserveLocalVariables: true); + VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); } [WorkItem(1087305, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1087305")] @@ -16998,8 +16998,7 @@ public void TopLevelStatements_AddAwait() var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "await", CSharpFeaturesResources.await_expression)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("Program.
$"), preserveLocalVariables: true)); } [Fact] @@ -17019,8 +17018,7 @@ public void TopLevelStatements_DeleteAwait() var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, null, CSharpFeaturesResources.await_expression)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("Program.
$"), preserveLocalVariables: true)); } [Fact] @@ -17232,8 +17230,7 @@ public void TopLevelStatements_TaskToVoid() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangeImplicitMainReturnType, "Console.Write(1);"), - Diagnostic(RudeEditKind.Delete, null, CSharpFeaturesResources.await_expression)); + Diagnostic(RudeEditKind.ChangeImplicitMainReturnType, "Console.Write(1);")); } [Fact] @@ -17286,8 +17283,7 @@ Task GetInt() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangeImplicitMainReturnType, "Console.Write(1);"), - Diagnostic(RudeEditKind.Delete, null, CSharpFeaturesResources.await_expression)); + Diagnostic(RudeEditKind.ChangeImplicitMainReturnType, "Console.Write(1);")); } [Fact] diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 12889bd67fdff..bdb4e90debbca 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -839,9 +839,6 @@ internal override SyntaxNode EmptyCompilationUnit internal override bool ExperimentalFeaturesEnabled(SyntaxTree tree) => false; - protected override bool StateMachineSuspensionPointKindEquals(SyntaxNode suspensionPoint1, SyntaxNode suspensionPoint2) - => (suspensionPoint1 is CommonForEachStatementSyntax) ? suspensionPoint2 is CommonForEachStatementSyntax : suspensionPoint1.RawKind == suspensionPoint2.RawKind; - protected override bool StatementLabelEquals(SyntaxNode node1, SyntaxNode node2) => SyntaxComparer.Statement.GetLabel(node1) == SyntaxComparer.Statement.GetLabel(node2); @@ -2649,8 +2646,17 @@ protected override TextSpan GetExceptionHandlingRegion(SyntaxNode node, out bool #region State Machines + protected override bool SupportsStateMachineUpdates + => true; + + protected override bool IsStateMachineResumableStateSyntax(SyntaxNode node) + => SyntaxBindingUtilities.BindsToResumableStateMachineState(node); + + protected override bool StateMachineSuspensionPointKindEquals(SyntaxNode suspensionPoint1, SyntaxNode suspensionPoint2) + => (suspensionPoint1 is CommonForEachStatementSyntax) ? suspensionPoint2 is CommonForEachStatementSyntax : suspensionPoint1.RawKind == suspensionPoint2.RawKind; + internal override bool IsStateMachineMethod(SyntaxNode declaration) - => SyntaxUtilities.IsAsyncDeclaration(declaration) || SyntaxUtilities.GetSuspensionPoints(declaration).Any(); + => SyntaxUtilities.IsAsyncDeclaration(declaration) || SyntaxUtilities.IsIterator(declaration); protected override void GetStateMachineInfo(SyntaxNode body, out ImmutableArray suspensionPoints, out StateMachineKinds kinds) { @@ -2658,7 +2664,7 @@ protected override void GetStateMachineInfo(SyntaxNode body, out ImmutableArray< kinds = StateMachineKinds.None; - if (suspensionPoints.Any(n => n.IsKind(SyntaxKind.YieldBreakStatement) || n.IsKind(SyntaxKind.YieldReturnStatement))) + if (SyntaxUtilities.IsIterator(body)) { kinds |= StateMachineKinds.Iterator; } @@ -2671,9 +2677,7 @@ protected override void GetStateMachineInfo(SyntaxNode body, out ImmutableArray< internal override void ReportStateMachineSuspensionPointRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode) { - // TODO: changes around suspension points (foreach, lock, using, etc.) - - if (newNode.IsKind(SyntaxKind.AwaitExpression)) + if (newNode.IsKind(SyntaxKind.AwaitExpression) && oldNode.IsKind(SyntaxKind.AwaitExpression)) { var oldContainingStatementPart = FindContainingStatementPart(oldNode); var newContainingStatementPart = FindContainingStatementPart(newNode); diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs index 34e99eab90b15..ab8eb640d3865 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs @@ -94,7 +94,8 @@ internal enum Label ForStatement, ForStatementPart, // tied to parent ForEachStatement, - UsingStatement, + UsingStatementWithExpression, + UsingStatementWithDeclarations, FixedStatement, LockStatement, WhileStatement, @@ -109,7 +110,8 @@ internal enum Label SwitchExpressionArm, // tied to parent WhenClause, // tied to parent - YieldStatement, // tied to parent + YieldReturnStatement, // tied to parent + YieldBreakStatement, // tied to parent GotoStatement, GotoCaseStatement, BreakContinueStatement, @@ -206,7 +208,8 @@ private static int TiedToAncestor(Label label) case Label.CatchFilterClause: case Label.FinallyClause: case Label.ForStatementPart: - case Label.YieldStatement: + case Label.YieldReturnStatement: + case Label.YieldBreakStatement: case Label.FromClauseLambda: case Label.LetClauseLambda: case Label.WhereClauseLambda: @@ -360,8 +363,11 @@ private static Label ClassifyStatementSyntax(SyntaxKind kind, SyntaxNode? node, return Label.ExpressionStatement; case SyntaxKind.YieldBreakStatement: + // yield break is distinct from yield return as it does not suspend the state machine in a resumable state + return Label.YieldBreakStatement; + case SyntaxKind.YieldReturnStatement: - return Label.YieldStatement; + return Label.YieldReturnStatement; case SyntaxKind.DoStatement: return Label.DoStatement; @@ -377,7 +383,14 @@ private static Label ClassifyStatementSyntax(SyntaxKind kind, SyntaxNode? node, return Label.ForEachStatement; case SyntaxKind.UsingStatement: - return Label.UsingStatement; + // We need to distinguish using statements with no or single variable declaration from one with multiple variable declarations. + // The former generates a single try-finally block, the latter one for each variable. The finally blocks need to match since they + // affect state machine state matching. For simplicity we do not match single-declaration to expression, we just treat usings + // with declarations entirely separately from usings with expressions. + // + // The parent is not available only when comparing nodes for value equality. + // In that case it doesn't matter what label the node has as long as it has some. + return node is UsingStatementSyntax { Declaration: not null } ? Label.UsingStatementWithDeclarations : Label.UsingStatementWithExpression; case SyntaxKind.FixedStatement: return Label.FixedStatement; @@ -845,12 +858,6 @@ protected override bool TryComputeWeightedDistance(SyntaxNode leftNode, SyntaxNo distance = ComputeWeightedDistanceOfNestedFunctions(leftNode, rightNode); return true; - case SyntaxKind.YieldBreakStatement: - case SyntaxKind.YieldReturnStatement: - // Ignore the expression of yield return. The structure of the state machine is more important than the yielded values. - distance = (leftNode.RawKind == rightNode.RawKind) ? 0.0 : 0.1; - return true; - case SyntaxKind.SingleVariableDesignation: distance = ComputeWeightedDistance((SingleVariableDesignationSyntax)leftNode, (SingleVariableDesignationSyntax)rightNode); return true; diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs index bc43e53d8df28..dcf9d28a2a1a1 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs @@ -287,35 +287,16 @@ public static bool IsAsyncDeclaration(SyntaxNode declaration) /// /// /// for await expressions, - /// for yield break and yield return statements, + /// for yield return statements, /// for await foreach statements, /// for await using declarators. + /// for await using statements. /// public static IEnumerable GetSuspensionPoints(SyntaxNode body) - => body.DescendantNodesAndSelf(LambdaUtilities.IsNotLambda).Where(IsSuspensionPoint); + => body.DescendantNodesAndSelf(LambdaUtilities.IsNotLambda).Where(SyntaxBindingUtilities.BindsToResumableStateMachineState); - public static bool IsSuspensionPoint(SyntaxNode node) - { - if (node.IsKind(SyntaxKind.AwaitExpression) || node.IsKind(SyntaxKind.YieldBreakStatement) || node.IsKind(SyntaxKind.YieldReturnStatement)) - { - return true; - } - - // await foreach statement translates to two suspension points: await MoveNextAsync and await DisposeAsync - if (node is CommonForEachStatementSyntax foreachStatement && foreachStatement.AwaitKeyword.IsKind(SyntaxKind.AwaitKeyword)) - { - return true; - } - - // each declarator in the declaration translates to a suspension point: await DisposeAsync - if (node.IsKind(SyntaxKind.VariableDeclarator) && - node.Parent.Parent.IsKind(SyntaxKind.LocalDeclarationStatement, out LocalDeclarationStatementSyntax localDecl) && - localDecl.AwaitKeyword.IsKind(SyntaxKind.AwaitKeyword)) - { - return true; - } - - return false; - } + // Presence of yield break or yield return indicates state machine, but yield break does not bind to a resumable state. + public static bool IsIterator(SyntaxNode body) + => body.DescendantNodesAndSelf(LambdaUtilities.IsNotLambda).Any(n => n is YieldStatementSyntax); } } diff --git a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj index 36bad4d099180..1e16f62589263 100644 --- a/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj +++ b/src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj @@ -57,6 +57,7 @@ + InternalUtilities\LambdaUtilities.cs diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 3b27e11fef575..f211423f9e324 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -241,9 +241,13 @@ protected abstract bool TryMatchActiveStatement( protected abstract bool StatementLabelEquals(SyntaxNode node1, SyntaxNode node2); + protected abstract bool SupportsStateMachineUpdates { get; } + + protected abstract bool IsStateMachineResumableStateSyntax(SyntaxNode node); + /// /// True if both nodes represent the same kind of suspension point - /// (await expression, await foreach statement, await using declarator, yield return, yield break). + /// (await expression, await foreach statement, await using statement, await using local variable declarator, yield return). /// protected virtual bool StateMachineSuspensionPointKindEquals(SyntaxNode suspensionPoint1, SyntaxNode suspensionPoint2) => suspensionPoint1.RawKind == suspensionPoint2.RawKind; @@ -1482,28 +1486,38 @@ private Match ComputeBodyMatch( } else if (oldStateMachineSuspensionPoints.Length > 0) { - Debug.Assert(oldStateMachineSuspensionPoints.Length == newStateMachineSuspensionPoints.Length); - - for (var i = 0; i < oldStateMachineSuspensionPoints.Length; i++) + if (SupportsStateMachineUpdates) { - var oldNode = oldStateMachineSuspensionPoints[i]; - var newNode = newStateMachineSuspensionPoints[i]; - - // changing yield return to yield break, await to await foreach, yield to await, etc. - if (StateMachineSuspensionPointKindEquals(oldNode, newNode)) + foreach (var (oldNode, newNode) in match.Matches) { - Debug.Assert(StatementLabelEquals(oldNode, newNode)); + ReportStateMachineSuspensionPointRudeEdits(diagnostics, oldNode, newNode); } - else + } + else + { + Debug.Assert(oldStateMachineSuspensionPoints.Length == newStateMachineSuspensionPoints.Length); + + for (var i = 0; i < oldStateMachineSuspensionPoints.Length; i++) { - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.ChangingStateMachineShape, - newNode.Span, - newNode, - new[] { GetSuspensionPointDisplayName(oldNode, EditKind.Update), GetSuspensionPointDisplayName(newNode, EditKind.Update) })); - } + var oldNode = oldStateMachineSuspensionPoints[i]; + var newNode = newStateMachineSuspensionPoints[i]; - ReportStateMachineSuspensionPointRudeEdits(diagnostics, oldNode, newNode); + // changing yield return to yield break, await to await foreach, yield to await, etc. + if (StateMachineSuspensionPointKindEquals(oldNode, newNode)) + { + Debug.Assert(StatementLabelEquals(oldNode, newNode)); + } + else + { + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.ChangingStateMachineShape, + newNode.Span, + newNode, + new[] { GetSuspensionPointDisplayName(oldNode, EditKind.Update), GetSuspensionPointDisplayName(newNode, EditKind.Update) })); + } + + ReportStateMachineSuspensionPointRudeEdits(diagnostics, oldNode, newNode); + } } } else if (activeNodes.Length > 0) @@ -1582,6 +1596,11 @@ private void AddMatchingStateMachineSuspensionPoints( ImmutableArray oldStateMachineSuspensionPoints, ImmutableArray newStateMachineSuspensionPoints) { + if (SupportsStateMachineUpdates) + { + return; + } + // State machine suspension points (yield statements, await expressions, await foreach loops, await using declarations) // determine the structure of the generated state machine. // Change of the SM structure is far more significant then changes of the value (arguments) of these nodes. diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 1e9eddee2ea70..d0d54f9c54fe5 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -2612,6 +2612,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue #Region "State Machines" + Protected Overrides ReadOnly Property SupportsStateMachineUpdates As Boolean + Get + Return False + End Get + End Property + + Protected Overrides Function IsStateMachineResumableStateSyntax(node As SyntaxNode) As Boolean + Return node.IsKind(SyntaxKind.AwaitExpression, SyntaxKind.YieldStatement) + End Function + Friend Overrides Function IsStateMachineMethod(declaration As SyntaxNode) As Boolean Return SyntaxUtilities.IsAsyncMethodOrLambda(declaration) OrElse SyntaxUtilities.IsIteratorMethodOrLambda(declaration) @@ -2634,7 +2644,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Friend Overrides Sub ReportStateMachineSuspensionPointRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), oldNode As SyntaxNode, newNode As SyntaxNode) ' TODO: changes around suspension points (foreach, lock, using, etc.) - If newNode.IsKind(SyntaxKind.AwaitExpression) Then + If newNode.IsKind(SyntaxKind.AwaitExpression) AndAlso oldNode.IsKind(SyntaxKind.AwaitExpression) Then Dim oldContainingStatementPart = FindContainingStatementPart(oldNode) Dim newContainingStatementPart = FindContainingStatementPart(newNode)