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/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index 95140adcafde8..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,11 +335,11 @@ 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); - Assert.True(result.HasChangesAndCompilationErrors); + Assert.True(result.HasChangesAndSyntaxErrors); } [Fact] @@ -361,11 +361,11 @@ 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); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); } [Fact] @@ -402,11 +402,11 @@ 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); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); } [Fact] @@ -435,11 +435,11 @@ 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); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); Assert.True(result.RudeEditErrors.IsEmpty); } @@ -486,11 +486,11 @@ 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); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); Assert.Equal(RudeEditKind.ExperimentalFeaturesEnabled, result.RudeEditErrors.Single().Kind); } } @@ -519,11 +519,11 @@ 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); - Assert.False(result.HasChangesAndCompilationErrors); + Assert.False(result.HasChangesAndSyntaxErrors); } [Fact, WorkItem(10683, "https://github.com/dotnet/roslyn/issues/10683")] @@ -562,13 +562,13 @@ 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); // 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")] @@ -605,11 +605,14 @@ 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); - 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.HasChangesAndSyntaxErrors); } [Fact] @@ -659,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()); @@ -708,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()); @@ -743,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/CSharpTest/EditAndContinue/Helpers/Extensions.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs similarity index 80% rename from src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/Extensions.cs rename to src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditAndContinueValidation.cs index 6bb7c4713248b..f4f6a74a1bea7 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,53 +74,36 @@ internal static void VerifySemanticDiagnostics( params RudeEditDiagnosticDescription[] expectedDiagnostics) { VerifySemantics( - editScript, + new[] { editScript }, targetFrameworks: targetFrameworks, expectedDiagnostics: expectedDiagnostics); } - internal static void VerifySemanticDiagnostics( - this EditScript editScript, - DiagnosticDescription expectedDeclarationError, - params RudeEditDiagnosticDescription[] expectedDiagnostics) - { - VerifySemantics( - editScript, - expectedDeclarationError: expectedDeclarationError, - expectedDiagnostics: expectedDiagnostics); - } - internal static void VerifySemantics( this EditScript editScript, ActiveStatementsDescription activeStatements, 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) { 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..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; @@ -36,7 +37,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 +46,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)); @@ -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; @@ -135,10 +136,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) { @@ -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/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 945e562f5e05d..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() { @@ -8786,13 +8909,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 +8944,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 +9691,7 @@ static async Task F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( + edits.VerifySemanticDiagnostics( targetFrameworks: new[] { TargetFramework.MinimalAsync }, expectedDiagnostics: new[] { @@ -9614,7 +9727,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..0f56eb6ba6fba 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 { }"; @@ -4723,22 +4762,18 @@ 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()) - }, - expectedDeclarationError: null); + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) + }); } [Fact] - public void InstanceCtor_Partial_DeletePrivate() + public void InstanceCtor_Partial_DeletePrivateInsertPrivate() { var srcA1 = "partial class C { C() { } }"; var srcB1 = "partial class C { }"; @@ -4746,45 +4781,35 @@ 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); + 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() { } }"; - 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); + 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 { }"; @@ -4792,20 +4817,17 @@ 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.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() { } }"; @@ -4813,22 +4835,17 @@ 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] - public void InstanceCtor_Partial_InsertPublic() + public void InstanceCtor_Partial_InsertPublicDeletePublic() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { public C() { } }"; @@ -4836,22 +4853,17 @@ 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] - public void InstanceCtor_Partial_InsertPrivate() + public void InstanceCtor_Partial_InsertPrivateDeletePrivate() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { private C() { } }"; @@ -4859,22 +4871,17 @@ 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] - public void InstanceCtor_Partial_InsertInternal() + public void InstanceCtor_Partial_DeleteInternalInsertInternal() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { internal C() { } }"; @@ -4882,20 +4889,33 @@ 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_InsertInternalDeleteInternal_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] - public void InstanceCtor_Partial_InsertPrivateToPublic() + public void InstanceCtor_Partial_InsertPublicDeletePrivate() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { private C() { } }"; @@ -4903,20 +4923,17 @@ 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] - public void InstanceCtor_Partial_InsertPrivateToInternal() + public void InstanceCtor_Partial_InsertInternalDeletePrivate() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { private C() { } }"; @@ -4924,16 +4941,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] @@ -5268,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")] @@ -6884,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 f30485687662b..ce4c509d7be6a 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -9,6 +9,8 @@ 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; using Roslyn.Test.Utilities; @@ -41,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(), @@ -55,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); @@ -80,15 +85,18 @@ 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"); + var testAccessor = Analyzer.GetTestAccessor(); - Analyzer.GetTestAccessor().AnalyzeSyntax( + testAccessor.AnalyzeMemberBodiesSyntax( editScript, editMap, oldText, @@ -100,6 +108,8 @@ internal void VerifyRudeDiagnostics( updatedActiveMethodMatches, diagnostics); + testAccessor.ReportTopLevelSynctactiveRudeEdits(diagnostics, editScript, editMap); + diagnostics.Verify(newSource, expectedDiagnostics); // check active statements: @@ -121,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); @@ -148,11 +158,11 @@ 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); - var triviaEdits = new List<(SyntaxNode OldNode, SyntaxNode NewNode)>(); - var actualLineEdits = new List(); + var triviaEdits = new ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode)>(); + var actualLineEdits = new ArrayBuilder(); Analyzer.GetTestAccessor().AnalyzeTrivia( oldText, @@ -173,102 +183,92 @@ 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 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 ArrayBuilder(); + var actualDeclarationErrors = new ArrayBuilder(); - var actualNewActiveStatements = new ActiveStatement[activeStatements.OldStatements.Length]; - var actualNewExceptionRegions = new ImmutableArray[activeStatements.OldStatements.Length]; + var actualNewActiveStatements = ImmutableArray.CreateBuilder(activeStatements.OldStatements.Length); + actualNewActiveStatements.Count = actualNewActiveStatements.Capacity; - 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); + var actualNewExceptionRegions = ImmutableArray.CreateBuilder>(activeStatements.OldStatements.Length); + actualNewExceptionRegions.Count = actualNewExceptionRegions.Capacity; - diagnostics.Verify(newSource); + var testAccessor = Analyzer.GetTestAccessor(); - Analyzer.GetTestAccessor().AnalyzeSemantics( - editScript, - editMap, - oldText, - oldActiveStatements, - triviaEdits, - updatedActiveMethodMatches, - oldModel, - newModel, - actualSemanticEdits, - diagnostics, - out var firstDeclarationErrorOpt, - CancellationToken.None); - - var actualDeclarationErrors = (firstDeclarationErrorOpt != null) ? new[] { firstDeclarationErrorOpt } : Array.Empty(); - var expectedDeclarationErrors = (expectedDeclarationError != null) ? new[] { expectedDeclarationError } : Array.Empty(); - actualDeclarationErrors.Verify(expectedDeclarationErrors); + 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 ArrayBuilder(); + var updatedActiveMethodMatches = new ArrayBuilder(); + + 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, + CancellationToken.None); + + actualDiagnosticDescriptions.AddRange(diagnostics.ToDescription(newSource, includeFirstLineInDiagnostics)); + } - diagnostics.Verify(newSource, expectedDiagnostics); + actualDiagnosticDescriptions.Verify(expectedDiagnostics); if (expectedSemanticEdits == null) { @@ -292,6 +292,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 +304,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 +311,6 @@ internal void VerifySemantics( var actualOldNode = actualSyntaxMap(newNode); Assert.Equal(expectedOldNode, actualOldNode); - - newNodes.Add(newNode); } } else if (!expectedSemanticEdits[i].PreserveLocalVariables) @@ -336,7 +335,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/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 54% rename from src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/Extensions.vb rename to src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditAndContinueValidation.vb index 3d4abb0093455..094e3b79bf762 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, @@ -48,58 +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, Nothing, Nothing, expectedSemanticEdits, 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, Nothing, Nothing, expectedSemanticEdits, expectedDeclarationError, expectedDiagnostics) + VerifySemantics({editScript}, activeStatements, expectedSemanticEdits, 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 expectedDiagnostics As RudeEditDiagnosticDescription() = Nothing) VisualBasicEditAndContinueTestHelpers.CreateInstance().VerifySemantics( - editScript, + editScripts, activeStatements, - additionalOldSources, - additionalNewSources, expectedSemanticEdits, - expectedDeclarationError, expectedDiagnostics) End Sub End Module 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/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 e49b9db2ea9f1..ac7c736e51c27 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/StatementEditingTests.vb @@ -5,11 +5,13 @@ 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 - Public Class RudeEditStatementTests + Public Class StatementEditingTests Inherits EditingTestBase #Region "Matching" @@ -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, "Function(a)", VBFeaturesResources.Lambda)) End Sub #End Region @@ -6012,13 +6056,10 @@ 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) + expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Iterator Function F()", "System.Runtime.CompilerServices.IteratorStateMachineAttribute")}) End Sub @@ -6043,13 +6084,10 @@ 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) + expectedDiagnostics:=Nothing) End Sub #End Region @@ -6130,13 +6168,10 @@ 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) + expectedDiagnostics:={Diagnostic(RudeEditKind.UpdatingStateMachineMethodMissingAttribute, "Shared Async Function F()", "System.Runtime.CompilerServices.AsyncStateMachineAttribute")}) End Sub @@ -6161,13 +6196,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/TopLevelEditingTests.vb similarity index 97% rename from src/EditorFeatures/VisualBasicTest/EditAndContinue/RudeEditTopLevelTests.vb rename to src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 6927e79e76926..a0291eec5980f 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,17 +2793,16 @@ 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" 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(), preserveLocalVariables:=True)}) End Sub @@ -2777,173 +2813,152 @@ 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(), 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" 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(), 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" 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(), 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" 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.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" 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 - 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" 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 - 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" 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 - 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" 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 - 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" 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) + 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_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" - 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 - Public Sub InstanceCtor_Partial_InsertPrivateToPublic() + 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" 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 - 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" 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 @@ -3242,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 @@ -5904,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 @@ -5912,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 @@ -5921,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 @@ -5929,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 @@ -5940,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..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,11 +500,11 @@ 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) - Assert.False(result.HasChangesAndCompilationErrors) + Assert.False(result.HasChangesAndSyntaxErrors) End Using End Function @@ -534,11 +534,11 @@ 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) - Assert.False(result.HasChangesAndCompilationErrors) + Assert.False(result.HasChangesAndSyntaxErrors) End Using End Function @@ -558,11 +558,11 @@ 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) - Assert.False(result.HasChangesAndCompilationErrors) + Assert.False(result.HasChangesAndSyntaxErrors) End Using End Function @@ -594,11 +594,11 @@ 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) - Assert.False(result.HasChangesAndCompilationErrors) + Assert.False(result.HasChangesAndSyntaxErrors) End Using End Function @@ -627,11 +627,14 @@ 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) - 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.HasChangesAndSyntaxErrors) End Using End Function @@ -693,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/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 492e15a0bb273..b6cef0d5793da 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; @@ -638,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); @@ -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, @@ -3030,8 +3031,8 @@ public void ClassifyDeclarationBodyRudeUpdates(SyntaxNode newDeclarationOrBody) #endregion } - internal override void ReportSyntacticRudeEdits( - List diagnostics, + internal override void ReportTopLevelSyntacticRudeEdits( + ArrayBuilder diagnostics, Match match, Edit edit, Dictionary editMap) @@ -3045,7 +3046,7 @@ internal override void ReportSyntacticRudeEdits( 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 f493ffd7429b0..f6c233dbeac5d 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"). @@ -306,14 +306,14 @@ 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 ReportSyntacticRudeEdits(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); @@ -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); @@ -418,18 +417,29 @@ 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.SyntaxErrors(document.Id, hasChanges); + } + + var newActiveStatements = ImmutableArray.CreateBuilder(baseActiveStatements.Length); + newActiveStatements.Count = baseActiveStatements.Length; - var newActiveStatements = new ActiveStatement[baseActiveStatements.Length]; - var newExceptionRegions = (syntaxErrorCount == 0) ? new ImmutableArray[baseActiveStatements.Length] : null; + var newExceptionRegions = ImmutableArray.CreateBuilder>(baseActiveStatements.Length); + newExceptionRegions.Count = 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 @@ -443,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(document.Id, newActiveStatements.MoveToImmutable(), newExceptionRegions.MoveToImmutable()); } - 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(document.Id, activeStatementsOpt: default, ImmutableArray.Create( new RudeEditDiagnostic(RudeEditKind.ExperimentalFeaturesEnabled, default))); } @@ -482,8 +473,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 updatedMethods = new List(); - var diagnostics = new List(); + using var _1 = ArrayBuilder.GetInstance(out var updatedMembers); + using var _2 = ArrayBuilder.GetInstance(out var diagnostics); cancellationToken.ThrowIfCancellationRequested(); @@ -491,7 +482,7 @@ public async Task AnalyzeDocumentAsync( var syntacticEdits = topMatch.GetTreeEdits(); var editMap = BuildEditMap(syntacticEdits); - AnalyzeSyntax( + AnalyzeMemberBodiesSyntax( syntacticEdits, editMap, oldText, @@ -500,13 +491,15 @@ public async Task AnalyzeDocumentAsync( newActiveStatementSpans, newActiveStatements, newExceptionRegions, - updatedMethods, + updatedMembers, diagnostics); + ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap); + 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(document.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable()); } // Disallow addition of a new file. @@ -515,14 +508,14 @@ 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(document.Id, newActiveStatements.MoveToImmutable(), ImmutableArray.Create( new RudeEditDiagnostic(RudeEditKind.InsertFile, default))); } 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, @@ -539,12 +532,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(newActiveStatements.AsImmutable(), diagnostics.AsImmutable()); + return DocumentAnalysisResults.Errors(document.Id, newActiveStatements.MoveToImmutable(), 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); @@ -552,45 +545,40 @@ public async Task AnalyzeDocumentAsync( Contract.ThrowIfNull(oldModel); Contract.ThrowIfNull(newModel); - semanticEdits = new List(); + using var _ = ArrayBuilder.GetInstance(out var semanticEditsBuilder); AnalyzeSemantics( syntacticEdits, editMap, oldText, baseActiveStatements, triviaEdits, - updatedMethods, + updatedMembers, oldModel, newModel, - semanticEdits, + semanticEditsBuilder, diagnostics, - out var firstDeclaratingErrorOpt, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); - if (firstDeclaratingErrorOpt != null) - { - var location = firstDeclaratingErrorOpt.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(document.Id, newActiveStatements.MoveToImmutable(), diagnostics.ToImmutable()); } + + semanticEdits = semanticEditsBuilder.ToImmutable(); } return new DocumentAnalysisResults( - newActiveStatements.AsImmutable(), - diagnostics.AsImmutable(), - semanticEdits.AsImmutableOrEmpty(), - newExceptionRegions.AsImmutable(), - lineEdits.AsImmutable(), - hasSemanticErrors: false); + document.Id, + newActiveStatements.MoveToImmutable(), + diagnostics.ToImmutable(), + semanticEdits, + newExceptionRegions.MoveToImmutable(), + lineEdits.ToImmutable(), + hasChanges: true, + hasSyntaxErrors: false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e)) { @@ -602,7 +590,15 @@ 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(document.Id, activeStatementsOpt: default, ImmutableArray.Create(diagnostic)); + } + } + + private void ReportTopLevelSyntacticRudeEdits(ArrayBuilder diagnostics, EditScript syntacticEdits, Dictionary editMap) + { + foreach (var edit in syntacticEdits.Edits) + { + ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits.Match, edit, editMap); } } @@ -632,56 +628,53 @@ 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 updatedMethods, - [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(updatedMethods.Count == 0); + 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++) { - 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, updatedMembers, 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( - List diagnostics, + private void AnalyzeUnchangedMemberBodies( + 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) { @@ -775,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; } @@ -802,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)); } } @@ -910,23 +896,23 @@ public UpdatedMemberInfo( } } - private void AnalyzeUpdatedActiveMethodBodies( + 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]; @@ -1199,8 +1185,8 @@ private void CalculateExceptionRegionsAroundActiveStatement( int ordinal, SourceText newText, bool isNonLeaf, - ImmutableArray[] newExceptionRegions, - List diagnostics) + ImmutableArray>.Builder newExceptionRegions, + ArrayBuilder diagnostics) { if (newStatementSyntax == null) { @@ -1236,7 +1222,7 @@ private BidirectionalMap ComputeMap( Match bodyMatch, ActiveNode[] activeNodes, ref Dictionary? lazyActiveOrMatchedLambdas, - List diagnostics) + ArrayBuilder diagnostics) { ArrayBuilder>? lambdaBodyMatches = null; var currentLambdaBodyMatch = -1; @@ -1318,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)) @@ -1346,7 +1332,7 @@ private Match ComputeBodyMatch( SyntaxNode oldBody, SyntaxNode newBody, ActiveNode[] activeNodes, - List diagnostics, + ArrayBuilder diagnostics, out bool oldHasStateMachineSuspensionPoint, out bool newHasStateMachineSuspensionPoint) { @@ -1382,7 +1368,7 @@ private Match ComputeBodyMatch( if (IsLocalFunction(match.OldRoot) && IsLocalFunction(match.NewRoot)) { - ReportLocalFunctionsDeclarationRudeEdits(match, diagnostics); + ReportLocalFunctionsDeclarationRudeEdits(diagnostics, match); } if (lazyRudeEdits != null) @@ -1468,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, @@ -1477,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, @@ -1722,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) @@ -1742,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 && @@ -1754,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) { @@ -1772,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, @@ -1781,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, @@ -1790,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, @@ -1800,7 +1786,7 @@ protected void AddRudeDeleteAroundActiveStatement(List diagn } protected void ReportUnmatchedStatements( - List diagnostics, + ArrayBuilder diagnostics, Match match, Func nodeSelector, SyntaxNode oldActiveStatement, @@ -1839,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; @@ -1867,7 +1853,7 @@ private void ReportRudeEditsAndInserts(List? oldNodes, List( List oldNodes, List newNodes, - List? diagnostics, + ArrayBuilder? diagnostics, Match? match, Func comparer) where TSyntaxNode : SyntaxNode @@ -1972,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) @@ -2183,16 +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 Diagnostic? firstDeclarationError, + [Out] ArrayBuilder semanticEdits, + [Out] ArrayBuilder diagnostics, CancellationToken cancellationToken) { // { new type -> constructor update } @@ -2202,7 +2187,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(); @@ -2239,17 +2223,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( @@ -2308,19 +2304,11 @@ 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. - 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 @@ -2368,15 +2356,8 @@ private void AnalyzeSemantics( } } - if (editKind == SemanticEditKind.Insert) - { - ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol); - 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, @@ -2401,7 +2382,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); } } } @@ -2413,33 +2394,35 @@ 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; } oldSymbol = GetSymbolForEdit(oldModel, edit.OldNode, edit.Kind, editMap, cancellationToken); - RoslynDebug.Assert(oldSymbol != null); + Contract.ThrowIfNull(oldSymbol); 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); @@ -2471,8 +2454,6 @@ private void AnalyzeSemantics( { syntaxMap = null; } - - updatedMemberIndex++; } else { @@ -2481,8 +2462,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)) { @@ -2529,17 +2510,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) || @@ -2552,8 +2522,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( @@ -2607,35 +2577,10 @@ 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( - List diagnostics, + ArrayBuilder diagnostics, ISymbol newSymbol, SyntaxNode newSyntax, SemanticModel newModel, @@ -2671,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; @@ -2831,6 +2776,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, @@ -2842,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)) @@ -2864,33 +2812,41 @@ 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; } 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)) + 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; } @@ -2901,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) @@ -3059,7 +3015,7 @@ private void ReportLambdaAndClosureRudeEdits( ISymbol newMember, IReadOnlyDictionary? matchedLambdas, BidirectionalMap map, - List diagnostics, + ArrayBuilder diagnostics, out bool newBodyHasLambdas, CancellationToken cancellationToken) { @@ -3303,7 +3259,7 @@ private void ReportMultiScopeCaptures( ArrayBuilder newCapturesToClosureScopes, PooledDictionary capturesIndex, ArrayBuilder reverseCapturesMap, - List diagnostics, + ArrayBuilder diagnostics, bool isInsert, CancellationToken cancellationToken) { @@ -3456,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) { @@ -3718,7 +3674,7 @@ protected virtual void ReportLambdaSignatureRudeEdits( SyntaxNode oldLambdaBody, SemanticModel newModel, SyntaxNode newLambdaBody, - List diagnostics, + ArrayBuilder diagnostics, out bool hasErrors, CancellationToken cancellationToken) { @@ -3820,7 +3776,7 @@ private void ReportStateMachineRudeEdits( Compilation oldCompilation, UpdatedMemberInfo updatedInfo, ISymbol oldMember, - List diagnostics) + ArrayBuilder diagnostics) { if (!updatedInfo.OldHasStateMachineSuspensionPoint) { @@ -3829,7 +3785,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; } @@ -3840,6 +3796,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( @@ -3887,27 +3851,30 @@ internal readonly struct TestAccessor public TestAccessor(AbstractEditAndContinueAnalyzer abstractEditAndContinueAnalyzer) => _abstractEditAndContinueAnalyzer = abstractEditAndContinueAnalyzer; - internal void AnalyzeSyntax( + internal void AnalyzeMemberBodiesSyntax( EditScript script, Dictionary editMap, SourceText oldText, 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.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(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); } @@ -3916,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); } @@ -3925,7 +3892,7 @@ internal Match ComputeBodyMatch( SyntaxNode oldBody, SyntaxNode newBody, ActiveNode[] activeNodes, - List diagnostics, + ArrayBuilder diagnostics, out bool oldHasStateMachineSuspensionPoint, out bool newHasStateMachineSuspensionPoint) { @@ -3936,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); @@ -3950,16 +3917,15 @@ 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 Diagnostic? firstDeclarationError, + [Out] ArrayBuilder semanticEdits, + [Out] ArrayBuilder diagnostics, 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); } } diff --git a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs index 67b8f8dc39b93..88bb455a730ff 100644 --- a/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/DocumentAnalysisResults.cs @@ -2,8 +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. -#nullable disable - +using System; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -15,8 +14,16 @@ namespace Microsoft.CodeAnalysis.EditAndContinue { internal sealed class DocumentAnalysisResults { + internal static readonly TraceLog Log = new(256, "EnC"); + + /// + /// 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; } @@ -27,15 +34,14 @@ internal sealed class DocumentAnalysisResults 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 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 syntax errors or rude edits, or if the compilation has semantic errors. + /// Null if the document has syntax errors or rude edits. /// /// /// Null if there are any rude edit diagnostics. @@ -56,8 +62,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 syntax errors or rude edits. /// /// /// Sorted by @@ -65,35 +70,37 @@ 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 errors that block EnC analysis. /// - private readonly bool? _hasCompilationErrors; + public readonly bool HasSyntaxErrors; - 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, + DocumentId documentId, + ImmutableArray activeStatementsOpt, ImmutableArray rudeEdits, ImmutableArray semanticEditsOpt, ImmutableArray> exceptionRegionsOpt, ImmutableArray lineEditsOpt, - bool? hasSemanticErrors) + bool hasChanges, + bool hasSyntaxErrors) { Debug.Assert(!rudeEdits.IsDefault); - Debug.Assert(!activeStatements.IsDefault); - Debug.Assert(activeStatements.All(a => a != null)); - if (hasSemanticErrors.HasValue) + if (hasSyntaxErrors) { - - 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(semanticEditsOpt.IsDefault); Debug.Assert(exceptionRegionsOpt.IsDefault); @@ -101,85 +108,74 @@ public DocumentAnalysisResults( } 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); } + DocumentId = documentId; RudeEditErrors = rudeEdits; SemanticEdits = semanticEditsOpt; - ActiveStatements = activeStatements; + ActiveStatements = activeStatementsOpt; ExceptionRegions = exceptionRegionsOpt; LineEdits = lineEditsOpt; - _hasCompilationErrors = hasSemanticErrors; + HasSyntaxErrors = hasSyntaxErrors; + HasChanges = hasChanges; } - public bool HasChanges => _hasCompilationErrors.HasValue; - public bool HasChangesAndErrors - { - get - { - return HasChanges && (_hasCompilationErrors.Value || !RudeEditErrors.IsEmpty); - } - } + => HasChanges && (HasSyntaxErrors || !RudeEditErrors.IsEmpty); - public bool HasChangesAndCompilationErrors - { - get - { - return _hasCompilationErrors == true; - } - } + public bool HasChangesAndSyntaxErrors + => HasChanges && HasSyntaxErrors; 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 SyntaxErrors(DocumentId documentId, bool hasChanges) + => new( + documentId, + activeStatementsOpt: default, + rudeEdits: ImmutableArray.Empty, + semanticEditsOpt: default, + exceptionRegionsOpt: default, + lineEditsOpt: default, + hasChanges, + hasSyntaxErrors: true); + + public static DocumentAnalysisResults Errors(DocumentId documentId, ImmutableArray activeStatementsOpt, ImmutableArray rudeEdits) + => new( + documentId, + activeStatementsOpt, rudeEdits, - default, - default, - default, - hasSemanticErrors); - } - - internal static readonly TraceLog Log = new(256, "EnC"); + semanticEditsOpt: default, + exceptionRegionsOpt: default, + lineEditsOpt: default, + hasChanges: true, + hasSyntaxErrors: false); + + 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, + hasSyntaxErrors: false); } } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs new file mode 100644 index 0000000000000..88d9b32080603 --- /dev/null +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDocumentAnalysesCache.cs @@ -0,0 +1,146 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; +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, Project baseProject, Document document, ImmutableArray activeStatementSpans)> _analyses = new(); + private readonly AsyncLazy _baseActiveStatements; + + public EditAndContinueDocumentAnalysesCache(AsyncLazy baseActiveStatements) + { + _baseActiveStatements = baseActiveStatements; + } + + public async ValueTask> GetActiveStatementsAsync(Document baseDocument, Document document, ImmutableArray activeStatementSpans, CancellationToken cancellationToken) + { + try + { + 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 async ValueTask> GetDocumentAnalysesAsync( + Project oldProject, + IReadOnlyList<(Document newDocument, ImmutableArray newActiveStatementSpans)> documentInfos, + CancellationToken cancellationToken) + { + try + { + 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); + + return allResults.ToImmutableArray(); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + } + + /// + /// Returns a document analysis or kicks off a new one if one is not available for the specified document snapshot. + /// + /// Base project. + /// Document snapshot to analyze. + /// Active statement spans tracked by the editor. + public async ValueTask GetDocumentAnalysisAsync(Project baseProject, Document document, ImmutableArray activeStatementSpans, CancellationToken cancellationToken) + { + try + { + 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; + } + } + + 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(baseProject, documentBaseActiveStatements, document, activeStatementSpans, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e)) + { + throw ExceptionUtilities.Unreachable; + } + }, + cacheResult: true); + + // 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 019ef11c5e560..0ffcef9df0721 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -41,13 +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 Dictionary)>(); - private readonly object _analysesGuard = new(); + internal readonly EditAndContinueDocumentAnalysesCache Analyses; /// /// A is added whenever EnC analyzer reports @@ -72,6 +68,7 @@ internal EditSession( _nonRemappableRegions = debuggingSession.NonRemappableRegions; BaseActiveStatements = new AsyncLazy(cancellationToken => GetBaseActiveStatementsAsync(cancellationToken), cacheResult: true); + Analyses = new EditAndContinueDocumentAnalysesCache(BaseActiveStatements); } internal PendingSolutionUpdate? Test_GetPendingSolutionUpdate() => _pendingUpdate; @@ -356,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) { @@ -386,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: @@ -394,64 +394,18 @@ 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()); - } + // 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); - 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; + var analyses = await Analyses.GetDocumentAnalysesAsync(oldProject, builder, cancellationToken).ConfigureAwait(false); + return (analyses, documentDiagnostics.ToImmutable()); } internal ImmutableArray GetDocumentsWithReportedDiagnostics() @@ -521,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}')", @@ -533,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); @@ -549,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.HasChangesAndCompilationErrors) + 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) @@ -596,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(ImmutableArray changedDocumentAnalyses) { 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)) { @@ -696,6 +641,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. @@ -710,7 +659,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 @@ -732,7 +682,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; @@ -758,12 +708,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(changedDocumentAnalyses); + 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; @@ -777,8 +731,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); } } 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, diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 5a3357fd41587..eecc1443df2a2 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() @@ -1158,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 @@ -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 ReportSyntacticRudeEdits(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)