diff --git a/src/Compilers/VisualBasic/Portable/Syntax/SyntaxEquivalence.vb b/src/Compilers/VisualBasic/Portable/Syntax/SyntaxEquivalence.vb index 338fd8626a921154a9b915af763f12b8f414bcc1..52a262a582f7590528bfbcc7a6608f4653564dd5 100644 --- a/src/Compilers/VisualBasic/Portable/Syntax/SyntaxEquivalence.vb +++ b/src/Compilers/VisualBasic/Portable/Syntax/SyntaxEquivalence.vb @@ -78,6 +78,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax Dim kind = CType(before.RawKind, SyntaxKind) + If Not AreModifiersEquivalent(before, after, kind) Then + Return False + End If + If topLevel Then Select Case kind Case SyntaxKind.SubBlock, @@ -173,5 +177,27 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax Return True End If End Function + + Private Function AreModifiersEquivalent(before As GreenNode, after As GreenNode, kind As SyntaxKind) As Boolean + Select Case kind + Case SyntaxKind.SubBlock, + SyntaxKind.FunctionBlock + Dim beforeModifiers = DirectCast(before, Green.MethodBlockBaseSyntax).Begin.Modifiers + Dim afterModifiers = DirectCast(after, Green.MethodBlockBaseSyntax).Begin.Modifiers + + If beforeModifiers.Count <> afterModifiers.Count Then + Return False + End If + + For i = 0 To beforeModifiers.Count - 1 + If Not beforeModifiers.Any(afterModifiers(i).Kind) Then + Return False + End If + Next + End Select + + Return True + End Function + End Module End Namespace diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index bd737156f0f1d55eba0ac5e551aba580553e854a..b2ea06f3d7846f9d59873a6d027fa0229586e90e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -7437,8 +7437,7 @@ static async Task F() var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.InsertAroundActiveStatement, "await", CSharpFeaturesResources.AwaitExpression), - Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "static async Task F()")); + Diagnostic(RudeEditKind.InsertAroundActiveStatement, "await", CSharpFeaturesResources.AwaitExpression)); } [Fact] @@ -7557,6 +7556,34 @@ static async void F() Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "static async void F()")); } + [Fact] + public void MethodToAsyncMethod_WithActiveStatementInLambda_3() + { + string src1 = @" +class C +{ + static void F() + { + var f = new Action(() => { Console.WriteLine(1); }); + } +} +"; + string src2 = @" +class C +{ + static async void F() + { + var f = new Action(async () => { Console.WriteLine(1); }); + } +} +"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "()")); + } + [Fact] public void MethodToAsyncMethod_WithLambda() { diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index 6f45e4e52a1732b24dd1bcf5f2a5967cab3feb57..e4e39b8a1fc25a788258588d3ad8acf61f810723 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -4738,7 +4738,7 @@ End Class End Sub - Public Sub MethodToAsyncMethod_WithActiveStatement() + Public Sub MethodToAsyncMethod_WithActiveStatement1() Dim src1 = " Imports System Imports System.Threading.Tasks @@ -4767,14 +4767,71 @@ End Class End Sub - Public Sub MethodToAsyncMethod_WithActiveStatementInLambda() + Public Sub MethodToAsyncMethod_WithActiveStatement2() + Dim src1 = " +Imports System +Imports System.Threading.Tasks +Class C + Function F() As Task(Of Integer) + Console.WriteLine(1) + Return Task.FromResult(1) + End Function +End Class +" + Dim src2 = " +Imports System +Imports System.Threading.Tasks +Class C + Async Function F() As Task(Of Integer) + Console.WriteLine(1) + Return 1 + End Function +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "Async Function F()")) + End Sub + + + Public Sub MethodToAsyncMethod_WithActiveStatement3() + Dim src1 = " +Imports System +Imports System.Threading.Tasks +Class C + Function F() As Task(Of Integer) + Console.WriteLine(1) + Return Task.FromResult(1) + End Function +End Class +" + Dim src2 = " +Imports System +Imports System.Threading.Tasks +Class C + Async Function F() As Task(Of Integer) + Console.WriteLine(1) + Return 1 + End Function +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "Async Function F()")) + End Sub + + + Public Sub MethodToAsyncMethod_WithActiveStatementInLambda1() Dim src1 = " Imports System Imports System.Threading.Tasks Class C Function F() As Task(Of Integer) Dim a = Sub() Console.WriteLine(1) - a() Return Task.FromResult(1) End Function End Class @@ -4785,7 +4842,6 @@ Imports System.Threading.Tasks Class C Async Function F() As Task(Of Integer) Dim a = Sub() Console.WriteLine(1) - a() Return Await Task.FromResult(1) End Function End Class @@ -4797,7 +4853,69 @@ End Class End Sub - Public Sub MethodToAsyncMethod_WithoutActiveStatement() + Public Sub MethodToAsyncMethod_WithActiveStatementInLambda2() + Dim src1 = " +Imports System +Imports System.Threading.Tasks +Class C + Function F() As Task(Of Integer) + Dim a = Sub() Console.WriteLine(1) + a() + Return Task.FromResult(1) + End Function +End Class +" + Dim src2 = " +Imports System +Imports System.Threading.Tasks +Class C + Async Function F() As Task(Of Integer) + Dim a = Sub() Console.WriteLine(1) + a() + Return 1 + End Function +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "Async Function F()")) + End Sub + + + Public Sub MethodToAsyncMethod_WithActiveStatementInLambda3() + Dim src1 = " +Imports System +Imports System.Threading.Tasks +Class C + Sub F() + Dim a = Sub() Console.WriteLine(1) + a() + Return + End Sub +End Class +" + Dim src2 = " +Imports System +Imports System.Threading.Tasks +Class C + Async Sub F() + Dim a = Sub() Console.WriteLine(1) + a() + Return + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, "Async Sub F()")) + End Sub + + + Public Sub MethodToAsyncMethod_WithoutActiveStatement1() Dim src1 = " Imports System Imports System.Threading.Tasks @@ -4823,6 +4941,34 @@ End Class edits.VerifyRudeDiagnostics(active) End Sub + + + Public Sub MethodToAsyncMethod_WithoutActiveStatement2() + Dim src1 = " +Imports System +Imports System.Threading.Tasks +Class C + Sub F() + Console.WriteLine(1) + Return + End Sub +End Class +" + Dim src2 = " +Imports System +Imports System.Threading.Tasks +Class C + Async Sub F() + Console.WriteLine(1) + Return + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + edits.VerifyRudeDiagnostics(active) + End Sub #End Region #Region "On Error" diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/RudeEditTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/RudeEditTestBase.vb index fa7628216916eeb4288c3816894487c837c77e80..3f19cb65871b1678dac1ca55bc77e26dc5e2c907 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/RudeEditTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/RudeEditTestBase.vb @@ -60,7 +60,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Dim diagnostics = New List(Of RudeEditDiagnostic)() Dim needsSyntaxMap As Boolean - Dim match = Analyzer.ComputeBodyMatch(m1, m2, {New AbstractEditAndContinueAnalyzer.ActiveNode}, diagnostics, needsSyntaxMap) + Dim match = Analyzer.ComputeBodyMatch(m1, m2, Array.Empty(Of AbstractEditAndContinueAnalyzer.ActiveNode)(), diagnostics, needsSyntaxMap) Assert.Equal(stateMachine <> StateMachineKind.None, needsSyntaxMap) diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb index a61f9fcdff5ed894930b576ed6a22f843722c1fe..09f4d8071a192243c6ae432e0eda23783cfe17c5 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb @@ -1898,16 +1898,32 @@ End Class End Sub - Public Sub MethodUpdate_AsyncModifier() + Public Sub MethodUpdate_AsyncModifier1() + Dim src1 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" + Dim src2 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Function F() As Task(Of String) : End Function]@11 -> [Async Function F() As Task(Of String) : End Function]@11", + "Update [Function F() As Task(Of String)]@11 -> [Async Function F() As Task(Of String)]@11") + + edits.VerifyRudeDiagnostics() + End Sub + + + Public Sub MethodUpdate_AsyncModifier2() Dim src1 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" Dim src2 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( + "Update [Async Function F() As Task(Of String) : End Function]@11 -> [Function F() As Task(Of String) : End Function]@11", "Update [Async Function F() As Task(Of String)]@11 -> [Function F() As Task(Of String)]@11") - edits.VerifyRudeDiagnostics() + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "Function F()", FeaturesResources.Method)) End Sub diff --git a/src/Features/CSharp/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index c9896ba9475af534032d84722e7b339a625089a8..d92b69865048d28d6b4d787a0d23b445b45ea9b8 100644 --- a/src/Features/CSharp/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -3011,15 +3011,12 @@ private static bool IsSimpleAwaitAssignment(SyntaxNode node, SyntaxNode awaitExp internal override void ReportOtherRudeEditsAroundActiveStatement( List diagnostics, Match match, - SyntaxNode oldBody, - SyntaxNode newBody, SyntaxNode oldActiveStatement, SyntaxNode newActiveStatement, bool isLeaf) { ReportRudeEditsForAncestorsDeclaringInterStatementTemps(diagnostics, match, oldActiveStatement, newActiveStatement, isLeaf); ReportRudeEditsForCheckedStatements(diagnostics, oldActiveStatement, newActiveStatement, isLeaf); - ReportRudeEditsForStateMachineMethod(diagnostics, oldBody, newBody, oldActiveStatement, newActiveStatement); } private void ReportRudeEditsForCheckedStatements( @@ -3132,19 +3129,11 @@ private static bool DeclareSameIdentifiers(SeparatedSyntaxList diagnostics, SyntaxNode oldBody, - SyntaxNode newBody, - SyntaxNode oldActiveStatement, - SyntaxNode newActiveStatement) + SyntaxNode newBody) { - var isInLambdaBody = FindEnclosingLambdaBody(oldBody, oldActiveStatement); - if (isInLambdaBody != null) - { - return; - } - // It is allow to update a regular method to an async method or an iterator. // The only restriction is a presence of an active statement in the method body // since the debugger does not support remapping active statements to a different method. diff --git a/src/Features/Core/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 2bcc7a1dc1e13e682c86eba45c6f6f5a4144339d..58426938cf5fbcd2092cfe663fd22458b85ba0e2 100644 --- a/src/Features/Core/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -234,7 +234,8 @@ private SyntaxNode FindStatement(SyntaxNode declarationBody, int position, out i internal abstract void ReportSyntacticRudeEdits(List diagnostics, Match match, Edit edit, Dictionary editMap); internal abstract void ReportEnclosingExceptionHandlingRudeEdits(List diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, SyntaxNode newStatement); - internal abstract void ReportOtherRudeEditsAroundActiveStatement(List diagnostics, Match match, SyntaxNode oldBody, SyntaxNode newBody, SyntaxNode oldStatement, SyntaxNode newStatement, bool isLeaf); + internal abstract void ReportOtherRudeEditsAroundActiveStatement(List diagnostics, Match match, SyntaxNode oldStatement, SyntaxNode newStatement, bool isLeaf); + internal abstract void ReportRudeEditsForStateMachineMethod(List diagnostics, SyntaxNode oldBody, SyntaxNode newBody); internal abstract void ReportMemberUpdateRudeEdits(List diagnostics, SyntaxNode newMember, TextSpan? span); internal abstract void ReportInsertedMemberSymbolRudeEdits(List diagnostics, ISymbol newSymbol); internal abstract void ReportStateMachineSuspensionPointRudeEdits(List diagnostics, SyntaxNode oldNode, SyntaxNode newNode); @@ -1062,7 +1063,7 @@ internal struct UpdatedMemberInfo } // other statements around active statement: - ReportOtherRudeEditsAroundActiveStatement(diagnostics, match, oldBody, newBody, oldStatementSyntax, newStatementSyntax, isLeaf); + ReportOtherRudeEditsAroundActiveStatement(diagnostics, match, oldStatementSyntax, newStatementSyntax, isLeaf); } else if (match == null) { @@ -1305,6 +1306,11 @@ internal struct UpdatedMemberInfo ReportStateMachineSuspensionPointRudeEdits(diagnostics, oldStateMachineSuspensionPoints[i], newStateMachineSuspensionPoints[i]); } } + else if (activeNodes.Length > 0) + { + // other state machine related issue around active statement: + ReportRudeEditsForStateMachineMethod(diagnostics, oldBody, newBody); + } return match; } diff --git a/src/Features/VisualBasic/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index b6c4d4e74b845dc6f2d2c0bd1a917ec42acdc009..aa9c3a0a258b493cbdcdcd31234add45cbd4ac7a 100644 --- a/src/Features/VisualBasic/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -2594,6 +2594,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue newModifiers = newModifiers.RemoveAt(newAsyncIndex) End If + ' 'async' keyword is allowed to add, but not to remove + If oldAsyncIndex >= 0 AndAlso newAsyncIndex < 0 Then + Return False + End If + Dim oldIteratorIndex = oldModifiers.IndexOf(SyntaxKind.IteratorKeyword) Dim newIteratorIndex = newModifiers.IndexOf(SyntaxKind.IteratorKeyword) @@ -2605,6 +2610,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue newModifiers = newModifiers.RemoveAt(newIteratorIndex) End If + ' 'iterator' keyword is allowed to add, but not to remove + If oldIteratorIndex >= 0 AndAlso newIteratorIndex < 0 Then + Return False + End If + Return SyntaxFactory.AreEquivalent(oldModifiers, newModifiers) End Function @@ -3091,8 +3101,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Friend Overrides Sub ReportOtherRudeEditsAroundActiveStatement(diagnostics As List(Of RudeEditDiagnostic), match As Match(Of SyntaxNode), - oldBody As SyntaxNode, - newBody As SyntaxNode, oldActiveStatement As SyntaxNode, newActiveStatement As SyntaxNode, isLeaf As Boolean) @@ -3154,6 +3162,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue areSimilar:=Function(n1, n2) AreEquivalentIgnoringLambdaBodies(DirectCast(n1.ForOrForEachStatement, ForEachStatementSyntax).ControlVariable, DirectCast(n2.ForOrForEachStatement, ForEachStatementSyntax).ControlVariable)) End Sub + + Friend Overrides Sub ReportRudeEditsForStateMachineMethod(diagnostics As List(Of RudeEditDiagnostic), + oldBody As SyntaxNode, + newBody As SyntaxNode) + ' It Is allow to update a regular method to an async method Or an iterator. + ' The only restriction Is a presence of an active statement in the method body + ' since the debugger does Not support remapping active statements to a different method. + Dim IsAsyncAdded = Not SyntaxUtilities.IsAsyncMethodOrLambda(oldBody) AndAlso SyntaxUtilities.IsAsyncMethodOrLambda(newBody) + Dim IsIteratorAdded = Not SyntaxUtilities.IsIteratorMethodOrLambda(oldBody) AndAlso SyntaxUtilities.IsIteratorMethodOrLambda(newBody) + If IsAsyncAdded OrElse IsIteratorAdded Then + diagnostics.Add(New RudeEditDiagnostic( + RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, + GetDiagnosticSpan(newBody, EditKind.Update))) + End If + End Sub + #End Region End Class End Namespace