diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 4fa5b89fd2ade..a5306fe2a6398 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -1867,7 +1867,7 @@ public C() {} var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ModifiersUpdate, "const int a = 1", FeaturesResources.const_field)); + Diagnostic(RudeEditKind.ModifiersUpdate, "a = 1", FeaturesResources.const_field)); } [Fact] @@ -1917,7 +1917,7 @@ public C() {} var active = GetActiveStatements(src1, src2); edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ModifiersUpdate, "const int a = 1, b = 2", FeaturesResources.const_field)); + Diagnostic(RudeEditKind.ModifiersUpdate, "a = 1", FeaturesResources.const_field)); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs index 4b417edbe5a0a..9e69b9b3ad552 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/CSharpEditAndContinueTestHelpers.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index fd1d4962d4c04..794c9d3f61a9e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests @@ -34,6 +35,17 @@ internal enum MethodKind ConstructorWithParameters } + public static string GetResource(string keyword) + => keyword switch + { + "class" => FeaturesResources.class_, + "struct" => CSharpFeaturesResources.struct_, + "interface" => FeaturesResources.interface_, + "record" => CSharpFeaturesResources.record_, + "record struct" => CSharpFeaturesResources.record_struct, + _ => throw ExceptionUtilities.UnexpectedValue(keyword) + }; + internal static SemanticEditDescription[] NoSemanticEdits = Array.Empty(); internal static RudeEditDiagnosticDescription Diagnostic(RudeEditKind rudeEditKind, string squiggle, params string[] arguments) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 095a59d3feecc..3827ac1e2d9b2 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -428,7 +428,7 @@ public void ExternAliasDelete() #endregion - #region Attributes + #region Assembly/Module Attributes [Fact] public void Insert_TopLevelAttribute() @@ -492,243 +492,157 @@ public void Reorder_TopLevelAttribute() edits.VerifyRudeDiagnostics(); } - [Fact] - public void UpdateAttributes1() - { - var attribute = "public class A1Attribute : System.Attribute { }\n\n" + - "public class A2Attribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[A1]class C { }"; - var src2 = attribute + "[A2]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[A1]class C { }]@98 -> [[A2]class C { }]@98"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); - } - - [Fact] - public void UpdateAttributes2() - { - var src1 = "[System.Obsolete(\"1\")]class C { }"; - var src2 = "[System.Obsolete(\"2\")]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[System.Obsolete(\"1\")]class C { }]@0 -> [[System.Obsolete(\"2\")]class C { }]@0"); + #endregion - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); - } + #region Types - [Fact] - public void DeleteAttributes() + [Theory] + [InlineData("class", "struct")] + [InlineData("class", "record")] // TODO: Allow this conversion: https://github.com/dotnet/roslyn/issues/51874 + [InlineData("class", "record struct")] + [InlineData("struct", "record struct")] // TODO: Allow this conversion: https://github.com/dotnet/roslyn/issues/51874 + public void Type_Kind_Update(string oldKeyword, string newKeyword) { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[A, B]class C { }"; - var src2 = attribute + "[A]class C { }"; + var src1 = oldKeyword + " C { }"; + var src2 = newKeyword + " C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [[A, B]class C { }]@96 -> [[A]class C { }]@96"); + "Update [" + oldKeyword + " C { }]@0 -> [" + newKeyword + " C { }]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + Diagnostic(RudeEditKind.TypeKindUpdate, newKeyword + " C")); } [Fact] - public void DeleteAttributes2() + public void Type_Modifiers_Static_Remove() { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[B, A]class C { }"; - var src2 = attribute + "[A]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[B, A]class C { }]@96 -> [[A]class C { }]@96"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); - } - - [Fact] - public void InsertAttributes_SupportedByRuntime() - { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[A]class C { }"; - var src2 = attribute + "[A, B]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[A]class C { }]@96 -> [[A, B]class C { }]@96"); - - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C")) }, - capabilities: EditAndContinueTestHelpers.Net5RuntimeCapabilities | EditAndContinueCapabilities.ChangeCustomAttributes); - } - - [Fact] - public void InsertAttributes1() - { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[A]class C { }"; - var src2 = attribute + "[A, B]class C { }"; + var src1 = "public static class C { }"; + var src2 = "public class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [[A]class C { }]@96 -> [[A, B]class C { }]@96"); + "Update [public static class C { }]@0 -> [public class C { }]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + Diagnostic(RudeEditKind.ModifiersUpdate, "public class C", FeaturesResources.class_)); } - [Fact] - public void InsertAttributes2() + [Theory] + [InlineData("public")] + [InlineData("protected")] + [InlineData("private")] + [InlineData("private protected")] + [InlineData("internal protected")] + public void Type_Modifiers_Accessibility_Change(string accessibility) { - var attribute = "public class AAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "class C { }"; - var src2 = attribute + "[A]class C { }"; + var src1 = accessibility + " class C { }"; + var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [class C { }]@48 -> [[A]class C { }]@48"); + "Update [" + accessibility + " class C { }]@0 -> [class C { }]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); - } - - [Fact] - public void ReorderAttributes1() - { - var src1 = "[A(1), B(2), C(3)]class C { }"; - var src2 = "[C(3), A(1), B(2)]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[A(1), B(2), C(3)]class C { }]@0 -> [[C(3), A(1), B(2)]class C { }]@0"); - - edits.VerifyRudeDiagnostics(); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", FeaturesResources.class_)); } - [Fact] - public void ReorderAttributes2() - { - var src1 = "[A, B, C]class C { }"; - var src2 = "[B, C, A]class C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[A, B, C]class C { }]@0 -> [[B, C, A]class C { }]@0"); + [Theory] + [InlineData("public", "public")] + [InlineData("internal", "internal")] + [InlineData("", "internal")] + [InlineData("internal", "")] + [InlineData("protected", "protected")] + [InlineData("private", "private")] + [InlineData("private protected", "private protected")] + [InlineData("internal protected", "internal protected")] + public void Type_Modifiers_Accessibility_Partial(string accessibilityA, string accessibilityB) + { + var srcA1 = accessibilityA + " partial class C { }"; + var srcB1 = "partial class C { }"; + var srcA2 = "partial class C { }"; + var srcB2 = accessibilityB + " partial class C { }"; - edits.VerifyRudeDiagnostics(); + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(), + }); } [Fact] - public void ReorderAndUpdateAttributes() + public void Type_Modifiers_Internal_Remove() { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "[System.Obsolete(\"1\"), A, B]class C { }"; - var src2 = attribute + "[A, B, System.Obsolete(\"2\")]class C { }"; + var src1 = "internal interface C { }"; + var src2 = "interface C { }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[System.Obsolete(\"1\"), A, B]class C { }]@96 -> [[A, B, System.Obsolete(\"2\")]class C { }]@96"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + edits.VerifySemantics(); } - #endregion - - #region Classes, Structs, Interfaces - [Fact] - public void TypeKindUpdate() + public void Type_Modifiers_Internal_Add() { - var src1 = "class C { }"; - var src2 = "struct C { }"; + var src1 = "struct C { }"; + var src2 = "internal struct C { }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [class C { }]@0 -> [struct C { }]@0"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "struct C", CSharpFeaturesResources.struct_)); + edits.VerifySemantics(); } - [Fact] - public void TypeKindUpdate2() + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("interface")] + [InlineData("record")] + [InlineData("record struct")] + public void Type_Modifiers_NestedPrivateInInterface_Remove(string keyword) { - // TODO: Allow this conversion: https://github.com/dotnet/roslyn/issues/51874 - var src1 = "class C { }"; - var src2 = "record C { }"; + var src1 = "interface C { private " + keyword + " S { } }"; + var src2 = "interface C { " + keyword + " S { } }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [class C { }]@0 -> [record C { }]@0"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "record C", CSharpFeaturesResources.record_)); + Diagnostic(RudeEditKind.ChangingAccessibility, keyword + " S", GetResource(keyword))); } - [Fact] - public void TypeKindUpdate3() + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("interface")] + [InlineData("record")] + [InlineData("record struct")] + public void Type_Modifiers_NestedPrivateInClass_Add(string keyword) { - var src1 = "record C { }"; - var src2 = "record struct C { }"; + var src1 = "class C { " + keyword + " S { } }"; + var src2 = "class C { private " + keyword + " S { } }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [record C { }]@0 -> [record struct C { }]@0"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "record struct C", CSharpFeaturesResources.record_struct)); + edits.VerifySemantics(); } - [Fact] - public void Class_Modifiers_Update() + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("interface")] + [InlineData("record")] + [InlineData("record struct")] + public void Type_Modifiers_NestedPublicInInterface_Add(string keyword) { - var src1 = "public static class C { }"; - var src2 = "public class C { }"; + var src1 = "interface C { " + keyword + " S { } }"; + var src2 = "interface C { public " + keyword + " S { } }"; var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [public static class C { }]@0 -> [public class C { }]@0"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public class C", FeaturesResources.class_)); + edits.VerifySemantics(); } [Fact, WorkItem(48628, "https://github.com/dotnet/roslyn/issues/48628")] - public void Class_ModifiersUpdate_IgnoreUnsafe() + public void Type_Modifiers_Unsafe_Add() { var src1 = "public class C { }"; var src2 = "public unsafe class C { }"; @@ -742,7 +656,7 @@ public void Class_ModifiersUpdate_IgnoreUnsafe() } [Fact, WorkItem(48628, "https://github.com/dotnet/roslyn/issues/48628")] - public void ModifiersUpdate_IgnoreUnsafe() + public void Type_Modifiers_Unsafe_Remove() { var src1 = @" using System; @@ -789,7 +703,7 @@ public event Action A { add { } remove { } } } [Fact, WorkItem(48628, "https://github.com/dotnet/roslyn/issues/48628")] - public void ModifiersUpdate_IgnoreUnsafe2() + public void Type_Modifiers_Unsafe_DeleteInsert() { var srcA1 = "partial class C { unsafe void F() { } }"; var srcB1 = "partial class C { }"; @@ -809,7 +723,7 @@ public void ModifiersUpdate_IgnoreUnsafe2() } [Fact] - public void Struct_Modifiers_Ref_Update1() + public void Type_Modifiers_Ref_Add() { var src1 = "public struct C { }"; var src2 = "public ref struct C { }"; @@ -824,7 +738,7 @@ public void Struct_Modifiers_Ref_Update1() } [Fact] - public void Struct_Modifiers_Ref_Update2() + public void Type_Modifiers_Ref_Remove() { var src1 = "public ref struct C { }"; var src2 = "public struct C { }"; @@ -839,7 +753,7 @@ public void Struct_Modifiers_Ref_Update2() } [Fact] - public void Struct_Modifiers_Readonly_Update1() + public void Type_Modifiers_ReadOnly_Add() { var src1 = "public struct C { }"; var src2 = "public readonly struct C { }"; @@ -854,7 +768,7 @@ public void Struct_Modifiers_Readonly_Update1() } [Fact] - public void Struct_Modifiers_Readonly_Update2() + public void Type_Modifiers_ReadOnly_Remove() { var src1 = "public readonly struct C { }"; var src2 = "public struct C { }"; @@ -869,49 +783,175 @@ public void Struct_Modifiers_Readonly_Update2() } [Fact] - public void Interface_Modifiers_Update() + public void Type_Attribute_Update_NotSupportedByRuntime1() { - var src1 = "public interface C { }"; - var src2 = "interface C { }"; + var attribute = "public class A1Attribute : System.Attribute { }\n\n" + + "public class A2Attribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[A1]class C { }"; + var src2 = attribute + "[A2]class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [public interface C { }]@0 -> [interface C { }]@0"); + "Update [[A1]class C { }]@98 -> [[A2]class C { }]@98"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "interface C", FeaturesResources.interface_)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); } [Fact] - public void Struct_Modifiers_Update() + public void Type_Attribute_Update_NotSupportedByRuntime2() { - var src1 = "struct C { }"; - var src2 = "public struct C { }"; + var src1 = "[System.Obsolete(\"1\")]class C { }"; + var src2 = "[System.Obsolete(\"2\")]class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [struct C { }]@0 -> [public struct C { }]@0"); + "Update [[System.Obsolete(\"1\")]class C { }]@0 -> [[System.Obsolete(\"2\")]class C { }]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public struct C", CSharpFeaturesResources.struct_)); + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); } [Fact] - public void Struct_UnsafeModifier_Update() + public void Type_Attribute_Delete_NotSupportedByRuntime1() { - var src1 = "unsafe struct C { }"; - var src2 = "struct C { }"; + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[A, B]class C { }"; + var src2 = attribute + "[A]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A, B]class C { }]@96 -> [[A]class C { }]@96"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + + [Fact] + public void Type_Attribute_Delete_NotSupportedByRuntime2() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[B, A]class C { }"; + var src2 = attribute + "[A]class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [unsafe struct C { }]@0 -> [struct C { }]@0"); + "Update [[B, A]class C { }]@96 -> [[A]class C { }]@96"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + + [Fact] + public void Type_Attribute_Add() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[A]class C { }"; + var src2 = attribute + "[A, B]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A]class C { }]@96 -> [[A, B]class C { }]@96"); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C")) }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + } + + [Fact] + public void Type_Attribute_Add_NotSupportedByRuntime1() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[A]class C { }"; + var src2 = attribute + "[A, B]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A]class C { }]@96 -> [[A, B]class C { }]@96"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + + [Fact] + public void Type_Attribute_Add_NotSupportedByRuntime2() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "class C { }"; + var src2 = attribute + "[A]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [class C { }]@48 -> [[A]class C { }]@48"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + + [Fact] + public void Type_Attribute_Reorder1() + { + var src1 = "[A(1), B(2), C(3)]class C { }"; + var src2 = "[C(3), A(1), B(2)]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A(1), B(2), C(3)]class C { }]@0 -> [[C(3), A(1), B(2)]class C { }]@0"); edits.VerifyRudeDiagnostics(); } + [Fact] + public void Type_Attribute_Reorder2() + { + var src1 = "[A, B, C]class C { }"; + var src2 = "[B, C, A]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[A, B, C]class C { }]@0 -> [[B, C, A]class C { }]@0"); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Type_Attribute_ReorderAndUpdate_NotSupportedByRuntime() + { + var attribute = "public class AAttribute : System.Attribute { }\n\n" + + "public class BAttribute : System.Attribute { }\n\n"; + + var src1 = attribute + "[System.Obsolete(\"1\"), A, B]class C { }"; + var src2 = attribute + "[A, B, System.Obsolete(\"2\")]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [[System.Obsolete(\"1\"), A, B]class C { }]@96 -> [[A, B, System.Obsolete(\"2\")]class C { }]@96"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", FeaturesResources.class_)); + } + [Fact] public void Class_Name_Update1() { @@ -1220,7 +1260,7 @@ public void RefStructInsert() } [Fact] - public void ReadOnlyStructInsert() + public void Struct_ReadOnly_Insert() { var src1 = ""; var src2 = "readonly struct X { }"; @@ -1234,7 +1274,7 @@ public void ReadOnlyStructInsert() } [Fact] - public void RefStructUpdate() + public void Struct_RefModifier_Add() { var src1 = "struct X { }"; var src2 = "ref struct X { }"; @@ -1249,7 +1289,7 @@ public void RefStructUpdate() } [Fact] - public void ReadOnlyStructUpdate() + public void Struct_ReadonlyModifier_Add() { var src1 = "struct X { }"; var src2 = "readonly struct X { }"; @@ -1263,6 +1303,25 @@ public void ReadOnlyStructUpdate() Diagnostic(RudeEditKind.ModifiersUpdate, "readonly struct X", SyntaxFacts.GetText(SyntaxKind.StructKeyword))); } + [Theory] + [InlineData("ref")] + [InlineData("readonly")] + public void Struct_Modifiers_Partial_InsertDelete(string modifier) + { + var srcA1 = modifier + " partial struct S { }"; + var srcB1 = "partial struct S { }"; + var srcA2 = "partial struct S { }"; + var srcB2 = modifier + " partial struct S { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults() + }); + } + [Fact] public void Class_ImplementingInterface_Add() { @@ -1626,7 +1685,7 @@ interface I { void F() {} } } [Fact] - public void Type_DeleteInsert_NonInsertableMembers() + public void Type_NonInsertableMembers_DeleteInsert() { var srcA1 = @" abstract class C @@ -1648,6 +1707,7 @@ void F() {} var srcA2 = srcB1; var srcB2 = srcA1; + // TODO: The methods without bodies do not need to be updated. EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] @@ -1657,14 +1717,72 @@ void F() {} DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("AbstractMethod")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("VirtualMethod")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("ToString")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("I.G")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("G")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), }) }); } + [Fact] + public void Type_Attribute_NonInsertableMembers_DeleteInsert() + { + var srcA1 = @" +abstract class C +{ + public abstract void AbstractMethod(); + public virtual void VirtualMethod() {} + public override string ToString() => null; + public void I.G() {} +} + +interface I +{ + void G(); + void F() {} +} +"; + var srcB1 = ""; + + var srcA2 = ""; + var srcB2 = @" +abstract class C +{ + [System.Obsolete]public abstract void AbstractMethod(); + public virtual void VirtualMethod() {} + public override string ToString() => null; + public void I.G() {} +} + +interface I +{ + [System.Obsolete]void G(); + void F() {} +}"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("AbstractMethod")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("VirtualMethod")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("ToString")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("I.G")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("G")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), + }) + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + } + [Fact] public void Type_DeleteInsert_DataMembers() { @@ -1698,6 +1816,8 @@ class C DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").SetMethod), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), }) }); @@ -1738,6 +1858,8 @@ partial class C DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").SetMethod), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), }) }); @@ -1778,6 +1900,8 @@ class C DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P").SetMethod), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), }), @@ -2001,7 +2125,7 @@ public void Record_ImplementSynthesized_PrintMembers() var src2 = @" record C { - protected bool PrintMembers(System.Text.StringBuilder builder) + protected virtual bool PrintMembers(System.Text.StringBuilder builder) { return true; } @@ -2715,24 +2839,36 @@ public int X [Fact] public void Record_MoveProperty_Partial() { - var srcA1 = @"partial record C(int X) + var srcA1 = @" +partial record C(int X) { public int Y { get; init; } }"; - var srcB1 = @"partial record C;"; - var srcA2 = @"partial record C(int X);"; - var srcB2 = @"partial record C + var srcB1 = @" +partial record C; +"; + + var srcA2 = @" +partial record C(int X); +"; + + var srcB2 = @" +partial record C { public int Y { get; init; } }"; EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults(), - - DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Y").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Y").SetMethod) + }), }); } @@ -2976,7 +3112,7 @@ public void EnumBaseTypeDelete() } [Fact] - public void EnumModifierUpdate() + public void EnumAccessibilityChange() { var src1 = "public enum Color { Red = 1, Blue = 2, }"; var src2 = "enum Color { Red = 1, Blue = 2, }"; @@ -2986,8 +3122,18 @@ public void EnumModifierUpdate() edits.VerifyEdits( "Update [public enum Color { Red = 1, Blue = 2, }]@0 -> [enum Color { Red = 1, Blue = 2, }]@0"); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "enum Color", FeaturesResources.enum_)); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAccessibility, "enum Color", FeaturesResources.enum_)); + } + + [Fact] + public void EnumAccessibilityNoChange() + { + var src1 = "internal enum Color { Red = 1, Blue = 2, }"; + var src2 = "enum Color { Red = 1, Blue = 2, }"; + + var edits = GetTopEdits(src1, src2); + edits.VerifySemantics(); } [Fact] @@ -3318,7 +3464,7 @@ public void Delegates_Rename() } [Fact] - public void Delegates_Update_Modifiers() + public void Delegates_Accessibility_Update() { var src1 = "public delegate void D();"; var src2 = "private delegate void D();"; @@ -3329,7 +3475,7 @@ public void Delegates_Update_Modifiers() "Update [public delegate void D();]@0 -> [private delegate void D();]@0"); edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "private delegate void D()", FeaturesResources.delegate_)); + Diagnostic(RudeEditKind.ChangingAccessibility, "private delegate void D()", FeaturesResources.delegate_)); } [Fact] @@ -4535,101 +4681,6 @@ public void NamespaceReorder2() #region Members - [Fact] - public void MemberUpdate_Modifier_ReadOnly_Remove() - { - var src1 = @" -using System; - -struct S -{ - // methods - public readonly int M() => 1; - - // properties - public readonly int P => 1; - public readonly int Q { get; } - public int R { readonly get; readonly set; } - - // events - public readonly event Action E { add {} remove {} } - public event Action F { readonly add {} readonly remove {} } -}"; - var src2 = @" -using System; -struct S -{ - // methods - public int M() => 1; - - // properties - public int P => 1; - public int Q { get; } - public int R { get; set; } - - // events - public event Action E { add {} remove {} } - public event Action F { add {} remove {} } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public int M()", FeaturesResources.method), - Diagnostic(RudeEditKind.ModifiersUpdate, "public int P", FeaturesResources.property_), - Diagnostic(RudeEditKind.ModifiersUpdate, "public int Q", FeaturesResources.auto_property), - Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.property_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "set", CSharpFeaturesResources.property_setter), - Diagnostic(RudeEditKind.ModifiersUpdate, "add", FeaturesResources.event_accessor), - Diagnostic(RudeEditKind.ModifiersUpdate, "remove", FeaturesResources.event_accessor)); - } - - [Fact] - public void MemberUpdate_Modifier_ReadOnly_Add() - { - var src1 = @" -using System; - -struct S -{ - // methods - public int M() => 1; - - // properties - public int P => 1; - public int Q { get; } - public int R { get; set; } - - // events - public event Action E { add {} remove {} } - public event Action F { add {} remove {} } -}"; - var src2 = @" -using System; - -struct S -{ - // methods - public readonly int M() => 1; - - // properties - public readonly int P => 1; - public readonly int Q { get; } - public int R { readonly get; readonly set; } - - // events - public readonly event Action E { add {} remove {} } - public event Action F { readonly add {} readonly remove {} } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int M()", FeaturesResources.method), - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int P", FeaturesResources.property_), - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int Q", FeaturesResources.auto_property), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.property_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.property_setter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly add", FeaturesResources.event_accessor), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly remove", FeaturesResources.event_accessor)); - } - [Fact] public void PartialMember_DeleteInsert_SingleDocument() { @@ -4753,6 +4804,8 @@ event Action E { add {} remove {} } semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("M"), preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P1").GetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P1").SetMethod, preserveLocalVariables: false), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P2").GetMethod, preserveLocalVariables: false), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P2").SetMethod, preserveLocalVariables: false), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Int32").GetMethod, preserveLocalVariables: false), @@ -5086,6 +5139,200 @@ public void PartialMember_DeleteInsert_ConstructorWithInitializers() #region Methods + [Theory] + [InlineData("static")] + [InlineData("virtual")] + [InlineData("abstract")] + [InlineData("override")] + [InlineData("sealed override", "override")] + public void Method_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "int F() => 0; }"; + var src2 = "class C { " + newModifiers + "int F() => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "int F() => 0;]@10 -> [" + newModifiers + "int F() => 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "int F()", FeaturesResources.method)); + } + + [Fact] + public void Method_NewModifier_Add() + { + var src1 = "class C { int F() => 0; }"; + var src2 = "class C { new int F() => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [int F() => 0;]@10 -> [new int F() => 0;]@10"); + + // Currently, an edit is produced eventhough there is no metadata/IL change. Consider improving. + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F"))); + } + + [Fact] + public void Method_NewModifier_Remove() + { + var src1 = "class C { new int F() => 0; }"; + var src2 = "class C { int F() => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [new int F() => 0;]@10 -> [int F() => 0;]@10"); + + // Currently, an edit is produced eventhough there is no metadata/IL change. Consider improving. + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F"))); + } + + [Fact] + public void Method_ReadOnlyModifier_Add_InMutableStruct() + { + var src1 = @" +struct S +{ + public int M() => 1; +}"; + var src2 = @" +struct S +{ + public readonly int M() => 1; +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int M()", FeaturesResources.method)); + } + + [Fact] + public void Method_ReadOnlyModifier_Add_InReadOnlyStruct1() + { + var src1 = @" +readonly struct S +{ + public int M() + => 1; +}"; + var src2 = @" +readonly struct S +{ + public readonly int M() + => 1; +}"; + + var edits = GetTopEdits(src1, src2); + + // Currently, an edit is produced eventhough the body nor IsReadOnly attribute have changed. Consider improving. + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("M"))); + } + + [Fact] + public void Method_ReadOnlyModifier_Add_InReadOnlyStruct2() + { + var src1 = @" +readonly struct S +{ + public int M() => 1; +}"; + var src2 = @" +struct S +{ + public readonly int M() => 1; +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "struct S", "struct")); + } + + [Fact] + public void Method_AsyncModifier_Remove() + { + var src1 = @" +class Test +{ + public async Task WaitAsync() + { + return 1; + } +}"; + var src2 = @" +class Test +{ + public Task WaitAsync() + { + return Task.FromResult(1); + } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "public Task WaitAsync()", FeaturesResources.method)); + } + + [Fact] + public void Method_AsyncModifier_Add() + { + var src1 = @" +class Test +{ + public Task WaitAsync() + { + return 1; + } +}"; + var src2 = @" +class Test +{ + public async Task WaitAsync() + { + await Task.Delay(1000); + return 1; + } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics(); + + VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); + } + + [Fact] + public void Method_AsyncModifier_Add_NotSupported() + { + var src1 = @" +class Test +{ + public Task WaitAsync() + { + return 1; + } +}"; + var src2 = @" +class Test +{ + public async Task WaitAsync() + { + await Task.Delay(1000); + return 1; + } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + capabilities: EditAndContinueTestHelpers.BaselineCapabilities, + Diagnostic(RudeEditKind.MakeMethodAsync, "public async Task WaitAsync()")); + } + [Fact] public void Method_Update() { @@ -5581,11 +5828,56 @@ public void ExternMethodInsert() var src1 = @" using System; using System.Runtime.InteropServices; - -class C -{ -}"; - var src2 = @" + +class C +{ +}"; + var src2 = @" +using System; +using System.Runtime.InteropServices; + +class C +{ + [DllImport(""msvcrt.dll"")] + private static extern int puts(string c); +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + @"Insert [[DllImport(""msvcrt.dll"")] + private static extern int puts(string c);]@74", + "Insert [(string c)]@135", + "Insert [string c]@136"); + + // CLR doesn't support methods without a body + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertExtern, "private static extern int puts(string c)", FeaturesResources.method)); + } + + [Fact] + [WorkItem(755784, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755784"), WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] + public void ExternMethodDeleteInsert() + { + var srcA1 = @" +using System; +using System.Runtime.InteropServices; + +class C +{ + [DllImport(""msvcrt.dll"")] + private static extern int puts(string c); +}"; + var srcA2 = @" +using System; +using System.Runtime.InteropServices; +"; + + var srcB1 = @" +using System; +using System.Runtime.InteropServices; +"; + var srcB2 = @" using System; using System.Runtime.InteropServices; @@ -5595,22 +5887,22 @@ class C private static extern int puts(string c); } "; - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - @"Insert [[DllImport(""msvcrt.dll"")] - private static extern int puts(string c);]@74", - "Insert [(string c)]@135", - "Insert [string c]@136"); - - // CLR doesn't support methods without a body - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.InsertExtern, "private static extern int puts(string c)", FeaturesResources.method)); + // TODO: The method does not need to be updated since there are no sequence points generated for it. + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.puts")), + }) + }); } - [WorkItem(755784, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755784"), WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] [Fact] - public void ExternMethodDeleteInsert() + [WorkItem(755784, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/755784"), WorkItem(835827, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/835827")] + public void ExternMethod_Attribute_DeleteInsert() { var srcA1 = @" using System; @@ -5637,6 +5929,7 @@ class C class C { [DllImport(""msvcrt.dll"")] + [Obsolete] private static extern int puts(string c); } "; @@ -5645,8 +5938,12 @@ class C new[] { DocumentResults(), - DocumentResults() - }); + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.puts")), + }) + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); } [Fact] @@ -5872,82 +6169,6 @@ static void Main() { } Diagnostic(RudeEditKind.Delete, "static void Main()", FeaturesResources.parameter)); } - [Fact] - public void MethodUpdate_Modifier_Async_Remove() - { - var src1 = @" -class Test -{ - public async Task WaitAsync() - { - return 1; - } -}"; - var src2 = @" -class Test -{ - public Task WaitAsync() - { - return Task.FromResult(1); - } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "public Task WaitAsync()", FeaturesResources.method)); - } - - [Fact] - public void MethodUpdate_Modifier_Async_Add() - { - var src1 = @" -class Test -{ - public Task WaitAsync() - { - return 1; - } -}"; - var src2 = @" -class Test -{ - public async Task WaitAsync() - { - await Task.Delay(1000); - return 1; - } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics(); - - VerifyPreserveLocalVariables(edits, preserveLocalVariables: false); - } - - [Fact] - public void MethodUpdate_Modifier_Async_Add_NotSupported() - { - var src1 = @" -class Test -{ - public Task WaitAsync() - { - return 1; - } -}"; - var src2 = @" -class Test -{ - public async Task WaitAsync() - { - await Task.Delay(1000); - return 1; - } -}"; - var edits = GetTopEdits(src1, src2); - edits.VerifyRudeDiagnostics( - capabilities: EditAndContinueTestHelpers.BaselineCapabilities, - Diagnostic(RudeEditKind.MakeMethodAsync, "public async Task WaitAsync()")); - } - [Fact] public void MethodUpdate_AsyncMethod0() { @@ -7221,6 +7442,34 @@ public void PartialMethod_Insert() #region Operators + [Theory] + [InlineData("implicit", "explicit")] + [InlineData("explicit", "implicit")] + public void Operator_Modifiers_Update(string oldModifiers, string newModifiers) + { + var src1 = "class C { public static " + oldModifiers + " operator int (C c) => 0; }"; + var src2 = "class C { public static " + newModifiers + " operator int (C c) => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [public static " + oldModifiers + " operator int (C c) => 0;]@10 -> [public static " + newModifiers + " operator int (C c) => 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public static " + newModifiers + " operator int (C c)", CSharpFeaturesResources.conversion_operator)); + } + + [Fact] + public void Operator_Conversion_ExternModifiers_Add() + { + var src1 = "class C { public static implicit operator bool (C c) => default; }"; + var src2 = "class C { extern public static implicit operator bool (C c); }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.MethodBodyDelete, "extern public static implicit operator bool (C c)", CSharpFeaturesResources.conversion_operator)); + } + [Fact] public void OperatorInsert() { @@ -7494,6 +7743,23 @@ public void Operator_ReadOnlyRef_Parameter_Update() #region Constructor, Destructor + [Fact] + [WorkItem(2068, "https://github.com/dotnet/roslyn/issues/2068")] + public void Constructor_ExternModifier_Add() + { + var src1 = "class C { }"; + var src2 = "class C { public extern C(); }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [public extern C();]@10", + "Insert [()]@25"); + + // The compiler generates an empty constructor. + edits.VerifySemanticDiagnostics(); + } + [Fact] public void ConstructorInitializer_Update1() { @@ -7634,7 +7900,7 @@ public void DestructorDelete_InsertConstructor() "Delete [~B() { }]@10"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "B()", FeaturesResources.constructor), + Diagnostic(RudeEditKind.ChangingAccessibility, "B()", FeaturesResources.constructor), Diagnostic(RudeEditKind.Delete, "class B", DeletedSymbolDisplay(CSharpFeaturesResources.destructor, "~B()"))); } @@ -7682,15 +7948,15 @@ public void InstanceCtorDelete_Public() [InlineData("internal")] [InlineData("private protected")] [InlineData("protected internal")] - public void InstanceCtorDelete_NonPublic(string visibility) + public void InstanceCtorDelete_NonPublic(string accessibility) { - var src1 = "class C { " + visibility + " C() { } }"; + var src1 = "class C { " + accessibility + " C() { } }"; var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -7810,7 +8076,7 @@ public void InstanceCtorInsert_Private_Implicit1() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "private C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "private C()", FeaturesResources.constructor)); } [Fact] @@ -7822,7 +8088,7 @@ public void InstanceCtorInsert_Private_Implicit2() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "C()", FeaturesResources.constructor)); } [Fact] @@ -7834,7 +8100,7 @@ public void InstanceCtorInsert_Protected_PublicImplicit() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "protected C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "protected C()", FeaturesResources.constructor)); } [Fact] @@ -7846,7 +8112,7 @@ public void InstanceCtorInsert_Internal_PublicImplicit() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "internal C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "internal C()", FeaturesResources.constructor)); } [Fact] @@ -7858,7 +8124,7 @@ public void InstanceCtorInsert_Internal_ProtectedImplicit() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "internal C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.ChangingAccessibility, "internal C()", FeaturesResources.constructor)); } [Fact] @@ -8012,12 +8278,12 @@ public void InstanceCtor_Partial_DeletePrivateInsertPublic() new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - // delete of the constructor in partial part will be reported as rude edit in the other document where it was inserted back with changed visibility + // delete of the constructor in partial part will be reported as rude edit in the other document where it was inserted back with changed accessibility DocumentResults( semanticEdits: NoSemanticEdits), DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.ModifiersUpdate, "public C()", FeaturesResources.constructor) }), + diagnostics: new[] { Diagnostic(RudeEditKind.ChangingAccessibility, "public C()", FeaturesResources.constructor) }), }); } @@ -8155,9 +8421,9 @@ public void InstanceCtor_Partial_InsertPublicDeletePrivate() new[] { DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.ModifiersUpdate, "public C()", FeaturesResources.constructor) }), + diagnostics: new[] { Diagnostic(RudeEditKind.ChangingAccessibility, "public C()", FeaturesResources.constructor) }), - // delete of the constructor in partial part will be reported as rude in the the other document where it was inserted with changed visibility + // delete of the constructor in partial part will be reported as rude in the the other document where it was inserted with changed accessibility DocumentResults(), }); } @@ -8176,7 +8442,7 @@ public void InstanceCtor_Partial_InsertInternalDeletePrivate() new[] { DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.ModifiersUpdate, "internal C()", FeaturesResources.constructor) }), + diagnostics: new[] { Diagnostic(RudeEditKind.ChangingAccessibility, "internal C()", FeaturesResources.constructor) }), DocumentResults(), }); @@ -8581,23 +8847,6 @@ public void InstanceCtor_Partial_Implicit_Update() }); } - [WorkItem(2068, "https://github.com/dotnet/roslyn/issues/2068")] - [Fact] - public void Insert_ExternConstruct() - { - var src1 = "class C { }"; - var src2 = "class C { public extern C(); }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Insert [public extern C();]@10", - "Insert [()]@25"); - - // The compiler generates an empty constructor. - edits.VerifySemanticDiagnostics(); - } - [Fact] public void ParameterlessConstructor_SemanticError_Delete1() { @@ -8616,7 +8865,7 @@ class C // The compiler interprets D() as a constructor declaration. edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -9070,7 +9319,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate_Private() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -9082,7 +9331,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate_Private() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } [Fact] @@ -10469,6 +10718,109 @@ public void FieldUpdate_FieldKind() Diagnostic(RudeEditKind.FieldKindUpdate, "event Action a", CSharpFeaturesResources.event_field)); } + [Theory] + [InlineData("static")] + [InlineData("volatile")] + [InlineData("const")] + public void Field_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "int F = 0; }"; + var src2 = "class C { " + newModifiers + "int F = 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "int F = 0;]@10 -> [" + newModifiers + "int F = 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "F = 0", FeaturesResources.field)); + } + + [Fact] + public void Field_Modifier_Add_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int F; }"; + + var srcA2 = "partial class C { static int F; }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + diagnostics: new[] + { + Diagnostic(RudeEditKind.ModifiersUpdate, "F", FeaturesResources.field) + }), + + DocumentResults(), + }); + } + + [Fact] + public void Field_Attribute_Add_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int F; }"; + + var srcA2 = "partial class C { [System.Obsolete]int F; }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")) + }), + + DocumentResults(), + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + } + + [Fact] + public void Field_FixedSize_Update() + { + var src1 = "struct S { public unsafe fixed byte bs[1]; }"; + var src2 = "struct S { public unsafe fixed byte bs[2]; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [bs[1]]@36 -> [bs[2]]@36"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.FixedSizeFieldUpdate, "bs[2]", FeaturesResources.field)); + } + + [WorkItem(1120407, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1120407")] + [Fact] + public void Field_Const_Update() + { + var src1 = "class C { const int x = 0; }"; + var src2 = "class C { const int x = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [x = 0]@20 -> [x = 1]@20"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Update, "x = 1", FeaturesResources.const_field)); + } + [Fact] public void EventFieldUpdate_VariableDeclarator() { @@ -10956,7 +11308,7 @@ public void FieldInsert_Static_NotSupportedByRuntime() } [Fact] - public void FieldUpdate_AddAttribute_Multiple() + public void Field_Attribute_Add_NotSupportedByRuntime() { var src1 = @" class C @@ -10966,40 +11318,91 @@ class C var src2 = @" class C { - [System.Obsolete]public int a = 1, x = 1; + [System.Obsolete]public int a = 1, x = 1; +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [public int a = 1, x = 1;]@18 -> [[System.Obsolete]public int a = 1, x = 1;]@18"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "a = 1", FeaturesResources.field)); + } + + [Fact] + public void Field_Attribute_Add() + { + var src1 = @" +class C +{ + public int a; +}"; + var src2 = @" +class C +{ + [System.Obsolete]public int a; }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [public int a = 1, x = 1;]@18 -> [[System.Obsolete]public int a = 1, x = 1;]@18"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "a = 1", FeaturesResources.field)); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a")) + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); } [Fact] - public void FieldUpdate_AddAttribute_SupportedByRuntime() + public void Field_Attribute_Add_WithInitializer() { var src1 = @" class C { - public int a; + int a; }"; var src2 = @" class C { - [System.Obsolete]public int a; + [System.Obsolete]int a = 0; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a")) + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true), }, - capabilities: EditAndContinueTestHelpers.Net5RuntimeCapabilities | EditAndContinueCapabilities.ChangeCustomAttributes); + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); + } + + [Fact] + public void Field_Attribute_DeleteInsertUpdate_WithInitializer() + { + var srcA1 = "partial class C { int a = 1; }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { [System.Obsolete]int a = 2; }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), + }, + capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); } [Fact] @@ -11118,6 +11521,35 @@ public void EventField_Partial_InsertDelete() #region Properties + [Theory] + [InlineData("static")] + [InlineData("virtual")] + [InlineData("abstract")] + [InlineData("override")] + [InlineData("sealed override", "override")] + public void Property_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "int F => 0; }"; + var src2 = "class C { " + newModifiers + "int F => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "int F => 0;]@10 -> [" + newModifiers + "int F => 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "int F", FeaturesResources.property_)); + } + [Fact] public void PropertyWithExpressionBody_Update() { @@ -11977,11 +12409,20 @@ public void AutoProperty_Partial_InsertDelete() var srcA2 = "partial class C { int P { get; set; } int Q { get; init; } }"; var srcB2 = "partial class C { }"; + // Accessors need to be updated even though they do not have an explicit body. + // There is still a sequence point generated for them whose location needs to be updated. EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("Q").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("Q").SetMethod), + }), DocumentResults(), }); } @@ -11995,12 +12436,19 @@ public void AutoPropertyWithInitializer_Partial_InsertDelete() var srcA2 = "partial class C { int P { get; set; } = 1; }"; var srcB2 = "partial class C { }"; + // Accessors need to be updated even though they do not have an explicit body. + // There is still a sequence point generated for them whose location needs to be updated. EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }), + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), DocumentResults(), }); @@ -12026,10 +12474,114 @@ public void PropertyWithExpressionBody_Partial_InsertDeleteUpdate() }); } + [Fact] + public void AutoProperty_ReadOnly_Add() + { + var src1 = @" +struct S +{ + int P { get; } +}"; + var src2 = @" +struct S +{ + readonly int P { get; } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifySemanticDiagnostics(); + } + + [Fact] + public void Property_InMutableStruct_ReadOnly_Add() + { + var src1 = @" +struct S +{ + int P1 { get => 1; } + int P2 { get => 1; set {}} + int P3 { get => 1; set {}} + int P4 { get => 1; set {}} +}"; + var src2 = @" +struct S +{ + readonly int P1 { get => 1; } + int P2 { readonly get => 1; set {}} + int P3 { get => 1; readonly set {}} + readonly int P4 { get => 1; set {}} +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.property_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.property_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "set", CSharpFeaturesResources.property_setter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.property_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.property_setter)); + } + + [Fact] + public void Property_InReadOnlyStruct_ReadOnly_Add() + { + // indent to align accessor bodies and avoid updates caused by sequence point location changes + + var src1 = @" +readonly struct S +{ + int P1 { get => 1; } + int P2 { get => 1; set {}} + int P3 { get => 1; set {}} + int P4 { get => 1; set {}} +}"; + var src2 = @" +readonly struct S +{ + readonly int P1 { get => 1; } + int P2 { readonly get => 1; set {}} + int P3 { get => 1; readonly set {}} + readonly int P4 { get => 1; set {}} +}"; + var edits = GetTopEdits(src1, src2); + + // updates only for accessors whose modifiers were explicitly updated + edits.VerifySemantics(new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("P2").GetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("P3").SetMethod, preserveLocalVariables: false) + }); + } + #endregion #region Indexers + [Theory] + [InlineData("virtual")] + [InlineData("abstract")] + [InlineData("override")] + [InlineData("sealed override", "override")] + public void Indexer_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "int this[int a] => 0; }"; + var src2 = "class C { " + newModifiers + "int this[int a] => 0; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "int this[int a] => 0;]@10 -> [" + newModifiers + "int this[int a] => 0;]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "int this[int a]", FeaturesResources.indexer_)); + } + [Fact] public void Indexer_GetterUpdate() { @@ -12694,49 +13246,6 @@ public void Indexer_Insert() edits.VerifySemanticDiagnostics(); } - [WorkItem(1120407, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1120407")] - [Fact] - public void ConstField_Update() - { - var src1 = "class C { const int x = 0; }"; - var src2 = "class C { const int x = 1; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits("Update [x = 0]@20 -> [x = 1]@20"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Update, "x = 1", FeaturesResources.const_field)); - } - - [Fact] - public void ConstField_Delete() - { - var src1 = "class C { const int x = 0; }"; - var src2 = "class C { int x = 0; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits("Update [const int x = 0;]@10 -> [int x = 0;]@10"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "int x = 0", FeaturesResources.field)); - } - - [Fact] - public void ConstField_Add() - { - var src1 = "class C { int x = 0; }"; - var src2 = "class C { const int x = 0; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits("Update [int x = 0;]@10 -> [const int x = 0;]@10"); - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "const int x = 0", FeaturesResources.const_field)); - } - [Fact] public void Indexer_ReadOnlyRef_Parameter_InsertWhole() { @@ -12851,16 +13360,23 @@ public void IndexerInit_Partial_InsertDelete() public void AutoIndexer_Partial_InsertDelete() { var srcA1 = "partial class C { }"; - var srcB1 = "partial class C { int this[int x] { get; set; } int Q { get; init; } }"; + var srcB1 = "partial class C { int this[int x] { get; set; } }"; - var srcA2 = "partial class C { int this[int x] { get; set; } int Q { get; init; } }"; + var srcA2 = "partial class C { int this[int x] { get; set; } }"; var srcB2 = "partial class C { }"; + // Accessors need to be updated even though they do not have an explicit body. + // There is still a sequence point generated for them whose location needs to be updated. EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").SetMethod), + }), DocumentResults(), }); } @@ -12897,10 +13413,116 @@ partial class C }); } + [Fact] + public void AutoIndexer_ReadOnly_Add() + { + var src1 = @" +struct S +{ + int this[int x] { get; } +}"; + var src2 = @" +struct S +{ + readonly int this[int x] { get; } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.indexer_getter)); + } + + [Fact] + public void Indexer_InMutableStruct_ReadOnly_Add() + { + var src1 = @" +struct S +{ + int this[int x] { get => 1; } + int this[uint x] { get => 1; set {}} + int this[byte x] { get => 1; set {}} + int this[sbyte x] { get => 1; set {}} +}"; + var src2 = @" +struct S +{ + readonly int this[int x] { get => 1; } + int this[uint x] { readonly get => 1; set {}} + int this[byte x] { get => 1; readonly set {}} + readonly int this[sbyte x] { get => 1; set {}} +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.indexer_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "get", CSharpFeaturesResources.indexer_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "set", CSharpFeaturesResources.indexer_setter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.indexer_getter), + Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.indexer_setter)); + } + + [Fact] + public void Indexer_InReadOnlyStruct_ReadOnly_Add() + { + // indent to align accessor bodies and avoid updates caused by sequence point location changes + + var src1 = @" +readonly struct S +{ + int this[int x] { get => 1; } + int this[uint x] { get => 1; set {}} + int this[byte x] { get => 1; set {}} + int this[sbyte x] { get => 1; set {}} +}"; + var src2 = @" +readonly struct S +{ + readonly int this[int x] { get => 1; } + int this[uint x] { readonly get => 1; set {}} + int this[byte x] { get => 1; readonly set {}} + readonly int this[sbyte x] { get => 1; set {}} +}"; + var edits = GetTopEdits(src1, src2); + + // updates only for accessors whose modifiers were explicitly updated + edits.VerifySemantics(new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMembers("this[]").Cast().Single(m => m.Parameters.Single().Type.Name == "UInt32").GetMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMembers("this[]").Cast().Single(m => m.Parameters.Single().Type.Name == "Byte").SetMethod, preserveLocalVariables: false) + }); + } + #endregion #region Events + [Theory] + [InlineData("static")] + [InlineData("virtual")] + [InlineData("abstract")] + [InlineData("override")] + [InlineData("sealed override", "override")] + public void Event_Modifiers_Update(string oldModifiers, string newModifiers = "") + { + if (oldModifiers != "") + { + oldModifiers += " "; + } + + if (newModifiers != "") + { + newModifiers += " "; + } + + var src1 = "class C { " + oldModifiers + "event Action F { add {} remove {} } }"; + var src2 = "class C { " + newModifiers + "event Action F { add {} remove {} } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [" + oldModifiers + "event Action F { add {} remove {} }]@10 -> [" + newModifiers + "event Action F { add {} remove {} }]@10"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "event Action F", FeaturesResources.event_)); + } + [Fact] public void EventAccessorReorder1() { @@ -13074,6 +13696,45 @@ public void Event_Partial_InsertDelete() }); } + [Fact] + public void Event_InMutableStruct_ReadOnly_Add() + { + var src1 = @" +struct S +{ + public event Action E { add {} remove {} } +}"; + var src2 = @" +struct S +{ + public readonly event Action E { add {} remove {} } +}"; + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly event Action E", FeaturesResources.event_)); + } + + [Fact] + public void Event_InReadOnlyStruct_ReadOnly_Add1() + { + var src1 = @" +readonly struct S +{ + public event Action E { add {} remove {} } +}"; + var src2 = @" +readonly struct S +{ + public readonly event Action E { add {} remove {} } +}"; + var edits = GetTopEdits(src1, src2); + + // Currently, an edit is produced eventhough bodies nor IsReadOnly attribute have changed. Consider improving. + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("E").AddMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("E").RemoveMethod)); + } + #endregion #region Parameter diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index fe39597f7c5a2..88b41f12b85b4 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -24,11 +24,17 @@ namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests internal abstract class EditAndContinueTestHelpers { public static readonly EditAndContinueCapabilities BaselineCapabilities = EditAndContinueCapabilities.Baseline; - public static readonly EditAndContinueCapabilities Net5RuntimeCapabilities = EditAndContinueCapabilities.Baseline | - EditAndContinueCapabilities.AddInstanceFieldToExistingType | - EditAndContinueCapabilities.AddStaticFieldToExistingType | - EditAndContinueCapabilities.AddMethodToExistingType | - EditAndContinueCapabilities.NewTypeDefinition; + + public static readonly EditAndContinueCapabilities Net5RuntimeCapabilities = + EditAndContinueCapabilities.Baseline | + EditAndContinueCapabilities.AddInstanceFieldToExistingType | + EditAndContinueCapabilities.AddStaticFieldToExistingType | + EditAndContinueCapabilities.AddMethodToExistingType | + EditAndContinueCapabilities.NewTypeDefinition; + + public static readonly EditAndContinueCapabilities Net6RuntimeCapabilities = + Net5RuntimeCapabilities | + EditAndContinueCapabilities.ChangeCustomAttributes; public abstract AbstractEditAndContinueAnalyzer Analyzer { get; } public abstract SyntaxNode FindNode(SyntaxNode root, TextSpan span); diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index aecb17e8655b2..1a465c7cc1b3a 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -1945,7 +1945,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ModifiersUpdate, "Private Const a As Integer = 1", FeaturesResources.const_field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "a As Integer = 1", FeaturesResources.const_field)) End Sub @@ -1990,7 +1990,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifyRudeDiagnostics(active, - Diagnostic(RudeEditKind.ModifiersUpdate, "Private Const a As Integer = 1, b As Integer = 2", FeaturesResources.const_field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "a As Integer = 1", FeaturesResources.const_field)) End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index c2c191621330f..5a6a8a423c7ee 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -29,6 +29,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Iterator End Enum + Public Shared Function GetResource(keyword As String) As String + Select Case keyword + Case "Class" + Return FeaturesResources.class_ + Case "Structure" + Return VBFeaturesResources.structure_ + Case "Module" + Return VBFeaturesResources.module_ + Case "Interface" + Return FeaturesResources.interface_ + Case Else + Throw ExceptionUtilities.UnexpectedValue(keyword) + End Select + End Function + Friend Shared NoSemanticEdits As SemanticEditDescription() = Array.Empty(Of SemanticEditDescription) Friend Overloads Shared Function Diagnostic(rudeEditKind As RudeEditKind, squiggle As String, ParamArray arguments As String()) As RudeEditDiagnosticDescription diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 519ab947a0a8d..976d8b43337a6 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -281,51 +281,6 @@ Option Strict Off #End Region #Region "Attributes" - - Public Sub UpdateAttributes1() - Dim attribute = "Public Class A1Attribute : Inherits System.Attribute : End Class" & vbCrLf & - "Public Class A2Attribute : Inherits System.Attribute : End Class" & vbCrLf - - Dim src1 = attribute & "Class C : End Class" - Dim src2 = attribute & "Class C : End Class" - - Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Class C]@132 -> [Class C]@132") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Class C", FeaturesResources.class_)) - End Sub - - - Public Sub UpdateAttributes2() - Dim src1 = "Class C : End Class" - Dim src2 = "Class C : End Class" - Dim edits = GetTopEdits(src1, src2) - - edits.VerifyEdits( - "Update [Class C]@0 -> [Class C]@0") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Class C", FeaturesResources.class_)) - End Sub - - - Public Sub UpdateAttributes3() - Dim attribute = "Public Class A1Attribute : Inherits System.Attribute : End Class" & vbCrLf & - "Public Class A2Attribute : Inherits System.Attribute : End Class" & vbCrLf - - Dim src1 = attribute & "Module C : End Module" - Dim src2 = attribute & "Module C : End Module" - - Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Module C]@132 -> [Module C]@132") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Module C", VBFeaturesResources.module_)) - End Sub - Public Sub UpdateAttributes_TopLevel1() Dim src1 = "" @@ -472,119 +427,173 @@ Option Strict Off #End Region -#Region "Top-level Classes, Structs, Interfaces, Modules" - - Public Sub ClassInsert() - Dim src1 = "" - Dim src2 = "Class C : End Class" +#Region "Types" + + + + + Public Sub Type_Kind_Update(oldKeyword As String, newKeyword As String) + Dim src1 = oldKeyword & " C : End " & oldKeyword + Dim src2 = newKeyword & " C : End " & newKeyword Dim edits = GetTopEdits(src1, src2) - edits.VerifyRudeDiagnostics() + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.TypeKindUpdate, newKeyword & " C")) End Sub - - Public Sub StructInsert() - Dim src1 = "" - Dim src2 = "Structure C : End Structure" - Dim edits = GetTopEdits(src1, src2) + + + Public Sub Type_Modifiers_Accessibility_Change(accessibility As String) - edits.VerifyRudeDiagnostics() - End Sub + Dim src1 = accessibility + " Class C : End Class" + Dim src2 = "Class C : End Class" - - Public Sub PartialInterfaceInsert() - Dim src1 = "" - Dim src2 = "Partial Interface C : End Interface" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemantics(semanticEdits:= - { - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C")) - }) + edits.VerifyEdits( + "Update [" + accessibility + " Class C]@0 -> [Class C]@0") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingAccessibility, "Class C", FeaturesResources.class_)) End Sub - - Public Sub PartialInterfaceDelete() - Dim src1 = "Partial Interface C : End Interface" - Dim src2 = "" - Dim edits = GetTopEdits(src1, src2) + + + + + + + + + + Public Sub Type_Modifiers_Accessibility_Partial(accessibilityA As String, accessibilityB As String) + + Dim srcA1 = accessibilityA + " Partial Class C : End Class" + Dim srcB1 = "Partial Class C : End Class" + Dim srcA2 = "Partial Class C : End Class" + Dim srcB2 = accessibilityB + " Partial Class C : End Class" - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.interface_, "C"))) + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) End Sub - - Public Sub ModuleInsert() - Dim src1 = "" - Dim src2 = "Module C : End Module" - Dim edits = GetTopEdits(src1, src2) + + + + + + Public Sub Type_Modifiers_Friend_Remove(keyword As String) + Dim src1 = "Friend " & keyword & " C : End " & keyword + Dim src2 = keyword & " C : End " & keyword - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Module C", VBFeaturesResources.module_)) + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics() End Sub - - Public Sub PartialModuleInsert() - Dim src1 = "" - Dim src2 = "Partial Module C : End Module" + + + + + + Public Sub Type_Modifiers_Friend_Add(keyword As String) + Dim src1 = keyword & " C : End " & keyword + Dim src2 = "Friend " & keyword & " C : End " & keyword + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics() + End Sub + + + + + + Public Sub Type_Modifiers_NestedPrivateInInterface_Remove(keyword As String) + Dim src1 = "Interface C : Private " & keyword & " S : End " & keyword & " : End Interface" + Dim src2 = "Interface C : " & keyword & " S : End " & keyword & " : End Interface" + Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Insert, "Partial Module C", VBFeaturesResources.module_)) + Diagnostic(RudeEditKind.ChangingAccessibility, keyword + " S", GetResource(keyword))) End Sub - - Public Sub PartialModuleDelete() - Dim src1 = "Partial Module C : End Module" - Dim src2 = "" + + + + + Public Sub Type_Modifiers_NestedPublicInClass_Add(keyword As String) + Dim src1 = "Class C : " & keyword & " S : End " & keyword & " : End Class" + Dim src2 = "Class C : Public " & keyword & " S : End " & keyword & " : End Class" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics() + End Sub - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(VBFeaturesResources.module_, "C"))) + + + + + Public Sub Type_Modifiers_NestedPublicInInterface_Add(keyword As String) + Dim src1 = "Interface I : " + keyword + " S : End " & keyword & " : End Interface" + Dim src2 = "Interface I : Public " + keyword + " S : End " & keyword & " : End Interface" + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics() End Sub - Public Sub TypeKindChange1() - Dim src1 = "Class C : End Class" - Dim src2 = "Structure C : End Structure" - Dim edits = GetTopEdits(src1, src2) + Public Sub Type_Attribute_Update_NotSupportedByRuntime1() + Dim attribute = "Public Class A1Attribute : Inherits System.Attribute : End Class" & vbCrLf & + "Public Class A2Attribute : Inherits System.Attribute : End Class" & vbCrLf + + Dim src1 = attribute & "Class C : End Class" + Dim src2 = attribute & "Class C : End Class" + Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Class C : End Class]@0 -> [Structure C : End Structure]@0", - "Update [Class C]@0 -> [Structure C]@0") + "Update [Class C]@132 -> [Class C]@132") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "Structure C", VBFeaturesResources.structure_)) + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Class C", FeaturesResources.class_)) End Sub - - Public Sub TypeKindChange2() - Dim src1 = "Public Module C : End Module" - Dim src2 = "Public Class C : End Class" + + + + + + Public Sub Type_Attribute_Update_NotSupportedByRuntime(keyword As String) + Dim src1 = "" & keyword & " C : End " & keyword + Dim src2 = "" & keyword & " C : End " & keyword Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Public Module C : End Module]@0 -> [Public Class C : End Class]@0", - "Update [Public Module C]@0 -> [Public Class C]@0") + "Update [" & keyword & " C]@0 -> [" & keyword & " C]@0") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.TypeKindUpdate, "Public Class C", FeaturesResources.class_)) + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, keyword & " C", GetResource(keyword))) End Sub - - Public Sub ModuleModifiersUpdate() - Dim src1 = "Module C : End Module" - Dim src2 = "Partial Module C : End Module" + + + + + + Public Sub Type_PartialModifier_Add(keyword As String) + Dim src1 = keyword & " C : End " & keyword + Dim src2 = "Partial " & keyword & " C : End " & keyword Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Update [Module C]@0 -> [Partial Module C]@0") + "Update [" & keyword & " C]@0 -> [Partial " & keyword & " C]@0") edits.VerifyRudeDiagnostics() End Sub - Public Sub ModuleModifiersUpdate2() + Public Sub Module_PartialModifier_Remove() Dim src1 = "Partial Module C : End Module" Dim src2 = "Module C : End Module" Dim edits = GetTopEdits(src1, src2) @@ -596,53 +605,73 @@ Option Strict Off End Sub - Public Sub InterfaceModifiersUpdate() - Dim src1 = "Public Interface C : End Interface" - Dim src2 = "Interface C : End Interface" + Public Sub ClassInsert() + Dim src1 = "" + Dim src2 = "Class C : End Class" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Public Interface C]@0 -> [Interface C]@0") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Interface C", FeaturesResources.interface_)) + edits.VerifyRudeDiagnostics() End Sub - Public Sub InterfaceModifiersUpdate2() - Dim src1 = "Public Interface C : Sub Goo() : End Interface" - Dim src2 = "Interface C : Sub Goo() : End Interface" + Public Sub StructInsert() + Dim src1 = "" + Dim src2 = "Structure C : End Structure" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Public Interface C]@0 -> [Interface C]@0") + edits.VerifyRudeDiagnostics() + End Sub - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Interface C", FeaturesResources.interface_)) + + Public Sub PartialInterfaceInsert() + Dim src1 = "" + Dim src2 = "Partial Interface C : End Interface" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemantics(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C")) + }) End Sub - Public Sub InterfaceModifiersUpdate3() - Dim src1 = "Interface C : Sub Goo() : End Interface" - Dim src2 = "Partial Interface C : Sub Goo() : End Interface" + Public Sub PartialInterfaceDelete() + Dim src1 = "Partial Interface C : End Interface" + Dim src2 = "" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits( - "Update [Interface C]@0 -> [Partial Interface C]@0") + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.interface_, "C"))) + End Sub - edits.VerifyRudeDiagnostics() + + Public Sub ModuleInsert() + Dim src1 = "" + Dim src2 = "Module C : End Module" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "Module C", VBFeaturesResources.module_)) End Sub - Public Sub StructModifiersUpdate() - Dim src1 = "Structure C : End Structure" - Dim src2 = "Public Structure C : End Structure" + Public Sub PartialModuleInsert() + Dim src1 = "" + Dim src2 = "Partial Module C : End Module" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Insert, "Partial Module C", VBFeaturesResources.module_)) + End Sub + + Public Sub PartialModuleDelete() + Dim src1 = "Partial Module C : End Module" + Dim src2 = "" Dim edits = GetTopEdits(src1, src2) - edits.VerifyEdits("Update [Structure C]@0 -> [Public Structure C]@0") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Public Structure C", VBFeaturesResources.structure_)) + Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(VBFeaturesResources.module_, "C"))) End Sub @@ -872,7 +901,11 @@ End Interface {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { DocumentResults(), - DocumentResults() + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("I.VirtualMethod")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("I.VirtualFunction")) + }) }) End Sub @@ -1102,7 +1135,8 @@ Delegate Sub D() { SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("M").GetMember("F")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("S").GetMember("F")) + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("S").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("I").GetMember("F")) }) }) End Sub @@ -1177,9 +1211,11 @@ End Interface DocumentResults( semanticEdits:= { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("AbstractMethod")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("VirtualMethod")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("ToString")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("IG")) + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("IG")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("I").GetMember("G")) }) }) End Sub @@ -1313,7 +1349,7 @@ End Class End Sub - Public Sub Enum_Modifiers_Update() + Public Sub Enum_Accessibility_Change() Dim src1 = "Public Enum Color : Red = 1 : Blue = 2 : End Enum" Dim src2 = "Enum Color : Red = 1 : Blue = 2 : End Enum" Dim edits = GetTopEdits(src1, src2) @@ -1322,7 +1358,19 @@ End Class "Update [Public Enum Color]@0 -> [Enum Color]@0") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Enum Color", FeaturesResources.enum_)) + Diagnostic(RudeEditKind.ChangingAccessibility, "Enum Color", FeaturesResources.enum_)) + End Sub + + + Public Sub Enum_Accessibility_NoChange() + Dim src1 = "Friend Enum Color : Red = 1 : Blue = 2 : End Enum" + Dim src2 = "Enum Color : Red = 1 : Blue = 2 : End Enum" + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Friend Enum Color]@0 -> [Enum Color]@0") + + edits.VerifySemantics() End Sub @@ -1581,7 +1629,7 @@ End Class End Sub - Public Sub Delegates_Update_Modifiers() + Public Sub Delegates_Accessibility_Update() Dim src1 = "Public Delegate Sub D()" Dim src2 = "Private Delegate Sub D()" Dim edits = GetTopEdits(src1, src2) @@ -1590,7 +1638,7 @@ End Class "Update [Public Delegate Sub D()]@0 -> [Private Delegate Sub D()]@0") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Private Delegate Sub D()", FeaturesResources.delegate_)) + Diagnostic(RudeEditKind.ChangingAccessibility, "Private Delegate Sub D()", FeaturesResources.delegate_)) End Sub @@ -2969,6 +3017,98 @@ End Class" #End Region #Region "Methods" + + + + + + Public Sub Method_Modifiers_Update(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = "Class C" & vbCrLf & oldModifiers & "Sub F() : End Sub : End Class" + Dim src2 = "Class C" & vbCrLf & newModifiers & "Sub F() : End Sub : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [" & oldModifiers & "Sub F() : End Sub]@9 -> [" & newModifiers & "Sub F() : End Sub]@9", + "Update [" & oldModifiers & "Sub F()]@9 -> [" & newModifiers & "Sub F()]@9") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "Sub F()", FeaturesResources.method)) + End Sub + + + Public Sub Method_MustOverrideModifier_Update() + Dim src1 = "Class C" & vbCrLf & "MustOverride Sub F() : End Class" + Dim src2 = "Class C" & vbCrLf & "Sub F() : End Sub : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "Sub F()", FeaturesResources.method)) + End Sub + + + + + Public Sub Method_Modifiers_Update_NoImpact(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = "Class C " & vbLf & oldModifiers & "Sub F() : End Sub : End Class" + Dim src2 = "Class C " & vbLf & newModifiers & "Sub F() : End Sub : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [" & oldModifiers & "Sub F() : End Sub]@9 -> [" & newModifiers & "Sub F() : End Sub]@9", + "Update [" & oldModifiers & "Sub F()]@9 -> [" & newModifiers & "Sub F()]@9") + + ' Currently, an edit is produced eventhough there is no metadata/IL change. Consider improving. + edits.VerifySemantics( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F"))}) + End Sub + + + Public Sub Method_AsyncModifier_Add() + Dim src1 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" + Dim src2 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Function F() As Task(Of String) : End Function]@11 -> [Async Function F() As Task(Of String) : End Function]@11", + "Update [Function F() As Task(Of String)]@11 -> [Async Function F() As Task(Of String)]@11") + + edits.VerifyRudeDiagnostics() + End Sub + + + Public Sub Method_AsyncModifier_Remove() + Dim src1 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" + Dim src2 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Update [Async Function F() As Task(Of String) : End Function]@11 -> [Function F() As Task(Of String) : End Function]@11", + "Update [Async Function F() As Task(Of String)]@11 -> [Function F() As Task(Of String)]@11") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "Function F()", FeaturesResources.method)) + End Sub Public Sub MethodUpdate1() @@ -3316,35 +3456,6 @@ Imports System.Runtime.InteropServices Diagnostic(RudeEditKind.Renamed, "Sub Bar", FeaturesResources.method)) End Sub - - Public Sub MethodUpdate_AsyncModifier1() - Dim src1 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" - Dim src2 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" - - Dim edits = GetTopEdits(src1, src2) - - edits.VerifyEdits( - "Update [Function F() As Task(Of String) : End Function]@11 -> [Async Function F() As Task(Of String) : End Function]@11", - "Update [Function F() As Task(Of String)]@11 -> [Async Function F() As Task(Of String)]@11") - - edits.VerifyRudeDiagnostics() - End Sub - - - Public Sub MethodUpdate_AsyncModifier2() - Dim src1 = "Class C : " & vbLf & "Async Function F() As Task(Of String) : End Function : End Class" - Dim src2 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" - - Dim edits = GetTopEdits(src1, src2) - - edits.VerifyEdits( - "Update [Async Function F() As Task(Of String) : End Function]@11 -> [Function F() As Task(Of String) : End Function]@11", - "Update [Async Function F() As Task(Of String)]@11 -> [Function F() As Task(Of String)]@11") - - edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "Function F()", FeaturesResources.method)) - End Sub - Public Sub MethodUpdate_IteratorModifier1() Dim src1 = "Class C : " & vbLf & "Function F() As Task(Of String) : End Function : End Class" @@ -3943,19 +4054,14 @@ End Class Dim srcA2 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" Dim srcB2 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" - ' TODO: current - GetTopEdits(srcA1, srcA2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.ModifiersUpdate, "Private Sub F()", FeaturesResources.method)) - GetTopEdits(srcB1, srcB2).VerifyRudeDiagnostics(Diagnostic(RudeEditKind.ModifiersUpdate, "Partial Private Sub F()", FeaturesResources.method)) - - ' TODO: correct - ' EditAndContinueValidation.VerifySemantics( - ' { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - ' - ' { - ' DocumentResults(), - ' DocumentResults( - ' semanticEdits:= { SemanticEdit(SemanticEditKind.Update, c => c.GetMember(Of NamedTypeSymbol)("C").GetMember("F")) }), - ' }); + ' TODO: should be an update edit since the location of the implementation changed + ' semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F"))} + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) End Sub @@ -4035,6 +4141,21 @@ End Class #Region "Operators" + + + + Public Sub Operator_Modifiers_Update(oldModifiers As String, newModifiers As String) + Dim src1 = "Class C" & vbCrLf & "Public Shared " & oldModifiers & " Operator CType(d As C) As Integer : End Operator : End Class" + Dim src2 = "Class C" & vbCrLf & "Public Shared " & newModifiers & " Operator CType(d As C) As Integer : End Operator : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits("Update [Public Shared " + oldModifiers + " Operator CType(d As C) As Integer]@9 -> [Public Shared " + newModifiers + " Operator CType(d As C) As Integer]@9") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "Public Shared " & newModifiers & " Operator CType(d As C)", FeaturesResources.operator_)) + End Sub + Public Sub OperatorInsert() Dim src1 = " @@ -4299,7 +4420,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) + Diagnostic(RudeEditKind.ChangingAccessibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub @@ -4411,7 +4532,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, visibility & " Sub New()", FeaturesResources.constructor)) + Diagnostic(RudeEditKind.ChangingAccessibility, visibility & " Sub New()", FeaturesResources.constructor)) End Sub @@ -4535,7 +4656,7 @@ End Class { DocumentResults(), DocumentResults( - diagnostics:={Diagnostic(RudeEditKind.ModifiersUpdate, "Public Sub New()", FeaturesResources.constructor)}) + diagnostics:={Diagnostic(RudeEditKind.ChangingAccessibility, "Public Sub New()", FeaturesResources.constructor)}) }) End Sub @@ -4656,7 +4777,7 @@ End Class - Public Sub InstanceCtor_Partial_VisibilityUpdate(visibility As String) + Public Sub InstanceCtor_Partial_AccessibilityUpdate(visibility As String) If visibility.Length > 0 Then visibility &= " " End If @@ -4672,7 +4793,7 @@ End Class {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { DocumentResults( - diagnostics:={Diagnostic(RudeEditKind.ModifiersUpdate, visibility & "Sub New()", FeaturesResources.constructor)}), + diagnostics:={Diagnostic(RudeEditKind.ChangingAccessibility, visibility & "Sub New()", FeaturesResources.constructor)}), DocumentResults() }) End Sub @@ -5318,12 +5439,35 @@ End Class {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { DocumentResults(), - DocumentResults() + DocumentResults(semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("M.ExternSub"))}) }) End Sub #End Region #Region "Fields" + + + + Public Sub Field_Modifiers_Update(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = "Class C : " & oldModifiers & "Dim F As Integer = 0 : End Class" + Dim src2 = "Class C : " & newModifiers & "Dim F As Integer = 0 : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits("Update [" & oldModifiers & "Dim F As Integer = 0]@10 -> [" & newModifiers & "Dim F As Integer = 0]@10") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "F As Integer = 0", FeaturesResources.field)) + End Sub + Public Sub FieldUpdate_Rename1() Dim src1 = "Class C : Dim a As Integer = 0 : End Class" @@ -5633,7 +5777,7 @@ End Class "Update [Dim a As WE]@10 -> [WithEvents a As WE]@10") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "WithEvents a As WE", VBFeaturesResources.WithEvents_field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "a As WE", VBFeaturesResources.WithEvents_field)) End Sub @@ -5646,7 +5790,7 @@ End Class "Update [WithEvents a As WE]@10 -> [Dim a As WE]@10") edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Dim a As WE", FeaturesResources.field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "a As WE", FeaturesResources.field)) End Sub @@ -6120,6 +6264,42 @@ End Class #End Region #Region "Properties" + + + + + + Public Sub Property_Modifiers_Update(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = "Class C" & vbLf & oldModifiers & "ReadOnly Property F As Integer" & vbLf & "Get" & vbLf & "Return 1 : End Get : End Property : End Class" + Dim src2 = "Class C" & vbLf & newModifiers & "ReadOnly Property F As Integer" & vbLf & "Get" & vbLf & "Return 1 : End Get : End Property : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits("Update [" & oldModifiers & "ReadOnly Property F As Integer]@8 -> [" & newModifiers & "ReadOnly Property F As Integer]@8") + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "ReadOnly Property F", FeaturesResources.property_)) + End Sub + + + Public Sub Property_MustOverrideModifier_Update() + Dim src1 = "Class C" & vbLf & "ReadOnly MustOverride Property F As Integer" & vbLf & "End Class" + Dim src2 = "Class C" & vbLf & "ReadOnly Property F As Integer" & vbLf & "Get" & vbLf & "Return 1 : End Get : End Property : End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "ReadOnly Property F", FeaturesResources.property_)) + End Sub + Public Sub PropertyReorder1() Dim src1 = "Class C : ReadOnly Property P" & vbLf & "Get" & vbLf & "Return 1 : End Get : End Property : " & @@ -6780,7 +6960,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) + Diagnostic(RudeEditKind.ChangingAccessibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub @@ -6790,7 +6970,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingVisibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) + Diagnostic(RudeEditKind.ChangingAccessibility, "Class C", DeletedSymbolDisplay(FeaturesResources.constructor, "New()"))) End Sub @@ -7563,7 +7743,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Dim x = 0", FeaturesResources.field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "x = 0", FeaturesResources.field)) End Sub @@ -7573,7 +7753,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyRudeDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "Const x = 0", FeaturesResources.const_field)) + Diagnostic(RudeEditKind.ModifiersUpdate, "x = 0", FeaturesResources.const_field)) End Sub @@ -8317,6 +8497,47 @@ End Class #Region "Events" + + + Public Sub Event_Modifiers_Update(oldModifiers As String, Optional newModifiers As String = "") + If oldModifiers <> "" Then + oldModifiers &= " " + End If + + If newModifiers <> "" Then + newModifiers &= " " + End If + + Dim src1 = " +Class C + " & oldModifiers & " Custom Event E As Action + AddHandler(value As Action) + End AddHandler + RemoveHandler(value As Action) + End RemoveHandler + RaiseEvent() + End RaiseEvent + End Event +End Class" + + Dim src2 = " +Class C + " & newModifiers & " Custom Event E As Action + AddHandler(value As Action) + End AddHandler + RemoveHandler(value As Action) + End RemoveHandler + RaiseEvent() + End RaiseEvent + End Event +End Class" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, newModifiers + "Event E", FeaturesResources.event_)) + End Sub + Public Sub EventAccessorReorder1() Dim src1 = "Class C : " & diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 86631db5eeff5..b55a9e9a39ab9 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1181,7 +1181,10 @@ protected override SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference referen Contract.ThrowIfNull(oldNode); Contract.ThrowIfNull(newNode); - // Either old or new property/indexer has an expression body (or both do). + // Certain updates of a property/indexer node affects its accessors. + // Return all affected symbols for these updates. + + // 1) Old or new property/indexer has an expression body: // int this[...] => expr; // int this[...] { get => expr; } // int P => expr; @@ -1198,6 +1201,36 @@ protected override SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference referen return OneOrMany.Create(ImmutableArray.Create((oldSymbol, newSymbol), (oldGetterSymbol, newGetterSymbol))); } + // 2) Property/indexer declarations differ in readonly keyword. + if (oldNode is PropertyDeclarationSyntax oldProperty && newNode is PropertyDeclarationSyntax newProperty && DiffersInReadOnlyModifier(oldProperty.Modifiers, newProperty.Modifiers) || + oldNode is IndexerDeclarationSyntax oldIndexer && newNode is IndexerDeclarationSyntax newIndexer && DiffersInReadOnlyModifier(oldIndexer.Modifiers, newIndexer.Modifiers)) + { + Debug.Assert(oldSymbol is IPropertySymbol); + Debug.Assert(newSymbol is IPropertySymbol); + + var oldPropertySymbol = (IPropertySymbol)oldSymbol; + var newPropertySymbol = (IPropertySymbol)newSymbol; + + using var _ = ArrayBuilder<(ISymbol?, ISymbol?)>.GetInstance(out var builder); + + builder.Add((oldPropertySymbol, newPropertySymbol)); + + if (oldPropertySymbol.GetMethod != null && newPropertySymbol.GetMethod != null && oldPropertySymbol.GetMethod.IsReadOnly != newPropertySymbol.GetMethod.IsReadOnly) + { + builder.Add((oldPropertySymbol.GetMethod, newPropertySymbol.GetMethod)); + } + + if (oldPropertySymbol.SetMethod != null && newPropertySymbol.SetMethod != null && oldPropertySymbol.SetMethod.IsReadOnly != newPropertySymbol.SetMethod.IsReadOnly) + { + builder.Add((oldPropertySymbol.SetMethod, newPropertySymbol.SetMethod)); + } + + return OneOrMany.Create(builder.ToImmutable()); + } + + static bool DiffersInReadOnlyModifier(SyntaxTokenList oldModifiers, SyntaxTokenList newModifiers) + => (oldModifiers.IndexOf(SyntaxKind.ReadOnlyKeyword) >= 0) != (newModifiers.IndexOf(SyntaxKind.ReadOnlyKeyword) >= 0); + break; case EditKind.Delete: @@ -2597,19 +2630,6 @@ private void ClassifyUpdate(NamespaceDeclarationSyntax oldNode, NamespaceDeclara private void ClassifyUpdate(TypeDeclarationSyntax oldNode, TypeDeclarationSyntax newNode) { - if (oldNode.Kind() != newNode.Kind()) - { - ReportError(RudeEditKind.TypeKindUpdate); - return; - } - - // Allow partial keyword to be added or removed. - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.PartialKeyword, ignore2: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier)) { ReportError(RudeEditKind.Renamed); @@ -2630,12 +2650,6 @@ private void ClassifyUpdate(EnumDeclarationSyntax oldNode, EnumDeclarationSyntax return; } - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.BaseList, newNode.BaseList)) { ReportError(RudeEditKind.EnumUnderlyingTypeUpdate); @@ -2648,12 +2662,6 @@ private void ClassifyUpdate(EnumDeclarationSyntax oldNode, EnumDeclarationSyntax private void ClassifyUpdate(DelegateDeclarationSyntax oldNode, DelegateDeclarationSyntax newNode) { - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.ReturnType, newNode.ReturnType)) { ReportError(RudeEditKind.TypeUpdate); @@ -2673,12 +2681,6 @@ private void ClassifyUpdate(BaseFieldDeclarationSyntax oldNode, BaseFieldDeclara ReportError(RudeEditKind.FieldKindUpdate); return; } - - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } } private void ClassifyUpdate(VariableDeclarationSyntax oldNode, VariableDeclarationSyntax newNode) @@ -2737,13 +2739,6 @@ private void ClassifyUpdate(MethodDeclarationSyntax oldNode, MethodDeclarationSy return; } - // Ignore async keyword when matching modifiers. Async checks are done in ComputeBodyMatch. - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.AsyncKeyword, ignore2: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.ReturnType, newNode.ReturnType)) { ReportError(RudeEditKind.TypeUpdate); @@ -2765,15 +2760,9 @@ private void ClassifyUpdate(MethodDeclarationSyntax oldNode, MethodDeclarationSy private void ClassifyUpdate(ConversionOperatorDeclarationSyntax oldNode, ConversionOperatorDeclarationSyntax newNode) { - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.ImplicitOrExplicitKeyword, newNode.ImplicitOrExplicitKeyword)) { - ReportError(RudeEditKind.Renamed); + ReportError(RudeEditKind.ModifiersUpdate); return; } @@ -2792,12 +2781,6 @@ private void ClassifyUpdate(ConversionOperatorDeclarationSyntax oldNode, Convers private void ClassifyUpdate(OperatorDeclarationSyntax oldNode, OperatorDeclarationSyntax newNode) { - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.OperatorToken, newNode.OperatorToken)) { ReportError(RudeEditKind.Renamed); @@ -2819,12 +2802,6 @@ private void ClassifyUpdate(OperatorDeclarationSyntax oldNode, OperatorDeclarati private void ClassifyUpdate(AccessorDeclarationSyntax oldNode, AccessorDeclarationSyntax newNode) { - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (oldNode.Kind() != newNode.Kind()) { ReportError(RudeEditKind.AccessorKindUpdate); @@ -2860,12 +2837,6 @@ private void ClassifyUpdate(EnumMemberDeclarationSyntax oldNode, EnumMemberDecla private void ClassifyUpdate(ConstructorDeclarationSyntax oldNode, ConstructorDeclarationSyntax newNode) { - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - ClassifyMethodBodyRudeUpdate( (SyntaxNode?)oldNode.Body ?? oldNode.ExpressionBody?.Expression, (SyntaxNode?)newNode.Body ?? newNode.ExpressionBody?.Expression, @@ -2884,12 +2855,6 @@ private void ClassifyUpdate(DestructorDeclarationSyntax oldNode, DestructorDecla private void ClassifyUpdate(PropertyDeclarationSyntax oldNode, PropertyDeclarationSyntax newNode) { - if (!AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore: SyntaxKind.UnsafeKeyword)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type)) { ReportError(RudeEditKind.TypeUpdate); @@ -2944,12 +2909,6 @@ private void ClassifyUpdate(PropertyDeclarationSyntax oldNode, PropertyDeclarati private void ClassifyUpdate(IndexerDeclarationSyntax oldNode, IndexerDeclarationSyntax newNode) { - if (!SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers)) - { - ReportError(RudeEditKind.ModifiersUpdate); - return; - } - if (!SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type)) { ReportError(RudeEditKind.TypeUpdate); @@ -2962,16 +2921,17 @@ private void ClassifyUpdate(IndexerDeclarationSyntax oldNode, IndexerDeclaration return; } - Debug.Assert(!SyntaxFactory.AreEquivalent(oldNode.ExpressionBody, newNode.ExpressionBody)); - - var oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldNode.ExpressionBody, oldNode.AccessorList); - var newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newNode.ExpressionBody, newNode.AccessorList); + if (SyntaxFactory.AreEquivalent(oldNode.ExpressionBody, newNode.ExpressionBody)) + { + var oldBody = SyntaxUtilities.TryGetEffectiveGetterBody(oldNode.ExpressionBody, oldNode.AccessorList); + var newBody = SyntaxUtilities.TryGetEffectiveGetterBody(newNode.ExpressionBody, newNode.AccessorList); - ClassifyMethodBodyRudeUpdate( - oldBody, - newBody, - containingMethod: null, - containingType: (TypeDeclarationSyntax?)newNode.Parent); + ClassifyMethodBodyRudeUpdate( + oldBody, + newBody, + containingMethod: null, + containingType: (TypeDeclarationSyntax?)newNode.Parent); + } } private void ClassifyUpdate(TypeParameterSyntax oldNode, TypeParameterSyntax newNode) diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 49f7db9c60232..2e20695da1d9a 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2485,7 +2485,8 @@ private async Task> AnalyzeSemanticsAsync( // their accessors below. continue; } - else if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(oldDeclaration, newSymbol.ContainingType, out var isFirst)) + + if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(oldDeclaration, newSymbol.ContainingType, out var isFirst)) { // Defer a constructor edit to cover the property initializer changing DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); @@ -2505,10 +2506,11 @@ private async Task> AnalyzeSemanticsAsync( } } - // can't change visibility: + // The new symbol is implicitly declared and thus has implied accessibility that needs to be the same + // as the accessibility of the deleted explicit symbol. if (newSymbol.DeclaredAccessibility != oldSymbol.DeclaredAccessibility) { - ReportDeletedMemberRudeEdit(diagnostics, editScript, oldDeclaration, oldSymbol, RudeEditKind.ChangingVisibility); + ReportDeletedMemberRudeEdit(diagnostics, editScript, oldDeclaration, oldSymbol, RudeEditKind.ChangingAccessibility); continue; } @@ -2594,16 +2596,6 @@ private async Task> AnalyzeSemanticsAsync( if (oldSymbol.IsImplicitlyDeclared) { - // Replace implicit declaration with an explicit one with a different visibility is a rude edit. - if (oldSymbol.DeclaredAccessibility != newSymbol.DeclaredAccessibility) - { - diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.ChangingVisibility, - GetDiagnosticSpan(newDeclaration, edit.Kind), - arguments: new[] { GetDisplayName(newDeclaration, edit.Kind) })); - - continue; - } - // If a user explicitly implements a member of a record then we want to issue an update, not an insert. if (oldSymbol.DeclaringSyntaxReferences.Length == 1) { @@ -2666,25 +2658,14 @@ private async Task> AnalyzeSemanticsAsync( // if it changed in any way that's not allowed. ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol); - // If a node has been inserted but neither old nor new has a body, we can stop processing. - // The exception to this is explicitly implemented properties that implement positional parameters of - // records, as even not having an initializer is an "edit", since the compiler generated property would have - // had one. - var isRecordPrimaryConstructorParameter = IsRecordPrimaryConstructorParameter(oldDeclaration); - var oldBody = TryGetDeclarationBody(oldDeclaration); - var newBody = TryGetDeclarationBody(newDeclaration); - if (oldBody == null && newBody == null && !isRecordPrimaryConstructorParameter) - { - continue; - } - if (oldBody != null) { // The old symbol's declaration syntax may be located in a different document than the old version of the current document. var oldSyntaxDocument = oldProject.Solution.GetRequiredDocument(oldDeclaration.SyntaxTree); var oldSyntaxModel = await oldSyntaxDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var oldSyntaxText = await oldSyntaxDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + var newBody = TryGetDeclarationBody(newDeclaration); // Skip analysis of active statements. We already report rude edit for removal of code containing // active statements in the old declaration and don't currently support moving active statements. @@ -2711,16 +2692,21 @@ private async Task> AnalyzeSemanticsAsync( // 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. var isNewConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration); - if (isNewConstructorWithMemberInitializers || - IsDeclarationWithInitializer(oldDeclaration) || - IsDeclarationWithInitializer(newDeclaration) || - isRecordPrimaryConstructorParameter) + var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration); + var isRecordPrimaryConstructorParameter = IsRecordPrimaryConstructorParameter(oldDeclaration); + + if (isNewConstructorWithMemberInitializers || isDeclarationWithInitializer || isRecordPrimaryConstructorParameter) { if (isNewConstructorWithMemberInitializers) { processedSymbols.Remove(newSymbol); } + if (isDeclarationWithInitializer) + { + AnalyzeSymbolUpdate(oldSymbol, newSymbol, newDeclaration, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + } + DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); // Don't add a separate semantic edit. @@ -2888,18 +2874,19 @@ private async Task> AnalyzeSemanticsAsync( // 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. var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration); + var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration); - if (isConstructorWithMemberInitializers || - IsDeclarationWithInitializer(oldDeclaration) || - IsDeclarationWithInitializer(newDeclaration)) + if (isConstructorWithMemberInitializers || isDeclarationWithInitializer) { if (isConstructorWithMemberInitializers) { processedSymbols.Remove(newSymbol); } - // Need to check for attribute rude edits for fields and properties - AnalyzeCustomAttributes(oldSymbol, newSymbol, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + if (isDeclarationWithInitializer) + { + AnalyzeSymbolUpdate(oldSymbol, newSymbol, newDeclaration, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + } DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); @@ -2919,7 +2906,9 @@ private async Task> AnalyzeSemanticsAsync( if (editKind == SemanticEditKind.Update) { - AnalyzeCustomAttributes(oldSymbol, newSymbol, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + Contract.ThrowIfNull(oldSymbol); + + AnalyzeSymbolUpdate(oldSymbol, newSymbol, newDeclaration, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); // The only update to the type itself that's supported is an addition or removal of the partial modifier, // which does not have impact on the emitted type metadata. @@ -2928,9 +2917,9 @@ private async Task> AnalyzeSemanticsAsync( continue; } - // The field/property itself is being updated. Currently we do not allow any modifiers to be updated. Attribute - // updates will have been handled already - if (newSymbol is IFieldSymbol or IPropertySymbol) + // The field/property/event itself is being updated. Currently we do not allow any modifiers to be updated. + // Attribute updates will have been handled already. + if (newSymbol is IFieldSymbol or IPropertySymbol or IEventSymbol) { continue; } @@ -2988,7 +2977,9 @@ private async Task> AnalyzeSemanticsAsync( Contract.ThrowIfFalse(IsDeclarationWithInitializer(oldDeclaration) == IsDeclarationWithInitializer(newDeclaration)); var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration); - if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(newDeclaration)) + var isDeclarationWithInitializer = IsDeclarationWithInitializer(newDeclaration); + + if (isConstructorWithMemberInitializers || isDeclarationWithInitializer) { // TODO: only create syntax map if any field initializers are active/contain lambdas or this is a partial type syntaxMap ??= CreateSyntaxMapForEquivalentNodes(oldDeclaration, newDeclaration); @@ -3020,6 +3011,7 @@ private async Task> AnalyzeSemanticsAsync( oldModel, oldCompilation, processedSymbols, + capabilities, isStatic: false, semanticEdits, diagnostics, @@ -3034,6 +3026,7 @@ private async Task> AnalyzeSemanticsAsync( oldModel, oldCompilation, processedSymbols, + capabilities, isStatic: true, semanticEdits, diagnostics, @@ -3062,7 +3055,111 @@ private async Task> AnalyzeSemanticsAsync( } } - private void AnalyzeCustomAttributes(ISymbol? oldSymbol, ISymbol newSymbol, EditAndContinueCapabilities capabilities, ArrayBuilder diagnostics, ArrayBuilder? semanticEdits, Func? syntaxMap, CancellationToken cancellationToken) + private void ReportUpdatedSymbolDeclarationRudeEdits(ArrayBuilder diagnostics, ISymbol oldSymbol, ISymbol newSymbol, SyntaxNode newDeclaration) + { + var rudeEdit = RudeEditKind.None; + + if (oldSymbol.DeclaredAccessibility != newSymbol.DeclaredAccessibility) + { + rudeEdit = RudeEditKind.ChangingAccessibility; + } + + if (oldSymbol.IsStatic != newSymbol.IsStatic || + oldSymbol.IsVirtual != newSymbol.IsVirtual || + oldSymbol.IsAbstract != newSymbol.IsAbstract || + oldSymbol.IsOverride != newSymbol.IsOverride) + { + // Do not report for accessors as the error will be reported on their associated symbol. + if (oldSymbol is not IMethodSymbol { AssociatedSymbol: not null }) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + + if (oldSymbol is IFieldSymbol oldField && newSymbol is IFieldSymbol newField) + { + if (oldField.IsConst != newField.IsConst || + oldField.IsReadOnly != newField.IsReadOnly || + oldField.IsVolatile != newField.IsVolatile) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + else if (oldSymbol is IMethodSymbol oldMethod && newSymbol is IMethodSymbol newMethod) + { + if (oldMethod.IsReadOnly != newMethod.IsReadOnly) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + else if (oldSymbol is INamedTypeSymbol oldType && newSymbol is INamedTypeSymbol newType) + { + if (oldType.TypeKind != newType.TypeKind || + oldType.IsRecord != newType.IsRecord) // TODO: https://github.com/dotnet/roslyn/issues/51874 + { + rudeEdit = RudeEditKind.TypeKindUpdate; + } + else if (oldType.IsRefLikeType != newType.IsRefLikeType || + oldType.IsReadOnly != newType.IsReadOnly) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + else if (oldSymbol is IEventSymbol { AddMethod: not null, RemoveMethod: not null } oldEvent && + newSymbol is IEventSymbol { AddMethod: not null, RemoveMethod: not null } newEvent) + { + // "readonly" modifier can only be applied on the event itself, not on its accessors. + if (oldEvent.AddMethod.IsReadOnly != newEvent.AddMethod.IsReadOnly || + oldEvent.RemoveMethod.IsReadOnly != newEvent.RemoveMethod.IsReadOnly) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + + // Do not report modifier update if type kind changed. + if (rudeEdit == RudeEditKind.None && oldSymbol.IsSealed != newSymbol.IsSealed) + { + // Do not report for accessors as the error will be reported on their associated symbol. + if (oldSymbol is not IMethodSymbol { AssociatedSymbol: not null }) + { + rudeEdit = RudeEditKind.ModifiersUpdate; + } + } + + if (rudeEdit != RudeEditKind.None) + { + var arguments = rudeEdit switch + { + RudeEditKind.TypeKindUpdate => Array.Empty(), + _ => new[] { GetDisplayName(newDeclaration, EditKind.Update) } + }; + + diagnostics.Add(new RudeEditDiagnostic(rudeEdit, GetDiagnosticSpan(newDeclaration, EditKind.Update), arguments: arguments)); + } + } + + private void AnalyzeSymbolUpdate( + ISymbol oldSymbol, + ISymbol newSymbol, + SyntaxNode? newDeclaration, + EditAndContinueCapabilities capabilities, + ArrayBuilder diagnostics, + ArrayBuilder? semanticEdits, + Func? syntaxMap, + CancellationToken cancellationToken) + { + Contract.ThrowIfFalse(newSymbol.IsImplicitlyDeclared == newDeclaration is null); + + AnalyzeCustomAttributes(oldSymbol, newSymbol, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + + // We might be updating an explicit old declaration to an implicit new declaration. + if (newDeclaration != null) + { + ReportUpdatedSymbolDeclarationRudeEdits(diagnostics, oldSymbol, newSymbol, newDeclaration); + } + } + + private void AnalyzeCustomAttributes(ISymbol oldSymbol, ISymbol newSymbol, EditAndContinueCapabilities capabilities, ArrayBuilder diagnostics, ArrayBuilder? semanticEdits, Func? syntaxMap, CancellationToken cancellationToken) { var needsEdit = false; @@ -3077,26 +3174,30 @@ private void AnalyzeCustomAttributes(ISymbol? oldSymbol, ISymbol newSymbol, Edit } else if (newSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null } newType) { - var oldType = oldSymbol as INamedTypeSymbol; + if (oldSymbol is not INamedTypeSymbol { DelegateInvokeMethod: not null } oldType) + { + return; + } + // If this is a delegate with attributes on its return type for example, they are found on the DelegateInvokeMethod - AnalyzeCustomAttributes(oldType?.DelegateInvokeMethod, newType.DelegateInvokeMethod, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); + AnalyzeCustomAttributes(oldType.DelegateInvokeMethod, newType.DelegateInvokeMethod, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken); } foreach (var parameter in newSymbol.GetParameters()) { - var oldParameter = oldSymbol?.GetParameters().FirstOrDefault(p => p.Name.Equals(parameter.Name)); + var oldParameter = oldSymbol.GetParameters().FirstOrDefault(p => p.Name.Equals(parameter.Name)); needsEdit |= HasCustomAttributeChanges(oldParameter?.GetAttributes(), parameter.GetAttributes(), parameter, capabilities, diagnostics); } foreach (var typeParam in newSymbol.GetTypeParameters()) { - var oldParameter = oldSymbol?.GetTypeParameters().FirstOrDefault(p => p.Name.Equals(typeParam.Name)); + var oldParameter = oldSymbol.GetTypeParameters().FirstOrDefault(p => p.Name.Equals(typeParam.Name)); needsEdit |= HasCustomAttributeChanges(oldParameter?.GetAttributes(), typeParam.GetAttributes(), typeParam, capabilities, diagnostics); } // This is the only case we care about whether to issue an edit or not, because this is the only case where types have their attributes checked // and types are the only things that would otherwise not have edits reported. - needsEdit |= HasCustomAttributeChanges(oldSymbol?.GetAttributes(), newSymbol.GetAttributes(), newSymbol, capabilities, diagnostics); + needsEdit |= HasCustomAttributeChanges(oldSymbol.GetAttributes(), newSymbol.GetAttributes(), newSymbol, capabilities, diagnostics); // If we don't need to add an edit, then we're done if (!needsEdit || semanticEdits is null) @@ -3105,7 +3206,7 @@ private void AnalyzeCustomAttributes(ISymbol? oldSymbol, ISymbol newSymbol, Edit } // Most symbol types will automatically have an edit added, so we just need to handle a few - if (newSymbol is INamedTypeSymbol or IFieldSymbol or IPropertySymbol) + if (newSymbol is INamedTypeSymbol or IFieldSymbol or IPropertySymbol or IEventSymbol) { var symbolKey = SymbolKey.Create(newSymbol, cancellationToken); semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null, partialType: null)); @@ -3534,6 +3635,7 @@ private void AddConstructorEdits( SemanticModel? oldModel, Compilation oldCompilation, IReadOnlySet processedSymbols, + EditAndContinueCapabilities capabilities, bool isStatic, [Out] ArrayBuilder semanticEdits, [Out] ArrayBuilder diagnostics, @@ -3582,49 +3684,45 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) var syntaxMapToUse = aggregateSyntaxMap; + SyntaxNode? newDeclaration = null; ISymbol? oldCtor; if (!newCtor.IsImplicitlyDeclared) { // Constructors have to have a single declaration syntax, they can't be partial - var newDeclaration = GetSymbolDeclarationSyntax(newCtor.DeclaringSyntaxReferences.Single(), cancellationToken); + newDeclaration = GetSymbolDeclarationSyntax(newCtor.DeclaringSyntaxReferences.Single(), cancellationToken); + + // Implicit record constructors are represented by the record declaration itself. + // https://github.com/dotnet/roslyn/issues/54403 + var isPrimaryRecordConstructor = IsRecordDeclaration(newDeclaration); - // Compiler generated constructors of records are not implicitly declared, since they - // points to the actual record declaration. We want to skip these checks because we can't - // reason about initializers for them. - if (!IsRecordDeclaration(newDeclaration)) + // Constructor that doesn't contain initializers had a corresponding semantic edit produced previously + // or was not edited. In either case we should not produce a semantic edit for it. + if (!isPrimaryRecordConstructor && !IsConstructorWithMemberInitializers(newDeclaration)) { - // Constructor that doesn't contain initializers had a corresponding semantic edit produced previously - // or was not edited. In either case we should not produce a semantic edit for it. - if (!IsConstructorWithMemberInitializers(newDeclaration)) - { - continue; - } + continue; + } - // If no initializer updates were made in the type we only need to produce semantic edits for constructors - // whose body has been updated, otherwise we need to produce edits for all constructors that include initializers. - // If changes were made to initializers or constructors of a partial type in another document they will be merged - // when aggregating semantic edits from all changed documents. Rude edits resulting from those changes, if any, will - // be reported in the document they were made in. - if (!anyInitializerUpdatesInCurrentDocument && !updatesInCurrentDocument.ChangedDeclarations.ContainsKey(newDeclaration)) - { - continue; - } + // If no initializer updates were made in the type we only need to produce semantic edits for constructors + // whose body has been updated, otherwise we need to produce edits for all constructors that include initializers. + // If changes were made to initializers or constructors of a partial type in another document they will be merged + // when aggregating semantic edits from all changed documents. Rude edits resulting from those changes, if any, will + // be reported in the document they were made in. + if (!isPrimaryRecordConstructor && !anyInitializerUpdatesInCurrentDocument && !updatesInCurrentDocument.ChangedDeclarations.ContainsKey(newDeclaration)) + { + continue; } // To avoid costly SymbolKey resolution we first try to match the constructor in the current document // and special case parameter-less constructor. - // In the case of records, newDeclaration will point to the record declaration, and hence this - // actually finds the old record declaration, but that is actually sufficient for our needs, as all - // we're using it for is detecting an update, and any changes to the standard record constructors must - // be an update by definition. - if (topMatch.TryGetOldNode(newDeclaration, out var oldDeclaration)) + // In the case of records, newDeclaration will point to the record declaration, take the slow path. + if (!isPrimaryRecordConstructor && topMatch.TryGetOldNode(newDeclaration, out var oldDeclaration)) { Contract.ThrowIfNull(oldModel); oldCtor = oldModel.GetDeclaredSymbol(oldDeclaration, cancellationToken); - Contract.ThrowIfNull(oldCtor); + Contract.ThrowIfFalse(oldCtor is IMethodSymbol { MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor }); } - else if (newCtor.Parameters.Length == 0) + else if (!isPrimaryRecordConstructor && newCtor.Parameters.Length == 0) { oldCtor = TryGetParameterlessConstructor(oldType, isStatic); } @@ -3662,7 +3760,7 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) // When explicitly implementing the copy constructor of a record the parameter name must match for symbol matching to work // TODO: Remove this requirement with https://github.com/dotnet/roslyn/issues/52563 if (oldCtor != null && - !IsRecordDeclaration(newDeclaration) && + !isPrimaryRecordConstructor && oldCtor.DeclaringSyntaxReferences.Length == 0 && newCtor.Parameters.Length == 1 && newType.IsRecord && @@ -3719,6 +3817,8 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) if (oldCtor != null) { + AnalyzeSymbolUpdate(oldCtor, newCtor, newDeclaration, capabilities, diagnostics, semanticEdits: null, syntaxMap: null, cancellationToken); + semanticEdits.Add(new SemanticEditInfo( SemanticEditKind.Update, newCtorKey, diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index 32c79d77a897f..1029bfd4204c2 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -109,7 +109,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.StackAllocUpdate, nameof(FeaturesResources.Modifying_0_which_contains_the_stackalloc_operator_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ExperimentalFeaturesEnabled, nameof(FeaturesResources.Modifying_source_with_experimental_language_features_enabled_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.AwaitStatementUpdate, nameof(FeaturesResources.Updating_a_complex_statement_containing_an_await_expression_will_prevent_the_debug_session_from_continuing)); - AddRudeEdit(RudeEditKind.ChangingVisibility, nameof(FeaturesResources.Changing_visibility_of_0_will_prevent_the_debug_session_from_continuing)); + AddRudeEdit(RudeEditKind.ChangingAccessibility, nameof(FeaturesResources.Changing_visibility_of_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.CapturingVariable, nameof(FeaturesResources.Capturing_variable_0_that_hasn_t_been_captured_before_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.NotCapturingVariable, nameof(FeaturesResources.Ceasing_to_capture_variable_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.DeletingCapturedVariable, nameof(FeaturesResources.Deleting_captured_variable_0_will_prevent_the_debug_session_from_continuing)); diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index 98c571f9c6802..bd26ddb8f6edd 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -62,7 +62,7 @@ internal enum RudeEditKind : ushort ExperimentalFeaturesEnabled = 45, AwaitStatementUpdate = 46, - ChangingVisibility = 47, + ChangingAccessibility = 47, CapturingVariable = 48, NotCapturingVariable = 49, diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 813ae02f320a5..75eab22cc8d3b 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -1280,16 +1280,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue node = node.Parent ' for attributes on return types of functions End If - Case SyntaxKind.EventStatement - If node.Parent.IsKind(SyntaxKind.EventBlock) Then - Return False - End If - - Case SyntaxKind.PropertyStatement ' autoprop or interface property - If node.Parent.IsKind(SyntaxKind.PropertyBlock) Then - Return False - End If - Case SyntaxKind.Parameter If editKind = EditKind.Update Then ' for delegate invoke methods we need to go one step higher, to the delegate itself @@ -2462,7 +2452,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Case SyntaxKind.FieldDeclaration - ClassifyUpdate(DirectCast(oldNode, FieldDeclarationSyntax), DirectCast(newNode, FieldDeclarationSyntax)) Return Case SyntaxKind.VariableDeclarator @@ -2505,7 +2494,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Case SyntaxKind.SubNewStatement - ClassifyUpdate(DirectCast(oldNode, SubNewStatementSyntax), DirectCast(newNode, SubNewStatementSyntax)) Return Case SyntaxKind.PropertyBlock @@ -2593,16 +2581,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Sub Private Sub ClassifyUpdate(oldNode As TypeStatementSyntax, newNode As TypeStatementSyntax) - If oldNode.RawKind <> newNode.RawKind Then - ReportError(RudeEditKind.TypeKindUpdate) - Return - End If - - If Not AreModifiersEquivalent(oldNode.Modifiers, newNode.Modifiers, ignore:=SyntaxKind.PartialKeyword) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) End If @@ -2623,11 +2601,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return End If - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - If Not SyntaxFactory.AreEquivalent(oldNode.UnderlyingType, newNode.UnderlyingType) Then ReportError(RudeEditKind.EnumUnderlyingTypeUpdate) Return @@ -2635,11 +2608,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Sub Private Sub ClassifyUpdate(oldNode As DelegateStatementSyntax, newNode As DelegateStatementSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - ' Function changed to Sub or vice versa. Note that Function doesn't need to have AsClause. If oldNode.RawKind <> newNode.RawKind Then ReportError(RudeEditKind.TypeUpdate) @@ -2657,14 +2625,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End Sub - Private Sub ClassifyUpdate(oldNode As FieldDeclarationSyntax, newNode As FieldDeclarationSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - End If - - ' VariableDeclarator separators were modified - End Sub - Private Sub ClassifyUpdate(oldNode As ModifiedIdentifierSyntax, newNode As ModifiedIdentifierSyntax) If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) @@ -2716,12 +2676,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Sub Private Sub ClassifyUpdate(oldNode As PropertyStatementSyntax, newNode As PropertyStatementSyntax) - If Not IncludesSignificantPropertyModifiers(oldNode.Modifiers, newNode.Modifiers) OrElse - Not IncludesSignificantPropertyModifiers(newNode.Modifiers, oldNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) Return @@ -2742,23 +2696,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End Sub - Private Shared Function IncludesSignificantPropertyModifiers(subset As SyntaxTokenList, superset As SyntaxTokenList) As Boolean - For Each modifier In subset - ' ReadOnly and WriteOnly keywords are redundant, it would be a semantic error if they Then didn't match the present accessors. - ' We want to allow adding an accessor to a property, which requires change in the RO/WO modifiers. - If modifier.IsKind(SyntaxKind.ReadOnlyKeyword) OrElse - modifier.IsKind(SyntaxKind.WriteOnlyKeyword) Then - Continue For - End If - - If Not superset.Any(modifier.Kind) Then - Return False - End If - Next - - Return True - End Function - ' Returns true if the initializer has changed. Private Function ClassifyTypeAndInitializerUpdates(oldEqualsValue As EqualsValueSyntax, oldClause As AsClauseSyntax, @@ -2780,10 +2717,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ' A custom event can't be matched with a field event and vice versa: Debug.Assert(SyntaxFactory.AreEquivalent(oldNode.CustomKeyword, newNode.CustomKeyword)) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - End If - If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) Return @@ -2797,8 +2730,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Dim oldHasGeneratedType = oldNode.ParameterList IsNot Nothing Dim newHasGeneratedType = newNode.ParameterList IsNot Nothing - Debug.Assert(oldHasGeneratedType <> newHasGeneratedType) - ReportError(RudeEditKind.TypeUpdate) + If oldHasGeneratedType <> newHasGeneratedType Then + ReportError(RudeEditKind.TypeUpdate) + End If End Sub Private Sub ClassifyUpdate(oldNode As MethodBlockSyntax, newNode As MethodBlockSyntax) @@ -2819,11 +2753,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return End If - If Not ClassifyMethodModifierUpdate(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - ' TODO (tomat): We can support this If Not SyntaxFactory.AreEquivalent(oldNode.HandlesClause, newNode.HandlesClause) Then ReportError(RudeEditKind.HandlesClauseUpdate) @@ -2836,35 +2765,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End If End Sub - Private Shared Function ClassifyMethodModifierUpdate(oldModifiers As SyntaxTokenList, newModifiers As SyntaxTokenList) As Boolean - ' Ignore Async and Iterator keywords when matching modifiers. - ' State machine checks are done in ComputeBodyMatch. - - Dim oldAsyncIndex = oldModifiers.IndexOf(SyntaxKind.AsyncKeyword) - Dim newAsyncIndex = newModifiers.IndexOf(SyntaxKind.AsyncKeyword) - - If oldAsyncIndex >= 0 Then - oldModifiers = oldModifiers.RemoveAt(oldAsyncIndex) - End If - - If newAsyncIndex >= 0 Then - newModifiers = newModifiers.RemoveAt(newAsyncIndex) - End If - - Dim oldIteratorIndex = oldModifiers.IndexOf(SyntaxKind.IteratorKeyword) - Dim newIteratorIndex = newModifiers.IndexOf(SyntaxKind.IteratorKeyword) - - If oldIteratorIndex >= 0 Then - oldModifiers = oldModifiers.RemoveAt(oldIteratorIndex) - End If - - If newIteratorIndex >= 0 Then - newModifiers = newModifiers.RemoveAt(newIteratorIndex) - End If - - Return SyntaxFactory.AreEquivalent(oldModifiers, newModifiers) - End Function - Private Sub ClassifyUpdate(oldNode As DeclareStatementSyntax, newNode As DeclareStatementSyntax) If Not SyntaxFactory.AreEquivalent(oldNode.Identifier, newNode.Identifier) Then ReportError(RudeEditKind.Renamed) @@ -2898,13 +2798,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Sub Private Sub ClassifyUpdate(oldNode As OperatorStatementSyntax, newNode As OperatorStatementSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then + Dim oldWidening = oldNode.Modifiers.IndexOf(SyntaxKind.WideningKeyword) >= 0 + Dim oldNarrowing = oldNode.Modifiers.IndexOf(SyntaxKind.NarrowingKeyword) >= 0 + Dim newWidening = newNode.Modifiers.IndexOf(SyntaxKind.WideningKeyword) >= 0 + Dim newNarrowing = newNode.Modifiers.IndexOf(SyntaxKind.NarrowingKeyword) >= 0 + + If newWidening <> oldWidening OrElse newNarrowing <> oldNarrowing Then ReportError(RudeEditKind.ModifiersUpdate) Return End If - - Debug.Assert(Not SyntaxFactory.AreEquivalent(oldNode.OperatorToken, newNode.OperatorToken)) - ReportError(RudeEditKind.Renamed) End Sub Private Sub ClassifyUpdate(oldNode As AccessorBlockSyntax, newNode As AccessorBlockSyntax) @@ -2936,13 +2838,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue containingType:=DirectCast(newNode.Parent, TypeBlockSyntax)) End Sub - Private Sub ClassifyUpdate(oldNode As SubNewStatementSyntax, newNode As SubNewStatementSyntax) - If Not SyntaxFactory.AreEquivalent(oldNode.Modifiers, newNode.Modifiers) Then - ReportError(RudeEditKind.ModifiersUpdate) - Return - End If - End Sub - Private Sub ClassifyUpdate(oldNode As SimpleAsClauseSyntax, newNode As SimpleAsClauseSyntax) If Not SyntaxFactory.AreEquivalent(oldNode.Type, newNode.Type) Then ReportError(RudeEditKind.TypeUpdate, newNode.Parent, newNode.Parent) @@ -2984,21 +2879,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ClassifyUpdate(oldNode.Identifier, newNode.Identifier) End Sub - Private Shared Function AreModifiersEquivalent(oldModifiers As SyntaxTokenList, newModifiers As SyntaxTokenList, ignore As SyntaxKind) As Boolean - Dim oldIgnoredModifierIndex = oldModifiers.IndexOf(ignore) - Dim newIgnoredModifierIndex = newModifiers.IndexOf(ignore) - - If oldIgnoredModifierIndex >= 0 Then - oldModifiers = oldModifiers.RemoveAt(oldIgnoredModifierIndex) - End If - - If newIgnoredModifierIndex >= 0 Then - newModifiers = newModifiers.RemoveAt(newIgnoredModifierIndex) - End If - - Return SyntaxFactory.AreEquivalent(oldModifiers, newModifiers) - End Function - Private Sub ClassifyMethodBodyRudeUpdate(oldBody As MethodBlockBaseSyntax, newBody As MethodBlockBaseSyntax, containingMethod As MethodBlockSyntax,