diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index 5c501128a87f7..b31a2c3f21bf8 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -587,4 +587,4 @@ Convert all extension methods in '{0}' to extension - \ No newline at end of file + diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 13de4a97b5695..de6a871665996 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -929,7 +929,7 @@ internal override bool IsDeclarationWithInitializer(SyntaxNode declaration) } internal override bool IsPrimaryConstructorDeclaration(SyntaxNode declaration) - => declaration.Parent is TypeDeclarationSyntax { ParameterList: var parameterList } && parameterList == declaration; + => declaration.Parent is TypeDeclarationSyntax { ParameterList: var parameterList } and not ExtensionBlockDeclarationSyntax && parameterList == declaration; internal override bool IsConstructorWithMemberInitializers(ISymbol symbol, CancellationToken cancellationToken) { @@ -1643,6 +1643,10 @@ private static bool GroupBySignatureEquivalent(IMethodSymbol? oldMethod, IMethod return GetDiagnosticSpan(typeDeclaration.Modifiers, typeDeclaration.Keyword, typeDeclaration.TypeParameterList ?? (SyntaxNodeOrToken)typeDeclaration.Identifier); + case SyntaxKind.ExtensionBlockDeclaration: + var extensionBlockDeclaration = (ExtensionBlockDeclarationSyntax)node; + return extensionBlockDeclaration.Keyword.Span; + case SyntaxKind.BaseList: var baseList = (BaseListSyntax)node; return baseList.Types.Span; @@ -2248,6 +2252,9 @@ internal override string GetDisplayName(IMethodSymbol symbol) return CSharpFeaturesResources.local_variable_declaration; + case SyntaxKind.ExtensionBlockDeclaration: + return FeaturesResources.extension_block; + default: return null; } @@ -2395,6 +2402,11 @@ private void ClassifyInsert(SyntaxNode node) { switch (node.Kind()) { + case SyntaxKind.ExtensionBlockDeclaration: + // https://github.com/dotnet/roslyn/issues/78959 Disallowed for now + ReportError(RudeEditKind.Insert); + return; + case SyntaxKind.ExternAliasDirective: ReportError(RudeEditKind.Insert); return; @@ -2425,6 +2437,11 @@ private void ClassifyDelete(SyntaxNode oldNode) ReportError(RudeEditKind.Delete); return; + case SyntaxKind.ExtensionBlockDeclaration: + // https://github.com/dotnet/roslyn/issues/78959 Disallowed for now + ReportError(RudeEditKind.Delete); + return; + case SyntaxKind.AttributeList: case SyntaxKind.Attribute: // To allow removal of attributes we need to check if the removed attribute diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs index 11bb390f9acfc..6001081d45308 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs @@ -59,6 +59,7 @@ internal enum Label TypeDeclaration, EnumDeclaration, + ExtensionBlockDeclaration, // tied to parent BaseList, // tied to parent PrimaryConstructorBase, // tied to parent DelegateDeclaration, @@ -195,6 +196,7 @@ private static int TiedToAncestor(Label label) case Label.Parameter: case Label.AttributeList: case Label.Attribute: + case Label.ExtensionBlockDeclaration: return 1; // Statement syntax @@ -681,6 +683,9 @@ private static Label ClassifyTopSyntax(SyntaxKind kind, SyntaxNode? node, out bo } break; + + case SyntaxKind.ExtensionBlockDeclaration: + return Label.ExtensionBlockDeclaration; } // If we got this far, its an unlabelled node. For top @@ -917,6 +922,10 @@ protected override bool TryComputeWeightedDistance(SyntaxNode leftNode, SyntaxNo distance = ComputeDistance((AttributeSyntax)leftNode, (AttributeSyntax)rightNode); return true; + case SyntaxKind.ExtensionBlockDeclaration: + distance = ComputeDistance((ExtensionBlockDeclarationSyntax)leftNode, (ExtensionBlockDeclarationSyntax)rightNode); + return true; + default: var leftName = TryGetName(leftNode); var rightName = TryGetName(rightNode); @@ -1435,6 +1444,9 @@ private static double CombineOptional( case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)node).Identifier; + case SyntaxKind.ExtensionBlockDeclaration: + return null; + case SyntaxKind.FieldDeclaration: case SyntaxKind.EventFieldDeclaration: case SyntaxKind.VariableDeclaration: diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs index a425fe95b93df..4e659c1ff7669 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxUtilities.cs @@ -56,7 +56,7 @@ CompilationUnitSyntax unit when unit.ContainsGlobalStatements() when !fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword) => new FieldWithInitializerDeclarationBody(variableDeclarator), - ParameterListSyntax { Parent: TypeDeclarationSyntax typeDeclaration } + ParameterListSyntax { Parent: TypeDeclarationSyntax typeDeclaration and not ExtensionBlockDeclarationSyntax } => typeDeclaration is { BaseList.Types: [PrimaryConstructorBaseTypeSyntax { }, ..] } ? new PrimaryConstructorWithExplicitInitializerDeclarationBody(typeDeclaration) : new PrimaryConstructorWithImplicitInitializerDeclarationBody(typeDeclaration), diff --git a/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index eadc7ab944ea9..1623ae5b97aaf 100644 --- a/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -105,6 +105,7 @@ public static string GetResource(string keyword) "top-level statement" => CSharpFeaturesResources.top_level_statement, "top-level code" => CSharpFeaturesResources.top_level_code, "class with explicit or sequential layout" => string.Format(FeaturesResources.class_with_explicit_or_sequential_layout), + "extension block" => FeaturesResources.extension_block, _ => null }; diff --git a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 1a96a3566c8bd..8fa4682670cb4 100644 --- a/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/Features/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -6247,6 +6247,2104 @@ public void NestedPartialTypeInPartialType_InsertDeleteAndInsertVirtual() #endregion + #region Extensions + + [Fact] + public void Extension_AddedBlock_NoMethod() + { + var src1 = """ + static class Ext + { + } + """; + + var src2 = """ + static class Ext + { + extension(object) + { + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits(""" + Insert [extension(object) + { + }]@25 + """, + "Insert [(object)]@34", + "Insert [object]@35"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Insert, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "object", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_AddedSecondBlock() + { + var src1 = """ + static class Ext + { + extension(object) + { + } + } + """; + + var src2 = """ + static class Ext + { + extension(object) + { + } + extension(string) + { + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits(""" + Insert [extension(string) + { + }]@62 + """, + "Insert [(string)]@71", + "Insert [string]@72"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Insert, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "string", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_AddMethod() + { + var src1 = """ + static class Ext + { + extension(object o) + { + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [void M() {}]@61", + "Insert [()]@67" + ); + + edits.VerifySemanticDiagnostics(capabilities: EditAndContinueCapabilities.AddMethodToExistingType, + diagnostics: Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block"))); + } + + [Fact] + public void Extension_RemoveMethod() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Delete [void M() {}]@61", + "Delete [()]@67" + ); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block"))); + } + + [Fact] + public void Extension_ExtensionParameter_AddName() + { + var src1 = """ + static class Ext + { + extension(object) + { + } + } + """; + + var src2 = """ + static class Ext + { + extension(object x) + { + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [object]@35 -> [object x]@35"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object x", GetResource("extension block"))); + } + + [Fact] + public void Extension_ExtensionParameter_RemoveName() + { + var src1 = """ + static class Ext + { + extension(object x) + { + } + } + """; + + var src2 = """ + static class Ext + { + extension(object) + { + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [object x]@35 -> [object]@35"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object", GetResource("extension block"))); + } + + [Fact] + public void Extension_ExtensionParameter_Rename() + { + var src1 = """ + static class Ext + { + extension(object o) + { + } + } + """; + + var src2 = """ + static class Ext + { + extension(object x) + { + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [object o]@35 -> [object x]@35"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object x", GetResource("extension block"))); + } + + [Fact] + public void Extension_ExtensionParameter_Type_ChangeNullability() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object? o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [object o]@35 -> [object? o]@35"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object? o", GetResource("extension block"))); + } + + [Fact] + public void Extension_ExtensionParameter_Type_ChangeType() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(string o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [object o]@35 -> [string o]@35"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "string o", GetResource("extension block"))); + } + + [Fact] + public void Extension_ExtensionParameter_AddAttribute() + { + var src1 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + + class A : System.Attribute {} + """; + + var src2 = s_attributeSource + """ + static class Ext + { + extension([A]object o) + { + void M() {} + } + } + + class A : System.Attribute {} + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [object o]@155 -> [[A]object o]@155"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block"))); + } + + [Fact] + public void Extension_ExtensionParameter_DeleteAttribute() + { + var src1 = s_attributeSource + """ + static class Ext + { + extension([A]object o) + { + void M() {} + } + } + + class A : System.Attribute {} + """; + + var src2 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + + class A : System.Attribute {} + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [[A]object o]@155 -> [object o]@155"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block"))); + } + + [Fact] + public void Extension_ExtensionParameter_UpdateAttributeArgument() + { + var src1 = """ + static class Ext + { + extension([System.Obsolete("a")]object o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension([System.Obsolete("b")]object o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("""Update [[System.Obsolete("a")]object o]@35 -> [[System.Obsolete("b")]object o]@35"""); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block"))); + } + + [Fact] + public void Extension_ExtensionParameter_AddModifier() + { + var src1 = """ + static class Ext + { + extension(int i) + { + void M(int x) {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(ref int i) + { + void M(int y) {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [int i]@35 -> [ref int i]@35", + "Update [int x]@65 -> [int y]@69" + ); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "ref int i", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_ExtensionParameter_RemoveModifier() + { + var src1 = """ + static class Ext + { + extension(ref int i) + { + void M(int x) {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(int i) + { + void M(int y) {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [ref int i]@35 -> [int i]@35", + "Update [int x]@69 -> [int y]@65"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "int i", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_ExtensionParameter_ChangeModifier() + { + var src1 = """ + static class Ext + { + extension(ref int i) + { + void M(int x) {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(in int i) + { + void M(int y) {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [ref int i]@35 -> [in int i]@35", + "Update [int x]@69 -> [int y]@68" + ); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "in int i", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_TypeParameter_Add() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + void M(T t) {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert []@34", + "Insert [T]@35", + "Insert [T t]@71" + ); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "T", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "T", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "T t", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "T t", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_TypeParameter_Delete() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M(T t) {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Delete []@34", + "Delete [T]@35", + "Delete [T t]@71" + ); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_TypeParameter_Rename() + { + var src1 = """ + static class Ext + { + extension(T o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(U o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [T]@35 -> [U]@35", + "Update [T o]@38 -> [U o]@38"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "U", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "U", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "U o", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_TypeParameter_Constraint_Add() + { + var src1 = """ + static class Ext + { + extension(T o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(T o) where T : class + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Insert [where T : class]@43"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "where T : class", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "T", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_TypeParameter_Constraint_Delete() + { + var src1 = """ + static class Ext + { + extension(T o) where T : class + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(T o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Delete [where T : class]@43"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_TypeParameter_Constraint_Update() + { + var src1 = """ + static class Ext + { + extension(T o) where T : class + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(T o) where T : struct + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [where T : class]@43 -> [where T : struct]@43"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "where T : struct", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "where T : struct", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Method_Rename() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + void N() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [void M() {}]@61 -> [void N() {}]@61"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "void N()", GetResource("extension block"))); + } + + [Fact] + public void Extension_Method_ChangeModifier_Static() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + static void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [void M() {}]@61 -> [static void M() {}]@61"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "static void M()", GetResource("extension block"))); + } + + [Theory] + [CombinatorialData] + public void Extension_Method_ChangeModifier_Visibility( + [CombinatorialValues("public ", "internal ", "protected ", "private ", "protected internal ", "private protected ", "")] + string oldVisibility, + [CombinatorialValues("public ", "internal ", "protected ", "private ", "protected internal ", "private protected ", "")] + string newVisibility) + { + if (oldVisibility == newVisibility) + return; + + var src1 = $$""" + static class Ext + { + extension(object o) + { + {{oldVisibility}}void M() {} + } + } + """; + + var src2 = $$""" + static class Ext + { + extension(object o) + { + {{newVisibility}}void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits($$"""Update [{{oldVisibility}}void M() {}]@61 -> [{{newVisibility}}void M() {}]@61"""); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, $"{newVisibility}void M()", GetResource("extension block"))); + } + + [Fact] + public void Extension_Method_Parameter_Insert() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + void M(int x) {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Insert [int x]@68"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "int x", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int x", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Method_Parameter_Delete() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M(int x) {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Delete [int x]@68"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Method_Parameter_Rename() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M(int x) {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + void M(int y) {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [int x]@68 -> [int y]@68"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Method_Parameter_AddModifier() + { + var src1 = """ + static class Ext + { + extension(int i) + { + void M(int x) {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(int i) + { + void M(ref int y) {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [int x]@65 -> [ref int y]@65"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "ref int y", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "ref int y", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Method_Parameter_RemoveModifier() + { + var src1 = """ + static class Ext + { + extension(int i) + { + void M(ref int x) {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(int i) + { + void M(int y) {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [ref int x]@65 -> [int y]@65"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int y", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Method_Parameter_ChangeModifier() + { + var src1 = """ + static class Ext + { + extension(int i) + { + void M(ref int x) {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(int i) + { + void M(in int y) {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [ref int x]@65 -> [in int y]@65"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "in int y", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "in int y", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Method_BodyUpdate() + { + var src1 = """ + static class Ext + { + extension(object o) + { + int M() => 1; + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + int M() => 2; + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [int M() => 1;]@61 -> [int M() => 2;]@61"); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "int M()", GetResource("extension block"))); + } + + [Fact] + public void Extension_Method_AddSecondInBlock() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + void M() {} + void N() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [void N() {}]@82", + "Insert [()]@88" + ); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "void N()", GetResource("extension block"))); + } + + [Fact] + public void Extension_Block_Reorder() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + extension(string s) + { + void N() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(string s) + { + void N() {} + } + extension(object o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits(""" + Reorder [extension(string s) + { + void N() {} + }]@85 -> @25 + """); + + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block"))); + } + + [Fact] + public void Extension_Block_ChangeParent() + { + var src1 = """ + static class Ext1 + { + extension(object o) + { + void M() {} + } + } + + static class Ext2 + { + } + """; + + var src2 = """ + static class Ext1 + { + } + + static class Ext2 + { + extension(object o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + """ + Insert [extension(object o) + { + void M() {} + }]@53 + """, + "Insert [(object o)]@62", + "Insert [void M() {}]@89", + "Insert [object o]@63", + "Insert [()]@95", + """ + Delete [extension(object o) + { + void M() {} + }]@26 + """, + "Delete [(object o)]@35", + "Delete [object o]@36", + "Delete [void M() {}]@62", + "Delete [()]@68"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Insert, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Delete, "static class Ext1", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "void M()", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block")), + Diagnostic(RudeEditKind.Delete, "static class Ext1", "extension block 'extension(object)'"), + Diagnostic(RudeEditKind.Update, "static class Ext1", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "static class Ext1", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Block_DeleteSecondBlock() + { + var src1 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + extension(string s) + { + void N() {} + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + """ + Delete [extension(string s) + { + void N() {} + }]@85 + """, + "Delete [(string s)]@94", + "Delete [string s]@95", + "Delete [void N() {}]@121", + "Delete [()]@127" + ); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "static class Ext", GetResource("extension block")), + Diagnostic(RudeEditKind.Delete, "static class Ext", DeletedSymbolDisplay(FeaturesResources.extension_block, "extension(string)")), + Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block")) + ); + } + + [Fact] + public void ExtensionBlock_SurroundWithRemoveBlock() + { + var src1 = """ + static class Ext + { + static void M() {} + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + static void M() {} + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits( + """ + Insert [extension(object o) + { + static void M() {} + }]@25 + """, + "Insert [(object o)]@34", + "Insert [static void M() {}]@61", + "Insert [object o]@35", + "Insert [()]@74", + "Delete [static void M() {}]@29", + "Delete [()]@42" + ); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Insert, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "static void M()", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "object o", GetResource("extension block")) + ); + + edits = GetTopEdits(src2, src1); + edits.VerifyEdits( + "Insert [static void M() {}]@29", + "Insert [()]@42", + """ + Delete [extension(object o) + { + static void M() {} + }]@25 + """, + "Delete [(object o)]@34", + "Delete [object o]@35", + "Delete [static void M() {}]@61", + "Delete [()]@74" + ); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "static class Ext", GetResource("extension block")), + Diagnostic(RudeEditKind.Delete, "static class Ext", DeletedSymbolDisplay(FeaturesResources.extension_block, "extension(object)")), + Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Property_AddRemove() + { + var src1 = """ + static class Ext + { + extension(object o) + { + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits( + "Insert [int P { get => 1; }]@61", + "Insert [{ get => 1; }]@67", + "Insert [get => 1;]@69" + ); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "int P", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "get", GetResource("extension block")) + ); + + edits = GetTopEdits(src2, src1); + edits.VerifyEdits( + "Delete [int P { get => 1; }]@61", + "Delete [{ get => 1; }]@67", + "Delete [get => 1;]@69" + ); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Property_Rename() + { + var src1 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; } + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + int Q { get => 1; } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Update [int P { get => 1; }]@61 -> [int Q { get => 1; }]@61"); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "int Q", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "int Q", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Property_Accessor_Add_Set() + { + var src1 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; } + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; set { } } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Insert [set { }]@79"); + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "set", GetResource("extension block"))); + } + + [Fact] + public void Extension_Property_Accessor_Remove_Set() + { + var src1 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; set { } } + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Delete [set { }]@79"); + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block"))); + } + + [Fact] + public void Extension_Property_Accessor_Add_Get() + { + var src1 = """ + static class Ext + { + extension(object o) + { + int P { set { } } + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; set { } } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Insert [get => 1;]@69"); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "get", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "{", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Property_Accessor_Remove_Get() + { + var src1 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; set { } } + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + int P { set { } } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Delete [get => 1;]@69"); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "{", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Property_ReturnType_Change() + { + var src1 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; } + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + string P { get => ""; } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits( + """ + Update [int P { get => 1; }]@61 -> [string P { get => ""; }]@61 + """, + """ + Update [get => 1;]@69 -> [get => "";]@72 + """ + ); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "string P", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "string P", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Property_Attribute_Add() + { + var src1 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + int P { get => 1; } + } + } + + class A : System.Attribute {} + """; + + var src2 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + [A]int P { get => 1; } + } + } + + class A : System.Attribute {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Update [int P { get => 1; }]@181 -> [[A]int P { get => 1; }]@181"); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "int P", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "1", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Property_Attribute_Remove() + { + var src1 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + [A]int P { get => 1; } + } + } + + class A : System.Attribute {} + """; + + var src2 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + int P { get => 1; } + } + } + + class A : System.Attribute {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Update [[A]int P { get => 1; }]@181 -> [int P { get => 1; }]@181"); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "int P", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "1", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Property_Attribute_Update() + { + var src1 = """ + static class Ext + { + extension(object o) + { + [System.Obsolete("a")]int P { get => 1; } + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + [System.Obsolete("b")]int P { get => 1; } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits( + """ + Update [[System.Obsolete("a")]int P { get => 1; }]@61 -> [[System.Obsolete("b")]int P { get => 1; }]@61 + """ + ); + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "int P", GetResource("extension block"))); + } + + [Fact] + public void Extension_Property_AccessorAttribute_Add_Remove() + { + var src1 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + int P { get => 1; } + } + } + + class A : System.Attribute {} + """; + + var src2 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + int P { [A]get => 1; } + } + } + + class A : System.Attribute {} + """; + + var edits1 = GetTopEdits(src1, src2); + edits1.VerifyEdits("Update [get => 1;]@189 -> [[A]get => 1;]@189"); + edits1.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "get", GetResource("extension block"))); + + var edits2 = GetTopEdits(src2, src1); + edits2.VerifyEdits("Update [[A]get => 1;]@189 -> [get => 1;]@189"); + edits2.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "get", GetResource("extension block"))); + } + + [Fact] + public void Extension_Property_AccessorAttribute_Add_Remove_Set() + { + var src1 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + int P { get => 1; set { } } + } + } + + class A : System.Attribute {} + """; + + var src2 = s_attributeSource + """ + static class Ext + { + extension(object o) + { + int P { get => 1; [A]set { } } + } + } + + class A : System.Attribute {} + """; + + var edits1 = GetTopEdits(src1, src2); + edits1.VerifyEdits("Update [set { }]@199 -> [[A]set { }]@199"); + edits1.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "set", GetResource("extension block"))); + + var edits2 = GetTopEdits(src2, src1); + edits2.VerifyEdits("Update [[A]set { }]@199 -> [set { }]@199"); + edits2.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "set", GetResource("extension block"))); + } + + [Fact] + public void Extension_Property_Getter_BodyUpdate() + { + var src1 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; } + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + int P { get => 2; } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Update [get => 1;]@69 -> [get => 2;]@69"); + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "get", GetResource("extension block"))); + } + + [Fact] + public void Extension_Property_Setter_BodyUpdate() + { + var src1 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; set { } } + } + } + """; + + var src2 = """ + static class Ext + { + extension(object o) + { + int P { get => 1; set { var _ = 1; } } + } + } + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Update [set { }]@79 -> [set { var _ = 1; }]@79"); + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "set", GetResource("extension block"))); + } + + [Fact] + public void Extension_Operator_AddRemove() + { + var src1 = """ + static class Ext + { + extension(C c) + { + } + } + + class C {} + """; + + var src2 = """ + static class Ext + { + extension(C c) + { + public static int operator +(C a, C b) => 1; + } + } + + class C {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits( + "Insert [public static int operator +(C a, C b) => 1;]@56", + "Insert [(C a, C b)]@84", + "Insert [C a]@85", + "Insert [C b]@90" + ); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertOperator, "public static int operator +(C a, C b)", "operator"), + Diagnostic(RudeEditKind.Update, "public static int operator +(C a, C b)", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "C b", GetResource("extension block")) + ); + + edits = GetTopEdits(src2, src1); + edits.VerifyEdits( + "Delete [public static int operator +(C a, C b) => 1;]@56", + "Delete [(C a, C b)]@84", + "Delete [C a]@85", + "Delete [C b]@90" + ); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "extension", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "static class Ext", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Operator_ChangeOperator() + { + var src1 = """ + static class Ext + { + extension(C c) + { + public static int operator +(C a, C b) => 1; + } + } + + class C {} + """; + + var src2 = """ + static class Ext + { + extension(C c) + { + public static int operator -(C a, C b) => 1; + } + } + + class C {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Update [public static int operator +(C a, C b) => 1;]@56 -> [public static int operator -(C a, C b) => 1;]@56"); + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "public static int operator -(C a, C b)", GetResource("extension block"))); + } + + [Fact] + public void Extension_Operator_AddRemoveAttribute() + { + var src1 = """ + static class Ext + { + extension(C c) + { + public static int operator +(C a, C b) => 1; + } + } + + class C {} + class A : System.Attribute {} + """; + + var src2 = """ + static class Ext + { + extension(C c) + { + [A] + public static int operator +(C a, C b) => 1; + } + } + + class C {} + class A : System.Attribute {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits( + """ + Update [public static int operator +(C a, C b) => 1;]@56 -> [[A] + public static int operator +(C a, C b) => 1;]@56 + """ + ); + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "public static int operator +(C a, C b)", GetResource("extension block"))); + + edits = GetTopEdits(src2, src1); + edits.VerifyEdits( + """ + Update [[A] + public static int operator +(C a, C b) => 1;]@56 -> [public static int operator +(C a, C b) => 1;]@56 + """ + ); + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "public static int operator +(C a, C b)", GetResource("extension block"))); + } + + [Fact] + public void Extension_Operator_ChangeAttribute() + { + var src1 = """ + static class Ext + { + extension(C c) + { + [System.Obsolete("")] + public static int operator +(C a, C b) => 1; + } + } + + class C {} + """; + + var src2 = """ + static class Ext + { + extension(C c) + { + [System.Obsolete("ERR")] + public static int operator -(C a, C b) => 1; + } + } + + class C {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits( + """ + Update [[System.Obsolete("")] + public static int operator +(C a, C b) => 1;]@56 -> [[System.Obsolete("ERR")] + public static int operator -(C a, C b) => 1;]@56 + """ + ); + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Update, "public static int operator -(C a, C b)", GetResource("extension block"))); + } + + [Fact] + public void Extension_Operator_ChangeParameter_Type() + { + var src1 = """ + static class Ext + { + extension(C c) + { + public static int operator +(C a, C b) => 1; + } + } + + class C {} + """; + + var src2 = """ + static class Ext + { + extension(C c) + { + public static int operator +(C a, object b) => 1; + } + } + + class C {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Update [C b]@90 -> [object b]@90"); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "object b", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "object b", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Operator_ChangeParameter_Name() + { + var src1 = """ + static class Ext + { + extension(C c) + { + public static int operator +(C a, C b) => 1; + } + } + + class C {} + """; + + var src2 = """ + static class Ext + { + extension(C c) + { + public static int operator +(C a, C c) => 1; + } + } + + class C {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Update [C b]@90 -> [C c]@90"); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "C c", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "C c", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Operator_ChangeParameter_AddRemoveAttribute() + { + var src1 = """ + static class Ext + { + extension(C c) + { + public static int operator +(C a, C b) => 1; + } + } + + class C {} + class A : System.Attribute {} + """; + + var src2 = """ + static class Ext + { + extension(C c) + { + public static int operator +([A] C a, C b) => 1; + } + } + + class C {} + class A : System.Attribute {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits("Update [C a]@85 -> [[A] C a]@85"); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")) + ); + + edits = GetTopEdits(src2, src1); + edits.VerifyEdits("Update [[A] C a]@85 -> [C a]@85"); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")) + ); + } + + [Fact] + public void Extension_Operator_ChangeParameter_ChangeAttribute() + { + var src1 = """ + static class Ext + { + extension(C c) + { + public static int operator +([System.Obsolete("")] C a, C c) => 1; + } + } + + class C {} + class A : System.Attribute {} + """; + + var src2 = """ + static class Ext + { + extension(C c) + { + public static int operator +([System.Obsolete("ERR")] C a, C b) => 1; + } + } + + class C {} + class A : System.Attribute {} + """; + + var edits = GetTopEdits(src1, src2); + edits.VerifyEdits( + """ + Update [[System.Obsolete("")] C a]@85 -> [[System.Obsolete("ERR")] C a]@85 + """, + "Update [C c]@112 -> [C b]@115" + ); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "C b", GetResource("extension block")) + ); + + edits = GetTopEdits(src2, src1); + edits.VerifyEdits( + """ + Update [[System.Obsolete("ERR")] C a]@85 -> [[System.Obsolete("")] C a]@85 + """, + "Update [C b]@115 -> [C c]@112" + ); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "C a", GetResource("extension block")), + Diagnostic(RudeEditKind.Update, "C c", GetResource("extension block")) + ); + } + + #endregion + #region Namespaces [Fact] diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index e96503cc24680..51f6544991a11 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2914,6 +2914,17 @@ newSymbol is IPropertySymbol newProperty && continue; } + if (oldSymbol is { ContainingType.IsExtension: true }) + { + // This is inside a new extension declaration, and not currently supported. + // https://github.com/dotnet/roslyn/issues/78959 + diagnosticContext.Report(RudeEditKind.Update, + oldDeclaration, + cancellationToken, + [FeaturesResources.extension_block]); + continue; + } + ReportDeletedMemberActiveStatementsRudeEdits(); var rudeEditKind = RudeEditKind.Delete; @@ -3069,6 +3080,18 @@ newSymbol is IPropertySymbol newProperty && ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: oldContainingType != null); } + if (newContainingType.IsExtension) + { + // This is a new extension declaration, and not currently supported. + // https://github.com/dotnet/roslyn/issues/78959 + diagnosticContext.Report(RudeEditKind.Update, + cancellationToken, + GetDiagnosticSpan(newDeclaration, EditKind.Insert), + [FeaturesResources.extension_block]); + + continue; + } + if (oldContainingType == null) { // If a type parameter is inserted into the parameter list of a type declaration, the symbol key won't be resolved (because the arities do not match). @@ -4225,6 +4248,14 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( hasGeneratedAttributeChange = false; hasGeneratedReturnTypeAttributeChange = false; + if (IsOrIsContainedInNewExtension(oldSymbol) || IsOrIsContainedInNewExtension(newSymbol)) + { + // https://github.com/dotnet/roslyn/issues/78959 + // Currently not supported + diagnosticContext.Report(RudeEditKind.Update, cancellationToken, arguments: [FeaturesResources.extension_block]); + return; + } + if (oldSymbol.Kind != newSymbol.Kind) { rudeEdit = (oldSymbol.Kind == SymbolKind.Field || newSymbol.Kind == SymbolKind.Field) ? RudeEditKind.FieldKindUpdate : RudeEditKind.Update; @@ -4529,6 +4560,23 @@ oldSymbol is not INamedTypeSymbol and not ITypeParameterSymbol and not IParamete { diagnosticContext.Report(rudeEdit, cancellationToken); } + + bool IsOrIsContainedInNewExtension(ISymbol symbol) + { + var current = symbol; + do + { + if (current is INamedTypeSymbol { IsExtension: true }) + { + return true; + } + + current = current.ContainingType; + } + while (current != null); + + return false; + } } private static bool GeneratesParameterAttribute(RefKind kind) diff --git a/src/Features/Core/Portable/EditAndContinue/MemberBody.cs b/src/Features/Core/Portable/EditAndContinue/MemberBody.cs index 676b9fd568208..47a045f1d73ef 100644 --- a/src/Features/Core/Portable/EditAndContinue/MemberBody.cs +++ b/src/Features/Core/Portable/EditAndContinue/MemberBody.cs @@ -9,6 +9,13 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; +/// +/// Represents a declaration of a member with executable code directly inside. +/// +/// +/// Executable code here is code that a user might set breakpoints in. For example, a normal type with no primary constructor would not have any directly-nested +/// executable code, but a record with a primary constructor might. +/// internal abstract class MemberBody : DeclarationBody { /// diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 3240ff770f63c..91de537d5bf3a 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -3223,4 +3223,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Semantic search is only supported when code analysis runs in a separate process on the latest .NET (see Tools > Options > Text Editor > C# > Advanced). - \ No newline at end of file + + extension block + + diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 9a4e8402f7179..105af68d8d63b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -3613,6 +3613,11 @@ Pokud se specifikátor formátu d použije bez dalších specifikátorů vlastn zapuštěná Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external external diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index e7d7d0bd2c05c..d575007a71a90 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -3613,6 +3613,11 @@ Bei Verwendung des Formatbezeichners "d" ohne weitere benutzerdefinierte Formatb Eingebettet Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external Extern diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 5fc2e000e34c4..4f18ee9667fbb 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -3613,6 +3613,11 @@ Si el especificador de formato "d" se usa sin otros especificadores de formato p Incrustado Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external externo diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 2353fc2298c4e..9d79c5d12966c 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -3613,6 +3613,11 @@ Si le spécificateur de format "d" est utilisé sans autres spécificateurs de f Rapport Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external externe diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index e06d22570cba3..58bb39b99dffe 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -3613,6 +3613,11 @@ Se l'identificatore di formato "d" viene usato senza altri identificatori di for Incorporata Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external external diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 84e7882b5134e..d7bd998089a59 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's 埋め込み Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external 外部 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 2f09541302e10..406c90dcc7a60 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's 포함됨 Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external 외부 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index e4e3f1f225dab..4fee08df99525 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -3613,6 +3613,11 @@ Jeśli specyfikator formatu „d” zostanie użyty bez innych indywidualnych sp osadzone Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external zewnętrzny diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 8467f2031fb38..e6454321315c2 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -3613,6 +3613,11 @@ Se o especificador de formato "d" for usado sem outros especificadores de format embutido Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external externo diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 9a89486aff856..cf2e1a81c1bd9 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's внедренный Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external внешний diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index f0f503ab8de65..fd8404764d817 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's gömülü Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external dış diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 6487b76873dde..a1b991b4c986e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's 嵌入源 Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external 外部源 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 117f52c28b7cd..f1e1ffd6315d3 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -3613,6 +3613,11 @@ If the "d" format specifier is used without other custom format specifiers, it's 內嵌 Embedded is a technical term for "Embedded source", where souce files are embedded into the PDB + + extension block + extension block + + external 外部 diff --git a/src/Features/TestUtilities/EditAndContinue/EditScriptDescription.cs b/src/Features/TestUtilities/EditAndContinue/EditScriptDescription.cs index ddb568447c334..206142cd8b2f0 100644 --- a/src/Features/TestUtilities/EditAndContinue/EditScriptDescription.cs +++ b/src/Features/TestUtilities/EditAndContinue/EditScriptDescription.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.Differencing; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Test.Utilities; namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests; @@ -29,9 +30,39 @@ public void VerifyEdits() => VerifyEdits(Array.Empty()); public void VerifyEdits(params string[] expected) - => AssertEx.Equal(expected, Edits.Select(e => e.GetDebuggerDisplay()), itemSeparator: ",\r\n", itemInspector: s => $""" - "{s}" - """); + => AssertEx.Equal(expected, Edits.Select(e => e.GetDebuggerDisplay()), itemSeparator: ",\r\n", itemInspector: static s => + { + var maxQuoteRun = 0; + var currentRun = 0; + foreach (var c in s) + { + if (c == '"') + { + currentRun++; + maxQuoteRun = Math.Max(maxQuoteRun, currentRun); + } + else + { + currentRun = 0; + } + } + + if (maxQuoteRun >= 1 || s.ContainsLineBreak()) + { + var quoteBlock = new string('"', Math.Max(maxQuoteRun + 1, 3)); + return $""" + {quoteBlock} + {s} + {quoteBlock} + """; + } + else + { + return $""" + "{s}" + """; + } + }); public void VerifyEdits(params EditKind[] expected) => AssertEx.Equal(expected, Edits.Select(e => e.Kind));