From 2e6a90bd429a061a92c56b640d3d622b49774bb9 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Tue, 7 Apr 2020 11:18:04 -0700 Subject: [PATCH 1/3] Active statements in switch expression --- .../EditAndContinue/ActiveStatementTests.cs | 350 +++++++++++++++++- .../EditAndContinue/StatementEditingTests.cs | 41 +- .../EditAndContinue/StatementMatchingTests.cs | 99 ++++- .../EditAndContinue/TopLevelEditingTests.cs | 15 +- .../CSharpEditAndContinueAnalyzer.cs | 304 +++++++++------ .../StatementSyntaxComparer.cs | 14 +- .../AbstractEditAndContinueAnalyzer.cs | 10 +- .../EditAndContinueDiagnosticDescriptors.cs | 1 + .../VisualBasicEditAndContinueAnalyzer.vb | 7 +- 9 files changed, 661 insertions(+), 180 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 57b6575bb069b..27fc33556fced 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -341,7 +341,7 @@ unsafe static void Main(string[] args) for (int i = 0; i < 10; i++) { } - foreach (var i in new[] { 1, 2 }) { } + foreach (var i in new[] { 1, 2 }) { } using (var z = new C()) { } @@ -370,7 +370,7 @@ static void Goo(int a) Diagnostic(RudeEditKind.DeleteActiveStatement, "while (true)"), Diagnostic(RudeEditKind.DeleteActiveStatement, "do"), Diagnostic(RudeEditKind.DeleteActiveStatement, "for (int i = 0; i < 10; i++ )"), - Diagnostic(RudeEditKind.DeleteActiveStatement, "foreach (var i in new[] { 1, 2 })"), + Diagnostic(RudeEditKind.DeleteActiveStatement, "foreach (var i in new[] { 1, 2 })"), Diagnostic(RudeEditKind.DeleteActiveStatement, "using ( var z = new C() )"), Diagnostic(RudeEditKind.DeleteActiveStatement, "fixed ( char* p = \"s\" )"), Diagnostic(RudeEditKind.DeleteActiveStatement, "label")); @@ -4119,6 +4119,60 @@ static void F() edits.VerifyRudeDiagnostics(active); } + [Fact] + public void ForEach_DeleteBody() + { + string src1 = @" +class C +{ + static void F() + { + foreach (var s in new[] { 1 }) G(); + } +} +"; + string src2 = @" +class C +{ + static void F() + { + foreach (var s in new[] { 1 }) ; + } +} +"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + public void ForEachVariable_DeleteBody() + { + string src1 = @" +class C +{ + static void F() + { + foreach ((var a1, var a2) in new[] { (1,1) }) G(); + } +} +"; + string src2 = @" +class C +{ + static void F() + { + foreach ((var a1, var a2) in new[] { (1,1) }) ; + } +} +"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + #endregion #region For Statement @@ -5385,6 +5439,33 @@ public static void Main() edits.VerifyRudeDiagnostics(active); } + [Fact] + public void DoWhileBody_Delete() + { + string src1 = @" +class C +{ + static void F() + { + do G(); while (true); + } +} +"; + string src2 = @" +class C +{ + static void F() + { + do ; while (true); + } +} +"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + [Fact] public void SwitchCase_Update1() { @@ -5461,7 +5542,7 @@ public static void Main() #endregion - #region Switch When Clauses, Patterns + #region Switch Statement When Clauses, Patterns [Fact] public void SwitchWhenClause_PatternUpdate1() @@ -5981,6 +6062,238 @@ public static int F(object obj) #endregion + #region Switch Expression + + [Fact] + public void SwitchExpression() + { + var src = @" +class C +{ + public static int Main() + { + return F() switch + { + int a when F1() => F2(), + bool b => F3(), + _ => F4() + }; + } +}"; + + var edits = GetTopEdits(src, src); + var active = GetActiveStatements(src, src); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] + public void SwitchExpression_MemberExpressionBody() + { + var src1 = @" +class C +{ + public static int Main() => F() switch { 0 => 1, _ => 2}; +}"; + var src2 = @" +class C +{ + public static int Main() => G() switch { 0 => 10, _ => 20}; +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); + } + + [Fact] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] + public void SwitchExpression_LambdaBody() + { + var src1 = @" +class C +{ + public static Func M() => () => F() switch { 0 => 1, _ => 2}; +}"; + var src2 = @" +class C +{ + public static Func M() => () => G() switch { 0 => 10, _ => 20}; +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] + public void SwitchExpression_QueryLambdaBody() + { + var src1 = @" +class C +{ + public static IEnumerable M() + { + return + from a in new[] { 1 } + where F() switch { 0 => true, _ => false} + select a; + } +}"; + var src2 = @" +class C +{ + public static IEnumerable M() + { + return + from a in new[] { 1 } + where F() switch { 0 => true, _ => false} + select a; + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] + public void SwitchExpression_NestedInGoverningExpression() + { + var src1 = @" +class C +{ + public static int Main() => (F() switch { 0 => 1, _ => 2 }) switch { 1 => 10, _ => 20 }; +}"; + var src2 = @" +class C +{ + public static int Main() => (G() switch { 0 => 10, _ => 20 }) switch { 10 => 100, _ => 200 }; +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.ActiveStatementUpdate, "switch { 0 => 10, _ => 20 }"), + Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", "method"), + Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", "method")); + } + + [Fact] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] + public void SwitchExpression_NestedInArm() + { + var src1 = @" +class C +{ + public static int Main() => F1() switch + { + 1 when F2() switch { 0 => true, _ => false } => F3() switch { 0 => 1, _ => 2 }, + _ => 20 + }; +}"; + var src2 = @" +class C +{ + public static int Main() => F1() switch + { + 1 when F2() switch { 0 => true, _ => false } => F3() switch { 0 => 1, _ => 2 }, + _ => 20 + }; +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] + public void SwitchExpression_Delete1() + { + var src1 = @" +class C +{ + public static int Main() + { + return Method() switch { true => G(), _ => F2() switch { 1 => 0, _ => 2 } }; + } +}"; + var src2 = @" +class C +{ + public static int Main() + { + return Method() switch { true => G(), _ => 1 }; + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); + } + + [Fact] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] + public void SwitchExpression_Delete2() + { + var src1 = @" +class C +{ + public static int Main() + { + return F1() switch { 1 => 0, _ => F2() switch { 1 => 0, _ => 2 } }; + } +}"; + var src2 = @" +class C +{ + public static int Main() + { + return F1() switch { 1 => 0, _ => 1 }; + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); + } + + [Fact] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] + public void SwitchExpression_Delete3() + { + var src1 = @" +class C +{ + public static int Main() + { + return F1() switch { 1 when F2() switch { 1 => true, _ => false } => 0, _ => 2 }; + } +}"; + var src2 = @" +class C +{ + public static int Main() + { + return F1() switch { 1 when F3() => 0, _ => 1 }; + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); + } + + #endregion + #region Try [Fact] @@ -10299,6 +10612,37 @@ public static void F() Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); } + [Fact] + public void Block_Delete() + { + string src1 = @" +class C +{ + public static void F() + { + G(1); + { G(2); } + G(3); + } +} +"; + string src2 = @" +class C +{ + public static void F() + { + G(1); + G(3); + } +} +"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + #endregion } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 44b0ef5a8bc22..530e861f5ee3e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -9419,7 +9419,8 @@ public void PositionalPattern_Update1() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits( - "Update [r = (x, y, z) switch { (0, var b, int c) when c > 1 => 2, _ => 4 }]@6 -> [r = ((x, y, z)) switch { (_, int b1, double c1) when c1 > 2 => c1, _ => 4 }]@6", + "Update [(x, y, z) switch { (0, var b, int c) when c > 1 => 2, _ => 4 }]@10 -> [((x, y, z)) switch { (_, int b1, double c1) when c1 > 2 => c1, _ => 4 }]@10", + "Update [(0, var b, int c) when c > 1 => 2]@29 -> [(_, int b1, double c1) when c1 > 2 => c1]@31", "Reorder [c]@44 -> @39", "Update [c]@44 -> [b1]@39", "Update [b]@37 -> [c1]@50", @@ -9436,7 +9437,9 @@ public void PositionalPattern_Update2() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits( - "Update [r = (x, y, z) switch { (var a, 3, 4) => a, (1, 1, Point { X: 0 } p) => 3, _ => 4 }]@6 -> [r = ((x, y, z)) switch { (var a1, 3, 4) => a1 * 2, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 }]@6", + "Update [(x, y, z) switch { (var a, 3, 4) => a, (1, 1, Point { X: 0 } p) => 3, _ => 4 }]@10 -> [((x, y, z)) switch { (var a1, 3, 4) => a1 * 2, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 }]@10", + "Update [(var a, 3, 4) => a]@29 -> [(var a1, 3, 4) => a1 * 2]@31", + "Update [(1, 1, Point { X: 0 } p) => 3]@49 -> [(1, 1, Point { Y: 0 } p1) => 3]@57", "Update [a]@34 -> [a1]@36", "Update [p]@71 -> [p1]@79"); } @@ -9465,21 +9468,22 @@ public void PositionalPattern_Reorder() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits( - @"Update [r = (x, y, z) switch { + @"Update [(x, y, z) switch { (1, 2, 3) => 0, (var a, 3, 4) => a, (0, var b, int c) when c > 1 => 2, (1, 1, Point { X: 0 } p) => 3, _ => 4 -}]@6 -> [r = ((x, y, z)) switch { +}]@10 -> [((x, y, z)) switch { (1, 1, Point { X: 0 } p) => 3, (0, var b, int c) when c > 1 => 2, (var a, 3, 4) => a, (1, 2, 3) => 0, _ => 4 -}]@6", - "Reorder [a]@52 -> @105", - "Reorder [p]@126 -> @54"); +}]@10", + "Reorder [(var a, 3, 4) => a]@47 -> @100", + "Reorder [(0, var b, int c) when c > 1 => 2]@68 -> @64", + "Reorder [(1, 1, Point { X: 0 } p) => 3]@104 -> @32"); } [Fact] @@ -9538,26 +9542,9 @@ public void RecursivePatterns_Reorder() var edits = GetMethodEdits(src1, src2); edits.VerifyEdits( - @"Update [r = obj switch -{ - string s when s.Length > 0 => (s, obj1) switch - { - (""a"", int i) => i, - _ => 0 - }, - int i => i * i, - _ => -1 -}]@6 -> [r = obj switch -{ - int i => i * i, - string s when s.Length > 0 => (s, obj1) switch - { - (""a"", int i) => i, - _ => 0 - }, - _ => -1 -}]@6", - "Reorder [i]@102 -> @33"); + "Reorder [int i => i * i]@140 -> @29", + "Move [i]@102 -> @33", + "Move [i]@144 -> @123"); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs index 2e55498f3aaf3..72c08f95f45ab 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementMatchingTests.cs @@ -1042,24 +1042,24 @@ public void LocalFunctions3() [Fact] public void LocalFunctions4() { - var src1 = @"int a() { int b() { int c() { int d() { return 0; }; }; return c(); }; return b(); }"; - var src2 = @"int a() { int b() { int c() { int d() { return 0; }; }; return c(); }; return b(); }"; + var src1 = @"int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }"; + var src2 = @"int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }"; var matches = GetMethodMatches(src1, src2); var actual = ToMatchingPairs(matches); var expected = new MatchingPairs { - { "int a() { int b() { int c() { int d() { return 0; }; }; return c(); }; return b(); }", - "int a() { int b() { int c() { int d() { return 0; }; }; return c(); }; return b(); }" }, - { "{ int b() { int c() { int d() { return 0; }; }; return c(); }; return b(); }", - "{ int b() { int c() { int d() { return 0; }; }; return c(); }; return b(); }" }, - { "int b() { int c() { int d() { return 0; }; }; return c(); }", - "int b() { int c() { int d() { return 0; }; }; return c(); }" }, - { "{ int c() { int d() { return 0; }; }; return c(); }", - "{ int c() { int d() { return 0; }; }; return c(); }" }, - { "int c() { int d() { return 0; }; }", "int c() { int d() { return 0; }; }" }, - { "{ int d() { return 0; }; }", "{ int d() { return 0; }; }" }, + { "int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }", + "int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }" }, + { "{ int b() { int c() { int d() { return 0; } } return c(); } return b(); }", + "{ int b() { int c() { int d() { return 0; } } return c(); } return b(); }" }, + { "int b() { int c() { int d() { return 0; } } return c(); }", + "int b() { int c() { int d() { return 0; } } return c(); }" }, + { "{ int c() { int d() { return 0; } } return c(); }", + "{ int c() { int d() { return 0; } } return c(); }" }, + { "int c() { int d() { return 0; } }", "int c() { int d() { return 0; } }" }, + { "{ int d() { return 0; } }", "{ int d() { return 0; } }" }, { "int d() { return 0; }", "int d() { return 0; }" }, { "{ return 0; }", "{ return 0; }" }, { "return 0;", "return 0;" }, @@ -1091,7 +1091,7 @@ int G3(int w) F(G1); }; F(G4); -}; +} "; var src2 = @" @@ -1106,9 +1106,9 @@ int G3(int w) int G2(int c) => /*2*/d + 1; return G2(w); } F(G3); F(G1); int G6(int p) => p *2; F(G6); - }; + } F(G4); -}; +} "; var matches = GetMethodMatches(src1, src2); @@ -1117,9 +1117,9 @@ int G3(int w) var expected = new MatchingPairs { { "void G6(int a) { int G5(int c) => /*1*/d; F(G5); void G4() { void G1(int x) => x; int G3(int w) { int G2(int c) => /*2*/d; return G2(w); } F(G3); F(G1); }; F(G4); }", - "void G6(int a) { int G5(int c) => /*1*/d + 1;F(G5); void G4() { int G3(int w) { int G2(int c) => /*2*/d + 1; return G2(w); } F(G3); F(G1); int G6(int p) => p *2; F(G6); }; F(G4); }" }, + "void G6(int a) { int G5(int c) => /*1*/d + 1;F(G5); void G4() { int G3(int w) { int G2(int c) => /*2*/d + 1; return G2(w); } F(G3); F(G1); int G6(int p) => p *2; F(G6); } F(G4); }" }, { "{ int G5(int c) => /*1*/d; F(G5); void G4() { void G1(int x) => x; int G3(int w) { int G2(int c) => /*2*/d; return G2(w); } F(G3); F(G1); }; F(G4); }", - "{ int G5(int c) => /*1*/d + 1;F(G5); void G4() { int G3(int w) { int G2(int c) => /*2*/d + 1; return G2(w); } F(G3); F(G1); int G6(int p) => p *2; F(G6); }; F(G4); }" }, + "{ int G5(int c) => /*1*/d + 1;F(G5); void G4() { int G3(int w) { int G2(int c) => /*2*/d + 1; return G2(w); } F(G3); F(G1); int G6(int p) => p *2; F(G6); } F(G4); }" }, { "int G5(int c) => /*1*/d;", "int G5(int c) => /*1*/d + 1;" }, { "F(G5);", "F(G5);" }, { "void G4() { void G1(int x) => x; int G3(int w) { int G2(int c) => /*2*/d; return G2(w); } F(G3); F(G1); }", @@ -1775,11 +1775,17 @@ public void PositionalPattern() { "var r = (x, y, z) switch { (1, 2, 3) => 0, (var a, 3, 4) => a, (0, var b, int c) when c > 1 => 2, (1, 1, Point { X: 0 } p) => 3, _ => 4 };", "var r = ((x, y, z)) switch { (1, 2, 3) => 0, (var a1, 3, 4) => a1 * 2, (_, int b1, double c1) when c1 > 2 => c1, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 };" }, { "var r = (x, y, z) switch { (1, 2, 3) => 0, (var a, 3, 4) => a, (0, var b, int c) when c > 1 => 2, (1, 1, Point { X: 0 } p) => 3, _ => 4 }", "var r = ((x, y, z)) switch { (1, 2, 3) => 0, (var a1, 3, 4) => a1 * 2, (_, int b1, double c1) when c1 > 2 => c1, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 }" }, { "r = (x, y, z) switch { (1, 2, 3) => 0, (var a, 3, 4) => a, (0, var b, int c) when c > 1 => 2, (1, 1, Point { X: 0 } p) => 3, _ => 4 }", "r = ((x, y, z)) switch { (1, 2, 3) => 0, (var a1, 3, 4) => a1 * 2, (_, int b1, double c1) when c1 > 2 => c1, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 }" }, + { "(x, y, z) switch { (1, 2, 3) => 0, (var a, 3, 4) => a, (0, var b, int c) when c > 1 => 2, (1, 1, Point { X: 0 } p) => 3, _ => 4 }", "((x, y, z)) switch { (1, 2, 3) => 0, (var a1, 3, 4) => a1 * 2, (_, int b1, double c1) when c1 > 2 => c1, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 }" }, + { "(1, 2, 3) => 0", "(1, 2, 3) => 0" }, + { "(var a, 3, 4) => a", "(var a1, 3, 4) => a1 * 2" }, { "a", "a1" }, + { "(0, var b, int c) when c > 1 => 2", "(_, int b1, double c1) when c1 > 2 => c1" }, { "b", "c1" }, { "c", "b1" }, { "when c > 1", "when c1 > 2" }, - { "p", "p1" } + { "(1, 1, Point { X: 0 } p) => 3", "(1, 1, Point { Y: 0 } p1) => 3" }, + { "p", "p1" }, + { "_ => 4", "_ => 4" } }; expected.AssertEqual(actual); @@ -1848,16 +1854,25 @@ public void RecursivePatterns() var match = GetMethodMatches(src1, src2, kind: MethodKind.Async); var actual = ToMatchingPairs(match); - var expected = new MatchingPairs { + var expected = new MatchingPairs + { { "var r = obj switch { string s when s.Length > 0 => (s, obj1) switch { (\"a\", int i) => i, (\"\", Task t) => await t, _ => 0 }, int i => i * i, _ => -1 };", "var r = obj switch { string s when s.Length > 0 => (s, obj1) switch { (\"b\", decimal i1) => i1, (\"\", Task obj2) => await obj2, _ => 0 }, double i => i * i, _ => -1 };" }, { "var r = obj switch { string s when s.Length > 0 => (s, obj1) switch { (\"a\", int i) => i, (\"\", Task t) => await t, _ => 0 }, int i => i * i, _ => -1 }", "var r = obj switch { string s when s.Length > 0 => (s, obj1) switch { (\"b\", decimal i1) => i1, (\"\", Task obj2) => await obj2, _ => 0 }, double i => i * i, _ => -1 }" }, { "r = obj switch { string s when s.Length > 0 => (s, obj1) switch { (\"a\", int i) => i, (\"\", Task t) => await t, _ => 0 }, int i => i * i, _ => -1 }", "r = obj switch { string s when s.Length > 0 => (s, obj1) switch { (\"b\", decimal i1) => i1, (\"\", Task obj2) => await obj2, _ => 0 }, double i => i * i, _ => -1 }" }, + { "obj switch { string s when s.Length > 0 => (s, obj1) switch { (\"a\", int i) => i, (\"\", Task t) => await t, _ => 0 }, int i => i * i, _ => -1 }", "obj switch { string s when s.Length > 0 => (s, obj1) switch { (\"b\", decimal i1) => i1, (\"\", Task obj2) => await obj2, _ => 0 }, double i => i * i, _ => -1 }" }, + { "string s when s.Length > 0 => (s, obj1) switch { (\"a\", int i) => i, (\"\", Task t) => await t, _ => 0 }", "string s when s.Length > 0 => (s, obj1) switch { (\"b\", decimal i1) => i1, (\"\", Task obj2) => await obj2, _ => 0 }" }, { "s", "s" }, { "when s.Length > 0", "when s.Length > 0" }, + { "(s, obj1) switch { (\"a\", int i) => i, (\"\", Task t) => await t, _ => 0 }", "(s, obj1) switch { (\"b\", decimal i1) => i1, (\"\", Task obj2) => await obj2, _ => 0 }" }, + { "(\"a\", int i) => i", "(\"b\", decimal i1) => i1" }, { "i", "i" }, + { "(\"\", Task t) => await t", "(\"\", Task obj2) => await obj2" }, { "t", "obj2" }, { "await t", "await obj2" }, - { "i", "i1" } + { "_ => 0", "_ => 0" }, + { "int i => i * i", "double i => i * i" }, + { "i", "i1" }, + { "_ => -1", "_ => -1" } }; expected.AssertEqual(actual); @@ -1972,5 +1987,49 @@ public void WhenCondition() } #endregion + + #region Switch Expression + + [Fact] + public void SwitchExpressionArms_NestedSimilar() + { + // The inner switch is mapped to the outer one, which is assumed to be removed. + var src1 = @"F1() switch { 1 => 0, _ => F2() switch { 1 => 0, _ => 2 } };"; + var src2 = @"F1() switch { 1 => 0, _ => 1 };"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs { + { "F1() switch { 1 => 0, _ => F2() switch { 1 => 0, _ => 2 } };", "F1() switch { 1 => 0, _ => 1 };" }, + { "F2() switch { 1 => 0, _ => 2 }", "F1() switch { 1 => 0, _ => 1 }" }, + { "1 => 0", "1 => 0" }, + { "_ => 2", "_ => 1" } + }; + + expected.AssertEqual(actual); + } + + [Fact] + public void SwitchExpressionArms_NestedDissimilar() + { + // The inner switch is mapped to the outer one, which is assumed to be removed. + var src1 = @"Method() switch { true => G(), _ => F2() switch { 1 => 0, _ => 2 } };"; + var src2 = @"Method() switch { true => G(), _ => 1 };"; + + var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular); + var actual = ToMatchingPairs(match); + + var expected = new MatchingPairs { + { "Method() switch { true => G(), _ => F2() switch { 1 => 0, _ => 2 } };", "Method() switch { true => G(), _ => 1 };" }, + { "Method() switch { true => G(), _ => F2() switch { 1 => 0, _ => 2 } }", "Method() switch { true => G(), _ => 1 }" }, + { "true => G()", "true => G()" }, + { "_ => F2() switch { 1 => 0, _ => 2 }", "_ => 1" } + }; + + expected.AssertEqual(actual); + } + + #endregion } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index e9edc030fd018..8680967ff3a85 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3564,8 +3564,9 @@ public void MethodUpdate_UpdateStackAlloc2(string stackallocDecl) Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc", FeaturesResources.method)); } - [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] [Fact] + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] public void MethodUpdate_UpdateSwitchExpression() { var src1 = @" @@ -5936,8 +5937,9 @@ public void FieldInitializerUpdate_StackAllocInConstructor() Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc", FeaturesResources.constructor)); } - [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] [Fact] + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] public void FieldInitializerUpdate_SwitchExpressionInConstructor() { var src1 = "class C { int a = 1; public C() { var b = a switch { 0 => 0, _ => 1 }; } }"; @@ -5986,8 +5988,9 @@ public void PropertyInitializerUpdate_StackAllocInConstructor3() Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc", FeaturesResources.constructor)); } - [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] [Fact] + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] public void PropertyInitializerUpdate_SwitchExpressionInConstructor1() { var src1 = "class C { int a { get; } = 1; public C() { var b = a switch { 0 => 0, _ => 1 }; } }"; @@ -5999,8 +6002,9 @@ public void PropertyInitializerUpdate_SwitchExpressionInConstructor1() Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.constructor)); } - [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] [Fact] + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] public void PropertyInitializerUpdate_SwitchExpressionInConstructor2() { var src1 = "class C { int a { get; } = 1; public C() : this(1) { var b = a switch { 0 => 0, _ => 1 }; } public C(int a) { } }"; @@ -6011,8 +6015,9 @@ public void PropertyInitializerUpdate_SwitchExpressionInConstructor2() edits.VerifySemanticDiagnostics(); } - [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] [Fact] + [WorkItem(37172, "https://github.com/dotnet/roslyn/issues/37172")] + [WorkItem(43099, "https://github.com/dotnet/roslyn/issues/43099")] public void PropertyInitializerUpdate_SwitchExpressionInConstructor3() { var src1 = "class C { int a { get; } = 1; public C() { } public C(int b) { var b = a switch { 0 => 0, _ => 1 }; } }"; diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 924dfce64f4bb..7d4d5e7e52682 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -12,6 +12,7 @@ using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Formatting; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; @@ -35,26 +36,28 @@ public CSharpEditAndContinueAnalyzer() #region Syntax Analysis - private enum ConstructorPart + private enum BlockPart { - None = 0, - DefaultBaseConstructorCall = 1, + OpenBrace = DefaultStatementPart, + CloseBrace = 1, } - private enum BlockPart + private enum ForEachPart { - None = 0, - OpenBrace = 1, - CloseBrace = 2, + ForEach = DefaultStatementPart, + VariableDeclaration = 1, + In = 2, + Expression = 3, } - private enum ForEachPart + private enum SwitchExpressionPart { - None = 0, - ForEach = 1, - VariableDeclaration = 2, - In = 3, - Expression = 4, + WholeExpression = DefaultStatementPart, + + // An active statement that covers IL generated for the decision tree: + // [|switch { , ..., }|] + // This active statement is never a leaf active statement (does not correspond to a breakpoint span). + SwitchBody = 1, } /// @@ -275,7 +278,7 @@ protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody if (constructor.Initializer == null || position < constructor.Initializer.ColonToken.SpanStart) { - statementPart = (int)ConstructorPart.DefaultBaseConstructorCall; + statementPart = DefaultStatementPart; partner = partnerConstructor; return constructor; } @@ -302,47 +305,103 @@ protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody partner = null; } - while (node != declarationBody && !StatementSyntaxComparer.HasLabel(node) && !LambdaUtilities.IsLambdaBodyStatementOrExpression(node)) + while (true) { - node = node.Parent!; - if (partner != null) + bool isBody = node == declarationBody || LambdaUtilities.IsLambdaBodyStatementOrExpression(node); + + if (isBody || StatementSyntaxComparer.HasLabel(node)) { - partner = partner.Parent; - } - } + switch (node.Kind()) + { + case SyntaxKind.Block: + statementPart = (int)GetStatementPart((BlockSyntax)node!, position); + return node!; + + case SyntaxKind.ForEachStatement: + case SyntaxKind.ForEachVariableStatement: + Debug.Assert(!isBody); + statementPart = (int)GetStatementPart((CommonForEachStatementSyntax)node!, position); + return node!; + + case SyntaxKind.DoStatement: + // The active statement of DoStatement node is the while condition, + // which is lexically not the closest breakpoint span (the body is). + // do { ... } [|while (condition);|] + Debug.Assert(position == ((DoStatementSyntax)node).WhileKeyword.SpanStart); + Debug.Assert(!isBody); + goto default; - switch (node.Kind()) - { - case SyntaxKind.Block: - statementPart = (int)GetStatementPart((BlockSyntax)node!, position); - break; + case SyntaxKind.PropertyDeclaration: + // The active span corresponding to a property declaration is the span corresponding to its initializer (if any), + // not the span corresponding to the accessor. + // int P { [|get;|] } = [||]; + Debug.Assert(position == ((PropertyDeclarationSyntax)node).Initializer!.SpanStart); + goto default; + + case SyntaxKind.VariableDeclaration: + // VariableDeclaration ::= TypeSyntax CommaSeparatedList(VariableDeclarator) + // + // The compiler places sequence points after each local variable initialization. + // The TypeSyntax is considered to be part of the first sequence span. + Debug.Assert(!isBody); + + node = ((VariableDeclarationSyntax)node!).Variables.First(); + + if (partner != null) + { + partner = ((VariableDeclarationSyntax)partner).Variables.First(); + } - case SyntaxKind.ForEachStatement: - case SyntaxKind.ForEachVariableStatement: - statementPart = (int)GetStatementPart((CommonForEachStatementSyntax)node!, position); - break; + statementPart = DefaultStatementPart; + return node!; - case SyntaxKind.VariableDeclaration: - // VariableDeclaration ::= TypeSyntax CommaSeparatedList(VariableDeclarator) - // - // The compiler places sequence points after each local variable initialization. - // The TypeSyntax is considered to be part of the first sequence span. - node = ((VariableDeclarationSyntax)node!).Variables.First(); + case SyntaxKind.SwitchExpression: + // An active statement that covers IL generated for the decision tree: + // [|switch { , ..., }|] + // This active statement is never a leaf active statement (does not correspond to a breakpoint span). - if (partner != null) - { - partner = ((VariableDeclarationSyntax)partner).Variables.First(); - } + var switchExpression = (SwitchExpressionSyntax)node; + if (position == switchExpression.SwitchKeyword.SpanStart) + { + Debug.Assert(span.End == switchExpression.CloseBraceToken.Span.End); + statementPart = (int)SwitchExpressionPart.SwitchBody; + return node!; + } - statementPart = 0; - break; + // The switch expression itself can be (a part of) an active statement associated with the containing node + // For example, when it is used as a switch arm expression like so: + // switch { [|when switch { ... }|] ... } + Debug.Assert(position == switchExpression.Span.Start); + if (isBody) + { + goto default; + } - default: - statementPart = 0; - break; - } + // ascend to parent node: + break; + + case SyntaxKind.SwitchExpressionArm: + // An active statement may occur in the when clause and in the arm expression: + // [|when |] => [||] + // The former is covered by when-clause node - it's a labeled node. + // The latter isn't enclosed in a distinct labeled syntax node and thus needs to be covered + // by the arm node itself. + Debug.Assert(position == ((SwitchExpressionArmSyntax)node).Expression.SpanStart); + Debug.Assert(!isBody); + goto default; + + default: + statementPart = DefaultStatementPart; + return node!; + } + } - return node!; + node = node.Parent!; + if (partner != null) + { + partner = partner.Parent; + } + } } private static BlockPart GetStatementPart(BlockSyntax node, int position) @@ -352,17 +411,12 @@ private static BlockPart GetStatementPart(BlockSyntax node, int position) private static TextSpan GetActiveSpan(BlockSyntax node, BlockPart part) { - switch (part) + return part switch { - case BlockPart.OpenBrace: - return node.OpenBraceToken.Span; - - case BlockPart.CloseBrace: - return node.CloseBraceToken.Span; - - default: - throw ExceptionUtilities.UnexpectedValue(part); - } + BlockPart.OpenBrace => node.OpenBraceToken.Span, + BlockPart.CloseBrace => node.CloseBraceToken.Span, + _ => throw ExceptionUtilities.UnexpectedValue(part), + }; } private static ForEachPart GetStatementPart(CommonForEachStatementSyntax node, int position) @@ -375,44 +429,36 @@ private static ForEachPart GetStatementPart(CommonForEachStatementSyntax node, i private static TextSpan GetActiveSpan(ForEachStatementSyntax node, ForEachPart part) { - switch (part) + return part switch { - case ForEachPart.ForEach: - return node.ForEachKeyword.Span; - - case ForEachPart.VariableDeclaration: - return TextSpan.FromBounds(node.Type.SpanStart, node.Identifier.Span.End); - - case ForEachPart.In: - return node.InKeyword.Span; - - case ForEachPart.Expression: - return node.Expression.Span; - - default: - throw ExceptionUtilities.UnexpectedValue(part); - } + ForEachPart.ForEach => node.ForEachKeyword.Span, + ForEachPart.VariableDeclaration => TextSpan.FromBounds(node.Type.SpanStart, node.Identifier.Span.End), + ForEachPart.In => node.InKeyword.Span, + ForEachPart.Expression => node.Expression.Span, + _ => throw ExceptionUtilities.UnexpectedValue(part), + }; } private static TextSpan GetActiveSpan(ForEachVariableStatementSyntax node, ForEachPart part) { - switch (part) + return part switch { - case ForEachPart.ForEach: - return node.ForEachKeyword.Span; - - case ForEachPart.VariableDeclaration: - return TextSpan.FromBounds(node.Variable.SpanStart, node.Variable.Span.End); - - case ForEachPart.In: - return node.InKeyword.Span; - - case ForEachPart.Expression: - return node.Expression.Span; + ForEachPart.ForEach => node.ForEachKeyword.Span, + ForEachPart.VariableDeclaration => TextSpan.FromBounds(node.Variable.SpanStart, node.Variable.Span.End), + ForEachPart.In => node.InKeyword.Span, + ForEachPart.Expression => node.Expression.Span, + _ => throw ExceptionUtilities.UnexpectedValue(part), + }; + } - default: - throw ExceptionUtilities.UnexpectedValue(part); - } + private static TextSpan GetActiveSpan(SwitchExpressionSyntax node, SwitchExpressionPart part) + { + return part switch + { + SwitchExpressionPart.WholeExpression => node.Span, + SwitchExpressionPart.SwitchBody => TextSpan.FromBounds(node.SwitchKeyword.SpanStart, node.CloseBraceToken.Span.End), + _ => throw ExceptionUtilities.UnexpectedValue(part), + }; } protected override bool AreEquivalent(SyntaxNode left, SyntaxNode right) @@ -657,6 +703,8 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, int // The active statement of DoStatement node is the while condition, // which is lexically not the closest breakpoint span (the body is). // do { ... } [|while (condition);|] + Debug.Assert(statementPart == DefaultStatementPart); + var doStatement = (DoStatementSyntax)node; return BreakpointSpans.TryGetClosestBreakpointSpan(node, doStatement.WhileKeyword.SpanStart, out span); @@ -664,6 +712,8 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, int // The active span corresponding to a property declaration is the span corresponding to its initializer (if any), // not the span corresponding to the accessor. // int P { [|get;|] } = [||]; + Debug.Assert(statementPart == DefaultStatementPart); + var propertyDeclaration = (PropertyDeclarationSyntax)node; if (propertyDeclaration.Initializer != null && @@ -671,18 +721,34 @@ protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, int { return true; } - else - { - span = default; - return false; - } + + span = default; + return false; + + case SyntaxKind.SwitchExpression: + span = GetActiveSpan((SwitchExpressionSyntax)node, (SwitchExpressionPart)statementPart); + return true; + + case SyntaxKind.SwitchExpressionArm: + // An active statement may occur in the when clause and in the arm expression: + // [|when |] => [||] + // The former is covered by when-clause node - it's a labeled node. + // The latter isn't enclosed in a distinct labeled syntax node and thus needs to be covered + // by the arm node itself. + Debug.Assert(statementPart == DefaultStatementPart); + + span = ((SwitchExpressionArmSyntax)node).Expression.Span; + return true; default: + // make sure all nodes that use statement parts are handled above: + Debug.Assert(statementPart == DefaultStatementPart); + return BreakpointSpans.TryGetClosestBreakpointSpan(node, node.SpanStart, out span); } } - protected override IEnumerable> EnumerateNearStatements(SyntaxNode statement) + protected override IEnumerable<(SyntaxNode statement, int statementPart)> EnumerateNearStatements(SyntaxNode statement) { var direction = +1; SyntaxNodeOrToken nodeOrToken = statement; @@ -700,13 +766,19 @@ protected override IEnumerable> EnumerateNearState yield break; } - if (parent.IsKind(SyntaxKind.Block)) - { - yield return KeyValuePairUtil.Create(parent, (int)(direction > 0 ? BlockPart.CloseBrace : BlockPart.OpenBrace)); - } - else if (parent.IsKind(SyntaxKind.ForEachStatement)) + switch (parent.Kind()) { - yield return KeyValuePairUtil.Create(parent, (int)ForEachPart.ForEach); + case SyntaxKind.Block: + // The next sequence point hit after the last statement of a block is the closing brace: + yield return (parent, (int)(direction > 0 ? BlockPart.CloseBrace : BlockPart.OpenBrace)); + break; + + case SyntaxKind.ForEachStatement: + case SyntaxKind.ForEachVariableStatement: + // The next sequence point hit after the body is the in keyword: + // [|foreach|] ([|variable-declaration|] [|in|] [|expression|]) [||] + yield return (parent, (int)ForEachPart.In); + break; } if (direction > 0) @@ -722,14 +794,14 @@ protected override IEnumerable> EnumerateNearState // We don't have any better place where to place the span than the initial field. // Consider: in non-partial classes we could find a single constructor. // Otherwise, it would be confusing to select one arbitrarily. - yield return KeyValuePairUtil.Create(statement, -1); + yield return (statement, -1); } nodeOrToken = statement = parent; fieldOrPropertyModifiers = SyntaxUtilities.TryGetFieldOrPropertyModifiers(statement); direction = +1; - yield return KeyValuePairUtil.Create(nodeOrToken.AsNode()!, 0); + yield return (nodeOrToken.AsNode()!, DefaultStatementPart); } else { @@ -750,16 +822,19 @@ protected override IEnumerable> EnumerateNearState } } - if (node.IsKind(SyntaxKind.Block)) - { - yield return KeyValuePairUtil.Create(node, (int)(direction > 0 ? BlockPart.OpenBrace : BlockPart.CloseBrace)); - } - else if (node.IsKind(SyntaxKind.ForEachStatement)) + switch (node.Kind()) { - yield return KeyValuePairUtil.Create(node, (int)ForEachPart.ForEach); + case SyntaxKind.Block: + yield return (node, (int)(direction > 0 ? BlockPart.OpenBrace : BlockPart.CloseBrace)); + break; + + case SyntaxKind.ForEachStatement: + case SyntaxKind.ForEachVariableStatement: + yield return (node, (int)ForEachPart.ForEach); + break; } - yield return KeyValuePairUtil.Create(node, 0); + yield return (node, DefaultStatementPart); } } } @@ -774,10 +849,8 @@ protected override bool AreEquivalentActiveStatements(SyntaxNode oldStatement, S switch (oldStatement.Kind()) { case SyntaxKind.Block: - Debug.Assert(statementPart != 0); - // closing brace of a using statement or a block that contains using local declarations: - if (statementPart == 2) + if (statementPart == (int)BlockPart.CloseBrace) { if (oldStatement.Parent.IsKind(SyntaxKind.UsingStatement, out UsingStatementSyntax? oldUsing)) { @@ -791,15 +864,11 @@ protected override bool AreEquivalentActiveStatements(SyntaxNode oldStatement, S return true; case SyntaxKind.ConstructorDeclaration: - Debug.Assert(statementPart != 0); - // The call could only change if the base type of the containing class changed. return true; case SyntaxKind.ForEachStatement: case SyntaxKind.ForEachVariableStatement: - Debug.Assert(statementPart != 0); - // only check the expression, edits in the body and the variable declaration are allowed: return AreEquivalentActiveStatements((CommonForEachStatementSyntax)oldStatement, (CommonForEachStatementSyntax)newStatement); @@ -1411,6 +1480,7 @@ private static bool GroupBySignatureComparer(ImmutableArray ol case SyntaxKind.ReturnStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.ExpressionStatement: + case SyntaxKind.EmptyStatement: case SyntaxKind.GotoStatement: case SyntaxKind.GotoCaseStatement: case SyntaxKind.GotoDefaultStatement: @@ -1491,6 +1561,9 @@ private static bool GroupBySignatureComparer(ImmutableArray ol case SyntaxKind.SwitchExpression: return ((SwitchExpressionSyntax)node).SwitchKeyword.Span; + case SyntaxKind.SwitchExpressionArm: + return ((SwitchExpressionArmSyntax)node).EqualsGreaterThanToken.Span; + default: return null; } @@ -2903,6 +2976,7 @@ public void ClassifyDeclarationBodyRudeUpdates(SyntaxNode newDeclarationOrBody) return; case SyntaxKind.SwitchExpression: + // TODO: remove (https://github.com/dotnet/roslyn/issues/43099) ReportError(RudeEditKind.SwitchExpressionUpdate, node, _newNode); break; } diff --git a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs index 60f53f1fd6a95..775ffd5e412db 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/StatementSyntaxComparer.cs @@ -188,7 +188,9 @@ internal enum Label SwitchStatement, SwitchSection, CasePatternSwitchLabel, // tied to parent - WhenClause, + SwitchExpression, + SwitchExpressionArm, // tied to parent + WhenClause, // tied to parent YieldStatement, // tied to parent GotoStatement, @@ -256,6 +258,8 @@ private static int TiedToAncestor(Label label) case Label.GroupClauseLambda: case Label.QueryContinuation: case Label.CasePatternSwitchLabel: + case Label.WhenClause: + case Label.SwitchExpressionArm: return 1; default: @@ -312,7 +316,7 @@ internal static Label Classify(SyntaxKind kind, SyntaxNode? node, out bool isLea case SyntaxKind.EmptyStatement: isLeaf = true; - return Label.Ignored; + return Label.ExpressionStatement; case SyntaxKind.GotoStatement: isLeaf = true; @@ -393,6 +397,12 @@ internal static Label Classify(SyntaxKind kind, SyntaxNode? node, out bool isLea case SyntaxKind.CasePatternSwitchLabel: return Label.CasePatternSwitchLabel; + case SyntaxKind.SwitchExpression: + return Label.SwitchExpression; + + case SyntaxKind.SwitchExpressionArm: + return Label.SwitchExpressionArm; + case SyntaxKind.TryStatement: return Label.TryStatement; diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 57af62a726b8f..db050eff5136d 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -26,6 +26,8 @@ namespace Microsoft.CodeAnalysis.EditAndContinue { internal abstract class AbstractEditAndContinueAnalyzer : IEditAndContinueAnalyzer { + internal const int DefaultStatementPart = 0; + internal abstract bool ExperimentalFeaturesEnabled(SyntaxTree tree); /// @@ -194,7 +196,7 @@ protected abstract bool TryMatchActiveStatement( /// Pairs of (node, statement part), or (node, -1) indicating there is no logical following statement. /// The enumeration continues until the root is reached. /// - protected abstract IEnumerable> EnumerateNearStatements(SyntaxNode statement); + protected abstract IEnumerable<(SyntaxNode statement, int statementPart)> EnumerateNearStatements(SyntaxNode statement); protected abstract bool StatementLabelEquals(SyntaxNode node1, SyntaxNode node2); @@ -1005,7 +1007,7 @@ private void AnalyzeUpdatedActiveMethodBodies( if (hasActiveStatement) { - var newSpan = FindClosestActiveSpan(edit.NewNode, 0); + var newSpan = FindClosestActiveSpan(edit.NewNode, DefaultStatementPart); for (var i = start; i < end; i++) { Debug.Assert(newActiveStatements[i] == null && newSpan != default); @@ -1652,10 +1654,8 @@ private TextSpan FindClosestActiveSpan(SyntaxNode statement, int statementPart) internal TextSpan GetDeletedNodeActiveSpan(IReadOnlyDictionary forwardMap, SyntaxNode deletedNode) { - foreach (var nodeAndPart in EnumerateNearStatements(deletedNode)) + foreach (var (oldNode, part) in EnumerateNearStatements(deletedNode)) { - var oldNode = nodeAndPart.Key; - var part = nodeAndPart.Value; if (part == -1) { break; diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index 2acea5b035f7f..9b8f802fa7ae9 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -133,6 +133,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.InsertIntoInterface, nameof(FeaturesResources.Adding_0_into_an_interface_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.InsertLocalFunctionIntoInterfaceMethod, nameof(FeaturesResources.Adding_0_into_an_interface_method_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.InternalError, nameof(FeaturesResources.Modifying_source_file_will_prevent_the_debug_session_from_continuing_due_to_internal_error)); + // TODO: remove (https://github.com/dotnet/roslyn/issues/43099) AddRudeEdit(RudeEditKind.SwitchExpressionUpdate, nameof(FeaturesResources.Modifying_0_which_contains_a_switch_expression_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ChangingFromAsynchronousToSynchronous, nameof(FeaturesResources.Changing_0_from_asynchronous_to_synchronous_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ChangingStateMachineShape, nameof(FeaturesResources.Changing_0_to_1_will_prevent_the_debug_session_from_continuing_because_it_changes_the_shape_of_the_state_machine)); diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 6adf466d13831..efaeaca958807 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -759,7 +759,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return BreakpointSpans.TryGetEnclosingBreakpointSpan(node, node.SpanStart, minLength, span) End Function - Protected Overrides Iterator Function EnumerateNearStatements(statement As SyntaxNode) As IEnumerable(Of KeyValuePair(Of SyntaxNode, Integer)) + Protected Overrides Iterator Function EnumerateNearStatements(statement As SyntaxNode) As IEnumerable(Of ValueTuple(Of SyntaxNode, Integer)) Dim direction As Integer = +1 Dim nodeOrToken As SyntaxNodeOrToken = statement Dim propertyOrFieldModifiers As SyntaxTokenList? = GetFieldOrPropertyModifiers(statement) @@ -804,7 +804,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If If propertyOrFieldModifiers.HasValue Then - Yield KeyValuePairUtil.Create(statement, -1) + Yield ValueTuple.Create(statement, -1) + Return End If nodeOrToken = parent @@ -827,7 +828,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End If - Yield KeyValuePairUtil.Create(node, 0) + Yield ValueTuple.Create(node, -1) End While End Function From a46a8b1a82fbba48aedda0edfd6b4ef3d91ec49d Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Tue, 7 Apr 2020 11:33:49 -0700 Subject: [PATCH 2/3] VB fix --- .../EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index efaeaca958807..24116d3386da6 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -805,7 +805,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue If propertyOrFieldModifiers.HasValue Then Yield ValueTuple.Create(statement, -1) - Return End If nodeOrToken = parent @@ -828,7 +827,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End If - Yield ValueTuple.Create(node, -1) + Yield ValueTuple.Create(node, DefaultStatementPart) End While End Function From ae1063ce9ad057f4586591733e5ac75a33e04693 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Tue, 7 Apr 2020 17:55:23 -0700 Subject: [PATCH 3/3] Feedback --- .../EditAndContinue/ActiveStatementTests.cs | 4 +- .../CSharpEditAndContinueAnalyzer.cs | 95 ++++++------------- .../VisualBasicEditAndContinueAnalyzer.vb | 4 +- 3 files changed, 33 insertions(+), 70 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 27fc33556fced..bb648d29b896e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -6179,8 +6179,8 @@ class C edits.VerifyRudeDiagnostics(active, Diagnostic(RudeEditKind.ActiveStatementUpdate, "switch { 0 => 10, _ => 20 }"), - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", "method"), - Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", "method")); + Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method), + Diagnostic(RudeEditKind.SwitchExpressionUpdate, "switch", FeaturesResources.method)); } [Fact] diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 7d4d5e7e52682..e430c3f53c1a7 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -314,14 +314,14 @@ protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody switch (node.Kind()) { case SyntaxKind.Block: - statementPart = (int)GetStatementPart((BlockSyntax)node!, position); - return node!; + statementPart = (int)GetStatementPart((BlockSyntax)node, position); + return node; case SyntaxKind.ForEachStatement: case SyntaxKind.ForEachVariableStatement: Debug.Assert(!isBody); - statementPart = (int)GetStatementPart((CommonForEachStatementSyntax)node!, position); - return node!; + statementPart = (int)GetStatementPart((CommonForEachStatementSyntax)node, position); + return node; case SyntaxKind.DoStatement: // The active statement of DoStatement node is the while condition, @@ -345,7 +345,7 @@ protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody // The TypeSyntax is considered to be part of the first sequence span. Debug.Assert(!isBody); - node = ((VariableDeclarationSyntax)node!).Variables.First(); + node = ((VariableDeclarationSyntax)node).Variables.First(); if (partner != null) { @@ -353,7 +353,7 @@ protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody } statementPart = DefaultStatementPart; - return node!; + return node; case SyntaxKind.SwitchExpression: // An active statement that covers IL generated for the decision tree: @@ -365,7 +365,7 @@ protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody { Debug.Assert(span.End == switchExpression.CloseBraceToken.Span.End); statementPart = (int)SwitchExpressionPart.SwitchBody; - return node!; + return node; } // The switch expression itself can be (a part of) an active statement associated with the containing node @@ -392,7 +392,7 @@ protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody default: statementPart = DefaultStatementPart; - return node!; + return node; } } @@ -405,31 +405,24 @@ protected override SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody } private static BlockPart GetStatementPart(BlockSyntax node, int position) - { - return position < node.OpenBraceToken.Span.End ? BlockPart.OpenBrace : BlockPart.CloseBrace; - } + => position < node.OpenBraceToken.Span.End ? BlockPart.OpenBrace : BlockPart.CloseBrace; private static TextSpan GetActiveSpan(BlockSyntax node, BlockPart part) - { - return part switch + => part switch { BlockPart.OpenBrace => node.OpenBraceToken.Span, BlockPart.CloseBrace => node.CloseBraceToken.Span, _ => throw ExceptionUtilities.UnexpectedValue(part), }; - } private static ForEachPart GetStatementPart(CommonForEachStatementSyntax node, int position) - { - return position < node.OpenParenToken.SpanStart ? ForEachPart.ForEach : - position < node.InKeyword.SpanStart ? ForEachPart.VariableDeclaration : - position < node.Expression.SpanStart ? ForEachPart.In : - ForEachPart.Expression; - } + => position < node.OpenParenToken.SpanStart ? ForEachPart.ForEach : + position < node.InKeyword.SpanStart ? ForEachPart.VariableDeclaration : + position < node.Expression.SpanStart ? ForEachPart.In : + ForEachPart.Expression; private static TextSpan GetActiveSpan(ForEachStatementSyntax node, ForEachPart part) - { - return part switch + => part switch { ForEachPart.ForEach => node.ForEachKeyword.Span, ForEachPart.VariableDeclaration => TextSpan.FromBounds(node.Type.SpanStart, node.Identifier.Span.End), @@ -437,11 +430,9 @@ private static TextSpan GetActiveSpan(ForEachStatementSyntax node, ForEachPart p ForEachPart.Expression => node.Expression.Span, _ => throw ExceptionUtilities.UnexpectedValue(part), }; - } private static TextSpan GetActiveSpan(ForEachVariableStatementSyntax node, ForEachPart part) - { - return part switch + => part switch { ForEachPart.ForEach => node.ForEachKeyword.Span, ForEachPart.VariableDeclaration => TextSpan.FromBounds(node.Variable.SpanStart, node.Variable.Span.End), @@ -449,22 +440,17 @@ private static TextSpan GetActiveSpan(ForEachVariableStatementSyntax node, ForEa ForEachPart.Expression => node.Expression.Span, _ => throw ExceptionUtilities.UnexpectedValue(part), }; - } private static TextSpan GetActiveSpan(SwitchExpressionSyntax node, SwitchExpressionPart part) - { - return part switch + => part switch { SwitchExpressionPart.WholeExpression => node.Span, SwitchExpressionPart.SwitchBody => TextSpan.FromBounds(node.SwitchKeyword.SpanStart, node.CloseBraceToken.Span.End), _ => throw ExceptionUtilities.UnexpectedValue(part), }; - } protected override bool AreEquivalent(SyntaxNode left, SyntaxNode right) - { - return SyntaxFactory.AreEquivalent(left, right); - } + => SyntaxFactory.AreEquivalent(left, right); private static bool AreEquivalentIgnoringLambdaBodies(SyntaxNode left, SyntaxNode right) { @@ -478,9 +464,7 @@ private static bool AreEquivalentIgnoringLambdaBodies(SyntaxNode left, SyntaxNod } internal override SyntaxNode FindPartner(SyntaxNode leftRoot, SyntaxNode rightRoot, SyntaxNode leftNode) - { - return SyntaxUtilities.FindPartner(leftRoot, rightRoot, leftNode); - } + => SyntaxUtilities.FindPartner(leftRoot, rightRoot, leftNode); internal override SyntaxNode? FindPartnerInMemberInitializer(SemanticModel leftModel, INamedTypeSymbol leftType, SyntaxNode leftNode, INamedTypeSymbol rightType, CancellationToken cancellationToken) { @@ -524,9 +508,7 @@ internal override SyntaxNode FindPartner(SyntaxNode leftRoot, SyntaxNode rightRo } internal override bool IsClosureScope(SyntaxNode node) - { - return LambdaUtilities.IsClosureScope(node); - } + => LambdaUtilities.IsClosureScope(node); protected override SyntaxNode? FindEnclosingLambdaBody(SyntaxNode? container, SyntaxNode node) { @@ -547,19 +529,13 @@ internal override bool IsClosureScope(SyntaxNode node) } protected override IEnumerable GetLambdaBodyExpressionsAndStatements(SyntaxNode lambdaBody) - { - return SpecializedCollections.SingletonEnumerable(lambdaBody); - } + => SpecializedCollections.SingletonEnumerable(lambdaBody); protected override SyntaxNode TryGetPartnerLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda) - { - return LambdaUtilities.TryGetCorrespondingLambdaBody(oldBody, newLambda); - } + => LambdaUtilities.TryGetCorrespondingLambdaBody(oldBody, newLambda); protected override Match ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit) - { - return TopSyntaxComparer.Instance.ComputeMatch(oldCompilationUnit, newCompilationUnit); - } + => TopSyntaxComparer.Instance.ComputeMatch(oldCompilationUnit, newCompilationUnit); protected override Match ComputeBodyMatch(SyntaxNode oldBody, SyntaxNode newBody, IEnumerable>? knownMatches) { @@ -652,36 +628,23 @@ protected override bool TryMatchActiveStatement( #region Syntax and Semantic Utils protected override IEnumerable GetSyntaxSequenceEdits(ImmutableArray oldNodes, ImmutableArray newNodes) - { - return SyntaxComparer.GetSequenceEdits(oldNodes, newNodes); - } + => SyntaxComparer.GetSequenceEdits(oldNodes, newNodes); internal override SyntaxNode EmptyCompilationUnit - { - get - { - return SyntaxFactory.CompilationUnit(); - } - } + => SyntaxFactory.CompilationUnit(); + // there are no experimental features at this time. internal override bool ExperimentalFeaturesEnabled(SyntaxTree tree) - { - // there are no experimental features at this time. - return false; - } + => 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) - { - return StatementSyntaxComparer.GetLabelImpl(node1) == StatementSyntaxComparer.GetLabelImpl(node2); - } + => StatementSyntaxComparer.GetLabelImpl(node1) == StatementSyntaxComparer.GetLabelImpl(node2); protected override bool TryGetEnclosingBreakpointSpan(SyntaxNode root, int position, out TextSpan span) - { - return BreakpointSpans.TryGetClosestBreakpointSpan(root, position, out span); - } + => BreakpointSpans.TryGetClosestBreakpointSpan(root, position, out span); protected override bool TryGetActiveSpan(SyntaxNode node, int statementPart, int minLength, out TextSpan span) { diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 24116d3386da6..db672b55b9150 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -804,7 +804,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If If propertyOrFieldModifiers.HasValue Then - Yield ValueTuple.Create(statement, -1) + Yield (statement, -1) End If nodeOrToken = parent @@ -827,7 +827,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End If - Yield ValueTuple.Create(node, DefaultStatementPart) + Yield (node, DefaultStatementPart) End While End Function