diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index b2ea06f3d7846..f654f7b984ec2 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -5088,6 +5088,169 @@ static void Foo() edits.VerifyRudeDiagnostics(active); } + [Fact] + public void TryFinally_DeleteStatement_Inner() + { + string src1 = @" +class C +{ + static void Main() + { + Console.WriteLine(0); + + try + { + Console.WriteLine(1); + } + finally + { + Console.WriteLine(2); + } + } +}"; + string src2 = @" +class C +{ + static void Main() + { + Console.WriteLine(0); + + try + { + } + finally + { + Console.WriteLine(2); + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); + } + + [Fact] + public void TryFinally_DeleteStatement_Leaf() + { + string src1 = @" +class C +{ + static void Main(string[] args) + { + try + { + Console.WriteLine(0); + } + finally + { + Console.WriteLine(1); + } + } +}"; + string src2 = @" +class C +{ + static void Main(string[] args) + { + try + { + Console.WriteLine(0); + } + finally + { + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.UpdateAroundActiveStatement, "finally", CSharpFeaturesResources.FinallyClause)); + } + + [Fact] + public void Try_DeleteStatement_Inner() + { + string src1 = @" +class C +{ + static void Main() + { + Console.WriteLine(0); + + try + { + Console.WriteLine(1); + } + finally + { + Console.WriteLine(2); + } + } +}"; + string src2 = @" +class C +{ + static void Main() + { + Console.WriteLine(0); + + try + { + } + finally + { + Console.WriteLine(2); + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.DeleteActiveStatement, "{")); + } + + [Fact] + public void Try_DeleteStatement_Leaf() + { + string src1 = @" +class C +{ + static void Main() + { + try + { + Console.WriteLine(1); + } + finally + { + Console.WriteLine(2); + } + } +}"; + string src2 = @" +class C +{ + static void Main() + { + try + { + } + finally + { + Console.WriteLine(2); + } + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + #endregion #region Catch diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index 25e583f1da69b..024dd1e8b981f 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -3447,6 +3447,135 @@ End Class edits.VerifyRudeDiagnostics(active) End Sub + + Public Sub TryFinally_DeleteStatement_Inner() + Dim src1 = " +Class C + Sub Main() + Console.WriteLine(0) + + Try + Console.WriteLine(1) + Finally + Console.WriteLine(2) + End Try + End Sub +End Class +" + Dim src2 = " +Class C + Sub Main() + Console.WriteLine(0) + + Try + Finally + Console.WriteLine(2) + End Try + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.DeleteActiveStatement, "Try")) + End Sub + + + Public Sub TryFinally_DeleteStatement_Leaf() + Dim src1 = " +Class C + Sub Main() + Try + Console.WriteLine(0) + Finally + Console.WriteLine(1) + End Try + End Sub +End Class +" + Dim src2 = " +Class C + Sub Main() + Try + Console.WriteLine(0) + Finally + End Try + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.UpdateAroundActiveStatement, "Finally", VBFeaturesResources.FinallyClause)) + End Sub + + + Public Sub Try_DeleteStatement_Inner() + Dim src1 = " +Class C + Sub Main() + Console.WriteLine(0) + + Try + Console.WriteLine(1) + Finally + Console.WriteLine(2) + End Try + End Sub +End Class +" + Dim src2 = " +Class C + Sub Main() + Console.WriteLine(0) + + Try + Finally + Console.WriteLine(2) + End Try + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + edits.VerifyRudeDiagnostics(active, + Diagnostic(RudeEditKind.DeleteActiveStatement, "Try")) + End Sub + + + Public Sub Try_DeleteStatement_Leaf() + Dim src1 = " +Class C + Sub Main() + + Try + Console.WriteLine(1) + Finally + Console.WriteLine(2) + End Try + End Sub +End Class +" + Dim src2 = " +Class C + Sub Main() + + Try + Finally + Console.WriteLine(2) + End Try + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + Dim active = GetActiveStatements(src1, src2) + + edits.VerifyRudeDiagnostics(active) + End Sub + Public Sub Catch_Add_Inner() Dim src1 = " diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 54a19fa827624..60911f45c5989 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -2780,7 +2780,7 @@ internal override void ReportEnclosingExceptionHandlingRudeEdits( List diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, - SyntaxNode newStatement) + TextSpan newStatementSpan) { foreach (var edit in exceptionHandlingEdits) { @@ -2789,7 +2789,7 @@ internal override void ReportEnclosingExceptionHandlingRudeEdits( if (edit.Kind != EditKind.Update || !AreExceptionClausesEquivalent(edit.OldNode, edit.NewNode)) { - AddRudeDiagnostic(diagnostics, edit.OldNode, edit.NewNode, newStatement); + AddRudeDiagnostic(diagnostics, edit.OldNode, edit.NewNode, newStatementSpan); } } } @@ -3053,7 +3053,7 @@ private void ReportRudeEditsForCheckedStatements( if (isRude) { - AddRudeDiagnostic(diagnostics, oldCheckedStatement, newCheckedStatement, newActiveStatement); + AddRudeDiagnostic(diagnostics, oldCheckedStatement, newCheckedStatement, newActiveStatement.Span); } } diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 81735ffd66426..5388f97ef0ead 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -233,7 +233,7 @@ protected abstract bool TryMatchActiveStatement( protected abstract TextSpan GetExceptionHandlingRegion(SyntaxNode node, out bool coversAllChildren); 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 ReportEnclosingExceptionHandlingRudeEdits(List diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, TextSpan newStatementSpan); internal abstract void ReportOtherRudeEditsAroundActiveStatement(List diagnostics, Match match, SyntaxNode oldStatement, SyntaxNode newStatement, bool isLeaf); internal abstract void ReportMemberUpdateRudeEdits(List diagnostics, SyntaxNode newMember, TextSpan? span); internal abstract void ReportInsertedMemberSymbolRudeEdits(List diagnostics, ISymbol newSymbol); @@ -999,15 +999,15 @@ private void AnalyzeUpdatedActiveMethodBodies( newExceptionRegions[ordinal] = ImmutableArray.Create(); TextSpan newSpan; - SyntaxNode newStatementSyntax; + SyntaxNode newStatementSyntaxOpt; Match match; if (oldEnclosingLambdaBody == null) { match = bodyMatch; - hasMatching = TryMatchActiveStatement(oldStatementSyntax, statementPart, oldBody, newBody, out newStatementSyntax) || - match.TryGetNewNode(oldStatementSyntax, out newStatementSyntax); + hasMatching = TryMatchActiveStatement(oldStatementSyntax, statementPart, oldBody, newBody, out newStatementSyntaxOpt) || + match.TryGetNewNode(oldStatementSyntax, out newStatementSyntaxOpt); } else { @@ -1017,52 +1017,34 @@ private void AnalyzeUpdatedActiveMethodBodies( if (match != null) { - hasMatching = TryMatchActiveStatement(oldStatementSyntax, statementPart, oldEnclosingLambdaBody, newEnclosingLambdaBody, out newStatementSyntax) || - match.TryGetNewNode(oldStatementSyntax, out newStatementSyntax); + hasMatching = TryMatchActiveStatement(oldStatementSyntax, statementPart, oldEnclosingLambdaBody, newEnclosingLambdaBody, out newStatementSyntaxOpt) || + match.TryGetNewNode(oldStatementSyntax, out newStatementSyntaxOpt); } else { // Lambda match is null if lambdas can't be matched, // in such case we won't have active statement matched either. hasMatching = false; - newStatementSyntax = null; + newStatementSyntaxOpt = null; } } if (hasMatching) { - Debug.Assert(newStatementSyntax != null); + Debug.Assert(newStatementSyntaxOpt != null); // The matching node doesn't produce sequence points. // E.g. "const" keyword is inserted into a local variable declaration with an initializer. - newSpan = FindClosestActiveSpan(newStatementSyntax, statementPart); + newSpan = FindClosestActiveSpan(newStatementSyntaxOpt, statementPart); - if ((!isLeaf || isPartiallyExecuted) && !AreEquivalentActiveStatements(oldStatementSyntax, newStatementSyntax, statementPart)) + if ((!isLeaf || isPartiallyExecuted) && !AreEquivalentActiveStatements(oldStatementSyntax, newStatementSyntaxOpt, statementPart)) { // rude edit: internal active statement changed diagnostics.Add(new RudeEditDiagnostic(isLeaf ? RudeEditKind.PartiallyExecutedActiveStatementUpdate : RudeEditKind.ActiveStatementUpdate, newSpan)); } - // exception handling around the statement: - - var oldAncestors = GetExceptionHandlingAncestors(oldStatementSyntax, isLeaf); - var newAncestors = GetExceptionHandlingAncestors(newStatementSyntax, isLeaf); - - if (oldAncestors.Count > 0 || newAncestors.Count > 0) - { - var edits = match.GetSequenceEdits(oldAncestors, newAncestors); - ReportEnclosingExceptionHandlingRudeEdits(diagnostics, edits, oldStatementSyntax, newStatementSyntax); - - // Exception regions are not needed in presence of errors. - if (diagnostics.Count == 0) - { - Debug.Assert(oldAncestors.Count == newAncestors.Count); - newExceptionRegions[ordinal] = GetExceptionRegions(newAncestors, newText); - } - } - // other statements around active statement: - ReportOtherRudeEditsAroundActiveStatement(diagnostics, match, oldStatementSyntax, newStatementSyntax, isLeaf); + ReportOtherRudeEditsAroundActiveStatement(diagnostics, match, oldStatementSyntax, newStatementSyntaxOpt, isLeaf); } else if (match == null) { @@ -1088,6 +1070,18 @@ private void AnalyzeUpdatedActiveMethodBodies( } } + // exception handling around the statement: + CalculateExceptionRegionsAroundActiveStatement( + bodyMatch, + oldStatementSyntax, + newStatementSyntaxOpt, + newSpan, + ordinal, + newText, + isLeaf, + newExceptionRegions, + diagnostics); + Debug.Assert(newActiveStatements[ordinal] == default(LinePositionSpan) && newSpan != default(TextSpan)); newActiveStatements[ordinal] = newText.Lines.GetLinePositionSpan(newSpan); @@ -1100,6 +1094,44 @@ private void AnalyzeUpdatedActiveMethodBodies( } } + private void CalculateExceptionRegionsAroundActiveStatement( + Match bodyMatch, + SyntaxNode oldStatementSyntax, + SyntaxNode newStatementSyntaxOpt, + TextSpan newStatementSyntaxSpan, + int ordinal, + SourceText newText, + bool isLeaf, + ImmutableArray[] newExceptionRegions, + List diagnostics) + { + if (newStatementSyntaxOpt == null && bodyMatch.NewRoot.Span.Contains(newStatementSyntaxSpan.Start)) + { + newStatementSyntaxOpt = bodyMatch.NewRoot.FindToken(newStatementSyntaxSpan.Start).Parent; + } + + if (newStatementSyntaxOpt == null) + { + return; + } + + var oldAncestors = GetExceptionHandlingAncestors(oldStatementSyntax, isLeaf); + var newAncestors = GetExceptionHandlingAncestors(newStatementSyntaxOpt, isLeaf); + + if (oldAncestors.Count > 0 || newAncestors.Count > 0) + { + var edits = bodyMatch.GetSequenceEdits(oldAncestors, newAncestors); + ReportEnclosingExceptionHandlingRudeEdits(diagnostics, edits, oldStatementSyntax, newStatementSyntaxSpan); + + // Exception regions are not needed in presence of errors. + if (diagnostics.Count == 0) + { + Debug.Assert(oldAncestors.Count == newAncestors.Count); + newExceptionRegions[ordinal] = GetExceptionRegions(newAncestors, newText); + } + } + } + /// /// Calculates a syntax map of the entire method body including all lambda bodies it contains (recursively). /// Internal for testing. @@ -1601,7 +1633,7 @@ protected static bool HasEdit(Dictionary editMap, SyntaxNo #region Rude Edits around Active Statement - protected void AddRudeDiagnostic(List diagnostics, SyntaxNode oldNode, SyntaxNode newNode, SyntaxNode newActiveStatement) + protected void AddRudeDiagnostic(List diagnostics, SyntaxNode oldNode, SyntaxNode newNode, TextSpan newActiveStatementSpan) { if (oldNode == null) { @@ -1609,7 +1641,7 @@ protected void AddRudeDiagnostic(List diagnostics, SyntaxNod } else if (newNode == null) { - AddRudeDeleteAroundActiveStatement(diagnostics, oldNode, newActiveStatement); + AddRudeDeleteAroundActiveStatement(diagnostics, oldNode, newActiveStatementSpan); } else { @@ -1635,11 +1667,11 @@ protected void AddRudeInsertAroundActiveStatement(List diagn new[] { GetStatementDisplayName(newNode, EditKind.Insert) })); } - protected void AddRudeDeleteAroundActiveStatement(List diagnostics, SyntaxNode oldNode, SyntaxNode newActiveStatement) + protected void AddRudeDeleteAroundActiveStatement(List diagnostics, SyntaxNode oldNode, TextSpan newActiveStatementSpan) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.DeleteAroundActiveStatement, - newActiveStatement.Span, + newActiveStatementSpan, oldNode, new[] { GetStatementDisplayName(oldNode, EditKind.Delete) })); } diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index b87642f3ac6fb..7c74e8dfc5207 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -64,6 +64,11 @@ public override int GetHashCode() private readonly Dictionary _analyses; // A document id is added whenever any analysis reports rude edits. + // We collect a set of document ids that contained a rude edit + // at some point in time during the lifespan of an edit session. + // At the end of the session we aks the diagnostic analyzer to reanalyze + // the documents to clean up the diagnostics. + // An id may be present in this set even if the document doesn't have a rude edit anymore. private readonly object _documentsWithReportedRudeEditsGuard = new object(); private readonly HashSet _documentsWithReportedRudeEdits; diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 271067cd4e157..1b748f64ef81a 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -2912,12 +2912,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Friend Overrides Sub ReportEnclosingExceptionHandlingRudeEdits(diagnostics As List(Of RudeEditDiagnostic), exceptionHandlingEdits As IEnumerable(Of Edit(Of SyntaxNode)), oldStatement As SyntaxNode, - newStatement As SyntaxNode) + newStatementSpan As TextSpan) For Each edit In exceptionHandlingEdits Debug.Assert(edit.Kind <> EditKind.Update OrElse edit.OldNode.RawKind = edit.NewNode.RawKind) If edit.Kind <> EditKind.Update OrElse Not AreExceptionHandlingPartsEquivalent(edit.OldNode, edit.NewNode) Then - AddRudeDiagnostic(diagnostics, edit.OldNode, edit.NewNode, newStatement) + AddRudeDiagnostic(diagnostics, edit.OldNode, edit.NewNode, newStatementSpan) End If Next End Sub @@ -3112,7 +3112,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Dim onErrorOrResumeStatement = FindOnErrorOrResumeStatement(match.NewRoot) If onErrorOrResumeStatement IsNot Nothing Then - AddRudeDiagnostic(diagnostics, oldActiveStatement, onErrorOrResumeStatement, newActiveStatement) + AddRudeDiagnostic(diagnostics, oldActiveStatement, onErrorOrResumeStatement, newActiveStatement.Span) End If ReportRudeEditsForAncestorsDeclaringInterStatementTemps(diagnostics, match, oldActiveStatement, newActiveStatement, isLeaf) diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsENCRebuildableProjectImpl.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsENCRebuildableProjectImpl.cs index 67f8cb4399df8..6328599aef04a 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsENCRebuildableProjectImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/VsENCRebuildableProjectImpl.cs @@ -1118,7 +1118,12 @@ public int EncApplySucceeded(int hrApplyResult) /// /// Called when changes are being applied. /// - public int GetCurrentExceptionSpanPosition(uint id, VsTextSpan[] ptsNewPosition) + /// + /// The value of . + /// Set by to the index into . + /// + /// Output value holder. + public int GetCurrentExceptionSpanPosition(uint exceptionRegionId, VsTextSpan[] ptsNewPosition) { try { @@ -1129,7 +1134,7 @@ public int GetCurrentExceptionSpanPosition(uint id, VsTextSpan[] ptsNewPosition) Debug.Assert(!_encService.EditSession.StoppedAtException); Debug.Assert(ptsNewPosition.Length == 1); - var exceptionRegion = _exceptionRegions[(int)id]; + var exceptionRegion = _exceptionRegions[(int)exceptionRegionId]; var session = _encService.EditSession; var asid = _activeStatementIds[exceptionRegion.ActiveStatementId]; @@ -1142,7 +1147,7 @@ public int GetCurrentExceptionSpanPosition(uint id, VsTextSpan[] ptsNewPosition) Debug.Assert(!analysis.HasChangesAndErrors); Debug.Assert(!regions.IsDefault); - // Absence of rude edits guarantees that the exception regions around AS hasn't semantically changed. + // Absence of rude edits guarantees that the exception regions around AS haven't semantically changed. // Only their spans might have changed. ptsNewPosition[0] = regions[asid.Ordinal][exceptionRegion.Ordinal].ToVsTextSpan(); }