From 777d82629781450df76010c6683eaf02f31acded Mon Sep 17 00:00:00 2001 From: tmat Date: Wed, 13 Jan 2021 17:59:42 -0800 Subject: [PATCH 1/9] Test refactoring --- .../EditAndContinue/ActiveStatementTests.cs | 4 +- ...nsions.cs => EditAndContinueValidation.cs} | 20 ++- .../Helpers/EditingTestBase.cs | 8 +- .../EditAndContinue/StatementEditingTests.cs | 21 +-- .../EditAndContinue/TopLevelEditingTests.cs | 132 +++++++--------- .../EditAndContinueTestHelpers.cs | 149 +++++++++--------- .../EditAndContinue/Extensions.cs | 11 +- .../SemanticEditDescription.cs | 19 ++- .../EditAndContinue/ActiveStatementTests.vb | 4 +- ...nsions.vb => EditAndContinueValidation.vb} | 34 ++-- .../EditAndContinue/RudeEditStatementTests.vb | 20 +-- .../EditAndContinue/RudeEditTopLevelTests.vb | 107 +++++-------- .../CSharpEditAndContinueAnalyzer.cs | 2 +- .../AbstractEditAndContinueAnalyzer.cs | 44 ++++-- .../Portable/EditAndContinue/EditSession.cs | 3 +- .../VisualBasicEditAndContinueAnalyzer.vb | 2 +- 16 files changed, 249 insertions(+), 331 deletions(-) rename src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/{Extensions.cs => EditAndContinueValidation.cs} (91%) rename src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/{Extensions.vb => EditAndContinueValidation.vb} (69%) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 15261d07b29aa..2fc405947deb6 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -10000,7 +10000,7 @@ static void Goo() }"; var active = GetActiveStatements(src1, src2); - Extensions.VerifyUnchangedDocument(src2, active); + EditAndContinueValidation.VerifyUnchangedDocument(src2, active); } [Fact] @@ -10041,7 +10041,7 @@ static void Goo() }"; var active = GetActiveStatements(src1, src2); - Extensions.VerifyUnchangedDocument(src2, active); + EditAndContinueValidation.VerifyUnchangedDocument(src2, active); } #endregion diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/Extensions.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs similarity index 91% rename from src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/Extensions.cs rename to src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index 6bb7c4713248b..cc4b83fd6fe90 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/Extensions.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; @@ -13,7 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { - internal static class Extensions + internal static class EditAndContinueValidation { internal static void VerifyUnchangedDocument( string source, @@ -62,7 +64,7 @@ internal static void VerifySemanticDiagnostics( params RudeEditDiagnosticDescription[] expectedDiagnostics) { VerifySemantics( - editScript, + new[] { editScript }, expectedDiagnostics: expectedDiagnostics); } @@ -72,7 +74,7 @@ internal static void VerifySemanticDiagnostics( params RudeEditDiagnosticDescription[] expectedDiagnostics) { VerifySemantics( - editScript, + new[] { editScript }, targetFrameworks: targetFrameworks, expectedDiagnostics: expectedDiagnostics); } @@ -83,7 +85,7 @@ internal static void VerifySemanticDiagnostics( params RudeEditDiagnosticDescription[] expectedDiagnostics) { VerifySemantics( - editScript, + new[] { editScript }, expectedDeclarationError: expectedDeclarationError, expectedDiagnostics: expectedDiagnostics); } @@ -94,18 +96,16 @@ internal static void VerifySemantics( SemanticEditDescription[] expectedSemanticEdits) { VerifySemantics( - editScript, + new[] { editScript }, activeStatements, expectedSemanticEdits: expectedSemanticEdits, expectedDiagnostics: null); } internal static void VerifySemantics( - this EditScript editScript, + this EditScript[] editScripts, ActiveStatementsDescription? activeStatements = null, TargetFramework[]? targetFrameworks = null, - IEnumerable? additionalOldSources = null, - IEnumerable? additionalNewSources = null, SemanticEditDescription[]? expectedSemanticEdits = null, DiagnosticDescription? expectedDeclarationError = null, RudeEditDiagnosticDescription[]? expectedDiagnostics = null) @@ -113,10 +113,8 @@ internal static void VerifySemantics( foreach (var targetFramework in targetFrameworks ?? new[] { TargetFramework.NetStandard20, TargetFramework.NetCoreApp }) { new CSharpEditAndContinueTestHelpers(targetFramework).VerifySemantics( - editScript, + editScripts, activeStatements, - additionalOldSources, - additionalNewSources, expectedSemanticEdits, expectedDeclarationError, expectedDiagnostics); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index 6509002906865..f94c5e5aee4ea 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -36,7 +36,7 @@ internal enum MethodKind internal static SemanticEditDescription[] NoSemanticEdits = Array.Empty(); internal static RudeEditDiagnosticDescription Diagnostic(RudeEditKind rudeEditKind, string squiggle, params string[] arguments) - => new RudeEditDiagnosticDescription(rudeEditKind, squiggle, arguments, firstLine: null); + => new(rudeEditKind, squiggle, arguments, firstLine: null); internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func symbolProvider, IEnumerable> syntaxMap) { @@ -45,7 +45,7 @@ internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func } internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func symbolProvider, bool preserveLocalVariables = false) - => new SemanticEditDescription(kind, symbolProvider, null, preserveLocalVariables); + => new(kind, symbolProvider, syntaxMap: null, preserveLocalVariables); private static SyntaxTree ParseSource(string source) => CSharpEditAndContinueTestHelpers.CreateInstance().ParseText(ActiveStatementsDescription.ClearTags(source)); @@ -135,10 +135,10 @@ internal static string WrapMethodBodyWithClass(string bodySource, MethodKind kin }; internal static ActiveStatementsDescription GetActiveStatements(string oldSource, string newSource) - => new ActiveStatementsDescription(oldSource, newSource); + => new(oldSource, newSource); internal static SyntaxMapDescription GetSyntaxMap(string oldSource, string newSource) - => new SyntaxMapDescription(oldSource, newSource); + => new(oldSource, newSource); internal static void VerifyPreserveLocalVariables(EditScript edits, bool preserveLocalVariables) { diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 945e562f5e05d..c08c97be64551 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -8786,13 +8786,9 @@ static IEnumerable F() var edits = GetTopEdits(src1, src2); CSharpEditAndContinueTestHelpers.CreateInstance40().VerifySemantics( - edits, + new[] { edits }, ActiveStatementsDescription.Empty, - null, - null, - null, - null, - new[] + expectedDiagnostics: new[] { Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "static IEnumerable F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute") }); @@ -8825,13 +8821,7 @@ static IEnumerable F() "; var edits = GetTopEdits(src1, src2); - CSharpEditAndContinueTestHelpers.CreateInstance40().VerifySemantics( - edits, - ActiveStatementsDescription.Empty, - null, - null, - null, - null); + CSharpEditAndContinueTestHelpers.CreateInstance40().VerifySemantics(new[] { edits }); } #endregion @@ -9578,7 +9568,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( + edits.VerifySemanticDiagnostics( targetFrameworks: new[] { TargetFramework.MinimalAsync }, expectedDiagnostics: new[] { @@ -9614,7 +9604,8 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics(targetFrameworks: new[] { TargetFramework.MinimalAsync }); + edits.VerifySemanticDiagnostics( + targetFrameworks: new[] { TargetFramework.MinimalAsync }); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 0f971b1d5446e..cc49113b02e96 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -4723,13 +4723,10 @@ public void StaticCtor_Partial_Delete() var srcA2 = "partial class C { }"; var srcB2 = "partial class C { static C() { } }"; - var edits = GetTopEdits(srcA1, srcA2); - - edits.VerifySemantics( + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, activeStatements: ActiveStatementsDescription.Empty, targetFrameworks: null, - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single()) @@ -4746,18 +4743,13 @@ public void InstanceCtor_Partial_DeletePrivate() var srcA2 = "partial class C { }"; var srcB2 = "partial class C { C() { } }"; - var edits = GetTopEdits(srcA1, srcA2); - - edits.VerifySemantics( + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, activeStatements: ActiveStatementsDescription.Empty, - targetFrameworks: null, - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) - }, - expectedDeclarationError: null); + }); } [Fact] @@ -4769,18 +4761,13 @@ public void InstanceCtor_Partial_DeletePublic() var srcA2 = "partial class C { }"; var srcB2 = "partial class C { public C() { } }"; - var edits = GetTopEdits(srcA1, srcA2); - - edits.VerifySemantics( + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, activeStatements: ActiveStatementsDescription.Empty, - targetFrameworks: null, - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) - }, - expectedDeclarationError: null); + }); } [Fact] @@ -4792,16 +4779,13 @@ public void InstanceCtor_Partial_DeletePrivateToPublic() var srcA2 = "partial class C { }"; var srcB2 = "partial class C { public C() { } }"; - var edits = GetTopEdits(srcA1, srcA2); - - edits.VerifySemantics( + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, activeStatements: ActiveStatementsDescription.Empty, - targetFrameworks: null, - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, - expectedSemanticEdits: null, - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.Delete, "partial class C", FeaturesResources.constructor) }, - expectedDeclarationError: null); + expectedDiagnostics: new[] + { + Diagnostic(RudeEditKind.Delete, "partial class C", FeaturesResources.constructor) + }); } [Fact] @@ -4813,18 +4797,13 @@ public void StaticCtor_Partial_Insert() var srcA2 = "partial class C { static C() { } }"; var srcB2 = "partial class C { }"; - var edits = GetTopEdits(srcA1, srcA2); - - edits.VerifySemantics( + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, activeStatements: ActiveStatementsDescription.Empty, - targetFrameworks: null, - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) - }, - expectedDeclarationError: null); + }); } [Fact] @@ -4836,18 +4815,13 @@ public void InstanceCtor_Partial_InsertPublic() var srcA2 = "partial class C { public C() { } }"; var srcB2 = "partial class C { }"; - var edits = GetTopEdits(srcA1, srcA2); - - edits.VerifySemantics( + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, activeStatements: ActiveStatementsDescription.Empty, - targetFrameworks: null, - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) - }, - expectedDeclarationError: null); + }); } [Fact] @@ -4859,18 +4833,13 @@ public void InstanceCtor_Partial_InsertPrivate() var srcA2 = "partial class C { private C() { } }"; var srcB2 = "partial class C { }"; - var edits = GetTopEdits(srcA1, srcA2); - - edits.VerifySemantics( + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, activeStatements: ActiveStatementsDescription.Empty, - targetFrameworks: null, - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) - }, - expectedDeclarationError: null); + }); } [Fact] @@ -4882,16 +4851,29 @@ public void InstanceCtor_Partial_InsertInternal() var srcA2 = "partial class C { internal C() { } }"; var srcB2 = "partial class C { }"; - var edits = GetTopEdits(srcA1, srcA2); + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + expectedSemanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }); + } - edits.VerifySemantics( - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, + [Fact] + public void InstanceCtor_Partial_InsertInternal_WithBody() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { internal C() { } }"; + + var srcA2 = "partial class C { internal C() { Console.WriteLine(1); } }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) - }, - expectedDeclarationError: null); + }); } [Fact] @@ -4903,16 +4885,13 @@ public void InstanceCtor_Partial_InsertPrivateToPublic() var srcA2 = "partial class C { public C() { } }"; var srcB2 = "partial class C { }"; - var edits = GetTopEdits(srcA1, srcA2); - - edits.VerifySemantics( + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, activeStatements: ActiveStatementsDescription.Empty, - targetFrameworks: null, - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, - expectedSemanticEdits: null, - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingConstructorVisibility, "public C()") }, - expectedDeclarationError: null); + expectedDiagnostics: new[] + { + Diagnostic(RudeEditKind.ChangingConstructorVisibility, "public C()") + }); } [Fact] @@ -4924,16 +4903,13 @@ public void InstanceCtor_Partial_InsertPrivateToInternal() var srcA2 = "partial class C { internal C() { } }"; var srcB2 = "partial class C { }"; - var edits = GetTopEdits(srcA1, srcA2); - - edits.VerifySemantics( + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, activeStatements: ActiveStatementsDescription.Empty, - targetFrameworks: null, - additionalOldSources: new[] { srcB1 }, - additionalNewSources: new[] { srcB2 }, - expectedSemanticEdits: null, - expectedDiagnostics: new[] { Diagnostic(RudeEditKind.ChangingConstructorVisibility, "internal C()") }, - expectedDeclarationError: null); + expectedDiagnostics: new[] + { + Diagnostic(RudeEditKind.ChangingConstructorVisibility, "internal C()") + }); } [Fact] diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index f30485687662b..ac3af44f52fc5 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -87,8 +87,9 @@ internal void VerifyRudeDiagnostics( var editMap = BuildEditMap(editScript); var documentId = DocumentId.CreateNewId(ProjectId.CreateNewId("TestEnCProject"), "TestEnCDocument"); + var testAccessor = Analyzer.GetTestAccessor(); - Analyzer.GetTestAccessor().AnalyzeSyntax( + testAccessor.AnalyzeMemberBodiesSyntax( editScript, editMap, oldText, @@ -100,6 +101,8 @@ internal void VerifyRudeDiagnostics( updatedActiveMethodMatches, diagnostics); + testAccessor.ReportTopLevelSynctactiveRudeEdits(diagnostics, editScript, editMap); + diagnostics.Verify(newSource, expectedDiagnostics); // check active statements: @@ -173,102 +176,97 @@ internal void VerifyLineEdits( } internal void VerifySemantics( - EditScript editScript, + IEnumerable> editScripts, ActiveStatementsDescription? activeStatements = null, - IEnumerable? additionalOldSources = null, - IEnumerable? additionalNewSources = null, SemanticEditDescription[]? expectedSemanticEdits = null, DiagnosticDescription? expectedDeclarationError = null, RudeEditDiagnosticDescription[]? expectedDiagnostics = null) { activeStatements ??= ActiveStatementsDescription.Empty; - var editMap = BuildEditMap(editScript); - - var oldRoot = editScript.Match.OldRoot; - var newRoot = editScript.Match.NewRoot; - - var oldSource = oldRoot.SyntaxTree.ToString(); - var newSource = newRoot.SyntaxTree.ToString(); - - var oldText = SourceText.From(oldSource); - var newText = SourceText.From(newSource); - - IEnumerable oldTrees = new[] { oldRoot.SyntaxTree }; - IEnumerable newTrees = new[] { newRoot.SyntaxTree }; - - if (additionalOldSources != null) - { - oldTrees = oldTrees.Concat(additionalOldSources.Select(s => ParseText(s))); - } - - if (additionalOldSources != null) - { - newTrees = newTrees.Concat(additionalNewSources.Select(s => ParseText(s))); - } + var oldTrees = editScripts.Select(editScript => editScript.Match.OldRoot.SyntaxTree).ToArray(); + var newTrees = editScripts.Select(editScript => editScript.Match.NewRoot.SyntaxTree).ToArray(); var oldCompilation = CreateLibraryCompilation("Old", oldTrees); var newCompilation = CreateLibraryCompilation("New", newTrees); - var oldModel = oldCompilation.GetSemanticModel(oldRoot.SyntaxTree); - var newModel = newCompilation.GetSemanticModel(newRoot.SyntaxTree); - var oldActiveStatements = activeStatements.OldStatements.AsImmutable(); var updatedActiveMethodMatches = new List(); var triviaEdits = new List<(SyntaxNode OldNode, SyntaxNode NewNode)>(); var actualLineEdits = new List(); var actualSemanticEdits = new List(); - var diagnostics = new List(); - var documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()); + var includeFirstLineInDiagnostics = expectedDiagnostics?.Any(d => d.FirstLine != null) == true; + var actualDiagnosticDescriptions = new List(); + var actualDeclarationErrors = new List(); var actualNewActiveStatements = new ActiveStatement[activeStatements.OldStatements.Length]; var actualNewExceptionRegions = new ImmutableArray[activeStatements.OldStatements.Length]; + var testAccessor = Analyzer.GetTestAccessor(); - Analyzer.GetTestAccessor().AnalyzeSyntax( - editScript, - editMap, - oldText, - newText, - oldActiveStatements, - activeStatements.OldTrackingSpans.ToImmutableArrayOrEmpty(), - actualNewActiveStatements, - actualNewExceptionRegions, - updatedActiveMethodMatches, - diagnostics); - - diagnostics.Verify(newSource); - - Analyzer.GetTestAccessor().AnalyzeTrivia( - oldText, - newText, - editScript.Match, - editMap, - triviaEdits, - actualLineEdits, - diagnostics, - CancellationToken.None); - - diagnostics.Verify(newSource); + foreach (var editScript in editScripts) + { + var oldRoot = editScript.Match.OldRoot; + var newRoot = editScript.Match.NewRoot; + var oldSource = oldRoot.SyntaxTree.ToString(); + var newSource = newRoot.SyntaxTree.ToString(); + + var editMap = BuildEditMap(editScript); + var oldText = SourceText.From(oldSource); + var newText = SourceText.From(newSource); + var oldModel = oldCompilation.GetSemanticModel(oldRoot.SyntaxTree); + var newModel = newCompilation.GetSemanticModel(newRoot.SyntaxTree); + var diagnostics = new List(); + + testAccessor.AnalyzeMemberBodiesSyntax( + editScript, + editMap, + oldText, + newText, + oldActiveStatements, + activeStatements.OldTrackingSpans.ToImmutableArrayOrEmpty(), + actualNewActiveStatements, + actualNewExceptionRegions, + updatedActiveMethodMatches, + diagnostics); + + testAccessor.ReportTopLevelSynctactiveRudeEdits(diagnostics, editScript, editMap); + + testAccessor.AnalyzeTrivia( + oldText, + newText, + editScript.Match, + editMap, + triviaEdits, + actualLineEdits, + diagnostics, + CancellationToken.None); + + testAccessor.AnalyzeSemantics( + editScript, + editMap, + oldText, + oldActiveStatements, + triviaEdits, + updatedActiveMethodMatches, + oldModel, + newModel, + actualSemanticEdits, + diagnostics, + out var firstDeclarationError, + CancellationToken.None); + + if (firstDeclarationError != null) + { + actualDeclarationErrors.Add(firstDeclarationError); + } - Analyzer.GetTestAccessor().AnalyzeSemantics( - editScript, - editMap, - oldText, - oldActiveStatements, - triviaEdits, - updatedActiveMethodMatches, - oldModel, - newModel, - actualSemanticEdits, - diagnostics, - out var firstDeclarationErrorOpt, - CancellationToken.None); + actualDiagnosticDescriptions.AddRange(diagnostics.ToDescription(newSource, includeFirstLineInDiagnostics)); + } - var actualDeclarationErrors = (firstDeclarationErrorOpt != null) ? new[] { firstDeclarationErrorOpt } : Array.Empty(); var expectedDeclarationErrors = (expectedDeclarationError != null) ? new[] { expectedDeclarationError } : Array.Empty(); actualDeclarationErrors.Verify(expectedDeclarationErrors); - diagnostics.Verify(newSource, expectedDiagnostics); + actualDiagnosticDescriptions.Verify(expectedDiagnostics); if (expectedSemanticEdits == null) { @@ -292,6 +290,9 @@ internal void VerifySemantics( Assert.Equal(expectedNewSymbol, actualNewSymbol); var expectedSyntaxMap = expectedSemanticEdits[i].SyntaxMap; + var syntaxTreeOrdinal = expectedSemanticEdits[i].SyntaxTreeOrdinal; + var oldRoot = oldTrees[syntaxTreeOrdinal].GetRoot(); + var newRoot = newTrees[syntaxTreeOrdinal].GetRoot(); var actualSyntaxMap = actualSemanticEdits[i].SyntaxMap; Assert.Equal(expectedSemanticEdits[i].PreserveLocalVariables, actualSemanticEdits[i].PreserveLocalVariables); @@ -301,8 +302,6 @@ internal void VerifySemantics( Contract.ThrowIfNull(actualSyntaxMap); Assert.True(expectedSemanticEdits[i].PreserveLocalVariables); - var newNodes = new List(); - foreach (var expectedSpanMapping in expectedSyntaxMap) { var newNode = FindNode(newRoot, expectedSpanMapping.Value); @@ -310,8 +309,6 @@ internal void VerifySemantics( var actualOldNode = actualSyntaxMap(newNode); Assert.Equal(expectedOldNode, actualOldNode); - - newNodes.Add(newNode); } } else if (!expectedSemanticEdits[i].PreserveLocalVariables) diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/Extensions.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/Extensions.cs index ee2e2eb85c6c4..dda94c6ed5806 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/Extensions.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/Extensions.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests @@ -15,13 +14,15 @@ namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests internal static class Extensions { public static void Verify(this IEnumerable diagnostics, string newSource, params RudeEditDiagnosticDescription[] expectedDiagnostics) + => diagnostics.ToDescription(newSource, expectedDiagnostics.Any(d => d.FirstLine != null)).Verify(expectedDiagnostics); + + public static void Verify(this IEnumerable diagnostics, params RudeEditDiagnosticDescription[] expectedDiagnostics) { - expectedDiagnostics = expectedDiagnostics ?? Array.Empty(); - var actualDiagnostics = diagnostics.ToDescription(newSource, expectedDiagnostics.Any(d => d.FirstLine != null)).ToArray(); - AssertEx.SetEqual(expectedDiagnostics, actualDiagnostics, itemSeparator: ",\r\n"); + expectedDiagnostics ??= Array.Empty(); + AssertEx.SetEqual(expectedDiagnostics, diagnostics, itemSeparator: ",\r\n"); } - private static IEnumerable ToDescription(this IEnumerable diagnostics, string newSource, bool includeFirstLines) + public static IEnumerable ToDescription(this IEnumerable diagnostics, string newSource, bool includeFirstLines) { return diagnostics.Select(d => new RudeEditDiagnosticDescription( d.Kind, diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs index da3dda8f23164..ab646174bfa5a 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using Microsoft.CodeAnalysis.Emit; @@ -17,13 +15,20 @@ public sealed class SemanticEditDescription public readonly Func SymbolProvider; public readonly IEnumerable> SyntaxMap; public readonly bool PreserveLocalVariables; + public readonly int SyntaxTreeOrdinal; - public SemanticEditDescription(SemanticEditKind kind, Func symbolProvider, IEnumerable> syntaxMap, bool preserveLocalVariables) + public SemanticEditDescription( + SemanticEditKind kind, + Func symbolProvider, + IEnumerable> syntaxMap, + bool preserveLocalVariables, + int syntaxTreeOrdinal = 0) { - this.Kind = kind; - this.SymbolProvider = symbolProvider; - this.SyntaxMap = syntaxMap; - this.PreserveLocalVariables = preserveLocalVariables; + Kind = kind; + SymbolProvider = symbolProvider; + SyntaxMap = syntaxMap; + PreserveLocalVariables = preserveLocalVariables; + SyntaxTreeOrdinal = syntaxTreeOrdinal; } } } diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index 8ae9281e3db2d..529323c16a599 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -5615,7 +5615,7 @@ Module C End Module" Dim active = GetActiveStatements(src1, src2) - Extensions.VerifyUnchangedDocument(src2, active) + EditAndContinueValidation.VerifyUnchangedDocument(src2, active) End Sub @@ -5649,7 +5649,7 @@ Module C End Module" Dim active = GetActiveStatements(src1, src2) - Extensions.VerifyUnchangedDocument(src2, active) + EditAndContinueValidation.VerifyUnchangedDocument(src2, active) End Sub #End Region diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/Extensions.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb similarity index 69% rename from src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/Extensions.vb rename to src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb index 3d4abb0093455..68dd18248c272 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/Extensions.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb @@ -11,7 +11,7 @@ Imports Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests - Friend Module Extensions + Friend Module EditAndContinueValidation Friend Sub VerifyUnchangedDocument( source As String, @@ -55,7 +55,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Friend Sub VerifySemanticDiagnostics(editScript As EditScript(Of SyntaxNode), expectedDeclarationError As DiagnosticDescription, ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics(editScript, ActiveStatementsDescription.Empty, Nothing, expectedDeclarationError, expectedDiagnostics) + VerifySemantics({editScript}, ActiveStatementsDescription.Empty, Nothing, expectedDeclarationError, expectedDiagnostics) End Sub @@ -63,7 +63,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests activeStatements As ActiveStatementsDescription, expectedSemanticEdits As SemanticEditDescription(), ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics(editScript, activeStatements, Nothing, Nothing, expectedSemanticEdits, Nothing, expectedDiagnostics) + VerifySemantics({editScript}, activeStatements, expectedSemanticEdits, expectedDeclarationError:=Nothing, expectedDiagnostics) End Sub @@ -72,32 +72,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests expectedSemanticEdits As SemanticEditDescription(), expectedDeclarationError As DiagnosticDescription, ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics(editScript, activeStatements, Nothing, Nothing, expectedSemanticEdits, expectedDeclarationError, expectedDiagnostics) + VerifySemantics({editScript}, activeStatements, expectedSemanticEdits, expectedDeclarationError, expectedDiagnostics) End Sub - Friend Sub VerifySemantics(editScript As EditScript(Of SyntaxNode), - activeStatements As ActiveStatementsDescription, - additionalOldSources As IEnumerable(Of String), - additionalNewSources As IEnumerable(Of String), - expectedSemanticEdits As SemanticEditDescription(), - ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics(editScript, activeStatements, additionalOldSources, additionalNewSources, expectedSemanticEdits, Nothing, expectedDiagnostics) - End Sub - - - Friend Sub VerifySemantics(editScript As EditScript(Of SyntaxNode), - activeStatements As ActiveStatementsDescription, - additionalOldSources As IEnumerable(Of String), - additionalNewSources As IEnumerable(Of String), - expectedSemanticEdits As SemanticEditDescription(), - expectedDeclarationError As DiagnosticDescription, - ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) + Friend Sub VerifySemantics(editScripts As EditScript(Of SyntaxNode)(), + Optional activeStatements As ActiveStatementsDescription = Nothing, + Optional expectedSemanticEdits As SemanticEditDescription() = Nothing, + Optional expectedDeclarationError As DiagnosticDescription = Nothing, + Optional expectedDiagnostics As RudeEditDiagnosticDescription() = Nothing) VisualBasicEditAndContinueTestHelpers.CreateInstance().VerifySemantics( - editScript, + editScripts, activeStatements, - additionalOldSources, - additionalNewSources, expectedSemanticEdits, expectedDeclarationError, expectedDiagnostics) diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb index e49b9db2ea9f1..b7540901df46f 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb @@ -6012,10 +6012,8 @@ End Class " Dim edits = GetTopEdits(src1, src2) VisualBasicEditAndContinueTestHelpers.CreateInstance40().VerifySemantics( - editScript:=edits, + editScripts:={edits}, activeStatements:=ActiveStatementsDescription.Empty, - additionalOldSources:=Nothing, - additionalNewSources:=Nothing, expectedSemanticEdits:=Nothing, expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Iterator Function F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute")}, expectedDeclarationError:=Nothing) @@ -6043,10 +6041,8 @@ End Class " Dim edits = GetTopEdits(src1, src2) VisualBasicEditAndContinueTestHelpers.CreateInstance40().VerifySemantics( - editScript:=edits, + editScripts:={edits}, activeStatements:=ActiveStatementsDescription.Empty, - additionalOldSources:=Nothing, - additionalNewSources:=Nothing, expectedSemanticEdits:=Nothing, expectedDiagnostics:=Nothing, expectedDeclarationError:=Nothing) @@ -6130,10 +6126,8 @@ End Class " Dim edits = GetTopEdits(src1, src2) VisualBasicEditAndContinueTestHelpers.CreateInstanceMinAsync().VerifySemantics( - editScript:=edits, + editScripts:={edits}, activeStatements:=ActiveStatementsDescription.Empty, - additionalOldSources:=Nothing, - additionalNewSources:=Nothing, expectedSemanticEdits:=Nothing, expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Async Function F()", "System.Runtime.CompilerServices.AsyncStateMachineAttribute")}, expectedDeclarationError:=Nothing) @@ -6161,13 +6155,7 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - VisualBasicEditAndContinueTestHelpers.CreateInstanceMinAsync().VerifySemantics( - edits, - ActiveStatementsDescription.Empty, - Nothing, - Nothing, - Nothing, - Nothing) + VisualBasicEditAndContinueTestHelpers.CreateInstanceMinAsync().VerifySemantics({edits}) End Sub #End Region End Class diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb index 6927e79e76926..8ad35b4540c8f 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb @@ -2763,10 +2763,9 @@ End Class Dim srcA2 = "Partial Class C : End Class" Dim srcB2 = "Partial Class C" & vbLf & "Shared Sub New() : End Sub : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, {srcB1}, {srcB2}, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) End Sub @@ -2777,10 +2776,9 @@ End Class Dim srcA2 = "Partial Module C : End Module" Dim srcB2 = "Partial Module C" & vbLf & "Sub New() : End Sub : End Module" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, {srcB1}, {srcB2}, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) End Sub @@ -2791,12 +2789,9 @@ End Class Dim srcA2 = "Partial Class C : End Class" Dim srcB2 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) End Sub @@ -2807,12 +2802,9 @@ End Class Dim srcA2 = "Partial Class C : End Class" Dim srcB2 = "Partial Class C" & vbLf & "Public Sub New() : End Sub : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) End Sub @@ -2823,13 +2815,9 @@ End Class Dim srcA2 = "Partial Class C : End Class" Dim srcB2 = "Partial Class C" & vbLf & "Public Sub New() : End Sub : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - Nothing, - Diagnostic(RudeEditKind.Delete, "Partial Class C", FeaturesResources.constructor)) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedDiagnostics:={Diagnostic(RudeEditKind.Delete, "Partial Class C", FeaturesResources.constructor)}) End Sub @@ -2840,12 +2828,9 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Shared Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -2856,12 +2841,9 @@ End Class Dim srcA2 = "Partial Module C" & vbLf & "Sub New() : End Sub : End Module" Dim srcB2 = "Partial Module C : End Module" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -2872,12 +2854,9 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -2888,12 +2867,9 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -2904,12 +2880,9 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Friend Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -2920,13 +2893,9 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - Nothing, - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Sub New()")) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedDiagnostics:={Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Sub New()")}) End Sub @@ -2937,13 +2906,9 @@ End Class Dim srcA2 = "Partial Class C" & vbLf & "Friend Sub New() : End Sub : End Class" Dim srcB2 = "Partial Class C : End Class" - Dim edits = GetTopEdits(srcA1, srcA2) - - edits.VerifySemantics(ActiveStatementsDescription.Empty, - {srcB1}, - {srcB2}, - Nothing, - Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Friend Sub New()")) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedDiagnostics:=Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Friend Sub New()")) End Sub diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 492e15a0bb273..66fdf052fa24e 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -3030,7 +3030,7 @@ public void ClassifyDeclarationBodyRudeUpdates(SyntaxNode newDeclarationOrBody) #endregion } - internal override void ReportSyntacticRudeEdits( + internal override void ReportTopLevelSyntacticRudeEdits( List diagnostics, Match match, Edit edit, diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index f493ffd7429b0..2d9276aa67fc0 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -309,6 +309,8 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind protected abstract void ReportLocalFunctionsDeclarationRudeEdits(Match bodyMatch, List diagnostics); internal abstract void ReportSyntacticRudeEdits(List diagnostics, Match match, Edit edit, Dictionary editMap); + internal abstract void ReportTopLevelSyntacticRudeEdits(List diagnostics, Match match, Edit edit, Dictionary editMap); + 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 isNonLeaf); internal abstract void ReportMemberUpdateRudeEdits(List diagnostics, SyntaxNode newMember, TextSpan? span); @@ -491,7 +493,7 @@ public async Task AnalyzeDocumentAsync( var syntacticEdits = topMatch.GetTreeEdits(); var editMap = BuildEditMap(syntacticEdits); - AnalyzeSyntax( + AnalyzeMemberBodiesSyntax( syntacticEdits, editMap, oldText, @@ -503,6 +505,8 @@ public async Task AnalyzeDocumentAsync( updatedMethods, diagnostics); + ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); + if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0} syntactic rude edits, first: '{1}'", diagnostics.Count, document.FilePath); @@ -564,14 +568,14 @@ public async Task AnalyzeDocumentAsync( newModel, semanticEdits, diagnostics, - out var firstDeclaratingErrorOpt, + out var firstDeclaratingError, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); - if (firstDeclaratingErrorOpt != null) + if (firstDeclaratingError != null) { - var location = firstDeclaratingErrorOpt.Location; + var location = firstDeclaratingError.Location; DocumentAnalysisResults.Log.Write("Declaration errors, first: {0}", location.IsInSource ? location.SourceTree!.FilePath : location.MetadataModule!.Name); return DocumentAnalysisResults.Errors(newActiveStatements.AsImmutable(), ImmutableArray.Empty, hasSemanticErrors: true); @@ -606,6 +610,14 @@ public async Task AnalyzeDocumentAsync( } } + private void ReportTopLevelSyntacticRudeEdits(List diagnostics, EditScript syntacticEdits, Dictionary editMap) + { + foreach (var edit in syntacticEdits.Edits) + { + ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits.Match, edit, editMap); + } + } + internal static Dictionary BuildEditMap(EditScript editScript) { var map = new Dictionary(editScript.Edits.Length); @@ -632,7 +644,7 @@ internal static Dictionary BuildEditMap(EditScript script, Dictionary editMap, SourceText oldText, @@ -652,18 +664,15 @@ private void AnalyzeSyntax( for (var i = 0; i < script.Edits.Length; i++) { - var edit = script.Edits[i]; - - AnalyzeUpdatedActiveMethodBodies(script, i, editMap, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, updatedMethods, diagnostics); - ReportSyntacticRudeEdits(diagnostics, script.Match, edit, editMap); + AnalyzeChangedMemberBody(script, i, editMap, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, updatedMethods, diagnostics); } - UpdateUneditedSpans(diagnostics, script.Match, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions); + AnalyzeUnchangedMemberBodies(diagnostics, script.Match, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions); Debug.Assert(newActiveStatements.All(a => a != null)); } - private void UpdateUneditedSpans( + private void AnalyzeUnchangedMemberBodies( List diagnostics, Match topMatch, SourceText oldText, @@ -910,7 +919,7 @@ public UpdatedMemberInfo( } } - private void AnalyzeUpdatedActiveMethodBodies( + private void AnalyzeChangedMemberBody( EditScript topEditScript, int editOrdinal, Dictionary editMap, @@ -2878,11 +2887,11 @@ private bool DeferConstructorEdit( Dictionary constructorEdits; if (newSymbol.IsStatic) { - constructorEdits = staticConstructorEdits ??= new Dictionary(); + constructorEdits = staticConstructorEdits ??= new(); } else { - constructorEdits = instanceConstructorEdits ??= new Dictionary(); + constructorEdits = instanceConstructorEdits ??= new(); } if (!constructorEdits.TryGetValue(newType, out var edit)) @@ -3887,7 +3896,7 @@ internal readonly struct TestAccessor public TestAccessor(AbstractEditAndContinueAnalyzer abstractEditAndContinueAnalyzer) => _abstractEditAndContinueAnalyzer = abstractEditAndContinueAnalyzer; - internal void AnalyzeSyntax( + internal void AnalyzeMemberBodiesSyntax( EditScript script, Dictionary editMap, SourceText oldText, @@ -3899,9 +3908,12 @@ internal void AnalyzeSyntax( [Out] List updatedMethods, [Out] List diagnostics) { - _abstractEditAndContinueAnalyzer.AnalyzeSyntax(script, editMap, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, updatedMethods, diagnostics); + _abstractEditAndContinueAnalyzer.AnalyzeMemberBodiesSyntax(script, editMap, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, updatedMethods, diagnostics); } + internal void ReportTopLevelSynctactiveRudeEdits(List diagnostics, EditScript syntacticEdits, Dictionary editMap) + => _abstractEditAndContinueAnalyzer.ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); + internal void AnalyzeUnchangedDocument( ImmutableArray oldActiveStatements, SourceText newText, diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index 019ef11c5e560..b4c5f8f5759da 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -45,8 +45,7 @@ internal sealed class EditSession : IDisposable /// The work is triggered by an incremental analyzer on idle or explicitly when "continue" operation is executed. /// Contains analyses of the latest observed document versions. /// - private readonly Dictionary Results)> _analyses - = new Dictionary)>(); + private readonly Dictionary Results)> _analyses = new(); private readonly object _analysesGuard = new(); /// diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 5a3357fd41587..3d54db99f8c96 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -2831,7 +2831,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue #End Region End Structure - Friend Overrides Sub ReportSyntacticRudeEdits(diagnostics As List(Of RudeEditDiagnostic), + Friend Overrides Sub ReportTopLevelSyntacticRudeEdits(diagnostics As List(Of RudeEditDiagnostic), match As Match(Of SyntaxNode), edit As Edit(Of SyntaxNode), editMap As Dictionary(Of SyntaxNode, EditKind)) From ed9b9cbb39d211e28c9d0e6f5c8b649c0f408abd Mon Sep 17 00:00:00 2001 From: tmat Date: Thu, 14 Jan 2021 12:10:58 -0800 Subject: [PATCH 2/9] Fix issues with double-update of an implicit constructor --- .../EditAndContinue/TopLevelEditingTests.cs | 73 ++++++++++++---- .../EditAndContinueTestHelpers.cs | 3 +- ...ementTests.vb => StatementEditingTests.vb} | 2 +- ...pLevelTests.vb => TopLevelEditingTests.vb} | 86 +++++++++++++++---- .../AbstractEditAndContinueAnalyzer.cs | 82 +++++++++++------- 5 files changed, 179 insertions(+), 67 deletions(-) rename src/EditorFeatures/VisualBasicTest/EditAndContinue/{RudeEditStatementTests.vb => StatementEditingTests.vb} (99%) rename src/EditorFeatures/VisualBasicTest/EditAndContinue/{RudeEditTopLevelTests.vb => TopLevelEditingTests.vb} (98%) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index cc49113b02e96..3ab76f5231707 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -4577,6 +4577,23 @@ public void InstanceCtorInsert_Public_Implicit() new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } + [Fact] + public void InstanceCtorInsert_Partial_Public_Implicit() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { public C() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + expectedSemanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }); + } + [Fact] public void InstanceCtorInsert_Public_NoImplicit() { @@ -4585,7 +4602,29 @@ public void InstanceCtorInsert_Public_NoImplicit() var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + expectedSemanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters.IsEmpty)) + }); + } + + [Fact] + public void InstanceCtorInsert_Partial_Public_NoImplicit() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { public C(int a) { } }"; + + var srcA2 = "partial class C { public C() { } }"; + var srcB2 = "partial class C { public C(int a) { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + expectedSemanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters.IsEmpty)) + }); } [Fact] @@ -4715,7 +4754,7 @@ public void InstanceCtorInsert_InternalProtected_NoImplicit() } [Fact] - public void StaticCtor_Partial_Delete() + public void StaticCtor_Partial_DeleteInsert() { var srcA1 = "partial class C { static C() { } }"; var srcB1 = "partial class C { }"; @@ -4729,13 +4768,13 @@ public void StaticCtor_Partial_Delete() targetFrameworks: null, expectedSemanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single()) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) }, expectedDeclarationError: null); } [Fact] - public void InstanceCtor_Partial_DeletePrivate() + public void InstanceCtor_Partial_DeletePrivateInsertPrivate() { var srcA1 = "partial class C { C() { } }"; var srcB1 = "partial class C { }"; @@ -4748,15 +4787,15 @@ public void InstanceCtor_Partial_DeletePrivate() activeStatements: ActiveStatementsDescription.Empty, expectedSemanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } [Fact] - public void InstanceCtor_Partial_DeletePublic() + public void InstanceCtor_Partial_DeletePublicInsertPublic() { var srcA1 = "partial class C { public C() { } }"; - var srcB1 = "partial class C { }"; + var srcB1 = "partial class C { }"; var srcA2 = "partial class C { }"; var srcB2 = "partial class C { public C() { } }"; @@ -4766,12 +4805,12 @@ public void InstanceCtor_Partial_DeletePublic() activeStatements: ActiveStatementsDescription.Empty, expectedSemanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } [Fact] - public void InstanceCtor_Partial_DeletePrivateToPublic() + public void InstanceCtor_Partial_DeletePrivateInsertPublic() { var srcA1 = "partial class C { C() { } }"; var srcB1 = "partial class C { }"; @@ -4784,12 +4823,12 @@ public void InstanceCtor_Partial_DeletePrivateToPublic() activeStatements: ActiveStatementsDescription.Empty, expectedDiagnostics: new[] { - Diagnostic(RudeEditKind.Delete, "partial class C", FeaturesResources.constructor) + Diagnostic(RudeEditKind.ChangingConstructorVisibility, "public C()") }); } [Fact] - public void StaticCtor_Partial_Insert() + public void StaticCtor_Partial_InsertDelete() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { static C() { } }"; @@ -4807,7 +4846,7 @@ public void StaticCtor_Partial_Insert() } [Fact] - public void InstanceCtor_Partial_InsertPublic() + public void InstanceCtor_Partial_InsertPublicDeletePublic() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { public C() { } }"; @@ -4825,7 +4864,7 @@ public void InstanceCtor_Partial_InsertPublic() } [Fact] - public void InstanceCtor_Partial_InsertPrivate() + public void InstanceCtor_Partial_InsertPrivateDeletePrivate() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { private C() { } }"; @@ -4843,7 +4882,7 @@ public void InstanceCtor_Partial_InsertPrivate() } [Fact] - public void InstanceCtor_Partial_InsertInternal() + public void InstanceCtor_Partial_DeleteInternalInsertInternal() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { internal C() { } }"; @@ -4860,7 +4899,7 @@ public void InstanceCtor_Partial_InsertInternal() } [Fact] - public void InstanceCtor_Partial_InsertInternal_WithBody() + public void InstanceCtor_Partial_InsertInternalDeleteInternal_WithBody() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { internal C() { } }"; @@ -4877,7 +4916,7 @@ public void InstanceCtor_Partial_InsertInternal_WithBody() } [Fact] - public void InstanceCtor_Partial_InsertPrivateToPublic() + public void InstanceCtor_Partial_InsertPublicDeletePrivate() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { private C() { } }"; @@ -4895,7 +4934,7 @@ public void InstanceCtor_Partial_InsertPrivateToPublic() } [Fact] - public void InstanceCtor_Partial_InsertPrivateToInternal() + public void InstanceCtor_Partial_InsertInternalDeletePrivate() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { private C() { } }"; diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index ac3af44f52fc5..5fca661a6088f 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -191,7 +191,6 @@ internal void VerifySemantics( var newCompilation = CreateLibraryCompilation("New", newTrees); var oldActiveStatements = activeStatements.OldStatements.AsImmutable(); - var updatedActiveMethodMatches = new List(); var triviaEdits = new List<(SyntaxNode OldNode, SyntaxNode NewNode)>(); var actualLineEdits = new List(); var actualSemanticEdits = new List(); @@ -215,7 +214,9 @@ internal void VerifySemantics( var newText = SourceText.From(newSource); var oldModel = oldCompilation.GetSemanticModel(oldRoot.SyntaxTree); var newModel = newCompilation.GetSemanticModel(newRoot.SyntaxTree); + var diagnostics = new List(); + var updatedActiveMethodMatches = new List(); testAccessor.AnalyzeMemberBodiesSyntax( editScript, diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb similarity index 99% rename from src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb rename to src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb index b7540901df46f..22e2e0fba1ea1 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb @@ -9,7 +9,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests - Public Class RudeEditStatementTests + Public Class StatementEditingTests Inherits EditingTestBase #Region "Matching" diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb similarity index 98% rename from src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb rename to src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 8ad35b4540c8f..128ed44229d29 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -2658,13 +2658,50 @@ End Class {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub + + Public Sub InstanceCtorInsert_Partial_Public_Implicit() + Dim srcA1 = "Partial Class C" & vbLf & "End Class" + Dim srcB1 = "Partial Class C" & vbLf & "End Class" + + Dim srcA2 = "Partial Class C" & vbLf & "End Class" + Dim srcB2 = "Partial Class C" & vbLf & "Sub New() : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) + }) + End Sub + Public Sub InstanceCtorInsert_Public_NoImplicit() Dim src1 = "Class C" & vbLf & "Sub New(a As Integer) : End Sub : End Class" Dim src2 = "Class C" & vbLf & "Sub New(a As Integer) : End Sub : " & vbLf & "Sub New() : End Sub : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics() + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + expectedSemanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(Function(m) m.Parameters.IsEmpty)) + }) + End Sub + + + Public Sub InstanceCtorInsert_Partial_Public_NoImplicit() + Dim srcA1 = "Partial Class C" & vbLf & "End Class" + Dim srcB1 = "Partial Class C" & vbLf & "Sub New(a As Integer) : End Sub : End Class" + + Dim srcA2 = "Partial Class C" & vbLf & "Sub New() : End Sub : End Class" + Dim srcB2 = "Partial Class C" & vbLf & "Sub New(a As Integer) : End Sub : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(Function(m) m.Parameters.IsEmpty)) + }) End Sub @@ -2756,7 +2793,7 @@ End Class End Sub - Public Sub StaticCtor_Partial_Delete() + Public Sub StaticCtor_Partial_DeleteInsert() Dim srcA1 = "Partial Class C" & vbLf & "Shared Sub New() : End Sub : End Class" Dim srcB1 = "Partial Class C : End Class" @@ -2765,7 +2802,7 @@ End Class EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub @@ -2778,11 +2815,11 @@ End Class EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single(), preserveLocalVariables:=True)}) End Sub - Public Sub InstanceCtor_Partial_DeletePrivate() + Public Sub InstanceCtor_Partial_DeletePrivateInsertPrivate() Dim srcA1 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" Dim srcB1 = "Partial Class C : End Class" @@ -2791,11 +2828,11 @@ End Class EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub - Public Sub InstanceCtor_Partial_DeletePublic() + Public Sub InstanceCtor_Partial_DeletePublicInsertPublic() Dim srcA1 = "Partial Class C" & vbLf & "Public Sub New() : End Sub : End Class" Dim srcB1 = "Partial Class C : End Class" @@ -2804,11 +2841,11 @@ End Class EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) End Sub - Public Sub InstanceCtor_Partial_DeletePrivateToPublic() + Public Sub InstanceCtor_Partial_DeletePrivateInsertPublic() Dim srcA1 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" Dim srcB1 = "Partial Class C : End Class" @@ -2817,11 +2854,11 @@ End Class EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedDiagnostics:={Diagnostic(RudeEditKind.Delete, "Partial Class C", FeaturesResources.constructor)}) + expectedDiagnostics:={Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Public Sub New()")}) End Sub - Public Sub StaticCtor_Partial_Insert() + Public Sub StaticCtor_Partial_InsertDelete() Dim srcA1 = "Partial Class C : End Class" Dim srcB1 = "Partial Class C" & vbLf & "Shared Sub New() : End Sub : End Class" @@ -2834,7 +2871,7 @@ End Class End Sub - Public Sub ModuleCtor_Partial_Insert() + Public Sub ModuleCtor_Partial_InsertDelete() Dim srcA1 = "Partial Module C : End Module" Dim srcB1 = "Partial Module C" & vbLf & "Sub New() : End Sub : End Module" @@ -2847,7 +2884,7 @@ End Class End Sub - Public Sub InstanceCtor_Partial_InsertPublic() + Public Sub InstanceCtor_Partial_InsertPublicDeletePublic() Dim srcA1 = "Partial Class C : End Class" Dim srcB1 = "Partial Class C" & vbLf & "Sub New() : End Sub : End Class" @@ -2860,7 +2897,7 @@ End Class End Sub - Public Sub InstanceCtor_Partial_InsertPrivate() + Public Sub InstanceCtor_Partial_InsertPrivateDeletePrivate() Dim srcA1 = "Partial Class C : End Class" Dim srcB1 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" @@ -2873,7 +2910,7 @@ End Class End Sub - Public Sub InstanceCtor_Partial_InsertInternal() + Public Sub InstanceCtor_Partial_DeleteInternalInsertInternal() Dim srcA1 = "Partial Class C : End Class" Dim srcB1 = "Partial Class C" & vbLf & "Friend Sub New() : End Sub : End Class" @@ -2886,7 +2923,20 @@ End Class End Sub - Public Sub InstanceCtor_Partial_InsertPrivateToPublic() + Public Sub InstanceCtor_Partial_InsertInternalDeleteInternal_WithBody() + Dim srcA1 = "Partial Class C : End Class" + Dim srcB1 = "Partial Class C" & vbLf & "Friend Sub New() : End Sub : End Class" + + Dim srcA2 = "Partial Class C" & vbLf & "Friend Sub New()" & vbLf & "Console.WriteLine(1) : End Sub : End Class" + Dim srcB2 = "Partial Class C : End Class" + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + expectedSemanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True)}) + End Sub + + + Public Sub InstanceCtor_Partial_InsertPublicDeletePrivate() Dim srcA1 = "Partial Class C : End Class" Dim srcB1 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" @@ -2899,7 +2949,7 @@ End Class End Sub - Public Sub InstanceCtor_Partial_InsertPrivateToInternal() + Public Sub InstanceCtor_Partial_InsertInternalDeletePrivate() Dim srcA1 = "Partial Class C : End Class" Dim srcB1 = "Partial Class C" & vbLf & "Private Sub New() : End Sub : End Class" @@ -2908,7 +2958,7 @@ End Class EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, - expectedDiagnostics:=Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Friend Sub New()")) + expectedDiagnostics:={Diagnostic(RudeEditKind.ChangingConstructorVisibility, "Friend Sub New()")}) End Sub diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 2d9276aa67fc0..6b21d3c0f2638 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2248,17 +2248,29 @@ private void AnalyzeSemantics( // If the new type has a parameterless ctor of the same accessibility then UPDATE. // Error otherwise. - Debug.Assert(AsParameterlessConstructor(oldSymbol) != null); + Contract.ThrowIfNull(AsParameterlessConstructor(oldSymbol)); var oldTypeSyntax = TryGetContainingTypeDeclaration(edit.OldNode); - RoslynDebug.Assert(oldTypeSyntax != null); + Contract.ThrowIfNull(oldTypeSyntax); var newType = TryGetPartnerType(oldTypeSyntax, editScript.Match, newModel, cancellationToken); // If the type has been deleted we would have reported a rude edit based on syntax analysis and not get here. - RoslynDebug.Assert(newType != null); + Contract.ThrowIfNull(newType); newSymbol = TryGetParameterlessConstructor(newType, oldSymbol.IsStatic); + + // If the new constructor is explicitly declared then it must be in another part of a partial type declaration. + // A type can't have more than one parameterless constructor declaration. The current edit is deleting it. + // The new type symbol has a parameterless constructor. Therefore this must be either implicitly declared + // or it is now declared in another part of a partial type declaration. The former case results in + // an update of the constructor symbol. The latter is skipped since the update edit of the constructor will be + // created when the part where the declaration is inserted is analyzed. + if (newSymbol != null && !newSymbol.IsImplicitlyDeclared) + { + continue; + } + if (newSymbol == null || newSymbol.DeclaredAccessibility != oldSymbol.DeclaredAccessibility) { diagnostics.Add(new RudeEditDiagnostic( @@ -2317,8 +2329,8 @@ private void AnalyzeSemantics( var oldType = TryGetPartnerType(newTypeSyntax, editScript.Match, oldModel, cancellationToken); // There has to be a matching old type syntax since the containing type hasn't been inserted. - RoslynDebug.Assert(oldType != null); - RoslynDebug.Assert(newType != null); + Contract.ThrowIfNull(oldType); + Contract.ThrowIfNull(newType); // Validate that the type declarations are correct. If not we can't reason about their members. // Declaration diagnostics are cached on compilation, so we don't need to cache them here. @@ -2383,9 +2395,8 @@ private void AnalyzeSemantics( ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, edit.NewNode, newModel, ref lazyLayoutAttribute); } - bool isConstructorWithMemberInitializers; - if ((isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode)) || - IsDeclarationWithInitializer(edit.NewNode)) + var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode); + if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(edit.NewNode)) { if (DeferConstructorEdit( oldType, @@ -2410,7 +2421,7 @@ private void AnalyzeSemantics( else { // A semantic edit to create the field/property is gonna be added. - Debug.Assert(editKind == SemanticEditKind.Insert); + Contract.ThrowIfFalse(editKind == SemanticEditKind.Insert); } } } @@ -2430,7 +2441,7 @@ private void AnalyzeSemantics( } oldSymbol = GetSymbolForEdit(oldModel, edit.OldNode, edit.Kind, editMap, cancellationToken); - RoslynDebug.Assert(oldSymbol != null); + Contract.ThrowIfNull(oldSymbol); var oldContainingType = oldSymbol.ContainingType; var newContainingType = newSymbol.ContainingType; @@ -2490,8 +2501,8 @@ private void AnalyzeSemantics( // If a constructor changes from including initializers to not including initializers // we don't need to aggregate syntax map from all initializers for the constructor update semantic edit. - bool isConstructorWithMemberInitializers; - if ((isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode)) || + var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode); + if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(edit.OldNode) || IsDeclarationWithInitializer(edit.NewNode)) { @@ -2561,8 +2572,8 @@ private void AnalyzeSemantics( Debug.Assert(IsConstructorWithMemberInitializers(oldNode) == IsConstructorWithMemberInitializers(newNode)); Debug.Assert(IsDeclarationWithInitializer(oldNode) == IsDeclarationWithInitializer(newNode)); - bool isConstructorWithMemberInitializers; - if ((isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newNode)) || + var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newNode); + if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(newNode)) { if (DeferConstructorEdit( @@ -2840,6 +2851,9 @@ private static bool HasExplicitOrSequentialLayout( return method.Parameters.Length == 0 ? method : null; } + /// + /// Called when a body of a constructor or an initializer of a member is updated or inserted. + /// private bool DeferConstructorEdit( INamedTypeSymbol oldType, INamedTypeSymbol newType, @@ -2873,14 +2887,22 @@ private bool DeferConstructorEdit( } // TODO (bug https://github.com/dotnet/roslyn/issues/2504) - if (editKind == SemanticEditKind.Insert && HasMemberInitializerContainingLambda(oldType, newSymbol.IsStatic, cancellationToken)) + if (editKind == SemanticEditKind.Insert) { - // rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function - diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(newDeclaration, EditKind.Insert))); - return false; + if (HasMemberInitializerContainingLambda(oldType, newSymbol.IsStatic, cancellationToken)) + { + // rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function + diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(newDeclaration, EditKind.Insert))); + return false; + } + + syntaxMap = null; + } + else + { + syntaxMap = CreateSyntaxMapForPartialTypeConstructor(oldType, newType, newModel, syntaxMap); } - syntaxMap = CreateSyntaxMapForPartialTypeConstructor(oldType, newType, newModel, syntaxMap); return false; } @@ -2894,12 +2916,12 @@ private bool DeferConstructorEdit( constructorEdits = instanceConstructorEdits ??= new(); } - if (!constructorEdits.TryGetValue(newType, out var edit)) + if (!constructorEdits.TryGetValue(newType, out var constructorEdit)) { - constructorEdits.Add(newType, edit = new ConstructorEdit(oldType)); + constructorEdits.Add(newType, constructorEdit = new ConstructorEdit(oldType)); } - edit.ChangedDeclarations.Add(newDeclaration, syntaxMap); + constructorEdit.ChangedDeclarations.Add(newDeclaration, syntaxMap); return true; } @@ -3821,9 +3843,9 @@ private static bool AreEquivalentClosureScopes(SyntaxNode oldScopeOpt, SyntaxNod return reverseMap.TryGetValue(newScopeOpt, out var mappedScope) && mappedScope == oldScopeOpt; } - #endregion +#endregion - #region State Machines +#region State Machines private void ReportStateMachineRudeEdits( Compilation oldCompilation, @@ -3859,11 +3881,11 @@ private void ReportStateMachineRudeEdits( } } - #endregion +#endregion - #endregion +#endregion - #region Helpers +#region Helpers private static SyntaxNode? TryGetNode(SyntaxNode root, int position) => root.FullSpan.Contains(position) ? root.FindToken(position).Parent : null; @@ -3882,9 +3904,9 @@ private static bool TryGetTextSpan(TextLineCollection lines, LinePositionSpan li return true; } - #endregion +#endregion - #region Testing +#region Testing internal TestAccessor GetTestAccessor() => new(this); @@ -3975,6 +3997,6 @@ internal void AnalyzeSemantics( } } - #endregion +#endregion } } From 0ae1118ad5e3b57c313d1bb45bd1c0875b649c4b Mon Sep 17 00:00:00 2001 From: tmat Date: Thu, 14 Jan 2021 16:59:15 -0800 Subject: [PATCH 3/9] EditAndContinueDocumentAnalysesCache --- .../EditAndContinueDocumentAnalysesCache.cs | 93 +++++++++++++++++++ .../Portable/EditAndContinue/EditSession.cs | 66 +------------ 2 files changed, 98 insertions(+), 61 deletions(-) create mode 100644 src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs new file mode 100644 index 0000000000000..621bcd0a189dc --- /dev/null +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.EditAndContinue +{ + /// + /// Calculates and caches results of changed documents analysis. + /// The work is triggered by an incremental analyzer on idle or explicitly when "continue" operation is executed. + /// Contains analyses of the latest observed document versions. + /// + internal sealed class EditAndContinueDocumentAnalysesCache + { + private readonly object _guard = new(); + private readonly Dictionary results)> _analyses = new(); + private readonly AsyncLazy _baseActiveStatements; + + public EditAndContinueDocumentAnalysesCache(AsyncLazy baseActiveStatements) + { + _baseActiveStatements = baseActiveStatements; + } + + public AsyncLazy GetDocumentAnalysis(Document? baseDocument, Document document, ImmutableArray activeStatementSpans) + { + lock (_guard) + { + return GetDocumentAnalysisNoLock(baseDocument, document, activeStatementSpans); + } + } + + public ImmutableArray<(Document, AsyncLazy)> GetDocumentAnalyses(ArrayBuilder<(Document? oldDocument, Document newDocument, ImmutableArray newActiveStatementSpans)> builder) + { + if (builder.IsEmpty()) + { + return ImmutableArray<(Document, AsyncLazy)>.Empty; + } + + lock (_guard) + { + return builder.SelectAsArray(change => (change.newDocument, GetDocumentAnalysisNoLock(change.oldDocument, change.newDocument, change.newActiveStatementSpans))); + } + } + + /// + /// Returns a document analysis or kicks off a new one if one is not available for the specified document snapshot. + /// + /// Base document or null if the document did not exist in the baseline. + /// Document snapshot to analyze. + private AsyncLazy GetDocumentAnalysisNoLock(Document? baseDocument, Document document, ImmutableArray activeStatementSpans) + { + if (_analyses.TryGetValue(document.Id, out var analysis) && analysis.document == document) + { + return analysis.results; + } + + var analyzer = document.Project.LanguageServices.GetRequiredService(); + + var lazyResults = new AsyncLazy( + asynchronousComputeFunction: async cancellationToken => + { + try + { + var baseActiveStatements = await _baseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); + if (!baseActiveStatements.DocumentMap.TryGetValue(document.Id, out var documentBaseActiveStatements)) + { + documentBaseActiveStatements = ImmutableArray.Empty; + } + + return await analyzer.AnalyzeDocumentAsync(baseDocument, documentBaseActiveStatements, document, activeStatementSpans, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + }, + cacheResult: true); + + // TODO: this will replace potentially running analysis with another one. + // Consider cancelling the replaced one. + _analyses[document.Id] = (document, lazyResults); + return lazyResults; + } + } +} diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index b4c5f8f5759da..d77750d169eaa 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -41,12 +41,9 @@ internal sealed class EditSession : IDisposable internal ImmutableArray _lazyBaseActiveExceptionRegions; /// - /// Results of changed documents analysis. - /// The work is triggered by an incremental analyzer on idle or explicitly when "continue" operation is executed. - /// Contains analyses of the latest observed document versions. + /// Cache of document EnC analyses. /// - private readonly Dictionary Results)> _analyses = new(); - private readonly object _analysesGuard = new(); + private readonly EditAndContinueDocumentAnalysesCache _analysesCache; /// /// A is added whenever EnC analyzer reports @@ -71,6 +68,7 @@ internal EditSession( _nonRemappableRegions = debuggingSession.NonRemappableRegions; BaseActiveStatements = new AsyncLazy(cancellationToken => GetBaseActiveStatementsAsync(cancellationToken), cacheResult: true); + _analysesCache = new EditAndContinueDocumentAnalysesCache(BaseActiveStatements); } internal PendingSolutionUpdate? Test_GetPendingSolutionUpdate() => _pendingUpdate; @@ -393,65 +391,11 @@ private static async Task PopulateChangedAndAddedDocumentsAsync(CommittedSolutio } } - var result = ImmutableArray<(Document, AsyncLazy)>.Empty; - if (builder.Count != 0) - { - lock (_analysesGuard) - { - result = builder.SelectAsArray(change => (change.New, GetDocumentAnalysisNoLock(change.Old, change.New, change.NewActiveStatementSpans))); - } - } - - return (result, documentDiagnostics.ToImmutable()); + return (_analysesCache.GetDocumentAnalyses(builder), documentDiagnostics.ToImmutable()); } public AsyncLazy GetDocumentAnalysis(Document? baseDocument, Document document, ImmutableArray activeStatementSpans) - { - lock (_analysesGuard) - { - return GetDocumentAnalysisNoLock(baseDocument, document, activeStatementSpans); - } - } - - /// - /// Returns a document analysis or kicks off a new one if one is not available for the specified document snapshot. - /// - /// Base document or null if the document did not exist in the baseline. - /// Document snapshot to analyze. - private AsyncLazy GetDocumentAnalysisNoLock(Document? baseDocument, Document document, ImmutableArray activeStatementSpans) - { - if (_analyses.TryGetValue(document.Id, out var analysis) && analysis.Document == document) - { - return analysis.Results; - } - - var analyzer = document.Project.LanguageServices.GetRequiredService(); - - var lazyResults = new AsyncLazy( - asynchronousComputeFunction: async cancellationToken => - { - try - { - var baseActiveStatements = await BaseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); - if (!baseActiveStatements.DocumentMap.TryGetValue(document.Id, out var documentBaseActiveStatements)) - { - documentBaseActiveStatements = ImmutableArray.Empty; - } - - return await analyzer.AnalyzeDocumentAsync(baseDocument, documentBaseActiveStatements, document, activeStatementSpans, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) - { - throw ExceptionUtilities.Unreachable; - } - }, - cacheResult: true); - - // TODO: this will replace potentially running analysis with another one. - // Consider cancelling the replaced one. - _analyses[document.Id] = (document, lazyResults); - return lazyResults; - } + => _analysesCache.GetDocumentAnalysis(baseDocument, document, activeStatementSpans); internal ImmutableArray GetDocumentsWithReportedDiagnostics() { From 65bc3e7516c68d5b7d4bbaee247afee149106004 Mon Sep 17 00:00:00 2001 From: tmat Date: Fri, 15 Jan 2021 20:31:39 -0800 Subject: [PATCH 4/9] Remove declaration error checking in semantic analysis of members. This was added as a stop-gap measure to guard against unexpected error condition. It introduces dependency on partial declarations which is problematic wrt caching. It's actually not needed since the semantic analysis handles error symbols fine. Adds tests to validate. --- .../CSharpEditAndContinueAnalyzerTests.cs | 7 +- .../Helpers/EditAndContinueValidation.cs | 13 -- .../EditAndContinue/StatementEditingTests.cs | 131 +++++++++++- .../EditAndContinue/TopLevelEditingTests.cs | 23 ++- .../EditAndContinueTestHelpers.cs | 11 +- .../Helpers/EditAndContinueValidation.vb | 22 +- .../EditAndContinue/StatementEditingTests.vb | 59 +++++- .../EditAndContinue/TopLevelEditingTests.vb | 23 ++- ...VisualBasicEditAndContinueAnalyzerTests.vb | 7 +- .../AbstractEditAndContinueAnalyzer.cs | 188 ++++++------------ .../DocumentAnalysisResults.cs | 147 ++++++-------- .../EditAndContinue/ProjectAnalysisSummary.cs | 4 +- 12 files changed, 345 insertions(+), 290 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index 95140adcafde8..f4d6715a14bb8 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -608,8 +608,11 @@ public static void Main(Bar x) var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); Assert.True(result.HasChanges); - Assert.True(result.HasChangesAndErrors); - Assert.True(result.HasChangesAndCompilationErrors); + + // No errors reported: EnC analyzer is resilient against semantic errors. + // They will be reported by 1) compiler diagnostic analyzer 2) when emitting delta - if still present. + Assert.False(result.HasChangesAndErrors); + Assert.False(result.HasChangesAndCompilationErrors); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index cc4b83fd6fe90..f4f6a74a1bea7 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs @@ -79,17 +79,6 @@ internal static void VerifySemanticDiagnostics( expectedDiagnostics: expectedDiagnostics); } - internal static void VerifySemanticDiagnostics( - this EditScript editScript, - DiagnosticDescription expectedDeclarationError, - params RudeEditDiagnosticDescription[] expectedDiagnostics) - { - VerifySemantics( - new[] { editScript }, - expectedDeclarationError: expectedDeclarationError, - expectedDiagnostics: expectedDiagnostics); - } - internal static void VerifySemantics( this EditScript editScript, ActiveStatementsDescription activeStatements, @@ -107,7 +96,6 @@ internal static void VerifySemantics( ActiveStatementsDescription? activeStatements = null, TargetFramework[]? targetFrameworks = null, SemanticEditDescription[]? expectedSemanticEdits = null, - DiagnosticDescription? expectedDeclarationError = null, RudeEditDiagnosticDescription[]? expectedDiagnostics = null) { foreach (var targetFramework in targetFrameworks ?? new[] { TargetFramework.NetStandard20, TargetFramework.NetCoreApp }) @@ -116,7 +104,6 @@ internal static void VerifySemantics( editScripts, activeStatements, expectedSemanticEdits, - expectedDeclarationError, expectedDiagnostics); } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index c08c97be64551..45411ddddb79c 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -5,9 +5,12 @@ #nullable disable using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EditAndContinue; +using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -3123,7 +3126,7 @@ void F() } [Fact] - public void Lambdas_Signature_SemanticErrors() + public void Lambdas_Signature_MatchingErrorType() { var src1 = @" using System; @@ -3153,10 +3156,49 @@ void F() "; var edits = GetTopEdits(src1, src2); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + expectedSemanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("F").Single(), preserveLocalVariables: true) + }); + } + + [Fact] + public void Lambdas_Signature_NonMatchingErrorType() + { + var src1 = @" +using System; + +class C +{ + void G1(Func f) {} + void G2(Func f) {} + + void F() + { + G1(a => 1); + } +} +"; + var src2 = @" +using System; + +class C +{ + void G1(Func f) {} + void G2(Func f) {} + + void F() + { + G2(a => 2); + } +} +"; + var edits = GetTopEdits(src1, src2); + edits.VerifySemanticDiagnostics( - // (6,17): error CS0246: The type or namespace name 'Unknown' could not be found (are you missing a using directive or an assembly reference?) - // void G(Func f) {} - Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Unknown").WithArguments("Unknown").WithLocation(6, 17)); + Diagnostic(RudeEditKind.ChangingLambdaParameters, "a", "lambda")); } [Fact] @@ -7823,6 +7865,87 @@ void F() Diagnostic(RudeEditKind.ChangingQueryLambdaType, "group", CSharpFeaturesResources.groupby_clause)); } + [Fact] + public void Queries_Update_Signature_GroupBy_MatchingErrorTypes() + { + var src1 = @" +using System; +using System.Linq; +using System.Collections.Generic; + +class C +{ + Unknown G1(int a) => null; + Unknown G2(int a) => null; + + void F() + { + var result = from a in new[] {1} group G1(a) by a into z select z; + } +} +"; + var src2 = @" +using System; +using System.Linq; +using System.Collections.Generic; + +class C +{ + Unknown G1(int a) => null; + Unknown G2(int a) => null; + + void F() + { + var result = from a in new[] {1} group G2(a) by a into z select z; + } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics(); + } + + [Fact] + public void Queries_Update_Signature_GroupBy_NonMatchingErrorTypes() + { + var src1 = @" +using System; +using System.Linq; +using System.Collections.Generic; + +class C +{ + Unknown1 G1(int a) => null; + Unknown2 G2(int a) => null; + + void F() + { + var result = from a in new[] {1} group G1(a) by a into z select z; + } +} +"; + var src2 = @" +using System; +using System.Linq; +using System.Collections.Generic; + +class C +{ + Unknown1 G1(int a) => null; + Unknown2 G2(int a) => null; + + void F() + { + var result = from a in new[] {1} group G2(a) by a into z select z; + } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ChangingQueryLambdaType, "group", CSharpFeaturesResources.groupby_clause)); + } + [Fact] public void Queries_FromSelect_Update1() { diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 3ab76f5231707..0f56eb6ba6fba 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -4769,8 +4769,7 @@ public void StaticCtor_Partial_DeleteInsert() expectedSemanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) - }, - expectedDeclarationError: null); + }); } [Fact] @@ -5283,10 +5282,10 @@ partial void C(int x) "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - // (4,18): error CS0542: 'C': member names cannot be the same as their enclosing type - // partial void C(int x); - Diagnostic(ErrorCode.ERR_MemberNameSameAsType, "C").WithArguments("C").WithLocation(4, 18)); + edits.VerifySemantics(ActiveStatementsDescription.Empty, expectedSemanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("C").PartialImplementationPart) + }); } [Fact, WorkItem(17681, "https://github.com/dotnet/roslyn/issues/17681")] @@ -6899,16 +6898,18 @@ partial class C partial class C { - partial int P => 1; + partial int P => 2; public C() { } } "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - // (4,5): error CS0267: The 'partial' modifier can only appear immediately before 'class', 'struct', 'interface', or 'void' - // partial int P => 1; - Diagnostic(ErrorCode.ERR_PartialMisplaced, "partial").WithLocation(4, 5)); + + edits.VerifySemantics(ActiveStatementsDescription.Empty, expectedSemanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => ((IPropertySymbol)c.GetMember("C").GetMembers("P").Skip(1).First()).GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }); } #endregion diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index 5fca661a6088f..4cfc696ae6ec0 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -9,6 +9,7 @@ using System.Threading; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -179,7 +180,6 @@ internal void VerifySemantics( IEnumerable> editScripts, ActiveStatementsDescription? activeStatements = null, SemanticEditDescription[]? expectedSemanticEdits = null, - DiagnosticDescription? expectedDeclarationError = null, RudeEditDiagnosticDescription[]? expectedDiagnostics = null) { activeStatements ??= ActiveStatementsDescription.Empty; @@ -253,20 +253,11 @@ internal void VerifySemantics( newModel, actualSemanticEdits, diagnostics, - out var firstDeclarationError, CancellationToken.None); - if (firstDeclarationError != null) - { - actualDeclarationErrors.Add(firstDeclarationError); - } - actualDiagnosticDescriptions.AddRange(diagnostics.ToDescription(newSource, includeFirstLineInDiagnostics)); } - var expectedDeclarationErrors = (expectedDeclarationError != null) ? new[] { expectedDeclarationError } : Array.Empty(); - actualDeclarationErrors.Verify(expectedDeclarationErrors); - actualDiagnosticDescriptions.Verify(expectedDiagnostics); if (expectedSemanticEdits == null) diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb index 68dd18248c272..094e3b79bf762 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb @@ -48,44 +48,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Friend Sub VerifySemanticDiagnostics(editScript As EditScript(Of SyntaxNode), ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemanticDiagnostics(editScript, Nothing, expectedDiagnostics) - End Sub - - - Friend Sub VerifySemanticDiagnostics(editScript As EditScript(Of SyntaxNode), - expectedDeclarationError As DiagnosticDescription, - ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics({editScript}, ActiveStatementsDescription.Empty, Nothing, expectedDeclarationError, expectedDiagnostics) - End Sub - - - Friend Sub VerifySemantics(editScript As EditScript(Of SyntaxNode), - activeStatements As ActiveStatementsDescription, - expectedSemanticEdits As SemanticEditDescription(), - ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics({editScript}, activeStatements, expectedSemanticEdits, expectedDeclarationError:=Nothing, expectedDiagnostics) + VerifySemantics({editScript}, ActiveStatementsDescription.Empty, Nothing, expectedDiagnostics) End Sub Friend Sub VerifySemantics(editScript As EditScript(Of SyntaxNode), activeStatements As ActiveStatementsDescription, expectedSemanticEdits As SemanticEditDescription(), - expectedDeclarationError As DiagnosticDescription, ParamArray expectedDiagnostics As RudeEditDiagnosticDescription()) - VerifySemantics({editScript}, activeStatements, expectedSemanticEdits, expectedDeclarationError, expectedDiagnostics) + VerifySemantics({editScript}, activeStatements, expectedSemanticEdits, expectedDiagnostics) End Sub Friend Sub VerifySemantics(editScripts As EditScript(Of SyntaxNode)(), Optional activeStatements As ActiveStatementsDescription = Nothing, Optional expectedSemanticEdits As SemanticEditDescription() = Nothing, - Optional expectedDeclarationError As DiagnosticDescription = Nothing, Optional expectedDiagnostics As RudeEditDiagnosticDescription() = Nothing) VisualBasicEditAndContinueTestHelpers.CreateInstance().VerifySemantics( editScripts, activeStatements, expectedSemanticEdits, - expectedDeclarationError, expectedDiagnostics) End Sub End Module diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb index 22e2e0fba1ea1..9d256d276cbcf 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb @@ -5,6 +5,8 @@ Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue +Imports Microsoft.CodeAnalysis.Emit +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests @@ -4501,7 +4503,7 @@ End Class End Sub - Public Sub Lambdas_Signature_SemanticErrors() + Public Sub Lambdas_Signature_MatchingErrorType() Dim src1 = " Imports System @@ -4529,8 +4531,50 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(ERRID.ERR_UndefinedType1, "Unknown").WithArguments("Unknown").WithLocation(6, 24)) + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + expectedSemanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMembers("F").Single(), preserveLocalVariables:=True) + }) + End Sub + + + Public Sub Lambdas_Signature_NonMatchingErrorType() + Dim src1 = " +Imports System + +Class C + + Sub G1(f As Func(Of Unknown1, Unknown1)) + End Sub + + Sub G2(f As Func(Of Unknown2, Unknown2)) + End Sub + + Sub F() + G1(Function(a) 1) + End Sub +End Class +" + Dim src2 = " +Imports System + +Class C + + Sub G1(f As Func(Of Unknown1, Unknown1)) + End Sub + + Sub G2(f As Func(Of Unknown2, Unknown2)) + End Sub + + Sub F() + G2(Function(a) 2) + End Sub +End Class +" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.ChangingLambdaParameters, "a", "lambda")) End Sub #End Region @@ -6015,8 +6059,7 @@ End Class editScripts:={edits}, activeStatements:=ActiveStatementsDescription.Empty, expectedSemanticEdits:=Nothing, - expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Iterator Function F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute")}, - expectedDeclarationError:=Nothing) + expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Iterator Function F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute")}) End Sub @@ -6044,8 +6087,7 @@ End Class editScripts:={edits}, activeStatements:=ActiveStatementsDescription.Empty, expectedSemanticEdits:=Nothing, - expectedDiagnostics:=Nothing, - expectedDeclarationError:=Nothing) + expectedDiagnostics:=Nothing) End Sub #End Region @@ -6129,8 +6171,7 @@ End Class editScripts:={edits}, activeStatements:=ActiveStatementsDescription.Empty, expectedSemanticEdits:=Nothing, - expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Async Function F()", "System.Runtime.CompilerServices.AsyncStateMachineAttribute")}, - expectedDeclarationError:=Nothing) + expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Async Function F()", "System.Runtime.CompilerServices.AsyncStateMachineAttribute")}) End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 128ed44229d29..a0291eec5980f 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -3257,8 +3257,10 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(ERRID.ERR_ConstructorCannotBeDeclaredPartial, "Partial").WithArguments("Partial").WithLocation(3, 5)) + edits.VerifySemantics(ActiveStatementsDescription.Empty, expectedSemanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Skip(1).First(), preserveLocalVariables:=True) + }) End Sub #End Region @@ -5919,7 +5921,7 @@ End Class" Public Sub PropertyWithInitializer_SemanticError_Partial() Dim src1 = " Partial Class C - Partial Public ReadOnly Property NewProperty() As String + Partial Public ReadOnly Property P() As String Get Return 1 End Get @@ -5927,7 +5929,7 @@ Partial Class C End Class Partial Class C - Partial Public ReadOnly Property NewProperty() As String + Partial Public ReadOnly Property P() As String Get Return 1 End Get @@ -5936,7 +5938,7 @@ End Class " Dim src2 = " Partial Class C - Partial Public ReadOnly Property NewProperty() As String + Partial Public ReadOnly Property P() As String Get Return 1 End Get @@ -5944,9 +5946,9 @@ Partial Class C End Class Partial Class C - Partial Public ReadOnly Property NewProperty() As String + Partial Public ReadOnly Property P() As String Get - Return 1 + Return 2 End Get End Property @@ -5955,8 +5957,11 @@ Partial Class C End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(ERRID.ERR_BadPropertyFlags1, "Partial").WithArguments("Partial").WithLocation(3, 5)) + edits.VerifySemantics(ActiveStatementsDescription.Empty, expectedSemanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True), + SemanticEdit(SemanticEditKind.Update, Function(c) CType(c.GetMember(Of NamedTypeSymbol)("C").GetMembers("P").Skip(1).First(), IPropertySymbol).GetMethod) + }) End Sub #End Region diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb index 0e7dc2e18f264..5ac267a0ec913 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb @@ -630,8 +630,11 @@ End Class Dim result = Await analyzer.AnalyzeDocumentAsync(oldSolution.GetDocument(documentId), baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) Assert.True(result.HasChanges) - Assert.True(result.HasChangesAndErrors) - Assert.True(result.HasChangesAndCompilationErrors) + + ' No errors reported: EnC analyzer is resilient against semantic errors. + ' They will be reported by 1) compiler diagnostic analyzer 2) when emitting delta - if still present. + Assert.False(result.HasChangesAndErrors) + Assert.False(result.HasChangesAndCompilationErrors) End Using End Function diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 6b21d3c0f2638..67d8d2450933b 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -420,18 +420,26 @@ public async Task AnalyzeDocumentAsync( var newRoot = await newTree.GetRootAsync(cancellationToken).ConfigureAwait(false); var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var hasChanges = !oldText.ContentEquals(newText); _testFaultInjector?.Invoke(newRoot); cancellationToken.ThrowIfCancellationRequested(); // TODO: newTree.HasErrors? var syntaxDiagnostics = newRoot.GetDiagnostics(); - var syntaxErrorCount = syntaxDiagnostics.Count(d => d.Severity == DiagnosticSeverity.Error); + var hasSyntaxError = syntaxDiagnostics.Any(d => d.Severity == DiagnosticSeverity.Error); + if (hasSyntaxError) + { + // Bail, since we can't do syntax diffing on broken trees (it would not produce useful results anyways). + // If we needed to do so for some reason, we'd need to harden the syntax tree comparers. + DocumentAnalysisResults.Log.Write("{0}: syntax errors", document.Name); + return DocumentAnalysisResults.CompilationErrors(hasChanges); + } var newActiveStatements = new ActiveStatement[baseActiveStatements.Length]; - var newExceptionRegions = (syntaxErrorCount == 0) ? new ImmutableArray[baseActiveStatements.Length] : null; + var newExceptionRegions = new ImmutableArray[baseActiveStatements.Length]; - if (oldText.ContentEquals(newText)) + if (!hasChanges) { // The document might have been closed and reopened, which might have triggered analysis. // If the document is unchanged don't continue the analysis since @@ -445,36 +453,17 @@ public async Task AnalyzeDocumentAsync( newActiveStatements, newExceptionRegions); - if (syntaxErrorCount > 0) - { - DocumentAnalysisResults.Log.Write("{0}: unchanged with syntax errors", document.Name); - } - else - { - DocumentAnalysisResults.Log.Write("{0}: unchanged", document.Name); - } - - return DocumentAnalysisResults.Unchanged(newActiveStatements.AsImmutable(), newExceptionRegions.AsImmutableOrNull()); - } - - if (syntaxErrorCount > 0) - { - // Bail, since we can't do syntax diffing on broken trees (it would not produce useful results anyways). - // If we needed to do so for some reason, we'd need to harden the syntax tree comparers. - DocumentAnalysisResults.Log.Write("Syntax errors: {0} total", syntaxErrorCount); - - return DocumentAnalysisResults.SyntaxErrors(ImmutableArray.Empty); + DocumentAnalysisResults.Log.Write("{0}: unchanged", document.Name); + return DocumentAnalysisResults.Unchanged(newActiveStatements.AsImmutable(), newExceptionRegions.AsImmutable()); } - RoslynDebug.Assert(newExceptionRegions != null); - // Disallow modification of a file with experimental features enabled. // These features may not be handled well by the analysis below. if (ExperimentalFeaturesEnabled(newTree)) { DocumentAnalysisResults.Log.Write("{0}: experimental features enabled", document.Name); - return DocumentAnalysisResults.SyntaxErrors(ImmutableArray.Create( + return DocumentAnalysisResults.Errors(ImmutableArray.Create( new RudeEditDiagnostic(RudeEditKind.ExperimentalFeaturesEnabled, default))); } @@ -484,7 +473,7 @@ public async Task AnalyzeDocumentAsync( // 2) If there are syntactic rude edits we'll report them faster without waiting for semantic analysis. // The user may fix them before they address all the semantic errors. - var updatedMethods = new List(); + var updatedMembers = new List(); var diagnostics = new List(); cancellationToken.ThrowIfCancellationRequested(); @@ -502,7 +491,7 @@ public async Task AnalyzeDocumentAsync( newActiveStatementSpans, newActiveStatements, newExceptionRegions, - updatedMethods, + updatedMembers, diagnostics); ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); @@ -510,7 +499,7 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0} syntactic rude edits, first: '{1}'", diagnostics.Count, document.FilePath); - return DocumentAnalysisResults.Errors(newActiveStatements.AsImmutable(), diagnostics.AsImmutable()); + return DocumentAnalysisResults.Errors(diagnostics.AsImmutable()); } // Disallow addition of a new file. @@ -519,7 +508,7 @@ public async Task AnalyzeDocumentAsync( if (oldDocument == null) { DocumentAnalysisResults.Log.Write("A new file added: {0}", document.Name); - return DocumentAnalysisResults.SyntaxErrors(ImmutableArray.Create( + return DocumentAnalysisResults.Errors(ImmutableArray.Create( new RudeEditDiagnostic(RudeEditKind.InsertFile, default))); } @@ -543,7 +532,7 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0} trivia rude edits, first: {1}@{2}", diagnostics.Count, document.FilePath, diagnostics.First().Span.Start); - return DocumentAnalysisResults.Errors(newActiveStatements.AsImmutable(), diagnostics.AsImmutable()); + return DocumentAnalysisResults.Errors(diagnostics.AsImmutable()); } cancellationToken.ThrowIfCancellationRequested(); @@ -563,28 +552,19 @@ public async Task AnalyzeDocumentAsync( oldText, baseActiveStatements, triviaEdits, - updatedMethods, + updatedMembers, oldModel, newModel, semanticEdits, diagnostics, - out var firstDeclaratingError, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); - if (firstDeclaratingError != null) - { - var location = firstDeclaratingError.Location; - DocumentAnalysisResults.Log.Write("Declaration errors, first: {0}", location.IsInSource ? location.SourceTree!.FilePath : location.MetadataModule!.Name); - - return DocumentAnalysisResults.Errors(newActiveStatements.AsImmutable(), ImmutableArray.Empty, hasSemanticErrors: true); - } - if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0}@{1}: semantic rude edit ({2} total)", document.FilePath, diagnostics.First().Span.Start, diagnostics.Count); - return DocumentAnalysisResults.Errors(newActiveStatements.AsImmutable(), diagnostics.AsImmutable()); + return DocumentAnalysisResults.Errors(diagnostics.AsImmutable()); } } @@ -594,7 +574,8 @@ public async Task AnalyzeDocumentAsync( semanticEdits.AsImmutableOrEmpty(), newExceptionRegions.AsImmutable(), lineEdits.AsImmutable(), - hasSemanticErrors: false); + hasChanges: true, + hasCompilationErrors: false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e)) { @@ -606,7 +587,7 @@ public async Task AnalyzeDocumentAsync( new RudeEditDiagnostic(RudeEditKind.SourceFileTooBig, span: default, arguments: new[] { document.FilePath }) : new RudeEditDiagnostic(RudeEditKind.InternalError, span: default, arguments: new[] { document.FilePath, e.ToString() }); - return DocumentAnalysisResults.SyntaxErrors(ImmutableArray.Create(diagnostic)); + return DocumentAnalysisResults.Errors(ImmutableArray.Create(diagnostic)); } } @@ -653,18 +634,18 @@ private void AnalyzeMemberBodiesSyntax( ImmutableArray newActiveStatementSpans, [Out] ActiveStatement[] newActiveStatements, [Out] ImmutableArray[] newExceptionRegions, - [Out] List updatedMethods, + [Out] List updatedMembers, [Out] List diagnostics) { Debug.Assert(!newActiveStatementSpans.IsDefault); Debug.Assert(newActiveStatementSpans.IsEmpty || oldActiveStatements.Length == newActiveStatementSpans.Length); Debug.Assert(oldActiveStatements.Length == newActiveStatements.Length); Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Length); - Debug.Assert(updatedMethods.Count == 0); + Debug.Assert(updatedMembers.Count == 0); for (var i = 0; i < script.Edits.Length; i++) { - AnalyzeChangedMemberBody(script, i, editMap, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, updatedMethods, diagnostics); + AnalyzeChangedMemberBody(script, i, editMap, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, updatedMembers, diagnostics); } AnalyzeUnchangedMemberBodies(diagnostics, script.Match, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions); @@ -2201,7 +2182,6 @@ private void AnalyzeSemantics( SemanticModel newModel, [Out] List semanticEdits, [Out] List diagnostics, - out Diagnostic? firstDeclarationError, CancellationToken cancellationToken) { // { new type -> constructor update } @@ -2211,7 +2191,6 @@ private void AnalyzeSemantics( INamedTypeSymbol? lazyLayoutAttribute = null; var newSymbolsWithEdit = new HashSet(); var updatedMemberIndex = 0; - firstDeclarationError = null; for (var i = 0; i < editScript.Edits.Length; i++) { cancellationToken.ThrowIfCancellationRequested(); @@ -2332,16 +2311,8 @@ private void AnalyzeSemantics( Contract.ThrowIfNull(oldType); Contract.ThrowIfNull(newType); - // Validate that the type declarations are correct. If not we can't reason about their members. - // Declaration diagnostics are cached on compilation, so we don't need to cache them here. - firstDeclarationError = - GetFirstDeclarationError(oldModel, oldType, cancellationToken) ?? - GetFirstDeclarationError(newModel, newType, cancellationToken); - - if (firstDeclarationError != null) - { - continue; - } + ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol); + ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, edit.NewNode, newModel, ref lazyLayoutAttribute); // Inserting a parameterless constructor needs special handling: // 1) static ctor @@ -2389,12 +2360,6 @@ private void AnalyzeSemantics( } } - if (editKind == SemanticEditKind.Insert) - { - ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol); - ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, edit.NewNode, newModel, ref lazyLayoutAttribute); - } - var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(edit.NewNode); if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(edit.NewNode)) { @@ -2433,10 +2398,23 @@ private void AnalyzeSemantics( { editKind = SemanticEditKind.Update; + // An updated member info was added for a subset of edits in the order of the edits. + // Fetch the next info if it matches the current edit ordinal. + UpdatedMemberInfo? updatedMemberOpt; + if (updatedMemberIndex < updatedMembers.Count && updatedMembers[updatedMemberIndex].EditOrdinal == i) + { + updatedMemberOpt = updatedMembers[updatedMemberIndex++]; + } + else + { + updatedMemberOpt = null; + } + newSymbol = GetSymbolForEdit(newModel, edit.NewNode, edit.Kind, editMap, cancellationToken); if (newSymbol == null) { // node doesn't represent a symbol + Contract.ThrowIfTrue(updatedMemberOpt.HasValue); continue; } @@ -2446,20 +2424,9 @@ private void AnalyzeSemantics( var oldContainingType = oldSymbol.ContainingType; var newContainingType = newSymbol.ContainingType; - // Validate that the type declarations are correct to avoid issues with invalid partial declarations, etc. - // Declaration diagnostics are cached on compilation, so we don't need to cache them here. - firstDeclarationError = - GetFirstDeclarationError(oldModel, oldContainingType, cancellationToken) ?? - GetFirstDeclarationError(newModel, newContainingType, cancellationToken); - - if (firstDeclarationError != null) + if (updatedMemberOpt.HasValue) { - continue; - } - - if (updatedMemberIndex < updatedMembers.Count && updatedMembers[updatedMemberIndex].EditOrdinal == i) - { - var updatedMember = updatedMembers[updatedMemberIndex]; + var updatedMember = updatedMemberOpt.Value; ReportStateMachineRudeEdits(oldModel.Compilation, updatedMember, oldSymbol, diagnostics); @@ -2491,8 +2458,6 @@ private void AnalyzeSemantics( { syntaxMap = null; } - - updatedMemberIndex++; } else { @@ -2549,17 +2514,6 @@ private void AnalyzeSemantics( var oldContainingType = oldSymbol.ContainingType; var newContainingType = newSymbol.ContainingType; - // Validate that the type declarations are correct to avoid issues with invalid partial declarations, etc. - // Declaration diagnostics are cached on compilation, so we don't need to cache them here. - firstDeclarationError = - GetFirstDeclarationError(oldModel, oldContainingType, cancellationToken) ?? - GetFirstDeclarationError(newModel, newContainingType, cancellationToken); - - if (firstDeclarationError != null) - { - continue; - } - // We need to provide syntax map to the compiler if the member is active (see member update above): var isActiveMember = TryGetOverlappingActiveStatements(oldText, oldNode.Span, oldActiveStatements, out var start, out var end) || @@ -2627,31 +2581,6 @@ private void AnalyzeSemantics( } } - private static Diagnostic? GetFirstDeclarationError(SemanticModel primaryModel, ISymbol symbol, CancellationToken cancellationToken) - { - foreach (var syntaxReference in symbol.DeclaringSyntaxReferences) - { - SemanticModel model; - if (primaryModel.SyntaxTree == syntaxReference.SyntaxTree) - { - model = primaryModel; - } - else - { - model = primaryModel.Compilation.GetSemanticModel(syntaxReference.SyntaxTree, ignoreAccessibility: false); - } - - var diagnostics = model.GetDeclarationDiagnostics(syntaxReference.Span, cancellationToken); - var firstError = diagnostics.FirstOrDefault(d => d.Severity == DiagnosticSeverity.Error); - if (firstError != null) - { - return firstError; - } - } - - return null; - } - #region Type Layout Update Validation internal void ReportTypeLayoutUpdateRudeEdits( @@ -3843,9 +3772,9 @@ private static bool AreEquivalentClosureScopes(SyntaxNode oldScopeOpt, SyntaxNod return reverseMap.TryGetValue(newScopeOpt, out var mappedScope) && mappedScope == oldScopeOpt; } -#endregion + #endregion -#region State Machines + #region State Machines private void ReportStateMachineRudeEdits( Compilation oldCompilation, @@ -3860,7 +3789,7 @@ private void ReportStateMachineRudeEdits( // Only methods, local functions and anonymous functions can be async/iterators machines, // but don't assume so to be resiliant against errors in code. - if (!(oldMember is IMethodSymbol oldMethod)) + if (oldMember is not IMethodSymbol oldMethod) { return; } @@ -3871,6 +3800,14 @@ private void ReportStateMachineRudeEdits( // We assume that the attributes, if exist, are well formed. // If not an error will be reported during EnC delta emit. + + // Report rude edit if the type is not found in the compilation. + // Consider: This diagnostic is cached in the document analysis, + // so it could happen that the attribute type is added later to + // the compilation and we continue to report the diagnostic. + // We could report rude edit when adding these types or flush all + // (or specific) document caches. This is not a common scenario though, + // since the attribute has been long defined in the BCL. if (oldCompilation.GetTypeByMetadataName(stateMachineAttributeQualifiedName) == null) { diagnostics.Add(new RudeEditDiagnostic( @@ -3881,11 +3818,11 @@ private void ReportStateMachineRudeEdits( } } -#endregion + #endregion -#endregion + #endregion -#region Helpers + #region Helpers private static SyntaxNode? TryGetNode(SyntaxNode root, int position) => root.FullSpan.Contains(position) ? root.FindToken(position).Parent : null; @@ -3904,9 +3841,9 @@ private static bool TryGetTextSpan(TextLineCollection lines, LinePositionSpan li return true; } -#endregion + #endregion -#region Testing + #region Testing internal TestAccessor GetTestAccessor() => new(this); @@ -3990,13 +3927,12 @@ internal void AnalyzeSemantics( SemanticModel newModel, [Out] List semanticEdits, [Out] List diagnostics, - out Diagnostic? firstDeclarationError, CancellationToken cancellationToken) { - _abstractEditAndContinueAnalyzer.AnalyzeSemantics(editScript, editMap, oldText, oldActiveStatements, triviaEdits, updatedMembers, oldModel, newModel, semanticEdits, diagnostics, out firstDeclarationError, cancellationToken); + _abstractEditAndContinueAnalyzer.AnalyzeSemantics(editScript, editMap, oldText, oldActiveStatements, triviaEdits, updatedMembers, oldModel, newModel, semanticEdits, diagnostics, cancellationToken); } } -#endregion + #endregion } } diff --git a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs index 67b8f8dc39b93..da8fd8af2c2f8 100644 --- a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -15,27 +13,28 @@ namespace Microsoft.CodeAnalysis.EditAndContinue { internal sealed class DocumentAnalysisResults { + internal static readonly TraceLog Log = new(256, "EnC"); + /// - /// Spans of active statements in the document, or null if the document has syntax errors. + /// Spans of active statements in the document, or null if the document has compilation errors or rude edits. /// public ImmutableArray ActiveStatements { get; } /// - /// Diagnostics for rude edits in the document, or empty if the document is unchanged or has syntax errors. + /// Diagnostics for rude edits in the document, or empty if the document is unchanged or has compilation errors. /// If the compilation has semantic errors only syntactic rude edits are calculated. /// public ImmutableArray RudeEditErrors { get; } /// - /// Edits made in the document, or null if the document is unchanged, has syntax errors, has rude edits, - /// or if the compilation has semantic errors. + /// Edits made in the document, or null if the document is unchanged, has compilation errors or rude edits. /// public ImmutableArray SemanticEdits { get; } /// /// Exception regions -- spans of catch and finally handlers that surround the active statements. /// - /// Null if the document has syntax errors or rude edits, or if the compilation has semantic errors. + /// Null if the document has compilation errors or rude edits. /// /// /// Null if there are any rude edit diagnostics. @@ -56,8 +55,7 @@ internal sealed class DocumentAnalysisResults public ImmutableArray> ExceptionRegions { get; } /// - /// Line edits in the document, or null if the document has syntax errors or rude edits, - /// or if the compilation has semantic errors. + /// Line edits in the document, or null if the document has compilation errors or rude edits. /// /// /// Sorted by @@ -65,121 +63,108 @@ internal sealed class DocumentAnalysisResults public ImmutableArray LineEdits { get; } /// - /// The compilation has compilation errors (syntactic or semantic), - /// or null if the document doesn't have any modifications and - /// presence of compilation errors was not determined. + /// Document contains erros that block EnC analysis. /// - private readonly bool? _hasCompilationErrors; + public readonly bool HasCompilationErrors; - private DocumentAnalysisResults(ImmutableArray rudeEdits) - { - Debug.Assert(!rudeEdits.IsDefault); - _hasCompilationErrors = rudeEdits.Length == 0; - RudeEditErrors = rudeEdits; - } + /// + /// Document contains changes. + /// + public readonly bool HasChanges; public DocumentAnalysisResults( - ImmutableArray activeStatements, + ImmutableArray activeStatementsOpt, ImmutableArray rudeEdits, ImmutableArray semanticEditsOpt, ImmutableArray> exceptionRegionsOpt, ImmutableArray lineEditsOpt, - bool? hasSemanticErrors) + bool hasChanges, + bool hasCompilationErrors) { Debug.Assert(!rudeEdits.IsDefault); - Debug.Assert(!activeStatements.IsDefault); - Debug.Assert(activeStatements.All(a => a != null)); - if (hasSemanticErrors.HasValue) + if (hasCompilationErrors) { - - if (hasSemanticErrors.Value || rudeEdits.Length > 0) + Debug.Assert(activeStatementsOpt.IsDefault); + Debug.Assert(semanticEditsOpt.IsDefault); + Debug.Assert(exceptionRegionsOpt.IsDefault); + Debug.Assert(lineEditsOpt.IsDefault); + } + else if (hasChanges) + { + if (rudeEdits.Length > 0) { + Debug.Assert(activeStatementsOpt.IsDefault); Debug.Assert(semanticEditsOpt.IsDefault); Debug.Assert(exceptionRegionsOpt.IsDefault); Debug.Assert(lineEditsOpt.IsDefault); } else { + Debug.Assert(!activeStatementsOpt.IsDefault); Debug.Assert(!semanticEditsOpt.IsDefault); Debug.Assert(!exceptionRegionsOpt.IsDefault); Debug.Assert(!lineEditsOpt.IsDefault); - Debug.Assert(exceptionRegionsOpt.Length == activeStatements.Length); + Debug.Assert(exceptionRegionsOpt.Length == activeStatementsOpt.Length); } } else { + Debug.Assert(!activeStatementsOpt.IsDefault); Debug.Assert(semanticEditsOpt.IsEmpty); + Debug.Assert(!exceptionRegionsOpt.IsDefault); Debug.Assert(lineEditsOpt.IsEmpty); - Debug.Assert(exceptionRegionsOpt.IsDefault || exceptionRegionsOpt.Length == activeStatements.Length); + Debug.Assert(exceptionRegionsOpt.Length == activeStatementsOpt.Length); } RudeEditErrors = rudeEdits; SemanticEdits = semanticEditsOpt; - ActiveStatements = activeStatements; + ActiveStatements = activeStatementsOpt; ExceptionRegions = exceptionRegionsOpt; LineEdits = lineEditsOpt; - _hasCompilationErrors = hasSemanticErrors; + HasCompilationErrors = hasCompilationErrors; + HasChanges = hasChanges; } - public bool HasChanges => _hasCompilationErrors.HasValue; - public bool HasChangesAndErrors - { - get - { - return HasChanges && (_hasCompilationErrors.Value || !RudeEditErrors.IsEmpty); - } - } + => HasChanges && (HasCompilationErrors || !RudeEditErrors.IsEmpty); public bool HasChangesAndCompilationErrors - { - get - { - return _hasCompilationErrors == true; - } - } + => HasChanges && HasCompilationErrors; public bool HasSignificantValidChanges - { - get - { - return HasChanges && (!SemanticEdits.IsDefaultOrEmpty || !LineEdits.IsDefaultOrEmpty); - } - } - - public static DocumentAnalysisResults SyntaxErrors(ImmutableArray rudeEdits) - => new(rudeEdits); - - public static DocumentAnalysisResults Unchanged( - ImmutableArray activeStatements, - ImmutableArray> exceptionRegionsOpt) - { - return new DocumentAnalysisResults( - activeStatements, - ImmutableArray.Empty, - ImmutableArray.Empty, - exceptionRegionsOpt, - ImmutableArray.Empty, - hasSemanticErrors: null); - } - - public static DocumentAnalysisResults Errors( - ImmutableArray activeStatements, - ImmutableArray rudeEdits, - bool hasSemanticErrors = false) - { - return new DocumentAnalysisResults( - activeStatements, + => HasChanges && (!SemanticEdits.IsDefaultOrEmpty || !LineEdits.IsDefaultOrEmpty); + + public static DocumentAnalysisResults CompilationErrors(bool hasChanges) + => new( + activeStatementsOpt: default, + rudeEdits: ImmutableArray.Empty, + semanticEditsOpt: default, + exceptionRegionsOpt: default, + lineEditsOpt: default, + hasChanges, + hasCompilationErrors: true); + + public static DocumentAnalysisResults Errors(ImmutableArray rudeEdits) + => new( + activeStatementsOpt: default, rudeEdits, - default, - default, - default, - hasSemanticErrors); - } - - internal static readonly TraceLog Log = new(256, "EnC"); + semanticEditsOpt: default, + exceptionRegionsOpt: default, + lineEditsOpt: default, + hasChanges: true, + hasCompilationErrors: false); + + public static DocumentAnalysisResults Unchanged(ImmutableArray activeStatements, ImmutableArray> exceptionRegions) + => new( + activeStatements, + rudeEdits: ImmutableArray.Empty, + semanticEditsOpt: ImmutableArray.Empty, + exceptionRegions, + lineEditsOpt: ImmutableArray.Empty, + hasChanges: false, + hasCompilationErrors: false); } } diff --git a/src/Features/Core/Portable/EditAndContinue/ProjectAnalysisSummary.cs b/src/Features/Core/Portable/EditAndContinue/ProjectAnalysisSummary.cs index 7dc759d1c54b4..d25577fead4db 100644 --- a/src/Features/Core/Portable/EditAndContinue/ProjectAnalysisSummary.cs +++ b/src/Features/Core/Portable/EditAndContinue/ProjectAnalysisSummary.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - namespace Microsoft.CodeAnalysis.EditAndContinue { internal enum ProjectAnalysisSummary @@ -14,7 +12,7 @@ internal enum ProjectAnalysisSummary NoChanges, /// - /// Project contains syntactic and/or semantic errors. + /// Project contains compilation errors that block EnC analysis. /// CompilationErrors, From 5c379580065fac72b4068bfe95821ddcbdbfee45 Mon Sep 17 00:00:00 2001 From: tmat Date: Sat, 16 Jan 2021 12:41:45 -0800 Subject: [PATCH 5/9] Use ArrayBuilder/ImmutableArray.Builder instead of List/Array --- .../Helpers/EditingTestBase.cs | 5 +- .../EditAndContinueTestHelpers.cs | 53 ++-- .../Helpers/EditingTestBase.vb | 3 +- .../CSharpEditAndContinueAnalyzer.cs | 35 +-- .../AbstractEditAndContinueAnalyzer.cs | 228 +++++++++--------- .../VisualBasicEditAndContinueAnalyzer.vb | 25 +- 6 files changed, 182 insertions(+), 167 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index f94c5e5aee4ea..5bb9bdb679f3d 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EditAndContinue; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Xunit; @@ -76,7 +77,7 @@ internal static Match GetMethodMatch(string src1, string src2, Metho var m1 = MakeMethodBody(src1, kind); var m2 = MakeMethodBody(src2, kind); - var diagnostics = new List(); + var diagnostics = new ArrayBuilder(); var match = CreateAnalyzer().GetTestAccessor().ComputeBodyMatch(m1, m2, Array.Empty(), diagnostics, out var oldHasStateMachineSuspensionPoint, out var newHasStateMachineSuspensionPoint); var needsSyntaxMap = oldHasStateMachineSuspensionPoint && newHasStateMachineSuspensionPoint; @@ -148,7 +149,7 @@ internal static void VerifyPreserveLocalVariables(EditScript edits, var decl2 = (MethodDeclarationSyntax)((ClassDeclarationSyntax)((CompilationUnitSyntax)edits.Match.NewRoot).Members[0]).Members[0]; var body2 = ((MethodDeclarationSyntax)SyntaxFactory.SyntaxTree(decl2).GetRoot()).Body; - var diagnostics = new List(); + var diagnostics = new ArrayBuilder(); _ = CreateAnalyzer().GetTestAccessor().ComputeBodyMatch(body1, body2, Array.Empty(), diagnostics, out var oldHasStateMachineSuspensionPoint, out var newHasStateMachineSuspensionPoint); var needsSyntaxMap = oldHasStateMachineSuspensionPoint && newHasStateMachineSuspensionPoint; diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index 4cfc696ae6ec0..c1388c6ad871a 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -9,6 +9,7 @@ using System.Threading; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; @@ -42,8 +43,11 @@ internal void VerifyUnchangedDocument( var documentId = DocumentId.CreateNewId(ProjectId.CreateNewId("TestEnCProject"), "TestEnCDocument"); - var actualNewActiveStatements = new ActiveStatement[oldActiveStatements.Length]; - var actualNewExceptionRegions = new ImmutableArray[oldActiveStatements.Length]; + var actualNewActiveStatements = ImmutableArray.CreateBuilder(oldActiveStatements.Length); + actualNewActiveStatements.Count = actualNewActiveStatements.Capacity; + + var actualNewExceptionRegions = ImmutableArray.CreateBuilder>(oldActiveStatements.Length); + actualNewExceptionRegions.Count = actualNewExceptionRegions.Capacity; Analyzer.GetTestAccessor().AnalyzeUnchangedDocument( oldActiveStatements.AsImmutable(), @@ -56,7 +60,7 @@ internal void VerifyUnchangedDocument( AssertSpansEqual(expectedNewActiveStatements, actualNewActiveStatements.Select(s => s.Span), source, text); // check new exception regions: - Assert.Equal(expectedNewExceptionRegions.Length, actualNewExceptionRegions.Length); + Assert.Equal(expectedNewExceptionRegions.Length, actualNewExceptionRegions.Count); for (var i = 0; i < expectedNewExceptionRegions.Length; i++) { AssertSpansEqual(expectedNewExceptionRegions[i], actualNewExceptionRegions[i], source, text); @@ -81,10 +85,12 @@ internal void VerifyRudeDiagnostics( var oldText = SourceText.From(oldSource); var newText = SourceText.From(newSource); - var diagnostics = new List(); - var actualNewActiveStatements = new ActiveStatement[oldActiveStatements.Length]; - var actualNewExceptionRegions = new ImmutableArray[oldActiveStatements.Length]; - var updatedActiveMethodMatches = new List(); + var diagnostics = new ArrayBuilder(); + var updatedActiveMethodMatches = new ArrayBuilder(); + var actualNewActiveStatements = ImmutableArray.CreateBuilder(oldActiveStatements.Length); + actualNewActiveStatements.Count = actualNewActiveStatements.Capacity; + var actualNewExceptionRegions = ImmutableArray.CreateBuilder>(oldActiveStatements.Length); + actualNewExceptionRegions.Count = actualNewExceptionRegions.Capacity; var editMap = BuildEditMap(editScript); var documentId = DocumentId.CreateNewId(ProjectId.CreateNewId("TestEnCProject"), "TestEnCDocument"); @@ -125,7 +131,7 @@ internal void VerifyRudeDiagnostics( } // check new exception regions: - Assert.Equal(description.NewRegions.Length, actualNewExceptionRegions.Length); + Assert.Equal(description.NewRegions.Length, actualNewExceptionRegions.Count); for (var i = 0; i < description.NewRegions.Length; i++) { AssertSpansEqual(description.NewRegions[i], actualNewExceptionRegions[i], newSource, newText); @@ -152,11 +158,16 @@ internal void VerifyLineEdits( var oldText = SourceText.From(oldSource); var newText = SourceText.From(newSource); - var diagnostics = new List(); + var diagnostics = new ArrayBuilder(); var editMap = BuildEditMap(editScript); +<<<<<<< HEAD var triviaEdits = new List<(SyntaxNode OldNode, SyntaxNode NewNode)>(); var actualLineEdits = new List(); +======= + var triviaEdits = new ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>(); + var actualLineEdits = new ArrayBuilder(); +>>>>>>> b14da14d6d2 (Use ArrayBuilder/ImmutableArray.Builder instead of List/Array) Analyzer.GetTestAccessor().AnalyzeTrivia( oldText, @@ -191,15 +202,19 @@ internal void VerifySemantics( var newCompilation = CreateLibraryCompilation("New", newTrees); var oldActiveStatements = activeStatements.OldStatements.AsImmutable(); - var triviaEdits = new List<(SyntaxNode OldNode, SyntaxNode NewNode)>(); - var actualLineEdits = new List(); - var actualSemanticEdits = new List(); + var triviaEdits = new ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>(); + var actualLineEdits = new ArrayBuilder(); + var actualSemanticEdits = new ArrayBuilder(); var includeFirstLineInDiagnostics = expectedDiagnostics?.Any(d => d.FirstLine != null) == true; - var actualDiagnosticDescriptions = new List(); - var actualDeclarationErrors = new List(); + var actualDiagnosticDescriptions = new ArrayBuilder(); + var actualDeclarationErrors = new ArrayBuilder(); + + var actualNewActiveStatements = ImmutableArray.CreateBuilder(activeStatements.OldStatements.Length); + actualNewActiveStatements.Count = actualNewActiveStatements.Capacity; + + var actualNewExceptionRegions = ImmutableArray.CreateBuilder>(activeStatements.OldStatements.Length); + actualNewExceptionRegions.Count = actualNewExceptionRegions.Capacity; - var actualNewActiveStatements = new ActiveStatement[activeStatements.OldStatements.Length]; - var actualNewExceptionRegions = new ImmutableArray[activeStatements.OldStatements.Length]; var testAccessor = Analyzer.GetTestAccessor(); foreach (var editScript in editScripts) @@ -215,8 +230,8 @@ internal void VerifySemantics( var oldModel = oldCompilation.GetSemanticModel(oldRoot.SyntaxTree); var newModel = newCompilation.GetSemanticModel(newRoot.SyntaxTree); - var diagnostics = new List(); - var updatedActiveMethodMatches = new List(); + var diagnostics = new ArrayBuilder(); + var updatedActiveMethodMatches = new ArrayBuilder(); testAccessor.AnalyzeMemberBodiesSyntax( editScript, @@ -325,7 +340,7 @@ private static string DisplaySpan(string source, TextSpan span) internal static IEnumerable> GetMethodMatches(AbstractEditAndContinueAnalyzer analyzer, Match bodyMatch) { Dictionary? lazyActiveOrMatchedLambdas = null; - var map = analyzer.GetTestAccessor().ComputeMap(bodyMatch, Array.Empty(), ref lazyActiveOrMatchedLambdas, new List()); + var map = analyzer.GetTestAccessor().ComputeMap(bodyMatch, Array.Empty(), ref lazyActiveOrMatchedLambdas, new ArrayBuilder()); var result = new Dictionary(); foreach (var pair in map.Forward) diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index 1ce419d0121ae..306bf1acadef7 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -7,6 +7,7 @@ Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.EditAndContinue.UnitTests Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EditAndContinue Imports Microsoft.CodeAnalysis.Emit +Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -62,7 +63,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Dim m1 = MakeMethodBody(src1, stateMachine) Dim m2 = MakeMethodBody(src2, stateMachine) - Dim diagnostics = New List(Of RudeEditDiagnostic)() + Dim diagnostics = New ArrayBuilder(Of RudeEditDiagnostic)() Dim oldHasStateMachineSuspensionPoint = False, newHasStateMachineSuspensionPoint = False Dim match = CreateAnalyzer().GetTestAccessor().ComputeBodyMatch(m1, m2, Array.Empty(Of AbstractEditAndContinueAnalyzer.ActiveNode)(), diagnostics, oldHasStateMachineSuspensionPoint, newHasStateMachineSuspensionPoint) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 66fdf052fa24e..3e843bde1fb54 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -1050,7 +1051,7 @@ internal override bool IsPartial(INamedTypeSymbol type) || ((TypeDeclarationSyntax)syntaxRefs.Single().GetSyntax()).Modifiers.Any(SyntaxKind.PartialKeyword); } - protected override ISymbol? GetSymbolForEdit(SemanticModel model, SyntaxNode node, EditKind editKind, Dictionary editMap, CancellationToken cancellationToken) + protected override ISymbol? GetSymbolForEdit(SemanticModel model, SyntaxNode node, EditKind editKind, IReadOnlyDictionary editMap, CancellationToken cancellationToken) { if (node.IsKind(SyntaxKind.Parameter)) { @@ -1083,7 +1084,7 @@ internal override bool IsPartial(INamedTypeSymbol type) return model.GetDeclaredSymbol(node, cancellationToken); } - protected override bool TryGetDeclarationBodyEdit(Edit edit, Dictionary editMap, out SyntaxNode? oldBody, out SyntaxNode? newBody) + protected override bool TryGetDeclarationBodyEdit(Edit edit, IReadOnlyDictionary editMap, out SyntaxNode? oldBody, out SyntaxNode? newBody) { // Detect a transition between a property/indexer with an expression body and with an explicit getter. // int P => old_body; <-> int P { get { new_body } } @@ -1111,7 +1112,7 @@ protected override bool TryGetDeclarationBodyEdit(Edit edit, Diction return base.TryGetDeclarationBodyEdit(edit, editMap, out oldBody, out newBody); } - private static bool IsGetterToExpressionBodyTransformation(EditKind editKind, SyntaxNode node, Dictionary editMap) + private static bool IsGetterToExpressionBodyTransformation(EditKind editKind, SyntaxNode node, IReadOnlyDictionary editMap) { if ((editKind == EditKind.Insert || editKind == EditKind.Delete) && node.IsKind(SyntaxKind.GetAccessorDeclaration)) { @@ -1199,7 +1200,7 @@ protected override void ReportLambdaSignatureRudeEdits( SyntaxNode oldLambdaBody, SemanticModel newModel, SyntaxNode newLambdaBody, - List diagnostics, + ArrayBuilder diagnostics, out bool hasErrors, CancellationToken cancellationToken) { @@ -1883,7 +1884,7 @@ protected override string GetSuspensionPointDisplayName(SyntaxNode node, EditKin private readonly struct EditClassifier { private readonly CSharpEditAndContinueAnalyzer _analyzer; - private readonly List _diagnostics; + private readonly ArrayBuilder _diagnostics; private readonly Match? _match; private readonly SyntaxNode? _oldNode; private readonly SyntaxNode? _newNode; @@ -1893,7 +1894,7 @@ private readonly struct EditClassifier public EditClassifier( CSharpEditAndContinueAnalyzer analyzer, - List diagnostics, + ArrayBuilder diagnostics, SyntaxNode? oldNode, SyntaxNode? newNode, EditKind kind, @@ -3031,7 +3032,7 @@ public void ClassifyDeclarationBodyRudeUpdates(SyntaxNode newDeclarationOrBody) } internal override void ReportTopLevelSyntacticRudeEdits( - List diagnostics, + ArrayBuilder diagnostics, Match match, Edit edit, Dictionary editMap) @@ -3045,7 +3046,7 @@ internal override void ReportTopLevelSyntacticRudeEdits( classifier.ClassifyEdit(); } - internal override void ReportMemberUpdateRudeEdits(List diagnostics, SyntaxNode newMember, TextSpan? span) + internal override void ReportMemberUpdateRudeEdits(ArrayBuilder diagnostics, SyntaxNode newMember, TextSpan? span) { var classifier = new EditClassifier(this, diagnostics, oldNode: null, newMember, EditKind.Update, span: span); @@ -3061,7 +3062,7 @@ internal override void ReportMemberUpdateRudeEdits(List diag #region Semantic Rude Edits - internal override void ReportInsertedMemberSymbolRudeEdits(List diagnostics, ISymbol newSymbol) + internal override void ReportInsertedMemberSymbolRudeEdits(ArrayBuilder diagnostics, ISymbol newSymbol) { // We rejected all exported methods during syntax analysis, so no additional work is needed here. } @@ -3122,7 +3123,7 @@ protected override List GetExceptionHandlingAncestors(SyntaxNode nod } internal override void ReportEnclosingExceptionHandlingRudeEdits( - List diagnostics, + ArrayBuilder diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, TextSpan newStatementSpan) @@ -3231,7 +3232,7 @@ protected override void GetStateMachineInfo(SyntaxNode body, out ImmutableArray< } } - internal override void ReportStateMachineSuspensionPointRudeEdits(List diagnostics, SyntaxNode oldNode, SyntaxNode newNode) + internal override void ReportStateMachineSuspensionPointRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode) { // TODO: changes around suspension points (foreach, lock, using, etc.) @@ -3249,7 +3250,7 @@ internal override void ReportStateMachineSuspensionPointRudeEdits(List diagnostics, Match match, SyntaxNode deletedSuspensionPoint) + internal override void ReportStateMachineSuspensionPointDeletedRudeEdit(ArrayBuilder diagnostics, Match match, SyntaxNode deletedSuspensionPoint) { // Handle deletion of await keyword from await foreach statement. if (deletedSuspensionPoint is CommonForEachStatementSyntax deletedForeachStatement && @@ -3283,7 +3284,7 @@ newForEachStatement is CommonForEachStatementSyntax && base.ReportStateMachineSuspensionPointDeletedRudeEdit(diagnostics, match, deletedSuspensionPoint); } - internal override void ReportStateMachineSuspensionPointInsertedRudeEdit(List diagnostics, Match match, SyntaxNode insertedSuspensionPoint, bool aroundActiveStatement) + internal override void ReportStateMachineSuspensionPointInsertedRudeEdit(ArrayBuilder diagnostics, Match match, SyntaxNode insertedSuspensionPoint, bool aroundActiveStatement) { // Handle addition of await keyword to foreach statement. if (insertedSuspensionPoint is CommonForEachStatementSyntax insertedForEachStatement && @@ -3425,7 +3426,7 @@ private static bool IsSimpleAwaitAssignment(SyntaxNode node, SyntaxNode awaitExp #region Rude Edits around Active Statement internal override void ReportOtherRudeEditsAroundActiveStatement( - List diagnostics, + ArrayBuilder diagnostics, Match match, SyntaxNode oldActiveStatement, SyntaxNode newActiveStatement, @@ -3443,7 +3444,7 @@ internal override void ReportOtherRudeEditsAroundActiveStatement( /// exactly the same variables are emitted for the new switch as they were for the old one and their order didn't change either. /// This is guaranteed if none of the case clauses have changed. /// - private void ReportRudeEditsForSwitchWhenClauses(List diagnostics, SyntaxNode oldActiveStatement, SyntaxNode newActiveStatement) + private void ReportRudeEditsForSwitchWhenClauses(ArrayBuilder diagnostics, SyntaxNode oldActiveStatement, SyntaxNode newActiveStatement) { if (!oldActiveStatement.IsKind(SyntaxKind.WhenClause)) { @@ -3499,7 +3500,7 @@ private static bool AreLabelsEquivalent(SwitchLabelSyntax oldLabel, SwitchLabelS } private void ReportRudeEditsForCheckedStatements( - List diagnostics, + ArrayBuilder diagnostics, SyntaxNode oldActiveStatement, SyntaxNode newActiveStatement, bool isNonLeaf) @@ -3554,7 +3555,7 @@ private void ReportRudeEditsForCheckedStatements( } private void ReportRudeEditsForAncestorsDeclaringInterStatementTemps( - List diagnostics, + ArrayBuilder diagnostics, Match match, SyntaxNode oldActiveStatement, SyntaxNode newActiveStatement) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 67d8d2450933b..b21669d6cb0f7 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -88,7 +88,7 @@ protected AbstractEditAndContinueAnalyzer(Action? testFaultInjector) /// protected virtual bool TryGetDeclarationBodyEdit( Edit edit, - Dictionary editMap, + IReadOnlyDictionary editMap, out SyntaxNode? oldBody, out SyntaxNode? newBody) { @@ -228,7 +228,7 @@ protected virtual bool StateMachineSuspensionPointKindEquals(SyntaxNode suspensi /// protected abstract bool AreEquivalentActiveStatements(SyntaxNode oldStatement, SyntaxNode newStatement, int statementPart); - protected abstract ISymbol? GetSymbolForEdit(SemanticModel model, SyntaxNode node, EditKind editKind, Dictionary editMap, CancellationToken cancellationToken); + protected abstract ISymbol? GetSymbolForEdit(SemanticModel model, SyntaxNode node, EditKind editKind, IReadOnlyDictionary editMap, CancellationToken cancellationToken); /// /// Analyzes data flow in the member body represented by the specified node and returns all captured variables and parameters (including "this"). @@ -308,14 +308,12 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind protected abstract void ReportLocalFunctionsDeclarationRudeEdits(Match bodyMatch, List diagnostics); - internal abstract void ReportSyntacticRudeEdits(List diagnostics, Match match, Edit edit, Dictionary editMap); - internal abstract void ReportTopLevelSyntacticRudeEdits(List diagnostics, Match match, Edit edit, Dictionary editMap); - - 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 isNonLeaf); - 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); + internal abstract void ReportTopLevelSyntacticRudeEdits(ArrayBuilder diagnostics, Match match, Edit edit, Dictionary editMap); + internal abstract void ReportEnclosingExceptionHandlingRudeEdits(ArrayBuilder diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, TextSpan newStatementSpan); + internal abstract void ReportOtherRudeEditsAroundActiveStatement(ArrayBuilder diagnostics, Match match, SyntaxNode oldStatement, SyntaxNode newStatement, bool isNonLeaf); + internal abstract void ReportMemberUpdateRudeEdits(ArrayBuilder diagnostics, SyntaxNode newMember, TextSpan? span); + internal abstract void ReportInsertedMemberSymbolRudeEdits(ArrayBuilder diagnostics, ISymbol newSymbol); + internal abstract void ReportStateMachineSuspensionPointRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode); internal abstract bool IsLambda(SyntaxNode node); internal abstract bool IsInterfaceDeclaration(SyntaxNode node); @@ -436,8 +434,11 @@ public async Task AnalyzeDocumentAsync( return DocumentAnalysisResults.CompilationErrors(hasChanges); } - var newActiveStatements = new ActiveStatement[baseActiveStatements.Length]; - var newExceptionRegions = new ImmutableArray[baseActiveStatements.Length]; + var newActiveStatements = ImmutableArray.CreateBuilder(baseActiveStatements.Length); + newActiveStatements.Count = baseActiveStatements.Length; + + var newExceptionRegions = ImmutableArray.CreateBuilder>(baseActiveStatements.Length); + newExceptionRegions.Count = baseActiveStatements.Length; if (!hasChanges) { @@ -454,7 +455,7 @@ public async Task AnalyzeDocumentAsync( newExceptionRegions); DocumentAnalysisResults.Log.Write("{0}: unchanged", document.Name); - return DocumentAnalysisResults.Unchanged(newActiveStatements.AsImmutable(), newExceptionRegions.AsImmutable()); + return DocumentAnalysisResults.Unchanged(newActiveStatements.MoveToImmutable(), newExceptionRegions.MoveToImmutable()); } // Disallow modification of a file with experimental features enabled. @@ -473,8 +474,8 @@ public async Task AnalyzeDocumentAsync( // 2) If there are syntactic rude edits we'll report them faster without waiting for semantic analysis. // The user may fix them before they address all the semantic errors. - var updatedMembers = new List(); - var diagnostics = new List(); + using var _1 = ArrayBuilder.GetInstance(out var updatedMembers); + using var _2 = ArrayBuilder.GetInstance(out var diagnostics); cancellationToken.ThrowIfCancellationRequested(); @@ -499,7 +500,7 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0} syntactic rude edits, first: '{1}'", diagnostics.Count, document.FilePath); - return DocumentAnalysisResults.Errors(diagnostics.AsImmutable()); + return DocumentAnalysisResults.Errors(diagnostics.ToImmutable()); } // Disallow addition of a new file. @@ -514,8 +515,8 @@ public async Task AnalyzeDocumentAsync( cancellationToken.ThrowIfCancellationRequested(); - var triviaEdits = new List<(SyntaxNode OldNode, SyntaxNode NewNode)>(); - var lineEdits = new List(); + using var _3 = ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>.GetInstance(out var triviaEdits); + using var _4 = ArrayBuilder.GetInstance(out var lineEdits); AnalyzeTrivia( oldText, @@ -532,12 +533,12 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0} trivia rude edits, first: {1}@{2}", diagnostics.Count, document.FilePath, diagnostics.First().Span.Start); - return DocumentAnalysisResults.Errors(diagnostics.AsImmutable()); + return DocumentAnalysisResults.Errors(diagnostics.ToImmutable()); } cancellationToken.ThrowIfCancellationRequested(); - List? semanticEdits = null; + var semanticEdits = ImmutableArray.Empty; if (syntacticEdits.Edits.Length > 0 || triviaEdits.Count > 0) { var newModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); @@ -545,7 +546,7 @@ public async Task AnalyzeDocumentAsync( Contract.ThrowIfNull(oldModel); Contract.ThrowIfNull(newModel); - semanticEdits = new List(); + using var _ = ArrayBuilder.GetInstance(out var semanticEditsBuilder); AnalyzeSemantics( syntacticEdits, editMap, @@ -555,7 +556,7 @@ public async Task AnalyzeDocumentAsync( updatedMembers, oldModel, newModel, - semanticEdits, + semanticEditsBuilder, diagnostics, cancellationToken); @@ -564,16 +565,18 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0}@{1}: semantic rude edit ({2} total)", document.FilePath, diagnostics.First().Span.Start, diagnostics.Count); - return DocumentAnalysisResults.Errors(diagnostics.AsImmutable()); + return DocumentAnalysisResults.Errors(diagnostics.ToImmutable()); } + + semanticEdits = semanticEditsBuilder.ToImmutable(); } return new DocumentAnalysisResults( - newActiveStatements.AsImmutable(), - diagnostics.AsImmutable(), - semanticEdits.AsImmutableOrEmpty(), - newExceptionRegions.AsImmutable(), - lineEdits.AsImmutable(), + newActiveStatements.MoveToImmutable(), + diagnostics.ToImmutable(), + semanticEdits, + newExceptionRegions.MoveToImmutable(), + lineEdits.ToImmutable(), hasChanges: true, hasCompilationErrors: false); } @@ -591,7 +594,7 @@ public async Task AnalyzeDocumentAsync( } } - private void ReportTopLevelSyntacticRudeEdits(List diagnostics, EditScript syntacticEdits, Dictionary editMap) + private void ReportTopLevelSyntacticRudeEdits(ArrayBuilder diagnostics, EditScript syntacticEdits, Dictionary editMap) { foreach (var edit in syntacticEdits.Edits) { @@ -627,20 +630,20 @@ internal static Dictionary BuildEditMap(EditScript script, - Dictionary editMap, + IReadOnlyDictionary editMap, SourceText oldText, SourceText newText, ImmutableArray oldActiveStatements, ImmutableArray newActiveStatementSpans, - [Out] ActiveStatement[] newActiveStatements, - [Out] ImmutableArray[] newExceptionRegions, - [Out] List updatedMembers, - [Out] List diagnostics) + [Out] ImmutableArray.Builder newActiveStatements, + [Out] ImmutableArray>.Builder newExceptionRegions, + [Out] ArrayBuilder updatedMembers, + [Out] ArrayBuilder diagnostics) { Debug.Assert(!newActiveStatementSpans.IsDefault); Debug.Assert(newActiveStatementSpans.IsEmpty || oldActiveStatements.Length == newActiveStatementSpans.Length); - Debug.Assert(oldActiveStatements.Length == newActiveStatements.Length); - Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Length); + Debug.Assert(oldActiveStatements.Length == newActiveStatements.Count); + Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Count); Debug.Assert(updatedMembers.Count == 0); for (var i = 0; i < script.Edits.Length; i++) @@ -654,24 +657,24 @@ private void AnalyzeMemberBodiesSyntax( } private void AnalyzeUnchangedMemberBodies( - List diagnostics, + ArrayBuilder diagnostics, Match topMatch, SourceText oldText, SourceText newText, ImmutableArray oldActiveStatements, ImmutableArray newActiveStatementSpans, - [In, Out] ActiveStatement[] newActiveStatements, - [In, Out] ImmutableArray[] newExceptionRegions) + [In, Out] ImmutableArray.Builder newActiveStatements, + [In, Out] ImmutableArray>.Builder newExceptionRegions) { Debug.Assert(!newActiveStatementSpans.IsDefault); Debug.Assert(newActiveStatementSpans.IsEmpty || oldActiveStatements.Length == newActiveStatementSpans.Length); - Debug.Assert(oldActiveStatements.Length == newActiveStatements.Length); - Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Length); + Debug.Assert(oldActiveStatements.Length == newActiveStatements.Count); + Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Count); // Active statements in methods that were not updated // are not changed but their spans might have been. - for (var i = 0; i < newActiveStatements.Length; i++) + for (var i = 0; i < newActiveStatements.Count; i++) { if (newActiveStatements[i] == null) { @@ -765,26 +768,22 @@ private void AnalyzeUnchangedDocument( ImmutableArray oldActiveStatements, SourceText newText, SyntaxNode newRoot, - [In, Out] ActiveStatement[] newActiveStatements, - [In, Out] ImmutableArray[]? newExceptionRegions) + [In, Out] ImmutableArray.Builder newActiveStatements, + [In, Out] ImmutableArray>.Builder newExceptionRegions) { - Debug.Assert(oldActiveStatements.Length == newActiveStatements.Length); - Debug.Assert(newExceptionRegions == null || oldActiveStatements.Length == newExceptionRegions.Length); + Debug.Assert(oldActiveStatements.Length == newActiveStatements.Count); + Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Count); // Active statements in methods that were not updated // are not changed but their spans might have been. - for (var i = 0; i < newActiveStatements.Length; i++) + for (var i = 0; i < newActiveStatements.Count; i++) { if (!TryGetTextSpan(newText.Lines, oldActiveStatements[i].Span, out var oldStatementSpan) || !TryGetEnclosingBreakpointSpan(newRoot, oldStatementSpan.Start, out var newStatementSpan)) { newActiveStatements[i] = oldActiveStatements[i].WithSpan(default); - - if (newExceptionRegions != null) - { - newExceptionRegions[i] = ImmutableArray.Empty; - } + newExceptionRegions[i] = ImmutableArray.Empty; continue; } @@ -792,12 +791,9 @@ private void AnalyzeUnchangedDocument( var newNode = TryGetNode(newRoot, oldStatementSpan.Start); Contract.ThrowIfNull(newNode); // we wouldn't find a breakpoint span otherwise - if (newExceptionRegions != null) - { - var ancestors = GetExceptionHandlingAncestors(newNode, oldActiveStatements[i].IsNonLeaf); - newExceptionRegions[i] = GetExceptionRegions(ancestors, newText); - } + var ancestors = GetExceptionHandlingAncestors(newNode, oldActiveStatements[i].IsNonLeaf); + newExceptionRegions[i] = GetExceptionRegions(ancestors, newText); newActiveStatements[i] = oldActiveStatements[i].WithSpan(newText.Lines.GetLinePositionSpan(newStatementSpan)); } } @@ -903,20 +899,20 @@ public UpdatedMemberInfo( private void AnalyzeChangedMemberBody( EditScript topEditScript, int editOrdinal, - Dictionary editMap, + IReadOnlyDictionary editMap, SourceText oldText, SourceText newText, ImmutableArray oldActiveStatements, ImmutableArray newActiveStatementSpans, - [Out] ActiveStatement[] newActiveStatements, - [Out] ImmutableArray[] newExceptionRegions, - [Out] List updatedMembers, - [Out] List diagnostics) + [Out] ImmutableArray.Builder newActiveStatements, + [Out] ImmutableArray>.Builder newExceptionRegions, + [Out] ArrayBuilder updatedMembers, + [Out] ArrayBuilder diagnostics) { Debug.Assert(!newActiveStatementSpans.IsDefault); Debug.Assert(newActiveStatementSpans.IsEmpty || oldActiveStatements.Length == newActiveStatementSpans.Length); - Debug.Assert(oldActiveStatements.Length == newActiveStatements.Length); - Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Length); + Debug.Assert(oldActiveStatements.Length == newActiveStatements.Count); + Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Count); var edit = topEditScript.Edits[editOrdinal]; @@ -1189,8 +1185,8 @@ private void CalculateExceptionRegionsAroundActiveStatement( int ordinal, SourceText newText, bool isNonLeaf, - ImmutableArray[] newExceptionRegions, - List diagnostics) + ImmutableArray>.Builder newExceptionRegions, + ArrayBuilder diagnostics) { if (newStatementSyntax == null) { @@ -1226,7 +1222,7 @@ private BidirectionalMap ComputeMap( Match bodyMatch, ActiveNode[] activeNodes, ref Dictionary? lazyActiveOrMatchedLambdas, - List diagnostics) + ArrayBuilder diagnostics) { ArrayBuilder>? lambdaBodyMatches = null; var currentLambdaBodyMatch = -1; @@ -1308,7 +1304,7 @@ private Match ComputeLambdaBodyMatch( SyntaxNode newLambdaBody, ActiveNode[] activeNodes, [Out] Dictionary activeOrMatchedLambdas, - [Out] List diagnostics) + [Out] ArrayBuilder diagnostics) { ActiveNode[]? activeNodesInLambda; if (activeOrMatchedLambdas.TryGetValue(oldLambdaBody, out var info)) @@ -1336,7 +1332,7 @@ private Match ComputeBodyMatch( SyntaxNode oldBody, SyntaxNode newBody, ActiveNode[] activeNodes, - List diagnostics, + ArrayBuilder diagnostics, out bool oldHasStateMachineSuspensionPoint, out bool newHasStateMachineSuspensionPoint) { @@ -1458,7 +1454,7 @@ private Match ComputeBodyMatch( return match; } - internal virtual void ReportStateMachineSuspensionPointDeletedRudeEdit(List diagnostics, Match match, SyntaxNode deletedSuspensionPoint) + internal virtual void ReportStateMachineSuspensionPointDeletedRudeEdit(ArrayBuilder diagnostics, Match match, SyntaxNode deletedSuspensionPoint) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.Delete, @@ -1467,7 +1463,7 @@ internal virtual void ReportStateMachineSuspensionPointDeletedRudeEdit(List diagnostics, Match match, SyntaxNode insertedSuspensionPoint, bool aroundActiveStatement) + internal virtual void ReportStateMachineSuspensionPointInsertedRudeEdit(ArrayBuilder diagnostics, Match match, SyntaxNode insertedSuspensionPoint, bool aroundActiveStatement) { diagnostics.Add(new RudeEditDiagnostic( aroundActiveStatement ? RudeEditKind.InsertAroundActiveStatement : RudeEditKind.Insert, @@ -1712,7 +1708,7 @@ protected virtual bool TryGetOverlappingActiveStatements( return true; } - protected static bool HasParentEdit(Dictionary editMap, Edit edit) + protected static bool HasParentEdit(IReadOnlyDictionary editMap, Edit edit) { SyntaxNode node; switch (edit.Kind) @@ -1732,7 +1728,7 @@ protected static bool HasParentEdit(Dictionary editMap, Ed return HasEdit(editMap, node.Parent, edit.Kind); } - protected static bool HasEdit(Dictionary editMap, SyntaxNode? node, EditKind editKind) + protected static bool HasEdit(IReadOnlyDictionary editMap, SyntaxNode? node, EditKind editKind) { return node is object && @@ -1744,7 +1740,7 @@ node is object && #region Rude Edits around Active Statement - protected void AddAroundActiveStatementRudeDiagnostic(List diagnostics, SyntaxNode? oldNode, SyntaxNode? newNode, TextSpan newActiveStatementSpan) + protected void AddAroundActiveStatementRudeDiagnostic(ArrayBuilder diagnostics, SyntaxNode? oldNode, SyntaxNode? newNode, TextSpan newActiveStatementSpan) { if (oldNode == null) { @@ -1762,7 +1758,7 @@ protected void AddAroundActiveStatementRudeDiagnostic(List d } } - protected void AddRudeUpdateAroundActiveStatement(List diagnostics, SyntaxNode newNode) + protected void AddRudeUpdateAroundActiveStatement(ArrayBuilder diagnostics, SyntaxNode newNode) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.UpdateAroundActiveStatement, @@ -1771,7 +1767,7 @@ protected void AddRudeUpdateAroundActiveStatement(List diagn new[] { GetDisplayName(newNode, EditKind.Update) })); } - protected void AddRudeInsertAroundActiveStatement(List diagnostics, SyntaxNode newNode) + protected void AddRudeInsertAroundActiveStatement(ArrayBuilder diagnostics, SyntaxNode newNode) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.InsertAroundActiveStatement, @@ -1780,7 +1776,7 @@ protected void AddRudeInsertAroundActiveStatement(List diagn new[] { GetDisplayName(newNode, EditKind.Insert) })); } - protected void AddRudeDeleteAroundActiveStatement(List diagnostics, SyntaxNode oldNode, TextSpan newActiveStatementSpan) + protected void AddRudeDeleteAroundActiveStatement(ArrayBuilder diagnostics, SyntaxNode oldNode, TextSpan newActiveStatementSpan) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.DeleteAroundActiveStatement, @@ -1790,7 +1786,7 @@ protected void AddRudeDeleteAroundActiveStatement(List diagn } protected void ReportUnmatchedStatements( - List diagnostics, + ArrayBuilder diagnostics, Match match, Func nodeSelector, SyntaxNode oldActiveStatement, @@ -1829,7 +1825,7 @@ protected void ReportUnmatchedStatements( } } - private void ReportRudeEditsAndInserts(List? oldNodes, List newNodes, List diagnostics) + private void ReportRudeEditsAndInserts(List? oldNodes, List newNodes, ArrayBuilder diagnostics) { var oldNodeCount = (oldNodes != null) ? oldNodes.Count : 0; @@ -1857,7 +1853,7 @@ private void ReportRudeEditsAndInserts(List? oldNodes, List( List oldNodes, List newNodes, - List? diagnostics, + ArrayBuilder? diagnostics, Match? match, Func comparer) where TSyntaxNode : SyntaxNode @@ -1962,10 +1958,10 @@ private void AnalyzeTrivia( SourceText oldSource, SourceText newSource, Match topMatch, - Dictionary editMap, - [Out] List<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, - [Out] List lineEdits, - [Out] List diagnostics, + IReadOnlyDictionary editMap, + [Out] ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, + [Out] ArrayBuilder lineEdits, + [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { foreach (var (oldNode, newNode) in topMatch.Matches) @@ -2173,15 +2169,15 @@ public ConstructorEdit(INamedTypeSymbol oldType) private void AnalyzeSemantics( EditScript editScript, - Dictionary editMap, + IReadOnlyDictionary editMap, SourceText oldText, ImmutableArray oldActiveStatements, - List<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, - List updatedMembers, + IReadOnlyList<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, + IReadOnlyList updatedMembers, SemanticModel oldModel, SemanticModel newModel, - [Out] List semanticEdits, - [Out] List diagnostics, + [Out] ArrayBuilder semanticEdits, + [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { // { new type -> constructor update } @@ -2584,7 +2580,7 @@ private void AnalyzeSemantics( #region Type Layout Update Validation internal void ReportTypeLayoutUpdateRudeEdits( - List diagnostics, + ArrayBuilder diagnostics, ISymbol newSymbol, SyntaxNode newSyntax, SemanticModel newModel, @@ -2620,7 +2616,7 @@ internal void ReportTypeLayoutUpdateRudeEdits( } } - private void ReportTypeLayoutUpdateRudeEdits(List diagnostics, ISymbol symbol, SyntaxNode syntax) + private void ReportTypeLayoutUpdateRudeEdits(ArrayBuilder diagnostics, ISymbol symbol, SyntaxNode syntax) { var intoStruct = symbol.ContainingType.TypeKind == TypeKind.Struct; @@ -2794,7 +2790,7 @@ private bool DeferConstructorEdit( ref Func? syntaxMap, ref Dictionary? instanceConstructorEdits, ref Dictionary? staticConstructorEdits, - [Out] List diagnostics, + [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { if (IsPartial(newType)) @@ -2861,8 +2857,8 @@ private void AddConstructorEdits( SemanticModel oldModel, HashSet newSymbolsWithEdit, bool isStatic, - [Out] List semanticEdits, - [Out] List diagnostics, + [Out] ArrayBuilder semanticEdits, + [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { foreach (var (newType, update) in updatedTypes) @@ -3019,7 +3015,7 @@ private void ReportLambdaAndClosureRudeEdits( ISymbol newMember, IReadOnlyDictionary? matchedLambdas, BidirectionalMap map, - List diagnostics, + ArrayBuilder diagnostics, out bool newBodyHasLambdas, CancellationToken cancellationToken) { @@ -3263,7 +3259,7 @@ private void ReportMultiScopeCaptures( ArrayBuilder newCapturesToClosureScopes, PooledDictionary capturesIndex, ArrayBuilder reverseCapturesMap, - List diagnostics, + ArrayBuilder diagnostics, bool isInsert, CancellationToken cancellationToken) { @@ -3416,7 +3412,7 @@ private void CalculateCapturedVariablesMaps( [Out] ArrayBuilder reverseCapturesMap, // {new capture index -> old capture index} [Out] ArrayBuilder newCapturesToClosureScopes, // {new capture index -> new closure scope} [Out] ArrayBuilder oldCapturesToClosureScopes, // {old capture index -> old closure scope} - [Out] List diagnostics, + [Out] ArrayBuilder diagnostics, out bool hasErrors, CancellationToken cancellationToken) { @@ -3678,7 +3674,7 @@ protected virtual void ReportLambdaSignatureRudeEdits( SyntaxNode oldLambdaBody, SemanticModel newModel, SyntaxNode newLambdaBody, - List diagnostics, + ArrayBuilder diagnostics, out bool hasErrors, CancellationToken cancellationToken) { @@ -3780,7 +3776,7 @@ private void ReportStateMachineRudeEdits( Compilation oldCompilation, UpdatedMemberInfo updatedInfo, ISymbol oldMember, - List diagnostics) + ArrayBuilder diagnostics) { if (!updatedInfo.OldHasStateMachineSuspensionPoint) { @@ -3862,23 +3858,23 @@ internal void AnalyzeMemberBodiesSyntax( SourceText newText, ImmutableArray oldActiveStatements, ImmutableArray newActiveStatementSpans, - [Out] ActiveStatement[] newActiveStatements, - [Out] ImmutableArray[] newExceptionRegions, - [Out] List updatedMethods, - [Out] List diagnostics) + [Out] ImmutableArray.Builder newActiveStatements, + [Out] ImmutableArray>.Builder newExceptionRegions, + [Out] ArrayBuilder updatedMethods, + [Out] ArrayBuilder diagnostics) { _abstractEditAndContinueAnalyzer.AnalyzeMemberBodiesSyntax(script, editMap, oldText, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, updatedMethods, diagnostics); } - internal void ReportTopLevelSynctactiveRudeEdits(List diagnostics, EditScript syntacticEdits, Dictionary editMap) + internal void ReportTopLevelSynctactiveRudeEdits(ArrayBuilder diagnostics, EditScript syntacticEdits, Dictionary editMap) => _abstractEditAndContinueAnalyzer.ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); internal void AnalyzeUnchangedDocument( ImmutableArray oldActiveStatements, SourceText newText, SyntaxNode newRoot, - [In, Out] ActiveStatement[] newActiveStatements, - [In, Out] ImmutableArray[] newExceptionRegions) + [In, Out] ImmutableArray.Builder newActiveStatements, + [In, Out] ImmutableArray>.Builder newExceptionRegions) { _abstractEditAndContinueAnalyzer.AnalyzeUnchangedDocument(oldActiveStatements, newText, newRoot, newActiveStatements, newExceptionRegions); } @@ -3887,7 +3883,7 @@ internal BidirectionalMap ComputeMap( Match bodyMatch, ActiveNode[] activeNodes, ref Dictionary? lazyActiveOrMatchedLambdas, - List diagnostics) + ArrayBuilder diagnostics) { return _abstractEditAndContinueAnalyzer.ComputeMap(bodyMatch, activeNodes, ref lazyActiveOrMatchedLambdas, diagnostics); } @@ -3896,7 +3892,7 @@ internal Match ComputeBodyMatch( SyntaxNode oldBody, SyntaxNode newBody, ActiveNode[] activeNodes, - List diagnostics, + ArrayBuilder diagnostics, out bool oldHasStateMachineSuspensionPoint, out bool newHasStateMachineSuspensionPoint) { @@ -3907,10 +3903,10 @@ internal void AnalyzeTrivia( SourceText oldSource, SourceText newSource, Match topMatch, - Dictionary editMap, - [Out] List<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, - [Out] List lineEdits, - [Out] List diagnostics, + IReadOnlyDictionary editMap, + [Out] ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, + [Out] ArrayBuilder lineEdits, + [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { _abstractEditAndContinueAnalyzer.AnalyzeTrivia(oldSource, newSource, topMatch, editMap, triviaEdits, lineEdits, diagnostics, cancellationToken); @@ -3921,12 +3917,12 @@ internal void AnalyzeSemantics( Dictionary editMap, SourceText oldText, ImmutableArray oldActiveStatements, - List<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, - List updatedMembers, + ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)> triviaEdits, + ArrayBuilder updatedMembers, SemanticModel oldModel, SemanticModel newModel, - [Out] List semanticEdits, - [Out] List diagnostics, + [Out] ArrayBuilder semanticEdits, + [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { _abstractEditAndContinueAnalyzer.AnalyzeSemantics(editScript, editMap, oldText, oldActiveStatements, triviaEdits, updatedMembers, oldModel, newModel, semanticEdits, diagnostics, cancellationToken); diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 3d54db99f8c96..5cc2e8975e792 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -10,6 +10,7 @@ Imports Microsoft.CodeAnalysis.Differencing Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -1006,7 +1007,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue DirectCast(syntaxRefs.Single().GetSyntax(), TypeStatementSyntax).Modifiers.Any(SyntaxKind.PartialKeyword) End Function - Protected Overrides Function GetSymbolForEdit(model As SemanticModel, node As SyntaxNode, editKind As EditKind, editMap As Dictionary(Of SyntaxNode, EditKind), cancellationToken As CancellationToken) As ISymbol + Protected Overrides Function GetSymbolForEdit(model As SemanticModel, node As SyntaxNode, editKind As EditKind, editMap As IReadOnlyDictionary(Of SyntaxNode, EditKind), cancellationToken As CancellationToken) As ISymbol ' Avoid duplicate semantic edits - don't return symbols for statements within blocks. Select Case node.Kind() Case SyntaxKind.OperatorStatement, @@ -1059,7 +1060,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ' 1) variable declarator update (an initializer is changes) ' 2) modified identifier update (an array bound changes) ' Handle the first one here. - If editKind = EditKind.Update AndAlso node.Parent.IsKind(SyntaxKind.FieldDeclaration) Then + If editKind = editKind.Update AndAlso node.Parent.IsKind(SyntaxKind.FieldDeclaration) Then ' If multiple fields are defined by this declaration pick the first one. ' We want to analyze the associated initializer just once. Any of the fields is good. node = DirectCast(node, VariableDeclaratorSyntax).Names.First() @@ -1764,7 +1765,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Private Structure EditClassifier Private ReadOnly _analyzer As VisualBasicEditAndContinueAnalyzer - Private ReadOnly _diagnostics As List(Of RudeEditDiagnostic) + Private ReadOnly _diagnostics As ArrayBuilder(Of RudeEditDiagnostic) Private ReadOnly _match As Match(Of SyntaxNode) Private ReadOnly _oldNode As SyntaxNode Private ReadOnly _newNode As SyntaxNode @@ -1772,7 +1773,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Private ReadOnly _span As TextSpan? Public Sub New(analyzer As VisualBasicEditAndContinueAnalyzer, - diagnostics As List(Of RudeEditDiagnostic), + diagnostics As ArrayBuilder(Of RudeEditDiagnostic), oldNode As SyntaxNode, newNode As SyntaxNode, kind As EditKind, @@ -2831,7 +2832,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue #End Region End Structure - Friend Overrides Sub ReportTopLevelSyntacticRudeEdits(diagnostics As List(Of RudeEditDiagnostic), + Friend Overrides Sub ReportTopLevelSyntacticRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), match As Match(Of SyntaxNode), edit As Edit(Of SyntaxNode), editMap As Dictionary(Of SyntaxNode, EditKind)) @@ -2865,7 +2866,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue classifier.ClassifyEdit() End Sub - Friend Overrides Sub ReportMemberUpdateRudeEdits(diagnostics As List(Of RudeEditDiagnostic), newMember As SyntaxNode, span As TextSpan?) + Friend Overrides Sub ReportMemberUpdateRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), newMember As SyntaxNode, span As TextSpan?) Dim classifier = New EditClassifier(Me, diagnostics, Nothing, newMember, EditKind.Update, span:=span) classifier.ClassifyMemberBodyRudeUpdate( @@ -2879,7 +2880,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue #End Region #Region "Semantic Rude Edits" - Friend Overrides Sub ReportInsertedMemberSymbolRudeEdits(diagnostics As List(Of RudeEditDiagnostic), newSymbol As ISymbol) + Friend Overrides Sub ReportInsertedMemberSymbolRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), newSymbol As ISymbol) ' CLR doesn't support adding P/Invokes. ' VB needs to check if the type doesn't contain methods with DllImport attribute. @@ -2892,7 +2893,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End Sub - Private Shared Sub ReportDllImportInsertRudeEdit(diagnostics As List(Of RudeEditDiagnostic), member As ISymbol) + Private Shared Sub ReportDllImportInsertRudeEdit(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), member As ISymbol) If member.IsKind(SymbolKind.Method) AndAlso DirectCast(member, IMethodSymbol).GetDllImportData() IsNot Nothing Then @@ -2939,7 +2940,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return result End Function - Friend Overrides Sub ReportEnclosingExceptionHandlingRudeEdits(diagnostics As List(Of RudeEditDiagnostic), + Friend Overrides Sub ReportEnclosingExceptionHandlingRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), exceptionHandlingEdits As IEnumerable(Of Edit(Of SyntaxNode)), oldStatement As SyntaxNode, newStatementSpan As TextSpan) @@ -3023,7 +3024,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End Sub - Friend Overrides Sub ReportStateMachineSuspensionPointRudeEdits(diagnostics As List(Of RudeEditDiagnostic), oldNode As SyntaxNode, newNode As SyntaxNode) + Friend Overrides Sub ReportStateMachineSuspensionPointRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), oldNode As SyntaxNode, newNode As SyntaxNode) ' TODO: changes around suspension points (foreach, lock, using, etc.) If newNode.IsKind(SyntaxKind.AwaitExpression) Then @@ -3134,7 +3135,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue #Region "Rude Edits around Active Statement" - Friend Overrides Sub ReportOtherRudeEditsAroundActiveStatement(diagnostics As List(Of RudeEditDiagnostic), + Friend Overrides Sub ReportOtherRudeEditsAroundActiveStatement(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), match As Match(Of SyntaxNode), oldActiveStatement As SyntaxNode, newActiveStatement As SyntaxNode, @@ -3165,7 +3166,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Nothing End Function - Private Sub ReportRudeEditsForAncestorsDeclaringInterStatementTemps(diagnostics As List(Of RudeEditDiagnostic), + Private Sub ReportRudeEditsForAncestorsDeclaringInterStatementTemps(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), match As Match(Of SyntaxNode), oldActiveStatement As SyntaxNode, newActiveStatement As SyntaxNode) From dc0040f6816a799566ee3ed959b8f3be25f7f949 Mon Sep 17 00:00:00 2001 From: tmat Date: Tue, 16 Feb 2021 16:07:55 -0800 Subject: [PATCH 6/9] Merge --- .../EditAndContinue/EditAndContinueTestHelpers.cs | 7 +------ .../EditAndContinue/CSharpEditAndContinueAnalyzer.cs | 2 +- .../EditAndContinue/AbstractEditAndContinueAnalyzer.cs | 4 ++-- .../EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb | 2 +- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index c1388c6ad871a..ce4c509d7be6a 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -161,13 +161,8 @@ internal void VerifyLineEdits( var diagnostics = new ArrayBuilder(); var editMap = BuildEditMap(editScript); -<<<<<<< HEAD - var triviaEdits = new List<(SyntaxNode OldNode, SyntaxNode NewNode)>(); - var actualLineEdits = new List(); -======= var triviaEdits = new ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>(); - var actualLineEdits = new ArrayBuilder(); ->>>>>>> b14da14d6d2 (Use ArrayBuilder/ImmutableArray.Builder instead of List/Array) + var actualLineEdits = new ArrayBuilder(); Analyzer.GetTestAccessor().AnalyzeTrivia( oldText, diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 3e843bde1fb54..b6cef0d5793da 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -639,7 +639,7 @@ private static IEnumerable GetChildNodes(SyntaxNode root, SyntaxNode } } - protected override void ReportLocalFunctionsDeclarationRudeEdits(Match bodyMatch, List diagnostics) + protected override void ReportLocalFunctionsDeclarationRudeEdits(ArrayBuilder diagnostics, Match bodyMatch) { var bodyEditsForLambda = bodyMatch.GetTreeEdits(); var editMap = BuildEditMap(bodyEditsForLambda); diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index b21669d6cb0f7..67100c3b98971 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -306,7 +306,7 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind protected abstract void GetStateMachineInfo(SyntaxNode body, out ImmutableArray suspensionPoints, out StateMachineKinds kinds); protected abstract TextSpan GetExceptionHandlingRegion(SyntaxNode node, out bool coversAllChildren); - protected abstract void ReportLocalFunctionsDeclarationRudeEdits(Match bodyMatch, List diagnostics); + protected abstract void ReportLocalFunctionsDeclarationRudeEdits(ArrayBuilder diagnostics, Match bodyMatch); internal abstract void ReportTopLevelSyntacticRudeEdits(ArrayBuilder diagnostics, Match match, Edit edit, Dictionary editMap); internal abstract void ReportEnclosingExceptionHandlingRudeEdits(ArrayBuilder diagnostics, IEnumerable> exceptionHandlingEdits, SyntaxNode oldStatement, TextSpan newStatementSpan); @@ -1368,7 +1368,7 @@ private Match ComputeBodyMatch( if (IsLocalFunction(match.OldRoot) && IsLocalFunction(match.NewRoot)) { - ReportLocalFunctionsDeclarationRudeEdits(match, diagnostics); + ReportLocalFunctionsDeclarationRudeEdits(diagnostics, match); } if (lazyRudeEdits != null) diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 5cc2e8975e792..eecc1443df2a2 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -1159,7 +1159,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function - Protected Overrides Sub ReportLocalFunctionsDeclarationRudeEdits(bodyMatch As Match(Of SyntaxNode), diagnostics As List(Of RudeEditDiagnostic)) + Protected Overrides Sub ReportLocalFunctionsDeclarationRudeEdits(diagnostics As ArrayBuilder(Of RudeEditDiagnostic), bodyMatch As Match(Of SyntaxNode)) ' VB has no local functions so we don't have anything to report End Sub #End Region From 5965d9b7cd725c47ccc7d0d0f290543db9fa965e Mon Sep 17 00:00:00 2001 From: tmat Date: Tue, 16 Feb 2021 17:51:48 -0800 Subject: [PATCH 7/9] Fixups --- .../CSharpEditAndContinueAnalyzerTests.cs | 16 +++--- .../EditAndContinue/StatementEditingTests.vb | 2 +- ...VisualBasicEditAndContinueAnalyzerTests.vb | 10 ++-- .../AbstractEditAndContinueAnalyzer.cs | 19 +++---- .../DocumentAnalysisResults.cs | 53 +++++++++++-------- .../Portable/EditAndContinue/EditSession.cs | 2 +- 6 files changed, 57 insertions(+), 45 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index f4d6715a14bb8..77b8274ff8e09 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -339,7 +339,7 @@ public static void Main() Assert.True(result.HasChanges); Assert.True(result.HasChangesAndErrors); - Assert.True(result.HasChangesAndCompilationErrors); + Assert.True(result.HasChangesAndSyntaxErrors); } [Fact] @@ -365,7 +365,7 @@ public static void Main() Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); } [Fact] @@ -406,7 +406,7 @@ public static void Main() Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); } [Fact] @@ -439,7 +439,7 @@ public static void Main() Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); Assert.True(result.RudeEditErrors.IsEmpty); } @@ -490,7 +490,7 @@ public static void Main() Assert.True(result.HasChanges); Assert.True(result.HasChangesAndErrors); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); Assert.Equal(RudeEditKind.ExperimentalFeaturesEnabled, result.RudeEditErrors.Single().Kind); } } @@ -523,7 +523,7 @@ public static void Main() Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); } [Fact, WorkItem(10683, "https://github.com/dotnet/roslyn/issues/10683")] @@ -568,7 +568,7 @@ public static void Main() // no declaration errors (error in method body is only reported when emitting): Assert.False(result.HasChangesAndErrors); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); } [Fact, WorkItem(10683, "https://github.com/dotnet/roslyn/issues/10683")] @@ -612,7 +612,7 @@ public static void Main(Bar x) // No errors reported: EnC analyzer is resilient against semantic errors. // They will be reported by 1) compiler diagnostic analyzer 2) when emitting delta - if still present. Assert.False(result.HasChangesAndErrors); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); } [Fact] diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb index 9d256d276cbcf..ac7c736e51c27 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb @@ -4574,7 +4574,7 @@ Class C End Class " Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.ChangingLambdaParameters, "a", "lambda")) + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.ChangingLambdaParameters, "Function(a)", VBFeaturesResources.Lambda)) End Sub #End Region diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb index 5ac267a0ec913..45a8acf1da256 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb @@ -504,7 +504,7 @@ End Class Assert.False(result.HasChanges) Assert.False(result.HasChangesAndErrors) - Assert.False(result.HasChangesAndCompilationErrors) + Assert.False(result.HasChangesAndSyntaxErrors) End Using End Function @@ -538,7 +538,7 @@ End Class Assert.False(result.HasChanges) Assert.False(result.HasChangesAndErrors) - Assert.False(result.HasChangesAndCompilationErrors) + Assert.False(result.HasChangesAndSyntaxErrors) End Using End Function @@ -562,7 +562,7 @@ End Class Assert.False(result.HasChanges) Assert.False(result.HasChangesAndErrors) - Assert.False(result.HasChangesAndCompilationErrors) + Assert.False(result.HasChangesAndSyntaxErrors) End Using End Function @@ -598,7 +598,7 @@ End Class ' no declaration errors (error in method body is only reported when emitting) Assert.False(result.HasChangesAndErrors) - Assert.False(result.HasChangesAndCompilationErrors) + Assert.False(result.HasChangesAndSyntaxErrors) End Using End Function @@ -634,7 +634,7 @@ End Class ' No errors reported: EnC analyzer is resilient against semantic errors. ' They will be reported by 1) compiler diagnostic analyzer 2) when emitting delta - if still present. Assert.False(result.HasChangesAndErrors) - Assert.False(result.HasChangesAndCompilationErrors) + Assert.False(result.HasChangesAndSyntaxErrors) End Using End Function diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 67100c3b98971..e68a5f0ed7e7e 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -431,7 +431,7 @@ public async Task AnalyzeDocumentAsync( // Bail, since we can't do syntax diffing on broken trees (it would not produce useful results anyways). // If we needed to do so for some reason, we'd need to harden the syntax tree comparers. DocumentAnalysisResults.Log.Write("{0}: syntax errors", document.Name); - return DocumentAnalysisResults.CompilationErrors(hasChanges); + return DocumentAnalysisResults.SyntaxErrors(document.Id, hasChanges); } var newActiveStatements = ImmutableArray.CreateBuilder(baseActiveStatements.Length); @@ -455,7 +455,7 @@ public async Task AnalyzeDocumentAsync( newExceptionRegions); DocumentAnalysisResults.Log.Write("{0}: unchanged", document.Name); - return DocumentAnalysisResults.Unchanged(newActiveStatements.MoveToImmutable(), newExceptionRegions.MoveToImmutable()); + return DocumentAnalysisResults.Unchanged(document.Id, newActiveStatements.MoveToImmutable(), newExceptionRegions.MoveToImmutable()); } // Disallow modification of a file with experimental features enabled. @@ -464,7 +464,7 @@ public async Task AnalyzeDocumentAsync( { DocumentAnalysisResults.Log.Write("{0}: experimental features enabled", document.Name); - return DocumentAnalysisResults.Errors(ImmutableArray.Create( + return DocumentAnalysisResults.Errors(document.Id, activeStatementsOpt: default, ImmutableArray.Create( new RudeEditDiagnostic(RudeEditKind.ExperimentalFeaturesEnabled, default))); } @@ -500,7 +500,7 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0} syntactic rude edits, first: '{1}'", diagnostics.Count, document.FilePath); - return DocumentAnalysisResults.Errors(diagnostics.ToImmutable()); + return DocumentAnalysisResults.Errors(document.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable()); } // Disallow addition of a new file. @@ -509,7 +509,7 @@ public async Task AnalyzeDocumentAsync( if (oldDocument == null) { DocumentAnalysisResults.Log.Write("A new file added: {0}", document.Name); - return DocumentAnalysisResults.Errors(ImmutableArray.Create( + return DocumentAnalysisResults.Errors(document.Id, newActiveStatements.MoveToImmutable(), ImmutableArray.Create( new RudeEditDiagnostic(RudeEditKind.InsertFile, default))); } @@ -533,7 +533,7 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0} trivia rude edits, first: {1}@{2}", diagnostics.Count, document.FilePath, diagnostics.First().Span.Start); - return DocumentAnalysisResults.Errors(diagnostics.ToImmutable()); + return DocumentAnalysisResults.Errors(document.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable()); } cancellationToken.ThrowIfCancellationRequested(); @@ -565,20 +565,21 @@ public async Task AnalyzeDocumentAsync( if (diagnostics.Count > 0) { DocumentAnalysisResults.Log.Write("{0}@{1}: semantic rude edit ({2} total)", document.FilePath, diagnostics.First().Span.Start, diagnostics.Count); - return DocumentAnalysisResults.Errors(diagnostics.ToImmutable()); + return DocumentAnalysisResults.Errors(document.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable()); } semanticEdits = semanticEditsBuilder.ToImmutable(); } return new DocumentAnalysisResults( + document.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable(), semanticEdits, newExceptionRegions.MoveToImmutable(), lineEdits.ToImmutable(), hasChanges: true, - hasCompilationErrors: false); + hasSyntaxErrors: false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e)) { @@ -590,7 +591,7 @@ public async Task AnalyzeDocumentAsync( new RudeEditDiagnostic(RudeEditKind.SourceFileTooBig, span: default, arguments: new[] { document.FilePath }) : new RudeEditDiagnostic(RudeEditKind.InternalError, span: default, arguments: new[] { document.FilePath, e.ToString() }); - return DocumentAnalysisResults.Errors(ImmutableArray.Create(diagnostic)); + return DocumentAnalysisResults.Errors(document.Id, activeStatementsOpt: default, ImmutableArray.Create(diagnostic)); } } diff --git a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs index da8fd8af2c2f8..88bb455a730ff 100644 --- a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -16,25 +17,31 @@ internal sealed class DocumentAnalysisResults internal static readonly TraceLog Log = new(256, "EnC"); /// - /// Spans of active statements in the document, or null if the document has compilation errors or rude edits. + /// The id of the document the results are calculated for. + /// + public DocumentId DocumentId { get; } + + /// + /// Spans of active statements in the document, or null if the document has syntax errors. + /// Calculated even in presence of rude edits so that the active statements can be rendered in the editor. /// public ImmutableArray ActiveStatements { get; } /// - /// Diagnostics for rude edits in the document, or empty if the document is unchanged or has compilation errors. + /// Diagnostics for rude edits in the document, or empty if the document is unchanged or has syntax errors. /// If the compilation has semantic errors only syntactic rude edits are calculated. /// public ImmutableArray RudeEditErrors { get; } /// - /// Edits made in the document, or null if the document is unchanged, has compilation errors or rude edits. + /// Edits made in the document, or null if the document is unchanged, has syntax errors or rude edits. /// public ImmutableArray SemanticEdits { get; } /// /// Exception regions -- spans of catch and finally handlers that surround the active statements. /// - /// Null if the document has compilation errors or rude edits. + /// Null if the document has syntax errors or rude edits. /// /// /// Null if there are any rude edit diagnostics. @@ -55,7 +62,7 @@ internal sealed class DocumentAnalysisResults public ImmutableArray> ExceptionRegions { get; } /// - /// Line edits in the document, or null if the document has compilation errors or rude edits. + /// Line edits in the document, or null if the document has syntax errors or rude edits. /// /// /// Sorted by @@ -63,9 +70,9 @@ internal sealed class DocumentAnalysisResults public ImmutableArray LineEdits { get; } /// - /// Document contains erros that block EnC analysis. + /// Document contains errors that block EnC analysis. /// - public readonly bool HasCompilationErrors; + public readonly bool HasSyntaxErrors; /// /// Document contains changes. @@ -73,17 +80,18 @@ internal sealed class DocumentAnalysisResults public readonly bool HasChanges; public DocumentAnalysisResults( + DocumentId documentId, ImmutableArray activeStatementsOpt, ImmutableArray rudeEdits, ImmutableArray semanticEditsOpt, ImmutableArray> exceptionRegionsOpt, ImmutableArray lineEditsOpt, bool hasChanges, - bool hasCompilationErrors) + bool hasSyntaxErrors) { Debug.Assert(!rudeEdits.IsDefault); - if (hasCompilationErrors) + if (hasSyntaxErrors) { Debug.Assert(activeStatementsOpt.IsDefault); Debug.Assert(semanticEditsOpt.IsDefault); @@ -94,7 +102,6 @@ public DocumentAnalysisResults( { if (rudeEdits.Length > 0) { - Debug.Assert(activeStatementsOpt.IsDefault); Debug.Assert(semanticEditsOpt.IsDefault); Debug.Assert(exceptionRegionsOpt.IsDefault); Debug.Assert(lineEditsOpt.IsDefault); @@ -119,52 +126,56 @@ public DocumentAnalysisResults( Debug.Assert(exceptionRegionsOpt.Length == activeStatementsOpt.Length); } + DocumentId = documentId; RudeEditErrors = rudeEdits; SemanticEdits = semanticEditsOpt; ActiveStatements = activeStatementsOpt; ExceptionRegions = exceptionRegionsOpt; LineEdits = lineEditsOpt; - HasCompilationErrors = hasCompilationErrors; + HasSyntaxErrors = hasSyntaxErrors; HasChanges = hasChanges; } public bool HasChangesAndErrors - => HasChanges && (HasCompilationErrors || !RudeEditErrors.IsEmpty); + => HasChanges && (HasSyntaxErrors || !RudeEditErrors.IsEmpty); - public bool HasChangesAndCompilationErrors - => HasChanges && HasCompilationErrors; + public bool HasChangesAndSyntaxErrors + => HasChanges && HasSyntaxErrors; public bool HasSignificantValidChanges => HasChanges && (!SemanticEdits.IsDefaultOrEmpty || !LineEdits.IsDefaultOrEmpty); - public static DocumentAnalysisResults CompilationErrors(bool hasChanges) + public static DocumentAnalysisResults SyntaxErrors(DocumentId documentId, bool hasChanges) => new( + documentId, activeStatementsOpt: default, rudeEdits: ImmutableArray.Empty, semanticEditsOpt: default, exceptionRegionsOpt: default, lineEditsOpt: default, hasChanges, - hasCompilationErrors: true); + hasSyntaxErrors: true); - public static DocumentAnalysisResults Errors(ImmutableArray rudeEdits) + public static DocumentAnalysisResults Errors(DocumentId documentId, ImmutableArray activeStatementsOpt, ImmutableArray rudeEdits) => new( - activeStatementsOpt: default, + documentId, + activeStatementsOpt, rudeEdits, semanticEditsOpt: default, exceptionRegionsOpt: default, lineEditsOpt: default, hasChanges: true, - hasCompilationErrors: false); + hasSyntaxErrors: false); - public static DocumentAnalysisResults Unchanged(ImmutableArray activeStatements, ImmutableArray> exceptionRegions) + public static DocumentAnalysisResults Unchanged(DocumentId documentId, ImmutableArray activeStatements, ImmutableArray> exceptionRegions) => new( + documentId, activeStatements, rudeEdits: ImmutableArray.Empty, semanticEditsOpt: ImmutableArray.Empty, exceptionRegions, lineEditsOpt: ImmutableArray.Empty, hasChanges: false, - hasCompilationErrors: false); + hasSyntaxErrors: false); } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index d77750d169eaa..565f3909bcb36 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -510,7 +510,7 @@ private static async Task GetProjectAnalysisSymmaryAsync } // rude edit detection wasn't completed due to errors in compilation: - if (result.HasChangesAndCompilationErrors) + if (result.HasChangesAndSyntaxErrors) { return ProjectAnalysisSummary.CompilationErrors; } From eae115303431fbbf08111c31e69876cc66826314 Mon Sep 17 00:00:00 2001 From: tmat Date: Tue, 16 Feb 2021 19:19:42 -0800 Subject: [PATCH 8/9] Check base project when reusing cached analysis results --- .../CSharpEditAndContinueAnalyzerTests.cs | 24 ++-- ...VisualBasicEditAndContinueAnalyzerTests.vb | 14 +-- .../AbstractEditAndContinueAnalyzer.cs | 5 +- .../EditAndContinueDocumentAnalysesCache.cs | 95 ++++++++++---- .../EditAndContinueWorkspaceService.cs | 20 ++- .../Portable/EditAndContinue/EditSession.cs | 118 ++++++++++-------- .../IEditAndContinueAnalyzer.cs | 2 +- 7 files changed, 174 insertions(+), 104 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index 77b8274ff8e09..e1d2f3da6f9ce 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -289,7 +289,7 @@ public static void Main() var baseActiveStatements = ImmutableArray.Create(ActiveStatementsDescription.CreateActiveStatement(ActiveStatementFlags.IsLeafFrame, oldStatementSpan, DocumentId.CreateNewId(ProjectId.CreateNewId()))); var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray.Empty, CancellationToken.None); Assert.True(result.HasChanges); Assert.True(result.SemanticEdits[0].PreserveLocalVariables); @@ -335,7 +335,7 @@ public static void Main() var baseActiveStatements = ImmutableArray.Create(); var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); Assert.True(result.HasChanges); Assert.True(result.HasChangesAndErrors); @@ -361,7 +361,7 @@ public static void Main() var baseActiveStatements = ImmutableArray.Create(); var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, oldDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray.Empty, CancellationToken.None); Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); @@ -402,7 +402,7 @@ public static void Main() var baseActiveStatements = ImmutableArray.Create(); var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); @@ -435,7 +435,7 @@ public static void Main() var baseActiveStatements = ImmutableArray.Create(); var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, oldDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray.Empty, CancellationToken.None); Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); @@ -486,7 +486,7 @@ public static void Main() var baseActiveStatements = ImmutableArray.Create(); var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); Assert.True(result.HasChanges); Assert.True(result.HasChangesAndErrors); @@ -519,7 +519,7 @@ public static void Main() var baseActiveStatements = ImmutableArray.Create(); var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, oldDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray.Empty, CancellationToken.None); Assert.False(result.HasChanges); Assert.False(result.HasChangesAndErrors); @@ -562,7 +562,7 @@ public static void Main() var baseActiveStatements = ImmutableArray.Create(); var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); Assert.True(result.HasChanges); @@ -605,7 +605,7 @@ public static void Main(Bar x) var baseActiveStatements = ImmutableArray.Create(); var analyzer = new CSharpEditAndContinueAnalyzer(); - var result = await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray.Empty, CancellationToken.None); Assert.True(result.HasChanges); @@ -662,7 +662,7 @@ public class D foreach (var changedDocumentId in changedDocuments) { - result.Add(await analyzer.AnalyzeDocumentAsync(oldProject.GetDocument(changedDocumentId), baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray.Empty, CancellationToken.None)); + result.Add(await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray.Empty, CancellationToken.None)); } Assert.True(result.IsSingle()); @@ -711,7 +711,7 @@ public static void Main() foreach (var changedDocumentId in changedDocuments) { - result.Add(await analyzer.AnalyzeDocumentAsync(oldProject.GetDocument(changedDocumentId), baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray.Empty, CancellationToken.None)); + result.Add(await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray.Empty, CancellationToken.None)); } Assert.True(result.IsSingle()); @@ -746,7 +746,7 @@ public async Task AnalyzeDocumentAsync_InternalError(bool outOfMemory) } }); - var result = await analyzer.AnalyzeDocumentAsync(oldProject.GetDocument(documentId), baseActiveStatements, newDocument, ImmutableArray.Empty, CancellationToken.None); + var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray.Empty, CancellationToken.None); var expectedDiagnostic = outOfMemory ? $"ENC0089: {string.Format(FeaturesResources.Modifying_source_file_will_prevent_the_debug_session_from_continuing_because_the_file_is_too_big, "src.cs")}" : diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb index 45a8acf1da256..0bb91b616e648 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/VisualBasicEditAndContinueAnalyzerTests.vb @@ -470,7 +470,7 @@ End Class Dim analyzer = New VisualBasicEditAndContinueAnalyzer() Dim baseActiveStatements = ImmutableArray.Create(ActiveStatementsDescription.CreateActiveStatement(ActiveStatementFlags.IsLeafFrame, oldStatementSpan, DocumentId.CreateNewId(ProjectId.CreateNewId()))) - Dim result = Await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) Assert.True(result.HasChanges) Assert.True(result.SemanticEdits(0).PreserveLocalVariables) @@ -500,7 +500,7 @@ End Class Dim oldDocument = oldProject.Documents.Single() Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() Dim analyzer = New VisualBasicEditAndContinueAnalyzer() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, oldDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) Assert.False(result.HasChanges) Assert.False(result.HasChangesAndErrors) @@ -534,7 +534,7 @@ End Class Dim analyzer = New VisualBasicEditAndContinueAnalyzer() Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) Assert.False(result.HasChanges) Assert.False(result.HasChangesAndErrors) @@ -558,7 +558,7 @@ End Class Dim oldDocument = oldProject.Documents.Single() Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() Dim analyzer = New VisualBasicEditAndContinueAnalyzer() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, oldDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, oldDocument, ImmutableArray(Of TextSpan).Empty, CancellationToken.None) Assert.False(result.HasChanges) Assert.False(result.HasChangesAndErrors) @@ -594,7 +594,7 @@ End Class Dim analyzer = New VisualBasicEditAndContinueAnalyzer() Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldDocument, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) ' no declaration errors (error in method body is only reported when emitting) Assert.False(result.HasChangesAndErrors) @@ -627,7 +627,7 @@ End Class Dim analyzer = New VisualBasicEditAndContinueAnalyzer() Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() - Dim result = Await analyzer.AnalyzeDocumentAsync(oldSolution.GetDocument(documentId), baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) + Dim result = Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newSolution.GetDocument(documentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None) Assert.True(result.HasChanges) @@ -696,7 +696,7 @@ End Class Dim result = New List(Of DocumentAnalysisResults)() Dim baseActiveStatements = ImmutableArray.Create(Of ActiveStatement)() For Each changedDocumentId In changedDocuments - result.Add(Await analyzer.AnalyzeDocumentAsync(oldProject.GetDocument(changedDocumentId), baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None)) + result.Add(Await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newProject.GetDocument(changedDocumentId), ImmutableArray(Of TextSpan).Empty, CancellationToken.None)) Next Assert.True(result.IsSingle()) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index e68a5f0ed7e7e..f6c233dbeac5d 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -372,7 +372,7 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind #region Document Analysis public async Task AnalyzeDocumentAsync( - Document? oldDocument, + Project baseProject, ImmutableArray baseActiveStatements, Document document, ImmutableArray newActiveStatementSpans, @@ -381,8 +381,6 @@ public async Task AnalyzeDocumentAsync( DocumentAnalysisResults.Log.Write("Analyzing document {0}", document.Name); Debug.Assert(!newActiveStatementSpans.IsDefault); - Debug.Assert(oldDocument == null || oldDocument.SupportsSyntaxTree); - Debug.Assert(oldDocument == null || oldDocument.SupportsSemanticModel); Debug.Assert(document.SupportsSyntaxTree); Debug.Assert(document.SupportsSemanticModel); @@ -394,6 +392,7 @@ public async Task AnalyzeDocumentAsync( SyntaxNode oldRoot; SourceText oldText; + var oldDocument = baseProject.GetDocument(document.Id); if (oldDocument != null) { oldTree = await oldDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs index 621bcd0a189dc..88d9b32080603 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs @@ -5,9 +5,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Text; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -21,7 +22,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue internal sealed class EditAndContinueDocumentAnalysesCache { private readonly object _guard = new(); - private readonly Dictionary results)> _analyses = new(); + private readonly Dictionary results, Project baseProject, Document document, ImmutableArray activeStatementSpans)> _analyses = new(); private readonly AsyncLazy _baseActiveStatements; public EditAndContinueDocumentAnalysesCache(AsyncLazy baseActiveStatements) @@ -29,53 +30,102 @@ public EditAndContinueDocumentAnalysesCache(AsyncLazy baseA _baseActiveStatements = baseActiveStatements; } - public AsyncLazy GetDocumentAnalysis(Document? baseDocument, Document document, ImmutableArray activeStatementSpans) + public async ValueTask> GetActiveStatementsAsync(Document baseDocument, Document document, ImmutableArray activeStatementSpans, CancellationToken cancellationToken) { - lock (_guard) + try { - return GetDocumentAnalysisNoLock(baseDocument, document, activeStatementSpans); + var results = await GetDocumentAnalysisAsync(baseDocument.Project, document, activeStatementSpans, cancellationToken).ConfigureAwait(false); + return results.ActiveStatements; + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; } } - public ImmutableArray<(Document, AsyncLazy)> GetDocumentAnalyses(ArrayBuilder<(Document? oldDocument, Document newDocument, ImmutableArray newActiveStatementSpans)> builder) + public async ValueTask> GetDocumentAnalysesAsync( + Project oldProject, + IReadOnlyList<(Document newDocument, ImmutableArray newActiveStatementSpans)> documentInfos, + CancellationToken cancellationToken) { - if (builder.IsEmpty()) + try { - return ImmutableArray<(Document, AsyncLazy)>.Empty; - } + if (documentInfos.IsEmpty()) + { + return ImmutableArray.Empty; + } + + var tasks = documentInfos.Select(info => Task.Run(() => GetDocumentAnalysisAsync(oldProject, info.newDocument, info.newActiveStatementSpans, cancellationToken).AsTask(), cancellationToken)); + var allResults = await Task.WhenAll(tasks).ConfigureAwait(false); - lock (_guard) + return allResults.ToImmutableArray(); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) { - return builder.SelectAsArray(change => (change.newDocument, GetDocumentAnalysisNoLock(change.oldDocument, change.newDocument, change.newActiveStatementSpans))); + throw ExceptionUtilities.Unreachable; } } /// /// Returns a document analysis or kicks off a new one if one is not available for the specified document snapshot. /// - /// Base document or null if the document did not exist in the baseline. + /// Base project. /// Document snapshot to analyze. - private AsyncLazy GetDocumentAnalysisNoLock(Document? baseDocument, Document document, ImmutableArray activeStatementSpans) + /// Active statement spans tracked by the editor. + public async ValueTask GetDocumentAnalysisAsync(Project baseProject, Document document, ImmutableArray activeStatementSpans, CancellationToken cancellationToken) { - if (_analyses.TryGetValue(document.Id, out var analysis) && analysis.document == document) + try { - return analysis.results; + AsyncLazy lazyResults; + + lock (_guard) + { + lazyResults = GetDocumentAnalysisNoLock(baseProject, document, activeStatementSpans); + } + + return await lazyResults.GetValueAsync(cancellationToken).ConfigureAwait(false); } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + } - var analyzer = document.Project.LanguageServices.GetRequiredService(); + private AsyncLazy GetDocumentAnalysisNoLock(Project baseProject, Document document, ImmutableArray activeStatementSpans) + { + // Do not reuse an analysis of the document unless its snasphot is exactly the same as was used to calculate the results. + // Note that comparing document snapshots in effect compares the entire solution snapshots (when another document is changed a new solution snapshot is created + // that creates new document snapshots for all queried documents). + // Also check the base project snapshot since the analysis uses semantic information from the base project as well. + // + // It would be possible to reuse analysis results of documents whose content does not change in between two solution snapshots. + // However, we'd need rather sophisticated caching logic. The smantic analysis gathers information from other documents when + // calculating results for a specific document. In some cases it's easy to record the set of documents the analysis depends on. + // For example, when analyzing a partial class we can record all documents its declaration spans. However, in other cases the analysis + // checks for absence of a top-level type symbol. Adding a symbol to any document thus invalidates such analysis. It'd be possible + // to keep track of which type symbols an analysis is conditional upon, if it was worth the extra complexity. + if (_analyses.TryGetValue(document.Id, out var analysis) && + analysis.baseProject == baseProject && + analysis.document == document && + analysis.activeStatementSpans.SequenceEqual(activeStatementSpans)) + { + return analysis.results; + } var lazyResults = new AsyncLazy( asynchronousComputeFunction: async cancellationToken => { try { + var analyzer = document.Project.LanguageServices.GetRequiredService(); + var baseActiveStatements = await _baseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); if (!baseActiveStatements.DocumentMap.TryGetValue(document.Id, out var documentBaseActiveStatements)) { documentBaseActiveStatements = ImmutableArray.Empty; } - return await analyzer.AnalyzeDocumentAsync(baseDocument, documentBaseActiveStatements, document, activeStatementSpans, cancellationToken).ConfigureAwait(false); + return await analyzer.AnalyzeDocumentAsync(baseProject, documentBaseActiveStatements, document, activeStatementSpans, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) { @@ -84,9 +134,12 @@ private AsyncLazy GetDocumentAnalysisNoLock(Document? b }, cacheResult: true); - // TODO: this will replace potentially running analysis with another one. - // Consider cancelling the replaced one. - _analyses[document.Id] = (document, lazyResults); + // Previous results for this document id are discarded as they are no longer relevant. + // The only relevant analysis is for the latest base and document snapshots. + // Note that the base snapshot may evolve if documents are dicovered that were previously + // out-of-sync with the compiled outputs and are now up-to-date. + _analyses[document.Id] = (lazyResults, baseProject, document, activeStatementSpans); + return lazyResults; } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs index e2529be3e5602..49804e136d85c 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs @@ -213,8 +213,17 @@ public async ValueTask> GetDocumentDiagnosticsAsync(D return GetRunModeDocumentDiagnostics(document, newSyntaxTree, changedSpans); } + var oldProject = oldDocument?.Project ?? debuggingSession.LastCommittedSolution.GetProject(project.Id); + if (oldProject == null) + { + // TODO https://github.com/dotnet/roslyn/issues/1204: + // Project deleted (shouldn't happen since Project System does not allow removing projects while debugging) or + // was not loaded when the debugging session started. + return ImmutableArray.Empty; + } + var documentActiveStatementSpans = await activeStatementSpanProvider(cancellationToken).ConfigureAwait(false); - var analysis = await editSession.GetDocumentAnalysis(oldDocument, document, documentActiveStatementSpans).GetValueAsync(cancellationToken).ConfigureAwait(false); + var analysis = await editSession.Analyses.GetDocumentAnalysisAsync(oldProject, document, documentActiveStatementSpans, cancellationToken).ConfigureAwait(false); if (analysis.HasChanges) { // Once we detected a change in a document let the debugger know that the corresponding loaded module @@ -484,13 +493,13 @@ public void DiscardSolutionUpdate() } var documentActiveStatementSpans = await activeStatementSpanProvider(cancellationToken).ConfigureAwait(false); - var analysis = await editSession.GetDocumentAnalysis(baseDocument, document, documentActiveStatementSpans).GetValueAsync(cancellationToken).ConfigureAwait(false); - if (analysis.ActiveStatements.IsDefault) + var activeStatements = await editSession.Analyses.GetActiveStatementsAsync(baseDocument, document, documentActiveStatementSpans, cancellationToken).ConfigureAwait(false); + if (activeStatements.IsDefault) { return default; } - return analysis.ActiveStatements.SelectAsArray(s => (s.Span, s.Flags)); + return activeStatements.SelectAsArray(s => (s.Span, s.Flags)); } public async ValueTask GetCurrentActiveStatementPositionAsync(Solution solution, SolutionActiveStatementSpanProvider activeStatementSpanProvider, ManagedInstructionId instructionId, CancellationToken cancellationToken) @@ -529,8 +538,7 @@ public void DiscardSolutionUpdate() } var activeStatementSpans = await activeStatementSpanProvider(primaryDocument.Id, cancellationToken).ConfigureAwait(false); - var documentAnalysis = await editSession.GetDocumentAnalysis(oldPrimaryDocument, primaryDocument, activeStatementSpans).GetValueAsync(cancellationToken).ConfigureAwait(false); - var currentActiveStatements = documentAnalysis.ActiveStatements; + var currentActiveStatements = await editSession.Analyses.GetActiveStatementsAsync(oldPrimaryDocument, primaryDocument, activeStatementSpans, cancellationToken).ConfigureAwait(false); if (currentActiveStatements.IsDefault) { // The document has syntax errors. diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index 565f3909bcb36..51a0f81fa8767 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -43,7 +43,7 @@ internal sealed class EditSession : IDisposable /// /// Cache of document EnC analyses. /// - private readonly EditAndContinueDocumentAnalysesCache _analysesCache; + internal readonly EditAndContinueDocumentAnalysesCache Analyses; /// /// A is added whenever EnC analyzer reports @@ -68,7 +68,7 @@ internal EditSession( _nonRemappableRegions = debuggingSession.NonRemappableRegions; BaseActiveStatements = new AsyncLazy(cancellationToken => GetBaseActiveStatementsAsync(cancellationToken), cacheResult: true); - _analysesCache = new EditAndContinueDocumentAnalysesCache(BaseActiveStatements); + Analyses = new EditAndContinueDocumentAnalysesCache(BaseActiveStatements); } internal PendingSolutionUpdate? Test_GetPendingSolutionUpdate() => _pendingUpdate; @@ -353,11 +353,14 @@ private static async Task PopulateChangedAndAddedDocumentsAsync(CommittedSolutio } } - private async Task<(ImmutableArray<(Document Document, AsyncLazy Results)>, ImmutableArray DocumentDiagnostics)> AnalyzeDocumentsAsync( - ArrayBuilder changedOrAddedDocuments, SolutionActiveStatementSpanProvider newDocumentActiveStatementSpanProvider, CancellationToken cancellationToken) + private async Task<(ImmutableArray results, ImmutableArray diagnostics)> AnalyzeDocumentsAsync( + Project newProject, + ArrayBuilder changedOrAddedDocuments, + SolutionActiveStatementSpanProvider newDocumentActiveStatementSpanProvider, + CancellationToken cancellationToken) { using var _1 = ArrayBuilder.GetInstance(out var documentDiagnostics); - using var _2 = ArrayBuilder<(Document? Old, Document New, ImmutableArray NewActiveStatementSpans)>.GetInstance(out var builder); + using var _2 = ArrayBuilder<(Document newDocument, ImmutableArray newActiveStatementSpans)>.GetInstance(out var builder); foreach (var newDocument in changedOrAddedDocuments) { @@ -383,7 +386,7 @@ private static async Task PopulateChangedAndAddedDocumentsAsync(CommittedSolutio // These are the locations of the spans tracked by the editor from the base document to the current snapshot. var activeStatementSpans = await newDocumentActiveStatementSpanProvider(newDocument.Id, cancellationToken).ConfigureAwait(false); - builder.Add((oldDocument, newDocument, activeStatementSpans)); + builder.Add((newDocument, activeStatementSpans)); break; default: @@ -391,12 +394,20 @@ private static async Task PopulateChangedAndAddedDocumentsAsync(CommittedSolutio } } - return (_analysesCache.GetDocumentAnalyses(builder), documentDiagnostics.ToImmutable()); + // The base project may have been updated as documents were brought up-to-date in the committed solution. + // Get the latest available snapshot of the base project from the committed solution and use it for analyses of all documents, + // so that we use a single compilation for the base project (for efficiency). + // Note that some other request might be updating documents in the committed solution that were not changed (not in changedOrAddedDocuments) + // but are not up-to-date. These documents do not have impact on the analysis unless we read semantic information + // from the project compilation. When reading such information we need to be aware of its potential incompleteness + // and consult the compiler output binary (see https://github.com/dotnet/roslyn/issues/51261). + var oldProject = DebuggingSession.LastCommittedSolution.GetProject(newProject.Id); + Contract.ThrowIfNull(oldProject); + + var analyses = await Analyses.GetDocumentAnalysesAsync(oldProject, builder, cancellationToken).ConfigureAwait(false); + return (analyses, documentDiagnostics.ToImmutable()); } - public AsyncLazy GetDocumentAnalysis(Document? baseDocument, Document document, ImmutableArray activeStatementSpans) - => _analysesCache.GetDocumentAnalysis(baseDocument, document, activeStatementSpans); - internal ImmutableArray GetDocumentsWithReportedDiagnostics() { lock (_documentsWithReportedDiagnosticsGuard) @@ -464,7 +475,7 @@ from documentId in solution.GetDocumentIdsWithFilePath(sourceFilePath) continue; } - var (changedDocumentAnalyses, documentDiagnostics) = await AnalyzeDocumentsAsync(changedOrAddedDocuments, solutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false); + var (changedDocumentAnalyses, documentDiagnostics) = await AnalyzeDocumentsAsync(project, changedOrAddedDocuments, solutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false); if (documentDiagnostics.Any()) { EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: out-of-sync documents present (diagnostic: '{2}')", @@ -476,7 +487,7 @@ from documentId in solution.GetDocumentIdsWithFilePath(sourceFilePath) return true; } - var projectSummary = await GetProjectAnalysisSymmaryAsync(changedDocumentAnalyses, cancellationToken).ConfigureAwait(false); + var projectSummary = GetProjectAnalysisSymmary(changedDocumentAnalyses); if (projectSummary != ProjectAnalysisSummary.NoChanges) { EditAndContinueWorkspaceService.Log.Write("EnC state of '{0}' [0x{1:X8}] queried: {2}", project.Id.DebugName, project.Id, projectSummary); @@ -492,37 +503,33 @@ from documentId in solution.GetDocumentIdsWithFilePath(sourceFilePath) } } - private static async Task GetProjectAnalysisSymmaryAsync( - ImmutableArray<(Document Document, AsyncLazy Results)> documentAnalyses, - CancellationToken cancellationToken) + private static ProjectAnalysisSummary GetProjectAnalysisSymmary(ImmutableArray documentAnalyses) { var hasChanges = false; var hasSignificantValidChanges = false; foreach (var analysis in documentAnalyses) { - var result = await analysis.Results.GetValueAsync(cancellationToken).ConfigureAwait(false); - // skip documents that actually were not changed: - if (!result.HasChanges) + if (!analysis.HasChanges) { continue; } // rude edit detection wasn't completed due to errors in compilation: - if (result.HasChangesAndSyntaxErrors) + if (analysis.HasChangesAndSyntaxErrors) { return ProjectAnalysisSummary.CompilationErrors; } // rude edits detected: - if (!result.RudeEditErrors.IsEmpty) + if (!analysis.RudeEditErrors.IsEmpty) { return ProjectAnalysisSummary.RudeEdits; } hasChanges = true; - hasSignificantValidChanges |= result.HasSignificantValidChanges; + hasSignificantValidChanges |= analysis.HasSignificantValidChanges; } if (!hasChanges) @@ -539,58 +546,53 @@ private static async Task GetProjectAnalysisSymmaryAsync return ProjectAnalysisSummary.ValidChanges; } - private static async Task GetProjectChangesAsync(ImmutableArray<(Document Document, AsyncLazy Results)> changedDocumentAnalyses, CancellationToken cancellationToken) + private static ProjectChanges GetProjectChanges(Compilation newCompilation, ImmutableArray changedDocumentAnalyses, CancellationToken cancellationToken) { try { - var allEdits = ArrayBuilder.GetInstance(); - var allLineEdits = ArrayBuilder<(DocumentId, ImmutableArray)>.GetInstance(); - var activeStatementsInChangedDocuments = ArrayBuilder<(DocumentId, ImmutableArray, ImmutableArray>)>.GetInstance(); - using var _ = ArrayBuilder.GetInstance(out var allAddedSymbols); + using var _1 = ArrayBuilder.GetInstance(out var allEdits); + using var _2 = ArrayBuilder<(DocumentId, ImmutableArray)>.GetInstance(out var allLineEdits); + using var _3 = ArrayBuilder<(DocumentId, ImmutableArray, ImmutableArray>)>.GetInstance(out var activeStatementsInChangedDocuments); + using var _4 = ArrayBuilder.GetInstance(out var allAddedSymbols); - foreach (var (document, asyncResult) in changedDocumentAnalyses) + foreach (var analysis in changedDocumentAnalyses) { - var result = await asyncResult.GetValueAsync(cancellationToken).ConfigureAwait(false); - - if (!result.HasSignificantValidChanges) + if (!analysis.HasSignificantValidChanges) { continue; } // we shouldn't be asking for deltas in presence of errors: - Debug.Assert(!result.HasChangesAndErrors); + Contract.ThrowIfTrue(analysis.HasChangesAndErrors); - allEdits.AddRange(result.SemanticEdits); + allEdits.AddRange(analysis.SemanticEdits); - if (!result.HasChangesAndErrors) + foreach (var edit in analysis.SemanticEdits) { - foreach (var edit in result.SemanticEdits) + if (edit.Kind == SemanticEditKind.Insert) { - if (edit.Kind == SemanticEditKind.Insert) - { - allAddedSymbols.Add(edit.NewSymbol!); - } + allAddedSymbols.Add(edit.NewSymbol!); } } - if (result.LineEdits.Length > 0) + var documentId = analysis.DocumentId; + + if (analysis.LineEdits.Length > 0) { - allLineEdits.Add((document.Id, result.LineEdits)); + allLineEdits.Add((documentId, analysis.LineEdits)); } - if (result.ActiveStatements.Length > 0) + if (analysis.ActiveStatements.Length > 0) { - activeStatementsInChangedDocuments.Add((document.Id, result.ActiveStatements, result.ExceptionRegions)); + activeStatementsInChangedDocuments.Add((documentId, analysis.ActiveStatements, analysis.ExceptionRegions)); } } - var allAddedSymbolResult = allAddedSymbols.ToImmutableHashSet(); - return new ProjectChanges( - allEdits.ToImmutableAndFree(), - allLineEdits.ToImmutableAndFree(), - allAddedSymbolResult, - activeStatementsInChangedDocuments.ToImmutableAndFree()); + allEdits.ToImmutable(), + allLineEdits.ToImmutable(), + allAddedSymbols.ToImmutableHashSet(), + activeStatementsInChangedDocuments.ToImmutable()); } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) { @@ -598,6 +600,7 @@ private static async Task GetProjectChangesAsync(ImmutableArray< } } + public async Task EmitSolutionUpdateAsync(Solution solution, SolutionActiveStatementSpanProvider solutionActiveStatementSpanProvider, CancellationToken cancellationToken) { try @@ -639,6 +642,10 @@ public async Task EmitSolutionUpdateAsync(Solution solution, Sol continue; } + // PopulateChangedAndAddedDocumentsAsync returns no changes if base project does not exist + var baseProject = baseSolution.GetProject(project.Id); + Contract.ThrowIfNull(baseProject); + // Ensure that all changed documents are in-sync. Once a document is in-sync it can't get out-of-sync. // Therefore, results of further computations based on base snapshots of changed documents can't be invalidated by // incoming events updating the content of out-of-sync documents. @@ -653,7 +660,8 @@ public async Task EmitSolutionUpdateAsync(Solution solution, Sol // e.g. the binary was built with an overload C.M(object), but a generator updated class C to also contain C.M(string), // which change we have not observed yet. Then call-sites of C.M in a changed document observed by the analysis will be seen as C.M(object) // instead of the true C.M(string). - var (changedDocumentAnalyses, documentDiagnostics) = await AnalyzeDocumentsAsync(changedOrAddedDocuments, solutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false); + + var (changedDocumentAnalyses, documentDiagnostics) = await AnalyzeDocumentsAsync(project, changedOrAddedDocuments, solutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false); if (documentDiagnostics.Any()) { // The diagnostic hasn't been reported by GetDocumentDiagnosticsAsync since out-of-sync documents are likely to be synchronized @@ -675,7 +683,7 @@ public async Task EmitSolutionUpdateAsync(Solution solution, Sol isBlocked = true; } - var projectSummary = await GetProjectAnalysisSymmaryAsync(changedDocumentAnalyses, cancellationToken).ConfigureAwait(false); + var projectSummary = GetProjectAnalysisSymmary(changedDocumentAnalyses); if (projectSummary == ProjectAnalysisSummary.CompilationErrors || projectSummary == ProjectAnalysisSummary.RudeEdits) { isBlocked = true; @@ -701,12 +709,16 @@ public async Task EmitSolutionUpdateAsync(Solution solution, Sol EditAndContinueWorkspaceService.Log.Write("Emitting update of '{0}' [0x{1:X8}]", project.Id.DebugName, project.Id); + var currentCompilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(currentCompilation); + + var projectChanges = GetProjectChanges(currentCompilation, changedDocumentAnalyses, cancellationToken); + var baseActiveStatements = await BaseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); + // Exception regions of active statements in changed documents are calculated (non-default), // since we already checked that no changed document is out-of-sync above. var baseActiveExceptionRegions = await GetBaseActiveExceptionRegionsAsync(solution, cancellationToken).ConfigureAwait(false); - var baseActiveStatements = await BaseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); - var projectChanges = await GetProjectChangesAsync(changedDocumentAnalyses, cancellationToken).ConfigureAwait(false); var lineEdits = projectChanges.LineChanges.SelectAsArray((lineChange, project) => { var filePath = project.GetDocument(lineChange.DocumentId)!.FilePath; @@ -720,8 +732,6 @@ public async Task EmitSolutionUpdateAsync(Solution solution, Sol var updatedMethods = ImmutableArray.CreateBuilder(); - var currentCompilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - // project must support compilations since it supports EnC Contract.ThrowIfNull(currentCompilation); diff --git a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueAnalyzer.cs index a53b4eed29685..fc3602b9525f5 100644 --- a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueAnalyzer.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue { internal interface IEditAndContinueAnalyzer : ILanguageService { - Task AnalyzeDocumentAsync(Document? oldDocument, ImmutableArray activeStatements, Document document, ImmutableArray newActiveStatementSpans, CancellationToken cancellationToken); + Task AnalyzeDocumentAsync(Project baseProject, ImmutableArray baseActiveStatements, Document document, ImmutableArray newActiveStatementSpans, CancellationToken cancellationToken); ImmutableArray GetExceptionRegions(SourceText text, SyntaxNode syntaxRoot, LinePositionSpan activeStatementSpan, bool isLeaf, out bool isCovered); } } From 37413b400a0f486385676d292cf1e35b6687c154 Mon Sep 17 00:00:00 2001 From: tmat Date: Tue, 16 Feb 2021 22:19:10 -0800 Subject: [PATCH 9/9] Fix formatting --- src/Features/Core/Portable/EditAndContinue/EditSession.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index 51a0f81fa8767..0ffcef9df0721 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -546,7 +546,7 @@ private static ProjectAnalysisSummary GetProjectAnalysisSymmary(ImmutableArray changedDocumentAnalyses, CancellationToken cancellationToken) + private static ProjectChanges GetProjectChanges(ImmutableArray changedDocumentAnalyses) { try { @@ -600,7 +600,6 @@ private static ProjectChanges GetProjectChanges(Compilation newCompilation, Immu } } - public async Task EmitSolutionUpdateAsync(Solution solution, SolutionActiveStatementSpanProvider solutionActiveStatementSpanProvider, CancellationToken cancellationToken) { try @@ -712,7 +711,7 @@ public async Task EmitSolutionUpdateAsync(Solution solution, Sol var currentCompilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(currentCompilation); - var projectChanges = GetProjectChanges(currentCompilation, changedDocumentAnalyses, cancellationToken); + var projectChanges = GetProjectChanges(changedDocumentAnalyses); var baseActiveStatements = await BaseActiveStatements.GetValueAsync(cancellationToken).ConfigureAwait(false); // Exception regions of active statements in changed documents are calculated (non-default),