From 9283d265c51d3f5a7d8856cfcbe9f94993bfe31d Mon Sep 17 00:00:00 2001 From: tmat Date: Thu, 22 Jun 2023 12:41:36 -0700 Subject: [PATCH] EnC: Implement support for editing types with primary constructors --- .../EditAndContinue/EditAndContinueTest.cs | 9 +- .../EditAndContinue/EditAndContinueTests.cs | 371 +- .../Test/Utilities/CSharp/Extensions.cs | 41 +- .../Helpers/EditingTestBase.cs | 10 + .../EditAndContinue/StatementEditingTests.cs | 336 +- .../EditAndContinue/TopLevelEditingTests.cs | 4881 ++++++++++------- .../EditAndContinueTestHelpers.cs | 88 +- .../Helpers/EditingTestBase.vb | 19 + .../EditAndContinue/LineEditTests.vb | 6 +- .../EditAndContinue/TopLevelEditingTests.vb | 28 +- .../CSharpEditAndContinueAnalyzer.cs | 270 +- .../EditAndContinue/SyntaxComparer.cs | 25 +- .../AbstractEditAndContinueAnalyzer.cs | 1352 +++-- .../EditAndContinueDiagnosticDescriptors.cs | 3 - .../Portable/EditAndContinue/RudeEditKind.cs | 6 +- .../Utilities/BidirectionalMap.cs | 37 +- .../EditAndContinue/Utilities/Extensions.cs | 29 + .../Core/Portable/FeaturesResources.resx | 13 +- .../Portable/xlf/FeaturesResources.cs.xlf | 20 +- .../Portable/xlf/FeaturesResources.de.xlf | 20 +- .../Portable/xlf/FeaturesResources.es.xlf | 20 +- .../Portable/xlf/FeaturesResources.fr.xlf | 20 +- .../Portable/xlf/FeaturesResources.it.xlf | 20 +- .../Portable/xlf/FeaturesResources.ja.xlf | 20 +- .../Portable/xlf/FeaturesResources.ko.xlf | 20 +- .../Portable/xlf/FeaturesResources.pl.xlf | 20 +- .../Portable/xlf/FeaturesResources.pt-BR.xlf | 20 +- .../Portable/xlf/FeaturesResources.ru.xlf | 20 +- .../Portable/xlf/FeaturesResources.tr.xlf | 20 +- .../xlf/FeaturesResources.zh-Hans.xlf | 20 +- .../xlf/FeaturesResources.zh-Hant.xlf | 20 +- .../VisualBasicEditAndContinueAnalyzer.vb | 60 +- 32 files changed, 4953 insertions(+), 2891 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.cs index 018bec4f8190e..04f7915b95aa5 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.cs @@ -105,6 +105,8 @@ internal EditAndContinueTest AddGeneration(SourceWithMarkedNodes source, Semanti throw new Exception($"Exception during generation #{_generations.Count}. See inner stack trace for details.", ex); } + Assert.Empty(diff.EmitResult.Diagnostics); + // EncVariableSlotAllocator attempted to map from current source to the previous one, // but the mapping failed for these nodes. Mark the nodes in sources with node markers .... Assert.Empty(unmappedNodes); @@ -155,13 +157,18 @@ private ImmutableArray GetSemanticEdits( return ImmutableArray.CreateRange(edits.Select(e => { - var oldSymbol = e.SymbolProvider(oldCompilation); + var oldSymbol = e.Kind is SemanticEditKind.Update or SemanticEditKind.Delete ? e.SymbolProvider(oldCompilation) : null; + + // for delete the new symbol is the new containing type var newSymbol = e.NewSymbolProvider(newCompilation); Func? syntaxMap; if (e.PreserveLocalVariables) { Assert.Equal(SemanticEditKind.Update, e.Kind); + Debug.Assert(oldSymbol != null); + Debug.Assert(newSymbol != null); + syntaxMap = syntaxMapFromMarkers ?? EditAndContinueTestBase.GetEquivalentNodesMap( ((Public.MethodSymbol)newSymbol).GetSymbol(), ((Public.MethodSymbol)oldSymbol).GetSymbol()); } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 87a0c8c2462aa..b30c935579895 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -40,7 +40,7 @@ private static IEnumerable DumpTypeRefs(MetadataReader[] readers) } [Fact] - public void Constructor_Delete() + public void Constructor_Delete_WithParameterless() { using var _ = new EditAndContinueTest() .AddBaseline( @@ -106,6 +106,73 @@ .maxstack 8 .Verify(); } + [Fact] + public void Constructor_Delete() + { + using var _ = new EditAndContinueTest() + .AddBaseline( + source: $$""" + class C + { + public C(int x) + { + } + } + """, + validator: g => + { + g.VerifyMethodDefNames(".ctor"); + }) + + .AddGeneration( + source: """ + class C + { + } + """, + edits: new[] + { + Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C").InstanceConstructors.FirstOrDefault(c => c.Parameters.Length == 1), newSymbolProvider: c => c.GetMember("C")), + }, + validator: g => + { + // The default constructor is added and the deleted constructor is updated to throw: + g.VerifyMethodDefNames(".ctor", ".ctor"); + + g.VerifyEncLogDefinitions(new[] + { + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(1, TableIndex.MethodDef), + Handle(2, TableIndex.MethodDef), + Handle(1, TableIndex.Param) + }); + + g.VerifyIL(""" + { + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000005 + IL_0005: throw + } + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call 0x0A000006 + IL_0006: nop + IL_0007: ret + } + """); + }) + .Verify(); + } + [Fact] public void Diagnostics_Nullable() { @@ -14907,6 +14974,308 @@ record R(int X) "System.Runtime.CompilerServices: {NullableAttribute, NullableContextAttribute}"); } + [Fact] + public void Records_AddPrimaryConstructor() + { + using var _ = new EditAndContinueTest(options: TestOptions.DebugDll, targetFramework: TargetFramework.NetStandard20) + .AddBaseline( + source: IsExternalInitTypeDefinition + "record R {}", + validator: g => + { + g.VerifyMethodDefNames( + ".ctor", + ".ctor", + ".ctor", + ".ctor", + "get_EqualityContract", + "ToString", + "PrintMembers", + "op_Inequality", + "op_Equality", + "GetHashCode", + "Equals", + "Equals", + "$", + ".ctor", + ".ctor"); + }) + + .AddGeneration( + source: IsExternalInitTypeDefinition + "record R(int P) {}", + edits: new[] + { + // The IDE actually also adds Update edits for synthesized methods (PrintMembers, Equals, GetHashCode). + // This test demonstrates that the compiler does not emit them automatically given just the constructor insert. + Edit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("R")), + Edit(SemanticEditKind.Delete, c => c.GetParameterlessConstructor("R"), c => c.GetMember("R")) + }, + validator: g => + { + g.VerifyMethodDefNames( + ".ctor", // inserted primary ctor + ".ctor", // updated parameterless ctor + "get_P", + "set_P", + "Deconstruct"); + + g.VerifyEncLogDefinitions(new[] + { + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddField), + Row(3, TableIndex.Field, EditAndContinueOperation.Default), + Row(15, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(16, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(17, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(18, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(19, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.PropertyMap, EditAndContinueOperation.AddProperty), + Row(2, TableIndex.Property, EditAndContinueOperation.Default), + Row(16, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(9, TableIndex.Param, EditAndContinueOperation.Default), + Row(18, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(10, TableIndex.Param, EditAndContinueOperation.Default), + Row(19, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(11, TableIndex.Param, EditAndContinueOperation.Default), + Row(30, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(31, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(32, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(33, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(34, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + }); + }) + .Verify(); + } + + [Fact] + public void Records_AddPrimaryConstructorParameter() + { + using var _ = new EditAndContinueTest(options: TestOptions.DebugDll, targetFramework: TargetFramework.NetStandard20) + .AddBaseline( + source: IsExternalInitTypeDefinition + "record R(int P, int U) {}", + validator: g => + { + g.VerifyMethodDefNames( + ".ctor", + ".ctor", + ".ctor", + ".ctor", + ".ctor", + "get_EqualityContract", + "get_P", + "set_P", + "get_U", + "set_U", + "ToString", + "PrintMembers", + "op_Inequality", + "op_Equality", + "GetHashCode", + "Equals", + "Equals", + "$", + ".ctor", + "Deconstruct"); + }) + + .AddGeneration( + source: IsExternalInitTypeDefinition + "record R(int P, int Q, int U) {}", + edits: new[] + { + // The IDE actually also adds Update edits for synthesized methods (PrintMembers, Equals, GetHashCode, copy-constructor) and + // delete of the old primary constructor. + // This test demonstrates that the compiler does not emit them automatically given just the constructor insert. + // The synthesized auto-properties and Deconstruct method are emitted. + Edit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("R")) + }, + validator: g => + { + g.VerifyMethodDefNames( + ".ctor", + "get_Q", + "set_Q", + "Deconstruct"); + + g.VerifyEncLogDefinitions(new[] + { + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddField), + Row(5, TableIndex.Field, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(21, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(22, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(23, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(24, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.PropertyMap, EditAndContinueOperation.AddProperty), + Row(4, TableIndex.Property, EditAndContinueOperation.Default), + Row(21, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(15, TableIndex.Param, EditAndContinueOperation.Default), + Row(21, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(16, TableIndex.Param, EditAndContinueOperation.Default), + Row(21, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(17, TableIndex.Param, EditAndContinueOperation.Default), + Row(23, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(18, TableIndex.Param, EditAndContinueOperation.Default), + Row(24, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(19, TableIndex.Param, EditAndContinueOperation.Default), + Row(24, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(20, TableIndex.Param, EditAndContinueOperation.Default), + Row(24, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(21, TableIndex.Param, EditAndContinueOperation.Default), + Row(39, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(40, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(41, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(42, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(43, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(7, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + }); + + g.VerifyIL(""" + { + // Code size 29 (0x1d) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld 0x04000003 + IL_0007: ldarg.0 + IL_0008: ldarg.2 + IL_0009: stfld 0x04000005 + IL_000e: ldarg.0 + IL_000f: ldarg.3 + IL_0010: stfld 0x04000004 + IL_0015: ldarg.0 + IL_0016: call 0x0A000017 + IL_001b: nop + IL_001c: ret + } + { + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld 0x04000005 + IL_0006: ret + } + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld 0x04000005 + IL_0007: ret + } + { + // Code size 25 (0x19) + .maxstack 8 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: call 0x06000007 + IL_0007: stind.i4 + IL_0008: ldarg.2 + IL_0009: ldarg.0 + IL_000a: call 0x06000016 + IL_000f: stind.i4 + IL_0010: ldarg.3 + IL_0011: ldarg.0 + IL_0012: call 0x06000009 + IL_0017: stind.i4 + IL_0018: ret + } + """); + }) + .Verify(); + } + + [Fact] + public void Records_AddProperty_NonPrimary() + { + using var _ = new EditAndContinueTest(options: TestOptions.DebugDll, targetFramework: TargetFramework.NetStandard20) + .AddBaseline( + source: IsExternalInitTypeDefinition + "record R(int P) {}", + validator: g => + { + g.VerifyMethodDefNames( + ".ctor", + ".ctor", + ".ctor", + ".ctor", + ".ctor", + "get_EqualityContract", + "get_P", + "set_P", + "ToString", + "PrintMembers", + "op_Inequality", + "op_Equality", + "GetHashCode", + "Equals", + "Equals", + "$", + ".ctor", + "Deconstruct"); + }) + + .AddGeneration( + source: IsExternalInitTypeDefinition + "record R(int P) { int Q { get; init; } }", + edits: new[] + { + // The IDE actually adds Update edits for synthesized methods (PrintMembers, Equals, GetHashCode, copy-constructor). + // This test demonstrates that the compiler does not emit them automatically given just the property insert. + Edit(SemanticEditKind.Insert, c => c.GetMember("R.Q")), + }, + validator: g => + { + g.VerifyMethodDefNames( + "get_Q", + "set_Q"); + + g.VerifyEncLogDefinitions(new[] + { + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddField), + Row(4, TableIndex.Field, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(19, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(20, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.PropertyMap, EditAndContinueOperation.AddProperty), + Row(3, TableIndex.Property, EditAndContinueOperation.Default), + Row(20, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(12, TableIndex.Param, EditAndContinueOperation.Default), + Row(35, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(36, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(37, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(38, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + }); + + g.VerifyIL(""" + { + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldfld 0x04000004 + IL_0006: ret + } + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld 0x04000004 + IL_0007: ret + } + """); + }) + .Verify(); + } + [Fact] public void TopLevelStatement_Update() { diff --git a/src/Compilers/Test/Utilities/CSharp/Extensions.cs b/src/Compilers/Test/Utilities/CSharp/Extensions.cs index 3cc47f6453cd7..f45ecfed39658 100644 --- a/src/Compilers/Test/Utilities/CSharp/Extensions.cs +++ b/src/Compilers/Test/Utilities/CSharp/Extensions.cs @@ -163,25 +163,52 @@ private static ImmutableArray SplitMemberName(string name) } public static Symbol GetMember(this CSharpCompilation compilation, string qualifiedName) + => compilation.GlobalNamespace.GetMember(qualifiedName); + + public static ISymbol GetMember(this Compilation compilation, string qualifiedName) + => compilation.GlobalNamespace.GetMember(qualifiedName); + + public static T GetMember(this CSharpCompilation compilation, string qualifiedName) where T : Symbol + => (T)compilation.GlobalNamespace.GetMember(qualifiedName); + + public static T GetMember(this Compilation compilation, string qualifiedName) where T : ISymbol + => (T)compilation.GlobalNamespace.GetMember(qualifiedName); + + public static IMethodSymbol GetCopyConstructor(this Compilation compilation, string qualifiedTypeName) { - return compilation.GlobalNamespace.GetMember(qualifiedName); + var type = compilation.GetMember(qualifiedTypeName); + return type.InstanceConstructors.Single(c => c.Parameters is [{ Type: var parameterType }] && parameterType.Equals(type, SymbolEqualityComparer.Default)); } - public static ISymbol GetMember(this Compilation compilation, string qualifiedName) + public static IMethodSymbol GetPrimaryConstructor(this Compilation compilation, string qualifiedTypeName) { - return compilation.GlobalNamespace.GetMember(qualifiedName); + var type = compilation.GetMember(qualifiedTypeName); + return type.InstanceConstructors.Single(c => c.DeclaringSyntaxReferences.Any(r => r.GetSyntax() is TypeDeclarationSyntax)); } - public static T GetMember(this CSharpCompilation compilation, string qualifiedName) where T : Symbol + public static IMethodSymbol GetParameterlessConstructor(this Compilation compilation, string qualifiedTypeName) { - return (T)compilation.GlobalNamespace.GetMember(qualifiedName); + var type = compilation.GetMember(qualifiedTypeName); + return type.InstanceConstructors.Single(c => c.Parameters is []); } - public static T GetMember(this Compilation compilation, string qualifiedName) where T : ISymbol + public static IMethodSymbol GetIEquatableEquals(this Compilation compilation, string qualifiedTypeName) { - return (T)compilation.GlobalNamespace.GetMember(qualifiedName); + var type = compilation.GetMember(qualifiedTypeName); + return type.GetMembers("Equals").OfType().Single(m => m.Parameters is [{ Type: var parameterType }] && parameterType.Equals(type, SymbolEqualityComparer.Default)); } + public static IMethodSymbol GetPrimaryDeconstructor(this Compilation compilation, string qualifiedTypeName) + { + var primaryConstructor = compilation.GetPrimaryConstructor(qualifiedTypeName); + return primaryConstructor.ContainingType.GetMembers("Deconstruct").OfType().Single( + m => m.Parameters.Length == primaryConstructor.Parameters.Length && + m.Parameters.All(p => p.RefKind == RefKind.Out && p.Type.Equals(primaryConstructor.Parameters[p.Ordinal].Type, SymbolEqualityComparer.Default))); + } + + public static ImmutableArray GetMembers(this Compilation compilation, string qualifiedName) where T : ISymbol + => GetMembers(compilation, qualifiedName).SelectAsArray(s => (T)s.ISymbol); + public static ImmutableArray GetMembers(this Compilation compilation, string qualifiedName) { NamespaceOrTypeSymbol lastContainer; diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index 30da268821528..1d1a47d5fae6c 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -45,6 +45,14 @@ internal enum MethodKind public static string GetResource(string keyword, string symbolDisplayName) => string.Format(FeaturesResources.member_kind_and_name, TryGetResource(keyword) ?? throw ExceptionUtilities.UnexpectedValue(keyword), symbolDisplayName); + public static string GetResource(string keyword, string symbolDisplayName, string containerKeyword, string containerDisplayName) + => string.Format( + FeaturesResources.symbol_kind_and_name_of_member_kind_and_name, + TryGetResource(keyword) ?? throw ExceptionUtilities.UnexpectedValue(keyword), + symbolDisplayName, + TryGetResource(containerKeyword) ?? throw ExceptionUtilities.UnexpectedValue(containerKeyword), + containerDisplayName); + public static string GetResource(string keyword) => TryGetResource(keyword) ?? throw ExceptionUtilities.UnexpectedValue(keyword); @@ -63,6 +71,8 @@ public static string GetResource(string keyword) "field" => FeaturesResources.field, "method" => FeaturesResources.method, "property" => FeaturesResources.property_, + "property getter" => CSharpFeaturesResources.property_getter, + "property setter" => CSharpFeaturesResources.property_setter, "auto-property" => FeaturesResources.auto_property, "indexer" => CSharpFeaturesResources.indexer, "indexer getter" => CSharpFeaturesResources.indexer_getter, diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 63e38dbbb135a..d0ee06b0bbf0b 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -4013,8 +4013,8 @@ void F() Diagnostic(RudeEditKind.NotAccessingCapturedVariableInLambda, "a2", "y", CSharpFeaturesResources.lambda)); } - [Fact] - public void Lambdas_Update_CeaseCapture_IndexerParameter1() + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] + public void Lambdas_Update_CeaseCapture_IndexerParameter_WithExpressionBody() { var src1 = @" using System; @@ -4038,8 +4038,66 @@ class C Diagnostic(RudeEditKind.NotCapturingVariable, "int a1", "a1")); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] + public void Lambdas_Update_CeaseCapture_IndexerParameter_WithExpressionBody_LambdaBlock() + { + var src1 = @" +using System; + +class C +{ + int this[int a] => new Func(() => { return a + 1; })(); +} +"; + var src2 = @" +using System; + +class C +{ + int this[int a] => new Func(() => { return 2; })(); // not capturing a anymore +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [int this[int a] => new Func(() => { return a + 1; })();]@35 -> [int this[int a] => new Func(() => { return 2; })();]@35", + "Update [=> new Func(() => { return a + 1; })()]@51 -> [=> new Func(() => { return 2; })()]@51"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "int a", "a")); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] + public void Lambdas_Update_CeaseCapture_IndexerParameter_WithExpressionBody_Delegate() + { + var src1 = @" +using System; + +class C +{ + int this[int a] => new Func(delegate { return a + 1; })(); +} +"; + var src2 = @" +using System; + +class C +{ + int this[int a] => new Func(delegate { return 2; })(); // not capturing a anymore +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [int this[int a] => new Func(delegate { return a + 1; })();]@35 -> [int this[int a] => new Func(delegate { return 2; })();]@35", + "Update [=> new Func(delegate { return a + 1; })()]@51 -> [=> new Func(delegate { return 2; })()]@51"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "int a", "a")); + } + [Fact] - public void Lambdas_Update_CeaseCapture_IndexerParameter2() + public void Lambdas_Update_CeaseCapture_IndexerParameter_WithExpressionBody_Getter() { var src1 = @" using System; @@ -4063,6 +4121,38 @@ class C Diagnostic(RudeEditKind.NotCapturingVariable, "int a1", "a1")); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] + public void Lambdas_Update_CeaseCapture_IndexerParameter_WithExpressionBody_Partial() + { + var srcA1 = @" +partial class C +{ +}"; + var srcB1 = @" +partial class C +{ + int this[int a] => new System.Func(() => a + 1); +}"; + + var srcA2 = @" +partial class C +{ + int this[int a] => new System.Func(() => 2); // no capture +}"; + var srcB2 = @" +partial class C +{ +}"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(diagnostics: new[] { Diagnostic(RudeEditKind.NotCapturingVariable, "int a", "a") }), + DocumentResults(), + }); + } + [Fact] public void Lambdas_Update_CeaseCapture_IndexerParameter_ParameterDelete() { @@ -4089,7 +4179,7 @@ class C } [Fact] - public void Lambdas_Update_CeaseCapture_MethodParameter1() + public void Lambdas_Update_CeaseCapture_MethodParameter() { var src1 = @" using System; @@ -4119,8 +4209,8 @@ void F(int a1, int a2) Diagnostic(RudeEditKind.NotCapturingVariable, "int a2", "a2")); } - [Fact] - public void Lambdas_Update_CeaseCapture_MethodParameter2() + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] + public void Lambdas_Update_CeaseCapture_MethodParameter_WithExpressionBody() { var src1 = @" using System; @@ -4427,6 +4517,123 @@ event Action D Diagnostic(RudeEditKind.NotCapturingVariable, "remove", "value")); } + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/68731")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] + public void Lambdas_Update_CeaseCapture_ConstructorInitializer_This() + { + var src1 = "class C { C(int x) : this(() => x) {} C(Func f) {} }"; + var src2 = "class C { C(int x) : this(() => 1) {} C(Func f) {} }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "int x", "x")); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/68731")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] + public void Lambdas_Update_CeaseCapture_ConstructorInitializer_Base() + { + var src1 = "class C : B { C(int x) : base(() => x) {} } class B { public B(Func f) {} }"; + var src2 = "class C : B { C(int x) : base(() => 1) {} } class B { public B(Func f) {} }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "int x", "x")); + } + + [Theory] + [CombinatorialData] + public void Lambdas_Update_CeaseCapture_PrimaryParameter_InPrimaryConstructor_First( + [CombinatorialValues("class", "struct", "record", "record struct")] string keyword) + { + var src1 = keyword + " C(int x, int y) { System.Func z = () => x; }"; + var src2 = keyword + " C(int x, int y) { System.Func z = () => 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "int x", "x")); + } + + [Theory] + [CombinatorialData] + public void Lambdas_Update_CeaseCapture_PrimaryParameter_InPrimaryConstructor_Second( + [CombinatorialValues("class", "struct", "record", "record struct")] string keyword) + { + var src1 = keyword + " C(int x, int y) { System.Func z = () => x + y; }"; + var src2 = keyword + " C(int x, int y) { System.Func z = () => x; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "int y", "y")); + } + + [Theory(Skip = "https://github.com/dotnet/roslyn/issues/68731")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] + [CombinatorialData] + public void Lambdas_Update_CeaseCapture_PrimaryParameter_InPrimaryConstructor_BaseInitializer( + [CombinatorialValues("class", "struct", "record")] string keyword) + { + var src1 = keyword + " C(int x, int y) : B(() => x);"; + var src2 = keyword + " C(int x, int y) : B(() => 1);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "x", "x")); + } + + [Fact] + public void Lambdas_Update_CeaseCapture_PrimaryParameter_Method_First() + { + var src1 = "class C(int x, int y) { System.Func M() => () => x; }"; + var src2 = "class C(int x, int y) { System.Func M() => () => 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.NotCapturingVariable, "M", "x")); + } + + [Fact] + public void Lambdas_Update_CeaseCapture_PrimaryParameter_Method_Second() + { + var src1 = "class C(int x, int y) { System.Func M() => () => x + y; }"; + var src2 = "class C(int x, int y) { System.Func M() => () => x; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.M"), preserveLocalVariables: true)); + } + + [Fact] + public void Lambdas_Update_CeaseCapture_PrimaryParameter_Method_ThisToPrimaryCapture() + { + var src1 = "class C(int x, int y) { System.Func M() => () => x; }"; + var src2 = "class C(int x, int y) { System.Func M() => () => this.M()(); }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.M"), preserveLocalVariables: true)); + } + + [Fact] + public void Lambdas_Update_CeaseCapture_PrimaryParameter_Method_PrimaryToThisCapture() + { + var src1 = "class C(int x, int y) { System.Func M() => () => this.M()(); }"; + var src2 = "class C(int x, int y) { System.Func M() => () => x; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.M"), preserveLocalVariables: true)); + } + [Fact] public void Lambdas_Update_DeleteCapture1() { @@ -4822,6 +5029,123 @@ void F() Diagnostic(RudeEditKind.CapturingVariable, "a1", "a1")); } + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/68731")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] + public void Lambdas_Update_Capturing_ConstructorInitializer_This() + { + var src1 = "class C { C(int x) : this(() => 1) {} C(Func f) {} }"; + var src2 = "class C { C(int x) : this(() => x) {} C(Func f) {} }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.CapturingVariable, "x", "x")); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/68731")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] + public void Lambdas_Update_Capturing_ConstructorInitializer_Base() + { + var src1 = "class C : B { C(int x) : base(() => 1) {} } class B { public B(Func f) {} }"; + var src2 = "class C : B { C(int x) : base(() => x) {} } class B { public B(Func f) {} }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.CapturingVariable, "x", "x")); + } + + [Theory] + [CombinatorialData] + public void Lambdas_Update_Capturing_PrimaryParameter_InPrimaryConstructor_First( + [CombinatorialValues("class", "struct", "record", "record struct")] string keyword) + { + var src1 = keyword + " C(int x, int y) { System.Func z = () => 1; }"; + var src2 = keyword + " C(int x, int y) { System.Func z = () => x; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.CapturingVariable, "x", "x")); + } + + [Theory] + [CombinatorialData] + public void Lambdas_Update_Capturing_PrimaryParameter_InPrimaryConstructor_Second( + [CombinatorialValues("class", "struct", "record", "record struct")] string keyword) + { + var src1 = keyword + " C(int x, int y) { System.Func z = () => x; }"; + var src2 = keyword + " C(int x, int y) { System.Func z = () => x + y; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.CapturingVariable, "y", "y")); + } + + [Theory(Skip = "https://github.com/dotnet/roslyn/issues/68731")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] + [CombinatorialData] + public void Lambdas_Update_Capturing_PrimaryParameter_InPrimaryConstructor_BaseInitializer( + [CombinatorialValues("class", "struct", "record")] string keyword) + { + var src1 = keyword + " C(int x, int y) : B(() => 1);"; + var src2 = keyword + " C(int x, int y) : B(() => x);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.CapturingVariable, "x", "x")); + } + + [Fact] + public void Lambdas_Update_Capturing_PrimaryParameter_Method_First() + { + var src1 = "class C(int x, int y) { System.Func M() => () => 1; }"; + var src2 = "class C(int x, int y) { System.Func M() => () => x; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.CapturingVariable, "x", "x")); + } + + [Fact] + public void Lambdas_Update_Capturing_PrimaryParameter_Method_Second() + { + var src1 = "class C(int x, int y) { System.Func M() => () => x; }"; + var src2 = "class C(int x, int y) { System.Func M() => () => x + y; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.M"), preserveLocalVariables: true)); + } + + [Fact] + public void Lambdas_Update_Capturing_PrimaryParameter_Method_ThisToPrimaryCapture() + { + var src1 = "class C(int x, int y) { System.Func M() => () => this.M()(); }"; + var src2 = "class C(int x, int y) { System.Func M() => () => x; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.M"), preserveLocalVariables: true)); + } + + [Fact] + public void Lambdas_Update_Capturing_PrimaryParameter_Method_PrimaryToThisCapture() + { + var src1 = "class C(int x, int y) { System.Func M() => () => x; }"; + var src2 = "class C(int x, int y) { System.Func M() => () => this.M()(); }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.M"), preserveLocalVariables: true)); + } + [Fact] public void Lambdas_Update_StaticToThisOnly1() { diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 9d44aed19b0aa..460115a3230d0 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -5,6 +5,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -22,6 +23,9 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests [UseExportProvider] public class TopLevelEditingTests : EditingTestBase { + private static readonly string s_attributeSource = @" +[System.AttributeUsage(System.AttributeTargets.All)]class A : System.Attribute { } +"; #region Usings [Fact] @@ -1414,7 +1418,7 @@ public void Struct_NoModifiers_IntoType_Insert() } [Fact] - public void Type_BaseType_Add_Unchanged() + public void Type_BaseType_Insert_Unchanged() { var src1 = "class C { }"; var src2 = "class C : object { }"; @@ -1422,13 +1426,13 @@ public void Type_BaseType_Add_Unchanged() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [class C { }]@0 -> [class C : object { }]@0"); + "Insert [: object]@8"); edits.VerifySemantics(); } [Fact] - public void Type_BaseType_Add_Changed() + public void Type_BaseType_Insert_Changed() { var src1 = "class C { }"; var src2 = "class C : D { }"; @@ -1436,7 +1440,39 @@ public void Type_BaseType_Add_Changed() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [class C { }]@0 -> [class C : D { }]@0"); + "Insert [: D]@8"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "D", FeaturesResources.class_)); + } + + [Fact] + public void Type_BaseType_Insert_WithPrimaryInitializer() + { + var src1 = "class C() { }"; + var src2 = "class C() : D() { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [: D()]@10", + "Insert [D()]@12"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "D()", FeaturesResources.class_)); + } + + [Fact] + public void Type_BaseType_Delete_WithPrimaryInitializer() + { + var src1 = "class C() : D() { }"; + var src2 = "class C() { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Delete [: D()]@10", + "Delete [D()]@12"); edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "class C", FeaturesResources.class_)); @@ -1471,7 +1507,7 @@ public void Type_BaseType_Update_RuntimeTypeChanged(string oldType, string newTy var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "class C", FeaturesResources.class_)); + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "System.Collections.Generic.List<" + newType + ">", FeaturesResources.class_)); } [Fact] @@ -1494,10 +1530,10 @@ public void Type_BaseInterface_Add() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [class C { }]@0 -> [class C : IDisposable { }]@0"); + "Insert [: IDisposable]@8"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "class C", FeaturesResources.class_)); + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "IDisposable", FeaturesResources.class_)); } [Fact] @@ -1530,10 +1566,10 @@ public void Type_BaseInterface_Reorder() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [class C : IGoo, IBar { }]@0 -> [class C : IBar, IGoo { }]@0"); + "Update [: IGoo, IBar]@8 -> [: IBar, IGoo]@8"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "class C", FeaturesResources.class_)); + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "IBar, IGoo", FeaturesResources.class_)); } [Theory] @@ -1563,7 +1599,7 @@ public void Type_BaseInterface_Update_RuntimeTypeChanged(string oldType, string var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "class C", FeaturesResources.class_)); + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "System.Collections.Generic.IEnumerable<" + newType + ">", FeaturesResources.class_)); } [Fact] @@ -1870,6 +1906,7 @@ public class SubClass : BaseClass, IConflict string IConflict.Get() => String.Empty; }]@219", + "Insert [: BaseClass, IConflict]@241", "Insert [public override string Get() => string.Empty;]@272", "Insert [string IConflict.Get() => String.Empty;]@325", "Insert [()]@298", @@ -2542,7 +2579,7 @@ class C { 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(), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true), }) }); } @@ -2584,7 +2621,7 @@ partial class C { 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), + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true), }) }); } @@ -2626,7 +2663,7 @@ class C { 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), + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true), }), DocumentResults() @@ -2727,23 +2764,6 @@ public void Type_Move_MultiFile() #region Records - [Fact] - public void Record_Partial_MovePrimaryConstructor() - { - var src1 = @" -partial record C { } -partial record C(int X);"; - var src2 = @" -partial record C(int X); -partial record C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics(); - - edits.VerifySemanticDiagnostics(); - } - [Fact] public void Record_Name_Update() { @@ -2846,7 +2866,7 @@ public void Record_NoModifiers_IntoType_Insert() } [Fact] - public void Record_BaseTypeUpdate1() + public void Record_BaseType_Update1() { var src1 = "record C { }"; var src2 = "record C : D { }"; @@ -2854,14 +2874,14 @@ public void Record_BaseTypeUpdate1() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [record C { }]@0 -> [record C : D { }]@0"); + "Insert [: D]@9"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "D", CSharpFeaturesResources.record_)); } [Fact] - public void Record_BaseTypeUpdate2() + public void Record_BaseType_Update2() { var src1 = "record C : D1 { }"; var src2 = "record C : D2 { }"; @@ -2869,14 +2889,14 @@ public void Record_BaseTypeUpdate2() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [record C : D1 { }]@0 -> [record C : D2 { }]@0"); + "Update [: D1]@9 -> [: D2]@9"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "D2", CSharpFeaturesResources.record_)); } [Fact] - public void Record_BaseInterfaceUpdate1() + public void Record_BaseInterface_Update1() { var src1 = "record C { }"; var src2 = "record C : IDisposable { }"; @@ -2884,14 +2904,14 @@ public void Record_BaseInterfaceUpdate1() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [record C { }]@0 -> [record C : IDisposable { }]@0"); + "Insert [: IDisposable]@9"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "IDisposable", CSharpFeaturesResources.record_)); } [Fact] - public void Record_BaseInterfaceUpdate2() + public void Record_BaseInterface_Update2() { var src1 = "record C : IGoo, IBar { }"; var src2 = "record C : IGoo { }"; @@ -2899,14 +2919,14 @@ public void Record_BaseInterfaceUpdate2() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [record C : IGoo, IBar { }]@0 -> [record C : IGoo { }]@0"); + "Update [: IGoo, IBar]@9 -> [: IGoo]@9"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "IGoo", CSharpFeaturesResources.record_)); } [Fact] - public void Record_BaseInterfaceUpdate3() + public void Record_BaseInterface_Update3() { var src1 = "record C : IGoo, IBar { }"; var src2 = "record C : IBar, IGoo { }"; @@ -2914,14 +2934,14 @@ public void Record_BaseInterfaceUpdate3() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [record C : IGoo, IBar { }]@0 -> [record C : IBar, IGoo { }]@0"); + "Update [: IGoo, IBar]@9 -> [: IBar, IGoo]@9"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "IBar, IGoo", CSharpFeaturesResources.record_)); } [Fact] - public void RecordInsert_AbstractVirtualOverride() + public void Record_Method_Insert_AbstractVirtualOverride() { var src1 = ""; var src2 = @" @@ -2937,380 +2957,312 @@ public override void H() {} capabilities: EditAndContinueCapabilities.NewTypeDefinition); } - [Fact] - public void Record_ImplementSynthesized_ParameterlessConstructor() + [Theory] + [InlineData("Equals", "public virtual bool Equals(C rhs) => true;", "C rhs")] + [InlineData("PrintMembers", "protected virtual bool PrintMembers(System.Text.StringBuilder sb) => true;", "System.Text.StringBuilder sb")] + [InlineData("Deconstruct", "public void Deconstruct(out int Y) { Y = 1; }", "out int Y")] + [InlineData(".ctor", "protected C(C other) {}", "C other")] + public void Record_Method_Insert_ReplacingSynthesizedWithCustom_ParameterNameChanges(string methodName, string methodImpl, string parameterDecl) { - var src1 = "record C { }"; - var src2 = @" -record C -{ - public C() - { - } -}"; + var src1 = "record C(int X) { }"; + var src2 = "record C(int X) { " + methodImpl + " }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.First(c => c.ToString() == "C.C()"), preserveLocalVariables: true)); + edits.VerifySemanticDiagnostics( + new[] + { + Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, parameterDecl, FeaturesResources.parameter), + }, + capabilities: EditAndContinueCapabilities.Baseline); - edits.VerifySemanticDiagnostics(); + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Update, c => methodName switch { ".ctor" => c.GetCopyConstructor("C"), "Equals" => c.GetIEquatableEquals("C"), _ => c.GetMember("C." + methodName) }), + }, + capabilities: EditAndContinueCapabilities.UpdateParameters); } [Fact] - public void RecordStruct_ImplementSynthesized_ParameterlessConstructor() + public void Record_Method_Insert_ReplacingSynthesizedWithCustom_SemanticError() { - var src1 = "record struct C { }"; - var src2 = @" -record struct C + var src1 = "record C { }"; + var src2 = @"record C { - public C() - { - } +protected virtual bool PrintMembers(System.Text.StringBuilder sb) => false; +protected virtual bool PrintMembers(System.Text.StringBuilder sb) => false; }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.First(c => c.ToString() == "C.C()"), preserveLocalVariables: true)); + edits.VerifySemanticDiagnostics( + new[] + { + Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "System.Text.StringBuilder sb", GetResource("parameter")), + Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "System.Text.StringBuilder sb", GetResource("parameter")) + }, + capabilities: EditAndContinueCapabilities.Baseline); - edits.VerifySemanticDiagnostics(); + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.PrintMembers").First().ISymbol), + }, + capabilities: EditAndContinueCapabilities.UpdateParameters | EditAndContinueCapabilities.AddMethodToExistingType); } - [Fact] - public void Record_ImplementSynthesized_PrintMembers() + [Theory] + [InlineData("ToString", "public override string ToString() => null;")] + [InlineData("GetHashCode", "public override int GetHashCode() => 1;")] + [InlineData("Equals", "public virtual bool Equals(C other) => true;")] + [InlineData("PrintMembers", "protected virtual bool PrintMembers(System.Text.StringBuilder builder) => true;")] + [InlineData("Deconstruct", "public void Deconstruct(out int X) { X = 1; }")] + [InlineData(".ctor", "protected C(C original) {}")] + public void Record_Method_Insert_ReplacingSynthesizedWithCustom(string methodName, string methodImpl) { - var src1 = "record C { }"; - var src2 = @" -record C -{ - protected virtual bool PrintMembers(System.Text.StringBuilder builder) - { - return true; - } -}"; + var src1 = "record C(int X) { }"; + var src2 = "record C(int X) { " + methodImpl + " }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers"))); - - edits.VerifySemanticDiagnostics(); + SemanticEdit(SemanticEditKind.Update, c => methodName switch { ".ctor" => c.GetCopyConstructor("C"), "Equals" => c.GetIEquatableEquals("C"), _ => c.GetMember("C." + methodName) })); } - [Fact] - public void RecordStruct_ImplementSynthesized_PrintMembers() - { - var src1 = "record struct C { }"; - var src2 = @" -record struct C -{ - private readonly bool PrintMembers(System.Text.StringBuilder builder) - { - return true; - } -}"; + [Theory] + [InlineData("ToString", "public override string ToString() => null;")] + [InlineData("GetHashCode", "public override int GetHashCode() => 1;")] + [InlineData("Equals", "public virtual bool Equals(C other) => true;")] + [InlineData("PrintMembers", "protected virtual bool PrintMembers(System.Text.StringBuilder builder) => true;")] + [InlineData("Deconstruct", "public void Deconstruct(out int X) { X = 1; }")] + [InlineData(".ctor", "protected C(C original) {}")] + public void Record_Method_Delete_ReplacingCustomWithSynthesized(string methodName, string methodImpl) + { + var src1 = "record C(int X) { " + methodImpl + " }"; + var src2 = "record C(int X) { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers"))); + SemanticEdit(SemanticEditKind.Update, c => methodName switch { ".ctor" => c.GetCopyConstructor("C"), "Equals" => c.GetIEquatableEquals("C"), _ => c.GetMember("C." + methodName) })); edits.VerifySemanticDiagnostics(); } - [Fact] - public void Record_ImplementSynthesized_WrongParameterName() + [Theory] + [InlineData("Equals", "public virtual bool Equals(C rhs) => true;", "Equals(C other)", "other")] + [InlineData("PrintMembers", "protected virtual bool PrintMembers(System.Text.StringBuilder sb) => true;", "PrintMembers(StringBuilder builder)", "builder")] + [InlineData("Deconstruct", "public void Deconstruct(out int Y) { Y = 1; }", "Deconstruct(out int X)", "X")] + [InlineData(".ctor", "protected C(C other) {}", "C(C original)", "original")] + public void Record_Method_Delete_ReplacingSynthesizedWithCustom_ParameterNameChanges(string methodName, string methodImpl, string methodDisplay, string parameterDisplay) { - var src1 = "record C { }"; - var src2 = @" -record C -{ - protected virtual bool PrintMembers(System.Text.StringBuilder sb) - { - return false; - } - - public virtual bool Equals(C rhs) - { - return false; - } - - protected C(C other) - { - } -}"; + var src1 = "record C(int X) { " + methodImpl + " }"; + var src2 = "record C(int X) { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( new[] { - Diagnostic(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, "protected virtual bool PrintMembers(System.Text.StringBuilder sb)", "PrintMembers(System.Text.StringBuilder builder)"), - Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "System.Text.StringBuilder sb", FeaturesResources.parameter), - Diagnostic(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, "public virtual bool Equals(C rhs)", "Equals(C other)"), - Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "C rhs", FeaturesResources.parameter), - Diagnostic(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, "protected C(C other)", "C(C original)") + Diagnostic( + RudeEditKind.RenamingNotSupportedByRuntime, + "record C", + GetResource("parameter", parameterDisplay, methodName switch { ".ctor" => "constructor", _ => "method" }, methodDisplay)), }, capabilities: EditAndContinueCapabilities.Baseline); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.FirstOrDefault()?.Type.ToDisplayString() == "C"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Length == 0), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => methodName switch { ".ctor" => c.GetCopyConstructor("C"), "Equals" => c.GetIEquatableEquals("C"), _ => c.GetMember("C." + methodName) }), }, capabilities: EditAndContinueCapabilities.UpdateParameters); } [Fact] - public void Record_ImplementSynthesized_ToString() + public void Record_Field_Insert() { - var src1 = "record C { }"; - var src2 = @" -record C -{ - public override string ToString() - { - return ""R""; - } -}"; + var src1 = "record C(int X) { }"; + var src2 = "record C(int X) { private int _y; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.ToString"))); - - edits.VerifySemanticDiagnostics(); + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), + }, + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); } [Fact] - public void Record_UnImplementSynthesized_ToString() + public void Record_Field_Insert_WithExplicitMembers() { var src1 = @" -record C +record C(int X) { - public override string ToString() + public C(C other) { - return ""R""; } }"; - var src2 = "record C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.ToString"))); - - edits.VerifySemanticDiagnostics(); - } - - [Fact] - public void Record_AddProperty_Primary() - { - var src1 = "record C(int X);"; - var src2 = "record C(int X, int Y);"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "int Y", FeaturesResources.parameter)); - } - - [Fact] - public void Record_UnimplementSynthesized_ParameterlessConstructor() - { - var src1 = @" -record C + var src2 = @" +record C(int X) { - public C() + private int _y; + + public C(C other) { } }"; - var src2 = "record C { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.First(c => c.ToString() == "C.C()"), preserveLocalVariables: true)); - - edits.VerifySemanticDiagnostics(); + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), + }, + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); } [Fact] - public void RecordStruct_UnimplementSynthesized_ParameterlessConstructor() + public void Record_Field_Insert_WithInitializer() { - var src1 = @" -record struct C -{ - public C() - { - } -}"; - var src2 = "record struct C { }"; + var src1 = "record C(int X) { }"; + var src2 = "record C(int X) { private int _y = 1; }"; + var syntaxMap = GetSyntaxMap(src1, src2); var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.First(c => c.ToString() == "C.C()"), preserveLocalVariables: true)); - - edits.VerifySemanticDiagnostics(); + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true) + }, + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); } [Fact] - public void Record_AddProperty_NotPrimary() + public void Record_Field_Insert_WithExistingInitializer() { - var src1 = "record C(int X);"; - var src2 = @" -record C(int X) -{ - public int Y { get; set; } -}"; + var src1 = "record C(int X) { private int _y = 1; }"; + var src2 = "record C(int X) { private int _y = 1; private int _z; }"; var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); edits.VerifySemantics( new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._z")), }, - capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); } [Fact] - public void Record_AddProperty_NotPrimary_WithConstructor() + public void Record_Field_Insert_WithInitializerAndExistingInitializer() { - var src1 = @" -record C(int X) -{ - public C(string fromAString) - { - } -}"; - var src2 = @" -record C(int X) -{ - public int Y { get; set; } - - public C(string fromAString) - { - } -}"; + var src1 = "record C(int X) { private int _y = 1; }"; + var src2 = "record C(int X) { private int _y = 1; private int _z = 1; }"; var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); edits.VerifySemantics( new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._z")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), syntaxMap[0]), }, - capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); } [Fact] - public void Record_AddProperty_NotPrimary_WithExplicitMembers() + public void Record_Field_Delete() { - var src1 = @" -record C(int X) -{ - protected virtual bool PrintMembers(System.Text.StringBuilder builder) - { - return false; - } - - public override int GetHashCode() - { - return 0; - } + var src1 = "record C(int X) { private int _y; }"; + var src2 = "record C(int X) { }"; - public virtual bool Equals(C other) - { - return false; - } + var edits = GetTopEdits(src1, src2); - public C(C original) - { - } -}"; - var src2 = @" -record C(int X) -{ - public int Y { get; set; } + edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Delete, "record C", DeletedSymbolDisplay(FeaturesResources.field, "_y"))); + } - protected virtual bool PrintMembers(System.Text.StringBuilder builder) - { - return false; - } + [Fact] + public void Record_Property_Update_Initializer_NotPrimary() + { + var src1 = "record C { int X { get; } = 0; }"; + var src2 = "record C { int X { get; } = 1; }"; - public override int GetHashCode() - { - return 0; - } + var edits = GetTopEdits(src1, src2); - public virtual bool Equals(C other) - { - return false; - } + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Length == 0), preserveLocalVariables: true)); + } - public C(C original) - { - } -}"; + [Fact] + public void Record_Property_Update_Initializer_Primary() + { + var src1 = "record C(int X) { int X { get; } = 0; }"; + var src2 = "record C(int X) { int X { get; } = 1; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true) - }, - capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); } - [Fact] - public void Record_AddProperty_NotPrimary_WithInitializer() + [Theory] + [InlineData("set")] + [InlineData("init")] + public void Record_Property_Delete_Writable(string setter) { - var src1 = "record C(int X);"; - var src2 = @" -record C(int X) -{ - public int Y { get; set; } = 1; -}"; + var src1 = "record C(int X) { public int P { get; " + setter + "; } }"; + var src2 = "record C(int X) { }"; var edits = GetTopEdits(src1, src2); - var syntaxMap = GetSyntaxMap(src1, src2); edits.VerifySemantics( new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), deletedSymbolContainerProvider: c => c.GetMember("C")) }, - capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); + capabilities: EditAndContinueCapabilities.Baseline); } - [Fact] - public void Record_AddField() + [Theory] + [InlineData("get;")] + [InlineData("get => 1;")] + public void Record_Property_Delete_ReadOnly(string getter) { - var src1 = "record C(int X) { }"; - var src2 = "record C(int X) { private int _y; }"; + var src1 = "record C(int X) { public int P { " + getter + " } }"; + var src2 = "record C(int X) { }"; var edits = GetTopEdits(src1, src2); @@ -3318,76 +3270,75 @@ public void Record_AddField() new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, - capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Theory] + [InlineData("get;")] + [InlineData("get => 1;")] + public void Record_Property_Delete_ReadOnly_ReplacingCustomWithSynthesized(string getter) + { + var src1 = "record C(int X) { public int X { " + getter + " } }"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics(); } [Fact] - public void Record_AddField_WithExplicitMembers() + public void Record_Property_Delete_WriteOnly() { - var src1 = @" -record C(int X) -{ - public C(C other) - { - } -}"; - var src2 = @" -record C(int X) -{ - private int _y; - - public C(C other) - { - } -}"; + var src1 = "record C(int X) { public int P { set { } } }"; + var src2 = "record C(int X) { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, - capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Record_AddField_WithInitializer() + public void Record_Property_Delete_Static() { - var src1 = "record C(int X) { }"; - var src2 = "record C(int X) { private int _y = 1; }"; - var syntaxMap = GetSyntaxMap(src1, src2); + var src1 = "record C(int X) { public static int P { get; set; } }"; + var src2 = "record C(int X) { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, - capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Record_AddField_WithExistingInitializer() + public void Record_Property_Delete_WithInitializer() { - var src1 = "record C(int X) { private int _y = 1; }"; - var src2 = "record C(int X) { private int _y = 1; private int _z; }"; + var src1 = @" +record C(int X) +{ + public int Y { get; set; } = 1; + + public C(bool b) : this(1) { } +}"; + var src2 = @" +record C(int X) +{ + public C(bool b) : this(1) { } +}"; var edits = GetTopEdits(src1, src2); var syntaxMap = GetSyntaxMap(src1, src2); @@ -3396,261 +3347,436 @@ public void Record_AddField_WithExistingInitializer() new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._z")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), syntaxMap[0]), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), }, - capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); } - [Fact] - public void Record_AddField_WithInitializerAndExistingInitializer() + [Theory] + [InlineData("")] + [InlineData(" = 1;")] + [InlineData(" = X;")] + public void Record_Property_Delete_ReplacingCustomWithSynthesized_Auto(string initializer) { - var src1 = "record C(int X) { private int _y = 1; }"; - var src2 = "record C(int X) { private int _y = 1; private int _z = 1; }"; + var src1 = "record C(int X) { public int X { get; init; }" + initializer + " }"; + var src2 = "record C(int X);"; var edits = GetTopEdits(src1, src2); - var syntaxMap = GetSyntaxMap(src1, src2); edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + } + + [Theory] + [InlineData("")] + [InlineData(" = 1;")] + [InlineData(" = X;")] + public void Record_Property_Delete_ReplacingCustomWithSynthesized_Auto_Partial(string initializer) + { + var srcA1 = "partial record C(int X);"; + var srcB1 = "partial record C { public int X { get; init; }" + initializer + " }"; + + var srcA2 = "partial record C(int X);"; + var srcB2 = "partial record C;"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._z")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), syntaxMap[0]), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) - }, - capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), partialType: "C", preserveLocalVariables: true) + }) + }); } - [Fact] - public void Record_DeleteField() + [Theory] + [InlineData("get => 4; init => throw null;")] + [InlineData("get { return 4; } init { }")] + public void Record_Property_Delete_ReplacingCustomWithSynthesized_WithBody(string body) { - var src1 = "record C(int X) { private int _y; }"; - var src2 = "record C(int X) { }"; + var src1 = "record C(int X) { public int X { " + body + " } }"; + var src2 = "record C(int X);"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Delete, "record C", DeletedSymbolDisplay(FeaturesResources.field, "_y"))); + // The property changes from custom property to field-backed auto-prop. + // Methods using backing field must be updated, unless they are explicitly declared. + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + + edits.VerifySemanticDiagnostics(); } - [Fact] - public void Record_DeleteProperty_Primary() + [Theory] + [InlineData("PrintMembers", "protected virtual bool PrintMembers(System.Text.StringBuilder builder) => true;")] + [InlineData("GetHashCode", "public override int GetHashCode() => 1;")] + [InlineData(".ctor", "protected C(C original) {}")] + public void Record_Property_Delete_ReplacingCustomWithSynthesized_WithBodyAndMethod(string methodName, string methodImpl) { - var src1 = "record C(int X, int Y) { }"; - var src2 = "record C(int X) { }"; + var src1 = "record C(int X) { " + methodImpl + " public int X { get => 4; init => throw null; } }"; + var src2 = "record C(int X) { " + methodImpl + " }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(Diagnostic(RudeEditKind.Delete, "record C", DeletedSymbolDisplay(FeaturesResources.parameter, "int Y"))); + // The property changes from custom property to field-backed auto-prop. + // Methods using backing field must be updated, unless they are explicitly declared. + + var expectedEdits = new List(); + + if (methodName != "PrintMembers") + { + expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers"))); + } + + expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C"))); + + if (methodName != "GetHashCode") + { + expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode"))); + } + + if (methodName != ".ctor") + { + expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C"))); + } + + expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod)); + expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod)); + expectedEdits.Add(SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + + edits.VerifySemantics(expectedEdits.ToArray()); + } + + [Theory] + [InlineData("get => 4; init => throw null;")] + [InlineData("get { return 4; } init { }")] + public void Record_Property_Delete_ReplacingCustomWithSynthesized_WithBody_Partial(string body) + { + var srcA1 = "partial record C(int X);"; + var srcB1 = "partial record C { public int X { " + body + " } }"; + + var srcA2 = "partial record C(int X);"; + var srcB2 = "partial record C;"; + + // The property changes from custom property to field-backed auto-prop. + // Methods using backing field must be updated, unless they are explicitly declared. + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), partialType : "C", preserveLocalVariables: true), + }) + }); } [Fact] - public void Record_DeleteProperty_NotPrimary() + public void Record_Property_Delete_ReplacingCustomWithSynthesized_WithAttribute() { - var src1 = "record C(int X) { public int P { get; set; } }"; - var src2 = "record C(int X) { }"; + var src1 = "record C([property: System.Obsolete]int P) { public int P { get; init; } = P; }"; + var src2 = "record C([property: System.Obsolete]int P) { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( + ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "int P", GetResource("property")) }, capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Record_ImplementSynthesized_Property() + public void Record_Property_Delete_ReplacingCustomWithSynthesized_WithAttributeOnAccessor() { - var src1 = "record C(int X);"; - var src2 = @" -record C(int X) -{ - public int X { get; init; } -}"; + var src1 = "record C(int P) { public int P { get; [System.Obsolete] init; } = P; }"; + var src2 = "record C(int P) { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), + }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); - edits.VerifySemanticDiagnostics(); + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "record C", GetResource("property setter", "P.init")) }, + capabilities: EditAndContinueCapabilities.Baseline); } - [Fact] - public void Record_ImplementSynthesized_Property_WithBody() + [Theory] + [InlineData("get; set;")] + [InlineData("get; init;")] + [InlineData("get {} set {}")] + [InlineData("get;")] + public void Record_Property_Insert(string accessors) { var src1 = "record C(int X);"; - var src2 = @" -record C(int X) -{ - public int X - { - get - { - return 4; - } - init - { - throw null; - } - } -}"; + var src2 = "record C(int X) { public int Y { " + accessors + " } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); - - edits.VerifySemanticDiagnostics(); + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + }, + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void Record_ImplementSynthesized_Property_WithExpressionBody() + public void Record_Property_Insert_WriteOnly() { var src1 = "record C(int X);"; - var src2 = @" -record C(int X) -{ - public int X { get => 4; init => throw null; } -}"; + var src2 = "record C(int X) { public int Y { set { } } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); - - edits.VerifySemanticDiagnostics(); + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void Record_ImplementSynthesized_Property_InitToSet() + public void Record_Property_Insert_Static() { var src1 = "record C(int X);"; + var src2 = "record C(int X) { public static int Y { get; set; } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")) + }, + capabilities: EditAndContinueCapabilities.AddStaticFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Record_Property_Insert_WithInitializer() + { + var src1 = @" +record C(int X) +{ + public C(bool b) : this(1) { } +}"; var src2 = @" record C(int X) { - public int X { get; set; } + public int Y { get; set; } = 1; + + public C(bool b) : this(1) { } }"; var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ImplementRecordParameterWithSet, "public int X", "X")); + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), + }, + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void Record_ImplementSynthesized_Property_MakeReadOnly() + public void Record_Property_Insert_ReplacingSynthesizedWithCustom_WithSetter() { var src1 = "record C(int X);"; - var src2 = @" -record C(int X) -{ - public int X { get; } -}"; + var src2 = "record C(int X) { public int X { get; set; } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ImplementRecordParameterAsReadOnly, "public int X", "X")); + Diagnostic(RudeEditKind.AccessorKindUpdate, "set", GetResource("property setter"))); } [Fact] - public void Record_UnImplementSynthesized_Property() + public void Record_Property_Insert_ReplacingSynthesizedWithCustom_ReadOnly() { - var src1 = @" -record C(int X) -{ - public int X { get; init; } -}"; - var src2 = "record C(int X);"; + var src1 = "record C(int X);"; + var src2 = "record C(int X) { public int X { get; } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); - - edits.VerifySemanticDiagnostics(); + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_X"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), + }); } [Fact] - public void Record_UnImplementSynthesized_Property_WithExpressionBody() + public void Record_Property_Insert_NotPrimary_WithExplicitMembers() { var src1 = @" record C(int X) { - public int X { get => 4; init => throw null; } + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return false; + } + + public override int GetHashCode() + { + return 0; + } + + public virtual bool Equals(C other) + { + return false; + } + + public C(C original) + { + } +}"; + var src2 = @" +record C(int X) +{ + public int Y { get; set; } + + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return false; + } + + public override int GetHashCode() + { + return 0; + } + + public virtual bool Equals(C other) + { + return false; + } + + public C(C original) + { + } }"; - var src2 = "record C(int X);"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); - - edits.VerifySemanticDiagnostics(); + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + }, + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); } - [Fact] - public void Record_UnImplementSynthesized_Property_WithBody() + [Theory] + [InlineData("")] + [InlineData(" = 1;")] + [InlineData(" = X;")] + public void Record_Property_Insert_ReplacingSynthesizedWithCustom_Auto(string initializer) { - var src1 = @" -record C(int X) -{ - public int X { get { return 4; } init { } } -}"; - var src2 = "record C(int X);"; + var src1 = "record C(int X);"; + var src2 = "record C(int X) { public int X { get; init; } " + initializer + " }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); edits.VerifySemanticDiagnostics(); } - [Fact] - public void Record_ImplementSynthesized_Property_Partial() + [Theory] + [InlineData("")] + [InlineData(" = 1;")] + [InlineData(" = X;")] + public void Record_Property_Insert_ReplacingSynthesizedWithCustom_Auto_Partial(string initializer) { - var srcA1 = @"partial record C(int X);"; - var srcB1 = @"partial record C;"; - var srcA2 = @"partial record C(int X);"; - var srcB2 = @" -partial record C -{ - public int X { get; init; } -}"; + var srcA1 = "partial record C(int X);"; + var srcB1 = "partial record C;"; + + var srcA2 = "partial record C(int X);"; + var srcB2 = "partial record C { public int X { get; init; }" + initializer + " }"; EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, @@ -3660,57 +3786,49 @@ partial record C DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), partialType: "C", preserveLocalVariables: true) }) }); } - [Fact] - public void Record_UnImplementSynthesized_Property_Partial() + [Theory] + [InlineData("get => 4; init => throw null;")] + [InlineData("get { return 4; } init { }")] + public void Record_Property_Insert_ReplacingSynthesizedWithCustom_WithBody(string body) { - var srcA1 = @"partial record C(int X);"; - var srcB1 = @" -partial record C -{ - public int X { get; init; } -}"; - var srcA2 = @"partial record C(int X);"; - var srcB2 = @"partial record C;"; + var src1 = "record C(int X);"; + var src2 = "record C(int X) { public int X { " + body + " } }"; - EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - new[] - { - DocumentResults(), - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType: "C", preserveLocalVariables: true) - }) - }); - } + var edits = GetTopEdits(src1, src2); - [Fact] - public void Record_ImplementSynthesized_Property_Partial_WithBody() - { - var srcA1 = @"partial record C(int X);"; - var srcB1 = @"partial record C;"; - var srcA2 = @"partial record C(int X);"; - var srcB2 = @" -partial record C -{ - public int X - { - get - { - return 4; + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + + edits.VerifySemanticDiagnostics(); } - init + + [Theory] + [InlineData("get => 4; init => throw null;")] + [InlineData("get { return 4; } init { }")] + public void Record_Property_Insert_ReplacingSynthesizedWithCustom_WithBody_Partial(string body) { - throw null; - } - } -}"; + var srcA1 = "partial record C(int X);"; + var srcB1 = "partial record C;"; + + var srcA2 = "partial record C(int X);"; + var srcB2 = "partial record C { public int X { " + body + " } }"; EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, @@ -3721,78 +3839,79 @@ public int X semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType : "C", preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), partialType: "C", preserveLocalVariables: true) }) }); } [Fact] - public void Record_UnImplementSynthesized_Property_Partial_WithBody() - { - var srcA1 = @"partial record C(int X);"; - var srcB1 = @" -partial record C -{ - public int X - { - get + public void Record_Property_Insert_ReplacingSynthesizedWithCustom_WithAttribute() { - return 4; + var src1 = "record C([property: System.Obsolete]int P) { }"; + var src2 = "record C([property: System.Obsolete]int P) { public int P { get; init; } = P; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), + }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "public int P", GetResource("property")) }, + capabilities: EditAndContinueCapabilities.Baseline); } - init + + [Fact] + public void Record_Property_Insert_ReplacingSynthesizedWithCustom_WithAttributeOnAccessor() { - throw null; - } - } -}"; - var srcA2 = @"partial record C(int X);"; - var srcB2 = @"partial record C;"; + var src1 = "record C(int P) { }"; + var src2 = "record C(int P) { public int P { get; [System.Obsolete] init; } = P; }"; - EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, new[] { - DocumentResults(), - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType : "C", preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) - }) - }); + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), + }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "init", GetResource("property setter")) }, + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Record_MoveProperty_Partial() + public void Record_Property_DeleteInsert() { - 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 srcA1 = "partial record C(int X) { public int Y { get; init; } }"; + var srcB1 = "partial record C;"; - var srcB2 = @" -partial record C -{ - public int Y { get; init; } -}"; + 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) }, @@ -3808,117 +3927,36 @@ partial record C }); } + #endregion + + #region Enums + [Fact] - public void Record_UnImplementSynthesized_Property_WithInitializer() + public void Enum_NoModifiers_Insert() { - var src1 = @" -record C(int X) -{ - public int X { get; init; } = 1; -}"; - var src2 = "record C(int X);"; + var src1 = ""; + var src2 = "enum C { A }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); - - edits.VerifySemanticDiagnostics(); + edits.VerifySemanticDiagnostics( + capabilities: EditAndContinueCapabilities.NewTypeDefinition); } [Fact] - public void Record_UnImplementSynthesized_Property_WithInitializerMatchingCompilerGenerated() + public void Enum_NoModifiers_IntoNamespace_Insert() { - var src1 = @" -record C(int X) -{ - public int X { get; init; } = X; -}"; - var src2 = "record C(int X);"; + var src1 = "namespace N { }"; + var src2 = "namespace N { enum C { } }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); - - edits.VerifySemanticDiagnostics(); + edits.VerifySemanticDiagnostics( + capabilities: EditAndContinueCapabilities.NewTypeDefinition); } [Fact] - public void Record_Property_Delete_NotPrimary() - { - var src1 = @" -record C(int X) -{ - public int Y { get; init; } -}"; - var src2 = "record C(int X);"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), - }, - capabilities: EditAndContinueCapabilities.Baseline); - } - - [Fact] - public void Record_PropertyInitializer_Update_NotPrimary() - { - var src1 = "record C { int X { get; } = 0; }"; - var src2 = "record C { int X { get; } = 1; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Length == 0), preserveLocalVariables: true)); - } - - [Fact] - public void Record_PropertyInitializer_Update_Primary() - { - var src1 = "record C(int X) { int X { get; } = 0; }"; - var src2 = "record C(int X) { int X { get; } = 1; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); - } - - #endregion - - #region Enums - - [Fact] - public void Enum_NoModifiers_Insert() - { - var src1 = ""; - var src2 = "enum C { A }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); - } - - [Fact] - public void Enum_NoModifiers_IntoNamespace_Insert() - { - var src1 = "namespace N { }"; - var src2 = "namespace N { enum C { } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.NewTypeDefinition); - } - - [Fact] - public void Enum_NoModifiers_IntoType_Insert() + public void Enum_NoModifiers_IntoType_Insert() { var src1 = "struct N { }"; var src2 = "struct N { enum C { } }"; @@ -4045,10 +4083,10 @@ public void Enum_BaseType_Add() var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Update [enum Color { Red = 1, Blue = 2, }]@0 -> [enum Color : ushort { Red = 1, Blue = 2, }]@0"); + edits.VerifyEdits("Insert [: ushort]@11"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.EnumUnderlyingTypeUpdate, "enum Color", FeaturesResources.enum_)); + Diagnostic(RudeEditKind.EnumUnderlyingTypeUpdate, "ushort", FeaturesResources.enum_)); } [Fact] @@ -4059,7 +4097,7 @@ public void Enum_BaseType_Add_Unchanged() var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Update [enum Color { Red = 1, Blue = 2, }]@0 -> [enum Color : int { Red = 1, Blue = 2, }]@0"); + edits.VerifyEdits("Insert [: int]@11"); edits.VerifySemantics(); } @@ -4072,10 +4110,10 @@ public void Enum_BaseType_Update() var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Update [enum Color : ushort { Red = 1, Blue = 2, }]@0 -> [enum Color : long { Red = 1, Blue = 2, }]@0"); + edits.VerifyEdits("Update [: ushort]@11 -> [: long]@11"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.EnumUnderlyingTypeUpdate, "enum Color", FeaturesResources.enum_)); + Diagnostic(RudeEditKind.EnumUnderlyingTypeUpdate, "long", FeaturesResources.enum_)); } [Fact] @@ -4086,7 +4124,7 @@ public void Enum_BaseType_Delete_Unchanged() var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Update [enum Color : int { Red = 1, Blue = 2, }]@0 -> [enum Color { Red = 1, Blue = 2, }]@0"); + edits.VerifyEdits("Delete [: int]@11"); edits.VerifySemantics(); } @@ -4099,7 +4137,7 @@ public void Enum_BaseType_Delete_Changed() var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Update [enum Color : ushort { Red = 1, Blue = 2, }]@0 -> [enum Color { Red = 1, Blue = 2, }]@0"); + edits.VerifyEdits("Delete [: ushort]@11"); edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.EnumUnderlyingTypeUpdate, "enum Color", FeaturesResources.enum_)); @@ -4576,7 +4614,7 @@ public void Delegate_Parameter_Rename() "Update [int a]@22 -> [int b]@22"); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "int b", FeaturesResources.parameter) }, + new[] { Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "int b", GetResource("parameter", "int b")) }, capabilities: EditAndContinueCapabilities.Baseline); edits.VerifySemantics( @@ -4600,25 +4638,7 @@ public void Delegate_Parameter_Update() "Update [int a]@22 -> [byte a]@22"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.TypeUpdate, "byte a", FeaturesResources.parameter)); - } - - [Fact] - public void Delegate_Parameter_AddAttribute_NotSupportedByRuntime() - { - var attribute = "public class AAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + "public delegate int D(int a);"; - var src2 = attribute + "public delegate int D([A]int a);"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [int a]@70 -> [[A]int a]@70"); - - edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "int a", FeaturesResources.parameter) }, - capabilities: EditAndContinueCapabilities.Baseline); + Diagnostic(RudeEditKind.TypeUpdate, "byte a", GetResource("parameter", "byte a"))); } [Fact] @@ -4642,6 +4662,10 @@ public void Delegate_Parameter_AddAttribute() SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.BeginInvoke")) }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "int a", GetResource("parameter", "int a")) }, + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] @@ -4870,7 +4894,7 @@ public void Delegate_ReadOnlyRef_Parameter_Update() "Update [int b]@22 -> [in int b]@22"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "in int b", FeaturesResources.parameter)); + Diagnostic(RudeEditKind.ModifiersUpdate, "in int b", GetResource("parameter", "in int b"))); } [Fact] @@ -5592,7 +5616,7 @@ public void NestedDelegateInPartialType_InsertDeleteAndChangeOptionalParameterVa DocumentResults( diagnostics: new[] { - Diagnostic(RudeEditKind.InitializerUpdate, "int x = 2", FeaturesResources.parameter) + Diagnostic(RudeEditKind.InitializerUpdate, "int x = 2", GetResource("parameter", "int x")) }), DocumentResults() @@ -6883,7 +6907,7 @@ public void PartialMember_DeleteInsert_AddFieldInitializer() DocumentResults(), DocumentResults(semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }) }); } @@ -6904,7 +6928,7 @@ public void PartialMember_DeleteInsert_RemoveFieldInitializer() DocumentResults(), DocumentResults(semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }) }); } @@ -7344,34 +7368,6 @@ class C new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Main"), preserveLocalVariables: false) }); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] - public void MethodWithExpressionBody_Update_LiftedParameter() - { - var src1 = @" -using System; - -class C -{ - int M(int a) => new Func(() => a + 1)(); -} -"; - var src2 = @" -using System; - -class C -{ - int M(int a) => new Func(() => 2)(); // not capturing a anymore -}"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [int M(int a) => new Func(() => a + 1)();]@35 -> [int M(int a) => new Func(() => 2)();]@35"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.NotCapturingVariable, "int a", "a")); - } - [Fact] public void MethodWithExpressionBody_ToBlockBody() { @@ -8163,7 +8159,7 @@ static void Main() capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "static void Main()", DeletedSymbolDisplay(FeaturesResources.parameter, "string[] args")) }, + new[] { Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "static void Main()", GetResource("parameter", "args", "method", "Main(string[] args)")) }, capabilities: EditAndContinueCapabilities.Baseline); } @@ -8197,8 +8193,8 @@ void M(int a) edits.VerifySemanticDiagnostics( new[] { - Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "void M(int a)", DeletedSymbolDisplay(FeaturesResources.parameter, "int b")), - Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "void M(int a)", DeletedSymbolDisplay(FeaturesResources.parameter, "int c")) + Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "void M(int a)", GetResource("parameter", "b", "method", "M(int a, int b, int c)")), + Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "void M(int a)", GetResource("parameter", "c", "method", "M(int a, int b, int c)")) }, capabilities: EditAndContinueCapabilities.Baseline); } @@ -10113,36 +10109,16 @@ public void Operator_Delete() #endregion - #region Constructor, Destructor + #region Constructor [Fact] public void Constructor_Parameter_AddAttribute() { - var src1 = @" -class C -{ - private int x = 1; - - public C(int a) - { - } -}"; - var src2 = @" -class C -{ - private int x = 2; - - public C([System.Obsolete]int a) - { - } -}"; + var src1 = "class C { public C(int a) { } }"; + var src2 = "class C { public C([System.Obsolete]int a) { } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [x = 1]@30 -> [x = 2]@30", - "Update [int a]@53 -> [[System.Obsolete]int a]@53"); - edits.VerifySemantics( ActiveStatementsDescription.Empty, new[] @@ -10153,572 +10129,2117 @@ public C([System.Obsolete]int a) } [Fact] - public void Constructor_ChangeParameterType() + public void Constructor_Parameter_AddAttribute_Primary() { - var src1 = @" -class C -{ - public C(bool x) - { - } -}"; - var src2 = @" -class C -{ - public C(int x) - { - } -}"; + var src1 = "class C(int a);"; + var src2 = "class C([System.Obsolete] int a);"; + var edits = GetTopEdits(src1, src2); edits.VerifySemantics( + ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C..ctor").FirstOrDefault(m => m.GetParameterTypes().Any(t => t.SpecialType == SpecialType.System_Boolean))?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C..ctor").FirstOrDefault(m => m.GetParameterTypes().Any(t => t.SpecialType == SpecialType.System_Int32))?.ISymbol) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C..ctor")) }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - - edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "int x", FeaturesResources.parameter) }, - capabilities: EditAndContinueCapabilities.Baseline); + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/2068")] - public void Constructor_ExternModifier_Add() + [Theory] + [InlineData("")] + [InlineData("param:")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68458")] + public void Constructor_Parameter_AddAttribute_Record(string target) { - var src1 = "class C { }"; - var src2 = "class C { public extern C(); }"; + var src1 = "record C(int P);" + s_attributeSource; + var src2 = "record C([" + target + "A]int P);" + s_attributeSource; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Insert [public extern C();]@10", - "Insert [()]@25"); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + [InlineData("record struct")] + public void Constructor_Parameter_DefaultValue_Primary(string keyword) + { + var src1 = keyword + " C(int X = 1) : D { }"; + var src2 = keyword + " C(int X = 2) : D { }"; + + var edits = GetTopEdits(src1, src2); - // This can be allowed as the compiler generates an empty constructor, but it's not worth the complexity. edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public extern C()", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.InitializerUpdate, "int X = 2", GetResource("parameter"))); } - [Fact] - public void ConstructorInitializer_Update1() + [Theory(Skip = "https://github.com/dotnet/roslyn/issues/68458")] + [InlineData("field")] + [InlineData("property")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68458")] + public void Constructor_Parameter_AddAttribute_Record_NonParamTargets(string target) { - var src1 = @" -class C -{ - public C(int a) : base(a) { } -}"; - var src2 = @" -class C -{ - public C(int a) : base(a + 1) { } -}"; - var edits = GetTopEdits(src1, src2); + var src1 = "record C(int P);" + s_attributeSource; + var src2 = "record C([" + target + ": A]int P);" + s_attributeSource; - edits.VerifyEdits( - "Update [public C(int a) : base(a) { }]@18 -> [public C(int a) : base(a + 1) { }]@18"); + var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P")), + }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } [Fact] - public void ConstructorInitializer_Update2() + [WorkItem("https://github.com/dotnet/roslyn/issues/68458")] + public void Constructor_Parameter_AddAttribute_Record_ReplacingSynthesizedWithCustomProperty() { - var src1 = @" -class C -{ - public C(int a) : base(a) { } -}"; - var src2 = @" -class C -{ - public C(int a) { } -}"; - var edits = GetTopEdits(src1, src2); + var src1 = "record C(int P) { }" + s_attributeSource; + var src2 = "record C([property: A][field: A][param: A]int P) { public int P { get; init; } }" + s_attributeSource; - edits.VerifyEdits( - "Update [public C(int a) : base(a) { }]@21 -> [public C(int a) { }]@21"); + var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( + edits.VerifySemantics( + ActiveStatementsDescription.Empty, new[] { - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "public C(int a)", GetResource("constructor")) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_P")), + // TOOD: Should include update of P: https://github.com/dotnet/roslyn/issues/68458 }, - capabilities: EditAndContinueCapabilities.Baseline); + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Fact] + public void Constructor_Parameter_AddAttribute_Record_ReplacingCustomPropertyWithSynthesized() + { + var src1 = "record C(int P) { public int P { get; init; } }" + s_attributeSource; + var src2 = "record C([property: A][field: A][param: A]int P) {} " + s_attributeSource; + + var edits = GetTopEdits(src1, src2); edits.VerifySemantics( + ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_P")), }, - capabilities: EditAndContinueCapabilities.Baseline | EditAndContinueCapabilities.GenericUpdateMethod); + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } [Fact] - public void ConstructorInitializer_Update3() + public void Constructor_Parameter_ChangeType() { - var src1 = @" -class C -{ - public C(int a) { } -}"; - var src2 = @" -class C -{ - public C(int a) : base(a) { } -}"; + var src1 = "class C { public C(bool x) { } }"; + var src2 = "class C { public C(int x) { } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [public C(int a) { }]@18 -> [public C(int a) : base(a) { }]@18"); + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single()) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - edits.VerifySemanticDiagnostics(); + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "int x", FeaturesResources.parameter) }, + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void ConstructorInitializer_Update4() + public void Constructor_Parameter_ChangeType_Primary() { - var src1 = @" -class C -{ - public C(int a) : base(a) { } -}"; - var src2 = @" -class C -{ - public C(int a) : base(a + 1) { } -}"; + var src1 = @"class C(bool x);"; + var src2 = @"class C(int x);"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [public C(int a) : base(a) { }]@21 -> [public C(int a) : base(a + 1) { }]@21"); - - edits.VerifySemanticDiagnostics( + edits.VerifySemantics( new[] { - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "public C(int a)", GetResource("constructor")) + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")) }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "int x", FeaturesResources.parameter) }, capabilities: EditAndContinueCapabilities.Baseline); + } - edits.VerifySemantics( + [Fact] + public void Constructor_Parameter_ChangeType_Primary_PartialMove() + { + var srcA1 = "partial class C(bool a);"; + var srcB1 = "partial class C;"; + + var srcA2 = "partial class C;"; + var srcB2 = "partial class C(int a);"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + }), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")) + }), }, - capabilities: EditAndContinueCapabilities.Baseline | EditAndContinueCapabilities.GenericUpdateMethod); + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/743552")] - public void ConstructorUpdate_AddParameter() + [Fact] + public void Constructor_Parameter_ChangeType_Record() { - var src1 = @" -class C -{ - public C(int a) { } -}"; - var src2 = @" -class C -{ - public C(int a, int b) { } -}"; + var src1 = @"record C(bool x);"; + var src2 = @"record C(int x);"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [(int a)]@26 -> [(int a, int b)]@26", - "Insert [int b]@34"); - edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C..ctor").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C..ctor").FirstOrDefault(m => m.GetParameterCount() == 2)?.ISymbol) + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_x"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.set_x").FirstOrDefault(p => p.Parameters is [{ Type.SpecialType: SpecialType.System_Boolean }]), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.x")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int b", FeaturesResources.parameter) }, + new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "int x", FeaturesResources.parameter) }, capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void DestructorDelete() + public void Constructor_Parameter_ChangeType_ReplacingClassWithRecord() { - var src1 = @"class B { ~B() { } }"; - var src2 = @"class B { }"; - - var expectedEdit1 = @"Delete [~B() { }]@10"; - + var src1 = @"class C(bool x);"; + var src2 = @"record C(int x);"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits(expectedEdit1); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class B", DeletedSymbolDisplay(CSharpFeaturesResources.destructor, "~B()"))); + new[] + { + Diagnostic(RudeEditKind.TypeKindUpdate, "record C") + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void DestructorDelete_InsertConstructor() + public void Constructor_Parameter_Delete() { - var src1 = @"class B { ~B() { } }"; - var src2 = @"class B { B() { } }"; + var src1 = "class C { C(int x, int y) { } }"; + var src2 = "class C { C(int x) { } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Insert [B() { }]@10", - "Insert [()]@11", - "Delete [~B() { }]@10"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingAccessibility, "B()", FeaturesResources.constructor), - Diagnostic(RudeEditKind.Delete, "class B", DeletedSymbolDisplay(CSharpFeaturesResources.destructor, "~B()"))); + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single()), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/789577")] - public void ConstructorUpdate_AnonymousTypeInFieldInitializer() + [Fact] + public void Constructor_Parameter_Delete_Primary() { - var src1 = "class C { int a = F(new { A = 1, B = 2 }); C() { x = 1; } }"; - var src2 = "class C { int a = F(new { A = 1, B = 2 }); C() { x = 2; } }"; + var src1 = "class C(int x, int y) { }"; + var src2 = "class C(int x) { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(); + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void Constructor_Delete() + public void Constructor_Parameter_Delete_Primary_Record() { - var src1 = "class C { public C(int x) { } }"; - var src2 = "class C { }"; + var src1 = "record C(int X, int Y) { }"; + var src2 = "record C(int X) { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - new[] { SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.FirstOrDefault(c => c.Parameters.Length == 1), deletedSymbolContainerProvider: c => c.GetMember("C")) }, - capabilities: EditAndContinueCapabilities.Baseline); + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void Constructor_Static_Delete() + public void Constructor_Parameter_Delete_Primary_Record_ReplacingSynthesizedWithCustomProperty() { - var src1 = "class C { static C() { } }"; - var src2 = "class C { }"; + var src1 = "record C(int X, int Y) { }"; + var src2 = "record C(int X) { public int Y { get; init; } }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.static_constructor, "C()"))); + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void Constructor_Static_Delete_Reloadable() + public void Constructor_Parameter_Delete_Primary_Record_WithCustomProperty_WithUpdate() { - var src1 = ReloadableAttributeSrc + "[CreateNewOnMetadataUpdate]class C { static C() { } }"; - var src2 = ReloadableAttributeSrc + "[CreateNewOnMetadataUpdate]class C { }"; + var src1 = "record C(int X, int Y) { public int Y { get; init; } }"; + var src2 = "record C(int X ) { [Obsolete]public int Y { get; init; } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - new[] { SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C")) }, - capabilities: EditAndContinueCapabilities.NewTypeDefinition); + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.ChangeCustomAttributes); } [Fact] - public void Constructor_Static_Insert() + public void Constructor_Parameter_Delete_Primary_Record_WithCustomProperty_WithAccessorUpdate() { - var src1 = "class C { }"; - var src2 = "class C { static C() { } }"; + var src1 = "record C(int X, int Y) { public int Y { get => new System.Func(() => 1).Invoke(); init { } } }"; + var src2 = "record C(int X ) { public int Y { get => new System.Func(() => 2).Invoke(); init { } } }"; var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); edits.VerifySemantics( - new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").StaticConstructors.Single()) }, - EditAndContinueCapabilities.AddMethodToExistingType); + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Y"), syntaxMap: syntaxMap[0]), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void InstanceCtorDelete_Public() + public void Constructor_Parameter_Delete_Primary_Record_WithCustomField_WithUpdate() { - var src1 = "class C { public C() { } }"; - var src2 = "class C { }"; + var src1 = "record C(int X, int Y) { public int Y; }"; + var src2 = "record C(int X ) { [Obsolete]public int Y; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true)); + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.ChangeCustomAttributes); } - [Theory] - [InlineData("")] - [InlineData("private")] - [InlineData("protected")] - [InlineData("internal")] - [InlineData("private protected")] - [InlineData("protected internal")] - public void InstanceCtorDelete_NonPublic(string accessibility) + [Fact] + public void Constructor_Parameter_Delete_Primary_Record_WithCustomPropertyDelete() { - var src1 = "class C { [System.Obsolete] " + accessibility + " C() { } }"; - var src2 = "class C { }"; + var src1 = "record C(int X, int Y) { public int Y { get => 0; init {} } }"; + var src2 = "record C(int X ) { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( + edits.VerifySemantics( new[] { - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()")), - Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()")) + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, - capabilities: EditAndContinueCapabilities.Baseline); + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void InstanceCtorDelete_Public_PartialWithInitializerUpdate() + public void Constructor_Parameter_Delete_Primary_Record_WithCustomDeconstructor_NoUpdate() { - var srcA1 = "partial class C { public C() { } }"; - var srcB1 = "partial class C { int x = 1; }"; + var src1 = "record C(int X, int Y) { public void Deconstruct(out int X) => X = 1; }"; + var src2 = "record C(int X ) { public void Deconstruct(out int X) => X = 1; }"; - var srcA2 = "partial class C { }"; - var srcB2 = "partial class C { int x = 2; }"; + var edits = GetTopEdits(src1, src2); - EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + edits.VerifySemantics( new[] { - DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }), - - DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }) - }); + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void InstanceCtorInsert_Public_Implicit() + public void Constructor_Parameter_Delete_Primary_Record_WithCustomDeconstructor_Update() { - var src1 = "class C { }"; - var src2 = "class C { public C() { } }"; + var src1 = "record C(int X, int Y) { public void Deconstruct(out int X) => X = 1; }"; + var src2 = "record C(int X ) { public void Deconstruct(out int X) => X = 2; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.Deconstruct").Single(m => m.Parameters is [_])), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void InstanceCtorInsert_Partial_Public_Implicit() + public void Constructor_Parameter_Delete_Primary_Record_WithCustomDeconstructor_Insert() { - var srcA1 = "partial class C { }"; - var srcB1 = "partial class C { }"; + var src1 = "record C(int X, int Y) { }"; + var src2 = "record C(int X ) { public void Deconstruct(out int X) => X = 1; }"; - var srcA2 = "partial class C { }"; - var srcB2 = "partial class C { public C() { } }"; + var edits = GetTopEdits(src1, src2); - EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + edits.VerifySemantics( new[] { - // no change in document A - DocumentResults(), - - DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }), - }); + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void InstanceCtorInsert_Public_NoImplicit() + public void Constructor_Parameter_Delete_Primary_Record_WithCustomDeconstructor_Delete() { - var src1 = "class C { public C(int a) { } }"; - var src2 = "class C { public C(int a) { } public C() { } }"; + var src1 = "record C(int X, int Y) { public void Deconstruct(out int X, out int Y) => X = Y = 1; }"; + var src2 = "record C(int X ) { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - semanticEdits: new[] + new[] { - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters.IsEmpty)) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Y"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Fact] - public void InstanceCtorInsert_Partial_Public_NoImplicit() + [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/743552")] + public void Constructor_Parameter_Insert() { - var srcA1 = "partial class C { }"; - var srcB1 = "partial class C { public C(int a) { } }"; + var src1 = "class C { public C(int a) { } }"; + var src2 = "class C { public C(int a, int b) { } }"; + var edits = GetTopEdits(src1, src2); - var srcA2 = "partial class C { public C() { } }"; - var srcB2 = "partial class C { public C(int a) { } }"; + edits.VerifyEdits( + "Update [(int a)]@18 -> [(int a, int b)]@18", + "Insert [int b]@26"); - EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + edits.VerifySemantics( new[] { - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters.IsEmpty)) - }), - - // no change in document B - DocumentResults(), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single()) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int b", FeaturesResources.parameter) }, + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void InstanceCtorInsert_Private_Implicit1() + public void Constructor_Parameter_Insert_Primary() { - var src1 = "class C { }"; - var src2 = "class C { private C() { } }"; + var src1 = "class C(int a) { }"; + var src2 = "class C(int a, int b) { }"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [(int a)]@7 -> [(int a, int b)]@7", + "Insert [int b]@15"); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int b", FeaturesResources.parameter) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Constructor_Parameter_Insert_Primary_Record() + { + var src1 = "record C(int X) { }"; + var src2 = "record C(int X, int Y, int Z, int U) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Z")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.U")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Parameter_Insert_In() + { + var src1 = "class C { C() => throw null; }"; + var src2 = "class C { C(in int b) => throw null; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Insert [in int b]@12"); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetParameterlessConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single()) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "in int b", FeaturesResources.parameter) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Constructor_Parameter_Insert_Primary_Record_WithCustomDeconstructor_NoUpdate1() + { + var src1 = "record C(int X ) { public void Deconstruct(out int X) => X = 1; }"; + var src2 = "record C(int X, int Y) { public void Deconstruct(out int X) => X = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Parameter_Insert_Primary_Record_WithCustomDeconstructor_NoUpdate2() + { + var src1 = "record C(int X ) { public void Deconstruct(out int X, out int Y) => X = Y = 1; }"; + var src2 = "record C(int X, int Y) { public void Deconstruct(out int X, out int Y) => X = Y = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Parameter_Insert_Primary_Record_WithCustomDeconstructor_Update() + { + var src1 = "record C(int X ) { public void Deconstruct(out int X, out int Y) => X = Y = 1; }"; + var src2 = "record C(int X, int Y) { public void Deconstruct(out int X, out int Y) => X = Y = 2; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.Deconstruct").Single(m => m.Parameters is [_, _])), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Parameter_Insert_Primary_Record_WithCustomDeconstructor_Insert() + { + var src1 = "record C(int X ) { }"; + var src2 = "record C(int X, int Y) { public void Deconstruct(out int X, out int Y) => X = Y = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Parameter_Insert_Primary_Record_WithCustomDeconstructor_Delete() + { + var src1 = "record C(int X ) { public void Deconstruct(out int X) => X = 1; }"; + var src2 = "record C(int X, int Y) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Parameter_Update_In() + { + var src1 = "class Test { Test(int b) => throw null; }"; + var src2 = "class Test { Test(in int b) => throw null; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [int b]@18 -> [in int b]@18"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "in int b", FeaturesResources.parameter)); + } + + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + [InlineData("record struct")] + public void Constructor_Parameter_DeleteInsert_Primary(string keyword) + { + var srcA1 = "partial " + keyword + " C(int P);"; + var srcB1 = "partial " + keyword + " C;"; + + var srcA2 = "partial " + keyword + " C;"; + var srcB2 = "partial " + keyword + " C(int P);"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), partialType: "C", preserveLocalVariables: true) + }), + }); + } + + [Theory] + [InlineData("class")] + [InlineData("struct")] + public void Constructor_Parameter_DeleteInsert_ReplacingPrimaryWithNonPrimary(string keyword) + { + var srcA1 = "partial " + keyword + " C(int a);"; + var srcB1 = "partial " + keyword + " C;"; + + var srcA2 = "partial " + keyword + " C;"; + var srcB2 = "partial " + keyword + " C { public C(int a) { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "a"}]), partialType: "C", preserveLocalVariables: true) + }), + }); + } + + [Fact] + public void Constructor_Parameter_DeleteInsert_ReplacingPrimaryWithNonPrimary_Record() + { + var srcA1 = "partial record C(int P);"; + var srcB1 = "partial record C;"; + + var srcA2 = "partial record C;"; + var srcB2 = "partial record C { public int P { get; init; } public C(int P) { } public void Deconstruct(out int P) { P = this.P; } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Deconstruct")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "P"}]), partialType: "C", preserveLocalVariables: true), + }), + }); + } + + [Fact] + public void Constructor_Parameter_DeleteInsert_ReplacingNonPrimaryWithPrimary_Record() + { + var srcA1 = "partial record C { public int P { get; init; } public C(int P) { } public void Deconstruct(out int P) { P = this.P; } }"; + var srcB1 = "partial record C;"; + + var srcA2 = "partial record C;"; + var srcB2 = "partial record C(int P);"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "P" }]), partialType: "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Deconstruct")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + }), + }); + } + + [Fact] + public void Constructor_Parameter_DeleteInsert_ReplacingNonPrimaryWithPrimary_WithExplicitPropertyAdded_Record() + { + var srcA1 = "partial record C { public int P { get; init; } public C(int P) { } public void Deconstruct(out int P) { P = this.P; } }"; + var srcB1 = "partial record C;"; + + var srcA2 = "partial record C;"; + var srcB2 = "partial record C(int P) { public int P { get; init; } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "P" }]), partialType: "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Deconstruct")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + }), + }); + } + + [Fact] + public void Constructor_Parameter_DeleteInsert_ReplacingNonPrimaryWithPrimary_WithExplicitFieldAdded_Record() + { + var srcA1 = "partial record C { public int P { get; init; } public C(int P) { } public void Deconstruct(out int P) { P = this.P; } }"; + var srcB1 = "partial record C;"; + + var srcA2 = "partial record C;"; + var srcB2 = "partial record C(int P) { public int P; }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + }), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.Parameters is [{ Name: "P" }]), partialType: "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Deconstruct")), + }), + }, + capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Parameter_DeleteInsert_SwappingNonPrimaryWithPrimary_Record() + { + var srcA1 = "partial record C(int P) { public C() : this(1) { } }"; + var srcB1 = "partial record C;"; + + var srcA2 = "partial record C;"; + var srcB2 = "partial record C() { public C(int P) : this() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.Parameters is [{ Name: "P" }])), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.Parameters is []), partialType: "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")) + }), + }); + } + + [Fact] + public void Constructor_Parameter_DeleteInsert_ReplacingPropertyWithField_Record() + { + var srcA1 = "partial record C(int P) { public int P { get; init; } }"; + var srcB1 = "partial record C;"; + + var srcA2 = "partial record C;"; + var srcB2 = "partial record C(int P) { public int P; }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + }), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.P")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), partialType: "C", preserveLocalVariables: true), + }), + }, capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory(Skip = "https://github.com/dotnet/roslyn/issues/68458")] + [CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/68458")] + public void Constructor_Instance_Update_Primary_Attributes( + [CombinatorialValues("class", "struct", "record", "record struct")] string keyword) + { + var src1 = keyword + " C() { }"; + var src2 = "[method: System.Obsolete] " + keyword + " C() { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + } + + [Fact] + public void Constructor_Instance_Update_Initializer_Update() + { + var src1 = @" +class C +{ + public C(int a) : base(a) { } +}"; + var src2 = @" +class C +{ + public C(int a) : base(a + 1) { } +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [public C(int a) : base(a) { }]@18 -> [public C(int a) : base(a + 1) { }]@18"); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true)); + } + + [Fact] + public void Constructor_Instance_Update_Initializer_Update_Generic() + { + var src1 = @" +class C +{ + public C(int a) : base(a) { } +}"; + var src2 = @" +class C +{ + public C(int a) : base(a + 1) { } +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [public C(int a) : base(a) { }]@21 -> [public C(int a) : base(a + 1) { }]@21"); + + edits.VerifySemanticDiagnostics( + new[] + { + Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "public C(int a)", GetResource("constructor")) + }, + capabilities: EditAndContinueCapabilities.Baseline); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }, + capabilities: EditAndContinueCapabilities.Baseline | EditAndContinueCapabilities.GenericUpdateMethod); + } + + [Theory] + [InlineData("class")] + [InlineData("record")] + public void Constructor_Instance_Update_Initializer_Update_Primary(string keyword) + { + var src1 = keyword + " C(int a) : D(a);"; + var src2 = keyword + " C(int a) : D(a + 1);"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + } + + [Theory] + [InlineData("class")] + [InlineData("record")] + public void Constructor_Instance_Update_Initializer_Update_Primary_WithInterface(string keyword) + { + var src1 = keyword + " C(int a) : D(a), I;"; + var src2 = keyword + " C(int a) : D(a + 1), I;"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + } + + [Fact] + public void Constructor_Instance_Update_Initializer_Delete() + { + var src1 = @" +class C +{ + public C(int a) : base(a) { } +}"; + var src2 = @" +class C +{ + public C(int a) { } +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [public C(int a) : base(a) { }]@21 -> [public C(int a) { }]@21"); + + edits.VerifySemanticDiagnostics( + new[] + { + Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "public C(int a)", GetResource("constructor")) + }, + capabilities: EditAndContinueCapabilities.Baseline); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }, + capabilities: EditAndContinueCapabilities.Baseline | EditAndContinueCapabilities.GenericUpdateMethod); + } + + [Theory] + [InlineData("class ")] + [InlineData("record")] + public void Constructor_Instance_Update_Initializer_Delete_Primary(string keyword) + { + var src1 = keyword + " C(int a) : D(a);"; + var src2 = keyword + " C(int a) : D;"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [: D(a)]@16 -> [: D]@16", + "Delete [D(a)]@18"); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + } + + [Fact] + public void Constructor_Instance_Update_Initializer_Insert() + { + var src1 = @" +class C +{ + public C(int a) { } +}"; + var src2 = @" +class C +{ + public C(int a) : base(a) { } +}"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [public C(int a) { }]@18 -> [public C(int a) : base(a) { }]@18"); + + edits.VerifySemanticDiagnostics(); + } + + [Theory] + [InlineData("class ")] + [InlineData("record")] + public void Constructor_Instance_Update_Initializer_Insert_Primary(string keyword) + { + var src1 = keyword + " C(int a) : D;"; + var src2 = keyword + " C(int a) : D(a);"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [: D]@16 -> [: D(a)]@16", + "Insert [D(a)]@18"); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/68731")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] + public void Constructor_Instance_Update_Initializer_StackAlloc() + { + var src1 = "class C { C() : this(stackalloc int[1]) {} C(Span span) {} }"; + var src2 = "class C { C() : this(stackalloc int[2]) {} C(Span span) {} }"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc", FeaturesResources.constructor)); + } + + [Fact] + public void Constructor_Instance_Update_AnonymousTypeInFieldInitializer() + { + var src1 = "class C { int a = F(new { A = 1, B = 2 }); C() { x = 1; } }"; + var src2 = "class C { int a = F(new { A = 1, B = 2 }); C() { x = 2; } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true)); + } + + [Theory] + [InlineData("class ")] + [InlineData("record")] + public void Constructor_Instance_Update_AnonymousTypeInFieldInitializer_Primary(string keyword) + { + var src1 = keyword + " C() : D(1) { int a = F(new { A = 1, B = 2 }); }"; + var src2 = keyword + " C() : D(2) { int a = F(new { A = 1, B = 2 }); }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [D(1)]@13 -> [D(2)]@13"); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] + public void Constructor_Instance_Update_BlockBodyToExpressionBody() + { + var src1 = @" +public class C +{ + private int _value; + + public C(int value) { _value = value; } +} +"; + var src2 = @" +public class C +{ + private int _value; + + public C(int value) => _value = value; +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [public C(int value) { _value = value; }]@52 -> [public C(int value) => _value = value;]@52"); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] + public void Constructor_Instance_Update_BlockBodyToExpressionBody_WithInitializer() + { + var src1 = @" +public class B { B(int value) {} } +public class C : B +{ + private int _value; + public C(int value) : base(value) { _value = value; } +} +"; + var src2 = @" +public class B { B(int value) {} } +public class C : B +{ + private int _value; + public C(int value) : base(value) => _value = value; +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits("Update [public C(int value) : base(value) { _value = value; }]@90 -> [public C(int value) : base(value) => _value = value;]@90"); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] + public void Constructor_Instance_Update_ExpressionBodyToBlockBody() + { + var src1 = @" +public class C +{ + private int _value; + + public C(int value) => _value = value; +} +"; + var src2 = @" +public class C +{ + private int _value; + + public C(int value) { _value = value; } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits(@"Update [public C(int value) => _value = value;]@52 -> [public C(int value) { _value = value; }]@52"); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] + public void Constructor_Instance_Update_ExpressionBodyToBlockBody_WithInitializer() + { + var src1 = @" +public class B { B(int value) {} } +public class C : B +{ + private int _value; + public C(int value) : base(value) => _value = value; +} +"; + var src2 = @" +public class B { B(int value) {} } +public class C : B +{ + private int _value; + public C(int value) : base(value) { _value = value; } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits(@"Update [public C(int value) : base(value) => _value = value;]@90 -> [public C(int value) : base(value) { _value = value; }]@90"); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + }); + } + + [Fact] + public void Constructor_Instance_Update_SemanticError_Partial() + { + var src1 = @" +partial class C +{ + partial void C(int x); +} + +partial class C +{ + partial void C(int x) + { + System.Console.WriteLine(1); + } +} +"; + var src2 = @" +partial class C +{ + partial void C(int x); +} + +partial class C +{ + partial void C(int x) + { + System.Console.WriteLine(2); + } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("C").PartialImplementationPart)); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/2068")] + public void Constructor_Instance_Update_Modifier_Extern_Add() + { + var src1 = "class C { }"; + var src2 = "class C { public extern C(); }"; + + var edits = GetTopEdits(src1, src2); + + // This can be allowed as the compiler generates an empty constructor, but it's not worth the complexity. + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ModifiersUpdate, "public extern C()", GetResource("constructor"))); + } + + [Theory] + [InlineData("struct")] + [InlineData("record struct")] + public void Constructor_Instance_Insert_Struct(string keyword) + { + var src1 = keyword + " C { }"; + var src2 = keyword + " C { public C(int X) {} }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "X" }])) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Instance_Insert_Struct_Primary() + { + var src1 = "struct C { }"; + var src2 = "struct C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "X" }])) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Instance_Insert_Struct_Primary_Record() + { + var src1 = "record struct C { }"; + var src2 = "record struct C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory] + [InlineData("class")] + [InlineData("record class")] + public void Constructor_Instance_Insert_ReplacingDefault_Class(string keyword) + { + var src1 = keyword + " C { }"; + var src2 = keyword + " C { public C(int X) {} }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "X"}])), + SemanticEdit(SemanticEditKind.Delete, c => c.GetParameterlessConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Instance_Insert_ReplacingDefault_Class_Primary() + { + var src1 = "class C { }"; + var src2 = "class C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetParameterlessConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Instance_Insert_ReplacingDefault_Class_Primary_Record() + { + var src1 = "record C { }"; + var src2 = "record C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetParameterlessConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Insert_UpdatingImplicit( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("public", "protected")] string accessibility) + { + if (accessibility == "protected") + keyword = "abstract " + keyword; + + var src1 = keyword + " C { }"; + var src2 = keyword + " C { [System.Obsolete] " + accessibility + " C() { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Insert_UpdatingImplicit_Partial( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("public", "protected")] string accessibility) + { + if (accessibility == "protected") + keyword = "abstract " + keyword; + + var srcA1 = "partial " + keyword + " C { }"; + var srcB1 = "partial " + keyword + " C { }"; + + var srcA2 = "partial " + keyword + " C { }"; + var srcB2 = "partial " + keyword + " C { " + accessibility + " C() { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + // no change in document A + DocumentResults(), + + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), + }); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Insert_AddingParameterless( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("public", "internal", "private", "protected", "private protected", "internal protected")] string accessibility) + { + var src1 = keyword + " C { C(int a) { } }"; + var src2 = keyword + " C { C(int a) { } " + accessibility + " C() : this(1) { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetParameterlessConstructor("C")) }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Insert_AddingParameterless_Primary( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("public", "internal", "private", "protected", "private protected", "internal protected")] string accessibility) + { + var src1 = keyword + " C(int a) { }"; + var src2 = keyword + " C(int a) { " + accessibility + " C() : this(1) { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetParameterlessConstructor("C")) }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Insert_AddingParameterless_Partial( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("public", "internal", "private", "protected", "private protected", "internal protected")] string accessibility) + { + var srcA1 = "partial " + keyword + " C { }"; + var srcB1 = "partial " + keyword + " C { public C(int a) { } }"; + + var srcA2 = "partial " + keyword + " C { " + accessibility + " C() { } }"; + var srcB2 = "partial " + keyword + " C { public C(int a) { } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetParameterlessConstructor("C")) + }), + + // no change in document B + DocumentResults(), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Insert_AddingParameterless_Partial_Primary( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("public", "internal", "private", "protected", "private protected", "internal protected")] string accessibility) + { + var srcA1 = "partial " + keyword + " C { }"; + var srcB1 = "partial " + keyword + " C(int a) { }"; + + var srcA2 = "partial " + keyword + " C { " + accessibility + " C() { } }"; + var srcB2 = "partial " + keyword + " C(int a) { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetParameterlessConstructor("C")) + }), + + // no change in document B + DocumentResults(), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Insert_ReplacingSynthesizedWithCustom_ChangingAccessibilty( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("", "private", "protected", "internal", "private protected", "internal protected")] string accessibility) + { + var src1 = keyword + " C { }"; + var src2 = keyword + " C { " + accessibility + " C() { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + new[] + { + Diagnostic(RudeEditKind.ChangingAccessibility, (accessibility + " C()").Trim(), GetResource("constructor")) + }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Insert_ReplacingSynthesizedWithCustom_ChangingAccessibilty_AbstractType( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("", "private", "public", "internal", "private protected", "internal protected")] string accessibility) + { + var src1 = "abstract " + keyword + " C { }"; + var src2 = "abstract " + keyword + " C { " + accessibility + " C() { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + new[] + { + Diagnostic(RudeEditKind.ChangingAccessibility, (accessibility + " C()").Trim(), FeaturesResources.constructor) + }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Constructor_Instance_Insert_ReplacingSynthesizedWithCustom_Primary() + { + var src1 = "class C { }"; + var src2 = "class C() { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true)); + } + + [Fact] + public void Constructor_Instance_Insert_ReplacingSynthesizedWithCustom_Primary_Record() + { + var src1 = "record C { }"; + var src2 = "record C() { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C"))); + } + + [Theory] + [InlineData("struct")] + [InlineData("record struct")] + public void Constructor_Instance_Delete_Struct(string keyword) + { + var src1 = keyword + " C { public C(int X) {} }"; + var src2 = keyword + " C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "X" }]), deletedSymbolContainerProvider: c => c.GetMember("C")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Instance_Delete_Struct_Primary() + { + var src1 = "struct C(int X) { }"; + var src2 = "struct C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "X" }]), deletedSymbolContainerProvider: c => c.GetMember("C")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Instance_Delete_Struct_Primary_Record() + { + var src1 = "record struct C(int X) { }"; + var src2 = "record struct C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory] + [InlineData("class")] + [InlineData("record class")] + public void Constructor_Instance_Delete_ReplacingWithDefault_Class(string keyword) + { + var src1 = keyword + " C { public C(int X) {} }"; + var src2 = keyword + " C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + // The compiler emits default constructor automatically + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "X"}]), deletedSymbolContainerProvider: c => c.GetMember("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Instance_Delete_ReplacingWithDefault_Class_Primary() + { + var src1 = "class C(int X) { }"; + var src2 = "class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + // The compiler emits default constructor automatically + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Instance_Delete_ReplacingWithDefault_Class_Primary_Record() + { + var src1 = "record C(int X) { }"; + var src2 = "record C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + // The compiler emits default constructor automatically + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Constructor_Instance_Delete_SemanticError() + { + var src1 = "class C { D() {} }"; + var src2 = "class C { }"; + + var edits = GetTopEdits(src1, src2); + + // The compiler interprets D() as a constructor declaration. + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Delete_UpdatingImplicit( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("public", "protected")] string accessibility) + { + if (accessibility == "protected") + keyword = "abstract " + keyword; + + var src1 = keyword + " C { [System.Obsolete] " + accessibility + " C() { } }"; + var src2 = keyword + " C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Fact] + public void Constructor_Instance_Delete_Parameterless() + { + var src1 = @" +class C +{ + private int a = 10; + private int b; + + public C() { b = 3; } +} +"; + var src2 = @" +class C +{ + private int a = 10; + private int b; +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Delete [public C() { b = 3; }]@65", + "Delete [()]@73"); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) + }); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Delete_WithParameters([CombinatorialValues("record", "class")] string keyword) + { + var src1 = keyword + " C { public C(int x) { } }"; + var src2 = keyword + " C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] { SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "x" }]), deletedSymbolContainerProvider: c => c.GetMember("C")) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Theory] + [CombinatorialData] + public void Constructor_Instance_Delete_Primary_WithParameters([CombinatorialValues("record", "class")] string keyword) + { + var src1 = keyword + " C(int a) { public C(bool b) { } }"; + var src2 = keyword + " C(int a) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] { SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is [{ Name: "b" }]), deletedSymbolContainerProvider: c => c.GetMember("C")) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Constructor_Instance_Delete_Primary_Class() + { + var src1 = "class C(int a) { }"; + var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingAccessibility, "private C()", FeaturesResources.constructor)); + edits.VerifySemantics( + new[] { SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.FirstOrDefault(c => c.Parameters.Length == 1), deletedSymbolContainerProvider: c => c.GetMember("C")) }, + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void InstanceCtorInsert_Private_Implicit2() + public void Constructor_Instance_Delete_Primary_Record() { - var src1 = "class C { }"; - var src2 = "class C { C() { } }"; + var src1 = "record C(int a) { }"; + var src2 = "record C { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingAccessibility, "C()", FeaturesResources.constructor)); + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + }, + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void InstanceCtorInsert_Protected_PublicImplicit() + public void Constructor_Instance_Delete_Public_PartialWithInitializerUpdate() { - var src1 = "class C { }"; - var src2 = "class C { protected C() { } }"; + var srcA1 = "partial class C { public C() { } }"; + var srcB1 = "partial class C { int x = 1; }"; - var edits = GetTopEdits(src1, src2); + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int x = 2; }"; - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingAccessibility, "protected C()", FeaturesResources.constructor)); + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), + + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }) + }); } - [Fact] - public void InstanceCtorInsert_Internal_PublicImplicit() + [Theory] + [CombinatorialData] + public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized( + [CombinatorialValues("record", "class")] string keyword) { - var src1 = "class C { }"; - var src2 = "class C { internal C() { } }"; + var src1 = keyword + " C { public C() { } }"; + var src2 = keyword + " C { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingAccessibility, "internal C()", FeaturesResources.constructor)); + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(c => c.Parameters is []), preserveLocalVariables: true)); } - [Fact] - public void InstanceCtorInsert_Internal_ProtectedImplicit() + [Theory] + [CombinatorialData] + public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_ChangingAccessibility( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("", "private", "protected", "internal", "private protected", "internal protected")] string accessibility) { - var src1 = "abstract class C { }"; - var src2 = "abstract class C { internal C() { } }"; + var src1 = keyword + " C { " + accessibility + " C() { } }"; + var src2 = keyword + " C { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingAccessibility, "internal C()", FeaturesResources.constructor)); + new[] + { + Diagnostic(RudeEditKind.ChangingAccessibility, keyword + " C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()")) + }, + capabilities: EditAndContinueCapabilities.Baseline); } - [Fact] - public void InstanceCtorUpdate_ProtectedImplicit() + [Theory] + [CombinatorialData] + public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_AbstractType( + [CombinatorialValues("record", "class")] string keyword) { - var src1 = "abstract class C { }"; - var src2 = "abstract class C { protected C() { } }"; + var src1 = "abstract " + keyword + " C { protected C() { } }"; + var src2 = "abstract " + keyword + " C { }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true)); + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true)); } - [Fact] - public void InstanceCtorInsert_Private_NoImplicit() + [Theory] + [CombinatorialData] + public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_AbstractType_ChangingAccessibility( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("", "private", "public", "internal", "private protected", "internal protected")] string accessibility) { - var src1 = "class C { public C(int a) { } }"; - var src2 = "class C { public C(int a) { } private C() { } }"; + var src1 = "abstract " + keyword + " C { " + accessibility + " C() { } }"; + var src2 = "abstract " + keyword + " C { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( + edits.VerifySemanticDiagnostics( new[] { - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C") - .InstanceConstructors.Single(ctor => ctor.DeclaredAccessibility == Accessibility.Private)) + Diagnostic(RudeEditKind.ChangingAccessibility, "abstract " + keyword + " C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()")) }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void InstanceCtorInsert_Internal_NoImplicit() + public void Constructor_Instance_Delete_Primary_ReplacingWithSynthesized() { - var src1 = "class C { public C(int a) { } }"; - var src2 = "class C { public C(int a) { } internal C() { } }"; + var src1 = "class C() { }"; + var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true)); } [Fact] - public void InstanceCtorInsert_Protected_NoImplicit() + public void Constructor_Instance_Delete_Primary_ReplacingWithSynthesized_Record() { - var src1 = "class C { public C(int a) { } }"; - var src2 = "class C { public C(int a) { } protected C() { } }"; + var src1 = "record C() { }"; + var src2 = "record C { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetIEquatableEquals("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C"))); } - [Fact] - public void InstanceCtorInsert_InternalProtected_NoImplicit() + [Theory] + [CombinatorialData] + public void Constructor_Instance_Delete_Primary_ReplacingWithRegular( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("", "private", "protected", "internal", "private protected", "internal protected")] string accessibility) { - var src1 = "class C { public C(int a) { } }"; - var src2 = "class C { public C(int a) { } internal protected C() { } }"; + var src1 = keyword + " C() { }"; + var src2 = keyword + " C { " + accessibility + " C() { } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + new[] + { + Diagnostic(RudeEditKind.ChangingAccessibility, (accessibility + " C()").Trim(), GetResource("constructor")) + }, + capabilities: EditAndContinueCapabilities.Baseline); } - [Fact] - public void StaticCtor_Partial_DeleteInsert() + [Theory] + [CombinatorialData] + public void Constructor_Instance_Delete_Primary_ReplacingWithRegular_AbstractType( + [CombinatorialValues("record", "class")] string keyword, + [CombinatorialValues("", "private", "public", "internal", "private protected", "internal protected")] string accessibility) { - var srcA1 = "partial class C { static C() { } }"; - var srcB1 = "partial class C { }"; + var src1 = "abstract " + keyword + " C() { }"; + var src2 = "abstract " + keyword + " C { " + accessibility + " C() { } }"; - var srcA2 = "partial class C { }"; - var srcB2 = "partial class C { static C() { } }"; + var edits = GetTopEdits(src1, src2); - EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + edits.VerifySemanticDiagnostics( new[] { - // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back - DocumentResults(), + Diagnostic(RudeEditKind.ChangingAccessibility, (accessibility + " C()").Trim(), GetResource("constructor")) + }, + capabilities: EditAndContinueCapabilities.Baseline); + } - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), partialType: "C", preserveLocalVariables: true) - }), - }); + [Theory] + [InlineData("record")] + [InlineData("class")] + public void Constructor_Instance_InsertDelete_Primary_Partial(string keyword) + { + var src1 = $$""" + partial {{keyword}} C { } + partial {{keyword}} C(int X); + """; + var src2 = $$""" + partial {{keyword}} C(int X) { } + partial {{keyword}} C; + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); } [Fact] - public void InstanceCtor_Partial_DeletePrivateInsertPrivate() + public void Constructor_Instance_Partial_DeletePrivateInsertPrivate() { var srcA1 = "partial class C { C() { } }"; var srcB1 = "partial class C { }"; @@ -10736,13 +12257,13 @@ public void InstanceCtor_Partial_DeletePrivateInsertPrivate() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), }); } [Fact] - public void InstanceCtor_Partial_DeletePublicInsertPublic() + public void Constructor_Instance_Partial_DeletePublicInsertPublic() { var srcA1 = "partial class C { public C() { } }"; var srcB1 = "partial class C { }"; @@ -10760,13 +12281,13 @@ public void InstanceCtor_Partial_DeletePublicInsertPublic() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), }); } [Fact] - public void InstanceCtor_Partial_DeletePrivateInsertPublic() + public void Constructor_Instance_Partial_DeletePrivateInsertPublic() { var srcA1 = "partial class C { C() { } }"; var srcB1 = "partial class C { }"; @@ -10788,31 +12309,7 @@ public void InstanceCtor_Partial_DeletePrivateInsertPublic() } [Fact] - public void StaticCtor_Partial_InsertDelete() - { - var srcA1 = "partial class C { }"; - var srcB1 = "partial class C { static C() { } }"; - - var srcA2 = "partial class C { static C() { } }"; - 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").StaticConstructors.Single(), partialType: "C", preserveLocalVariables: true) - }), - - // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back - DocumentResults(), - }); - } - - [Fact] - public void InstanceCtor_Partial_InsertPublicDeletePublic() + public void Constructor_Instance_Partial_InsertPublicDeletePublic() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { public C() { } }"; @@ -10827,7 +12324,7 @@ public void InstanceCtor_Partial_InsertPublicDeletePublic() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back @@ -10836,7 +12333,7 @@ public void InstanceCtor_Partial_InsertPublicDeletePublic() } [Fact] - public void InstanceCtor_Partial_InsertPrivateDeletePrivate() + public void Constructor_Instance_Partial_InsertPrivateDeletePrivate() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { private C() { } }"; @@ -10851,7 +12348,7 @@ public void InstanceCtor_Partial_InsertPrivateDeletePrivate() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back @@ -10860,7 +12357,7 @@ public void InstanceCtor_Partial_InsertPrivateDeletePrivate() } [Fact] - public void InstanceCtor_Partial_DeleteInternalInsertInternal() + public void Constructor_Instance_Partial_DeleteInternalInsertInternal() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { internal C() { } }"; @@ -10875,7 +12372,7 @@ public void InstanceCtor_Partial_DeleteInternalInsertInternal() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back @@ -10884,7 +12381,7 @@ public void InstanceCtor_Partial_DeleteInternalInsertInternal() } [Fact] - public void InstanceCtor_Partial_InsertInternalDeleteInternal_WithBody() + public void Constructor_Instance_Partial_InsertInternalDeleteInternal_WithBody() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { internal C() { } }"; @@ -10899,7 +12396,7 @@ public void InstanceCtor_Partial_InsertInternalDeleteInternal_WithBody() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back @@ -10908,7 +12405,7 @@ public void InstanceCtor_Partial_InsertInternalDeleteInternal_WithBody() } [Fact] - public void InstanceCtor_Partial_InsertPublicDeletePrivate() + public void Constructor_Instance_Partial_InsertPublicDeletePrivate() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { private C() { } }"; @@ -10929,7 +12426,7 @@ public void InstanceCtor_Partial_InsertPublicDeletePrivate() } [Fact] - public void InstanceCtor_Partial_InsertInternalDeletePrivate() + public void Constructor_Instance_Partial_InsertInternalDeletePrivate() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { private C() { } }"; @@ -10949,7 +12446,7 @@ public void InstanceCtor_Partial_InsertInternalDeletePrivate() } [Fact] - public void InstanceCtor_Partial_Update_LambdaInInitializer1() + public void Constructor_Instance_Partial_Update_LambdaInInitializer1() { var src1 = @" using System; @@ -11000,7 +12497,7 @@ public C() } [Fact] - public void InstanceCtor_Partial_Update_LambdaInInitializer_Trivia1() + public void Constructor_Instance_Partial_Update_LambdaInInitializer_Trivia1() { var src1 = @" using System; @@ -11045,7 +12542,7 @@ partial class C } [Fact] - public void InstanceCtor_Partial_Update_LambdaInInitializer_ExplicitInterfaceImpl1() + public void Constructor_Instance_Partial_Update_LambdaInInitializer_ExplicitInterfaceImpl1() { var src1 = @" using System; @@ -11103,8 +12600,8 @@ public C() new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) }); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/2504")] - public void InstanceCtor_Partial_Insert_Parameterless_LambdaInInitializer1() + [Fact] + public void Constructor_Instance_Partial_Insert_Parameterless_LambdaInInitializer1() { var src1 = @" using System; @@ -11143,19 +12640,15 @@ partial class C "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, "public C()")); - - // TODO: - //var syntaxMap = GetSyntaxMap(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); - //edits.VerifySemantics( - // ActiveStatementsDescription.Empty, - // new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) }); + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) }); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/2504")] - public void InstanceCtor_Partial_Insert_WithParameters_LambdaInInitializer1() + public void Constructor_Instance_Partial_Insert_WithParameters_LambdaInInitializer1() { var src1 = @" using System; @@ -11205,7 +12698,7 @@ partial class C } [Fact] - public void InstanceCtor_Partial_Explicit_Update() + public void Constructor_Instance_Partial_Explicit_Update() { var srcA1 = @" using System; @@ -11269,7 +12762,7 @@ partial class C } [Fact] - public void InstanceCtor_Partial_Explicit_Update_SemanticError() + public void Constructor_Instance_Partial_Explicit_Update_SemanticError() { var srcA1 = @" using System; @@ -11323,7 +12816,7 @@ partial class C } [Fact] - public void InstanceCtor_Partial_Implicit_Update() + public void Constructor_Instance_Partial_Implicit_Update() { var srcA1 = "partial class C { int F = 1; }"; var srcB1 = "partial class C { int G = 1; }"; @@ -11338,74 +12831,16 @@ public void InstanceCtor_Partial_Implicit_Update() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), }); } - [Fact] - public void ParameterlessConstructor_SemanticError_Delete1() - { - var src1 = @" -class C -{ - D() {} -} -"; - var src2 = @" -class C -{ -} -"; - var edits = GetTopEdits(src1, src2); - - // The compiler interprets D() as a constructor declaration. - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingAccessibility, "class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); - } - - [Fact] - public void Constructor_SemanticError_Partial() - { - var src1 = @" -partial class C -{ - partial void C(int x); -} - -partial class C -{ - partial void C(int x) - { - System.Console.WriteLine(1); - } -} -"; - var src2 = @" -partial class C -{ - partial void C(int x); -} - -partial class C -{ - partial void C(int x) - { - System.Console.WriteLine(2); - } -} -"; - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("C").PartialImplementationPart)); - } - [Fact] public void PartialDeclaration_Delete() { @@ -11420,13 +12855,13 @@ public void PartialDeclaration_Delete() new[] { DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }), + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), DocumentResults( semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), }); } @@ -11448,11 +12883,11 @@ public void PartialDeclaration_Insert() semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) }), + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), }); } @@ -11486,15 +12921,13 @@ public void PartialDeclaration_Insert_Reloadable() [Theory] [InlineData("class ")] [InlineData("struct")] - public void Constructor_DeleteParameterless(string typeKind) + public void Constructor_Insert_Parameterless(string typeKind) { var src1 = @" " + typeKind + @" C { private int a = 10; private int b; - - public C() { b = 3; } } "; var src2 = @" @@ -11502,175 +12935,158 @@ public void Constructor_DeleteParameterless(string typeKind) { private int a = 10; private int b; + + public C() { b = 3; } } "; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Delete [public C() { b = 3; }]@66", "Delete [()]@74"); + edits.VerifyEdits("Insert [public C() { b = 3; }]@66", "Insert [()]@74"); edits.VerifySemantics( ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } - [Theory] - [InlineData("class ")] - [InlineData("struct")] - public void Constructor_InsertParameterless(string typeKind) + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/2068")] + public void Constructor_Static_Update_Modifier_Extern_Add() { - var src1 = @" -" + typeKind + @" C -{ - private int a = 10; - private int b; -} -"; - var src2 = @" -" + typeKind + @" C -{ - private int a = 10; - private int b; + var src1 = "class C { }"; + var src2 = "class C { static extern C(); }"; + + var edits = GetTopEdits(src1, src2); + + // This can be allowed as the compiler generates an empty constructor, but it's not worth the complexity. + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.InsertExtern, "static extern C()", GetResource("static constructor"))); + } + + [Fact] + public void Constructor_Static_Delete() + { + var src1 = "class C { static C() { } }"; + var src2 = "class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.static_constructor, "C()"))); + } + + [Fact] + public void Constructor_Static_Delete_Reloadable() + { + var src1 = ReloadableAttributeSrc + "[CreateNewOnMetadataUpdate]class C { static C() { } }"; + var src2 = ReloadableAttributeSrc + "[CreateNewOnMetadataUpdate]class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] { SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C")) }, + capabilities: EditAndContinueCapabilities.NewTypeDefinition); + } + + [Fact] + public void Constructor_Static_Insert() + { + var src1 = "class C { }"; + var src2 = "class C { static C() { } }"; - public C() { b = 3; } -} -"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits("Insert [public C() { b = 3; }]@66", "Insert [()]@74"); - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) - }); + new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").StaticConstructors.Single()) }, + EditAndContinueCapabilities.AddMethodToExistingType); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] - public void Constructor_BlockBodyToExpressionBody() + [Fact] + public void Constructor_Static_Partial_DeleteInsert() { - var src1 = @" -public class C -{ - private int _value; - - public C(int value) { _value = value; } -} -"; - var src2 = @" -public class C -{ - private int _value; - - public C(int value) => _value = value; -} -"; - var edits = GetTopEdits(src1, src2); + var srcA1 = "partial class C { static C() { } }"; + var srcB1 = "partial class C { }"; - edits.VerifyEdits("Update [public C(int value) { _value = value; }]@52 -> [public C(int value) => _value = value;]@52"); + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { static C() { } }"; - edits.VerifySemantics( - ActiveStatementsDescription.Empty, + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), }); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] - public void ConstructorWithInitializer_BlockBodyToExpressionBody() + [Fact] + public void Constructor_Static_Partial_InsertDelete() { - var src1 = @" -public class B { B(int value) {} } -public class C : B -{ - private int _value; - public C(int value) : base(value) { _value = value; } -} -"; - var src2 = @" -public class B { B(int value) {} } -public class C : B -{ - private int _value; - public C(int value) : base(value) => _value = value; -} -"; - var edits = GetTopEdits(src1, src2); + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { static C() { } }"; - edits.VerifyEdits("Update [public C(int value) : base(value) { _value = value; }]@90 -> [public C(int value) : base(value) => _value = value;]@90"); + var srcA2 = "partial class C { static C() { } }"; + var srcB2 = "partial class C { }"; - edits.VerifySemantics( - ActiveStatementsDescription.Empty, + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), + + // delete of the constructor in partial part will be represented as a semantic update in the other document where it was inserted back + DocumentResults(), }); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] - public void Constructor_ExpressionBodyToBlockBody() + #endregion + + #region Destructors + + [Fact] + public void DestructorDelete() { - var src1 = @" -public class C -{ - private int _value; + var src1 = @"class B { ~B() { } }"; + var src2 = @"class B { }"; - public C(int value) => _value = value; -} -"; - var src2 = @" -public class C -{ - private int _value; + var expectedEdit1 = @"Delete [~B() { }]@10"; - public C(int value) { _value = value; } -} -"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits(@"Update [public C(int value) => _value = value;]@52 -> [public C(int value) { _value = value; }]@52"); + edits.VerifyEdits(expectedEdit1); - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) - }); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class B", DeletedSymbolDisplay(CSharpFeaturesResources.destructor, "~B()"))); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] - public void ConstructorWithInitializer_ExpressionBodyToBlockBody() + [Fact] + public void DestructorDelete_InsertConstructor() { - var src1 = @" -public class B { B(int value) {} } -public class C : B -{ - private int _value; - public C(int value) : base(value) => _value = value; -} -"; - var src2 = @" -public class B { B(int value) {} } -public class C : B -{ - private int _value; - public C(int value) : base(value) { _value = value; } -} -"; + var src1 = @"class B { ~B() { } }"; + var src2 = @"class B { B() { } }"; + var edits = GetTopEdits(src1, src2); - edits.VerifyEdits(@"Update [public C(int value) : base(value) => _value = value;]@90 -> [public C(int value) : base(value) { _value = value; }]@90"); + edits.VerifyEdits( + "Insert [B() { }]@10", + "Insert [()]@11", + "Delete [~B() { }]@10"); - edits.VerifySemantics( - ActiveStatementsDescription.Empty, - new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) - }); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ChangingAccessibility, "B()", FeaturesResources.constructor), + Diagnostic(RudeEditKind.Delete, "class B", DeletedSymbolDisplay(CSharpFeaturesResources.destructor, "~B()"))); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] @@ -11727,100 +13143,40 @@ public class C }); } - [Fact] - public void Constructor_ReadOnlyRef_Parameter_InsertWhole() - { - var src1 = "class Test { }"; - var src2 = "class Test { Test(in int b) => throw null; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Insert [Test(in int b) => throw null;]@13", - "Insert [(in int b)]@17", - "Insert [in int b]@18"); - - edits.VerifySemanticDiagnostics( - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - } - - [Fact] - public void Constructor_ReadOnlyRef_Parameter_InsertParameter() - { - var src1 = "class Test { Test() => throw null; }"; - var src2 = "class Test { Test(in int b) => throw null; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Insert [in int b]@18"); - - edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("Test..ctor").FirstOrDefault(m => m.GetParameterCount() == 0)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("Test")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("Test..ctor").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol) - }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - - edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "in int b", FeaturesResources.parameter) }, - capabilities: EditAndContinueCapabilities.Baseline); - } - - [Fact] - public void Constructor_ReadOnlyRef_Parameter_Update() - { - var src1 = "class Test { Test(int b) => throw null; }"; - var src2 = "class Test { Test(in int b) => throw null; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [int b]@18 -> [in int b]@18"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "in int b", FeaturesResources.parameter)); - } - #endregion #region Fields and Properties with Initializers - [Theory] - [InlineData("class ")] - [InlineData("struct")] - public void FieldInitializer_Update1(string typeKind) + [Fact] + public void FieldInitializer_Update1() { - var src1 = typeKind + " C { int a = 0; }"; - var src2 = typeKind + " C { int a = 1; }"; + var src1 = "class C { int a = 0; }"; + var src2 = "class C { int a = 1; }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [a = 0]@15 -> [a = 1]@15"); + "Update [a = 0]@14 -> [a = 1]@14"); edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } - [Theory] - [InlineData("class ")] - [InlineData("struct")] - public void PropertyInitializer_Update1(string typeKind) + [Fact] + public void PropertyInitializer_Update1() { - var src1 = typeKind + " C { int a { get; } = 0; }"; - var src2 = typeKind + " C { int a { get; } = 1; }"; + var src1 = "class C { int a { get; } = 0; }"; + var src2 = "class C { int a { get; } = 1; }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int a { get; } = 0;]@11 -> [int a { get; } = 1;]@11"); + "Update [int a { get; } = 0;]@10 -> [int a { get; } = 1;]@10"); edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Fact] @@ -11858,7 +13214,8 @@ public void PropertyInitializer_Update2() public void PropertyInitializer_InsertDelete() { var srcA1 = "partial class C { }"; - var srcB1 = "partial class C { int a { get; } = 0; }"; + var srcB1 = "partial class C { int a { get; } = 1; }"; + var srcA2 = "partial class C { int a { get { return 1; } } }"; var srcB2 = "partial class C { }"; @@ -11872,7 +13229,7 @@ public void PropertyInitializer_InsertDelete() SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a").GetMethod), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), partialType: "C", preserveLocalVariables: true) }), - DocumentResults() + DocumentResults(), }); } @@ -11889,7 +13246,7 @@ public void FieldInitializer_Update3() edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Fact] @@ -11909,6 +13266,39 @@ public void PropertyInitializer_Update3() new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) + }); + } + + [Fact] + public void FieldInitializer_Delete() + { + var src1 = "class C { int a = 1; }"; + var src2 = "class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.field, "a"))); + } + + [Theory] + [InlineData("")] + [InlineData("public C() { }")] + [InlineData("public C(int x) { }")] + public void PropertyInitializer_Delete(string ctor) + { + var src1 = "class C { " + ctor + " int a { get; set; } = 1; }"; + var src2 = "class C { " + ctor + " }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_a"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_a"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); } @@ -11944,67 +13334,59 @@ public void PropertyInitializerUpdate_StaticCtorUpdate1() new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").StaticConstructors.Single(), preserveLocalVariables: true) }); } - [Theory] - [InlineData("class ")] - [InlineData("struct")] - public void FieldInitializerUpdate_InstanceCtorUpdate_Private(string typeKind) + [Fact] + public void FieldInitializerUpdate_InstanceCtorUpdate_Private() { - var src1 = typeKind + " C { int a; [System.Obsolete]C() { } }"; - var src2 = typeKind + " C { int a = 0; }"; + var src1 = "class C { int a; [System.Obsolete]C() { } }"; + var src2 = "class C { int a = 0; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( new[] { - Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, $"{typeKind} C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()")), - Diagnostic(RudeEditKind.ChangingAccessibility, $"{typeKind} C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()")) + Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, $"class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()")), + Diagnostic(RudeEditKind.ChangingAccessibility, $"class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()")) }, capabilities: EditAndContinueCapabilities.Baseline); } - [Theory] - [InlineData("class ")] - [InlineData("struct")] - public void PropertyInitializerUpdate_InstanceCtorUpdate_Private(string typeKind) + [Fact] + public void PropertyInitializerUpdate_InstanceCtorUpdate_Private() { - var src1 = typeKind + " C { int a { get; } = 1; C() { } }"; - var src2 = typeKind + " C { int a { get; } = 2; }"; + var src1 = "class C { int a { get; } = 1; C() { } }"; + var src2 = "class C { int a { get; } = 2; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingAccessibility, $"{typeKind} C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); + Diagnostic(RudeEditKind.ChangingAccessibility, $"class C", DeletedSymbolDisplay(FeaturesResources.constructor, "C()"))); } - [Theory] - [InlineData("class ")] - [InlineData("struct")] - public void FieldInitializerUpdate_InstanceCtorUpdate_Public(string typeKind) + [Fact] + public void FieldInitializerUpdate_InstanceCtorUpdate_Public() { - var src1 = typeKind + " C { int a; public C() { } }"; - var src2 = typeKind + " C { int a = 0; }"; + var src1 = "class C { int a; public C() { } }"; + var src2 = "class C { int a = 0; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } - [Theory] - [InlineData("class ")] - [InlineData("struct")] - public void PropertyInitializerUpdate_InstanceCtorUpdate_Public(string typeKind) + [Fact] + public void PropertyInitializerUpdate_InstanceCtorUpdate_Public() { - var src1 = typeKind + " C { int a { get; } = 1; public C() { } }"; - var src2 = typeKind + " C { int a { get; } = 2; }"; + var src1 = "class C { int a { get; } = 1; public C() { } }"; + var src2 = "class C { int a { get; } = 2; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Fact] @@ -12051,7 +13433,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate2(string typeKind) edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Theory] @@ -12066,7 +13448,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate2(string typeKind) edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Fact] @@ -12082,7 +13464,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate3() edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Fact] @@ -12095,7 +13477,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate3() edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Fact] @@ -12111,39 +13493,39 @@ public void FieldInitializerUpdate_InstanceCtorUpdate4() edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Fact] - public void FieldInitializerUpdate_InstanceCtorUpdate5() + public void FieldInitializerUpdate_InstanceCtorUpdate_Class() { - var src1 = "class C { int a; private C(int a) { } private C(bool a) { } }"; - var src2 = "class C { int a = 0; private C(int a) { } private C(bool a) { } }"; + var src1 = "class C { int a; private C(int a) { } private C(bool a) : this() { } private C() : this(1) { } private C(string a) : base() { } }"; + var src2 = "class C { int a = 1; private C(int a) { } private C(bool a) : this() { } private C() : this(1) { } private C(string a) : base() { } }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [a]@14 -> [a = 0]@14"); + "Update [a]@14 -> [a = 1]@14"); edits.VerifySemantics( ActiveStatementsDescription.Empty, new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.ToString() == "C.C(int)"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.ToString() == "C.C(bool)"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.ToString() == "C.C(string)"), preserveLocalVariables: true), }); } [Fact] - public void FieldInitializerUpdate_Struct_InstanceCtorUpdate5() + public void FieldInitializerUpdate_InstanceCtorUpdate_Struct() { - var src1 = "struct C { int a; private C(int a) { } private C(bool a) { } }"; - var src2 = "struct C { int a = 0; private C(int a) { } private C(bool a) { } }"; + var src1 = "struct C { int a; private C(int a) { } private C(bool a) : this() { } private C(char a) : this(1) { } }"; + var src2 = "struct C { int a = 1; private C(int a) { } private C(bool a) : this() { } private C(char a) : this(1) { } }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [a]@15 -> [a = 0]@15"); + "Update [a]@15 -> [a = 1]@15"); edits.VerifySemantics( ActiveStatementsDescription.Empty, @@ -12151,7 +13533,6 @@ public void FieldInitializerUpdate_Struct_InstanceCtorUpdate5() { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.ToString() == "C.C(int)"), preserveLocalVariables: true), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.ToString() == "C.C(bool)"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.ToString() == "C.C()"), preserveLocalVariables: true), }); } @@ -12186,7 +13567,6 @@ public void PropertyInitializerUpdate_Struct_InstanceCtorUpdate5() { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.ToString() == "C.C(int)"), preserveLocalVariables: true), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.ToString() == "C.C(bool)"), preserveLocalVariables: true), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(m => m.ToString() == "C.C()"), preserveLocalVariables: true), }); } @@ -12246,7 +13626,7 @@ public void FieldInitializerUpdate_StaticCtorInsertExplicit() [Theory] [InlineData("class ")] [InlineData("struct")] - public void FieldInitializerUpdate_InstanceCtorInsertExplicit(string typeKind) + public void FieldInitializerUpdate_Constructor_Instance_InsertExplicit(string typeKind) { var src1 = typeKind + " C { int a; }"; var src2 = typeKind + " C { int a = 0; public C() { } }"; @@ -12255,13 +13635,13 @@ public void FieldInitializerUpdate_InstanceCtorInsertExplicit(string typeKind) edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Theory] [InlineData("class ")] [InlineData("struct")] - public void PropertyInitializerUpdate_InstanceCtorInsertExplicit(string typeKind) + public void PropertyInitializerUpdate_Constructor_Instance_InsertExplicit(string typeKind) { var src1 = typeKind + " C { int a { get; } = 1; }"; var src2 = typeKind + " C { int a { get; } = 2; public C() { } }"; @@ -12270,7 +13650,7 @@ public void PropertyInitializerUpdate_InstanceCtorInsertExplicit(string typeKind edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }); + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); } [Fact] @@ -12295,7 +13675,7 @@ public void FieldInitializerUpdate_GenericType() edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }, capabilities: EditAndContinueCapabilities.Baseline | EditAndContinueCapabilities.GenericUpdateMethod); } @@ -12319,7 +13699,7 @@ public void PropertyInitializerUpdate_GenericType() edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }, capabilities: EditAndContinueCapabilities.GenericUpdateMethod); } @@ -12339,6 +13719,19 @@ public void FieldInitializerUpdate_StackAllocInConstructor() Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc", FeaturesResources.constructor)); } + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/68731")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] + public void FieldInitializerUpdate_StackAllocInConstructor_Initializer() + { + var src1 = "class C { int a = 1; C() : this(stackalloc int[1]) { } C(Span) { } }"; + var src2 = "class C { int a = 2; C() : this(stackalloc int[1]) { } C(Span) { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc", FeaturesResources.constructor)); + } + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/67307")] [WorkItem("https://github.com/dotnet/roslyn/issues/67307")] public void FieldInitializerUpdate_StackAllocInOtherInitializer() @@ -13259,6 +14652,87 @@ public C(bool b) }); } + [Theory] + [InlineData("")] + [InlineData(" : base()")] + public void FieldInitializerUpdate_Lambdas_ReplacingCustomWithSynthesized_ConstructorWithMemberInitializers(string initializer) + { + var src1 = $$""" +using System; + +class C : B +{ + static int F(Func x) => 1; + + int A = F(a => a + 1); + + public C() {{initializer}} + { + } +} +"""; + var src2 = @" +using System; + +class C : B +{ + static int F(Func x) => 1; + + int A = F(a => a + 1); +} +"; + var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) + }); + } + + [Theory] + [CombinatorialData] + public void FieldInitializerUpdate_Lambdas_ReplacingCustomWithSynthesized_ConstructorWithMemberInitializers_Primary( + [CombinatorialValues("", "()")] string initializer, bool isInsert) + { + var src1 = $$""" +using System; + +class C() : B{{initializer}} +{ + static int F(Func x) => 1; + + int A = F(a => a + 1); +} +"""; + var src2 = @" +using System; + +class C : B +{ + static int F(Func x) => 1; + + int A = F(a => a + 1); +} +"; + if (isInsert) + { + (src1, src2) = (src2, src1); + } + + var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), syntaxMap[0]) + }); + } + [Fact] public void FieldInitializerUpdate_Lambdas_PartialDeclarationDelete_SingleDocument() { @@ -13302,7 +14776,7 @@ partial class C new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), syntaxMap[0]), + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), syntaxMap[0]), }); } @@ -13377,7 +14851,7 @@ public C() { } edits.VerifySemantics( SemanticEdit(SemanticEditKind.Update, c => ((IPropertySymbol)c.GetMember("C").GetMembers("P").First()).GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true)); + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true)); } [Fact] @@ -13397,7 +14871,7 @@ public void Field_Partial_DeleteInsert_InitializerRemoval() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), }); } @@ -13419,7 +14893,7 @@ public void Field_Partial_DeleteInsert_InitializerUpdate() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), }); } @@ -13609,7 +15083,7 @@ public void Field_Insert() new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.a")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }, capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); } @@ -13970,7 +15444,8 @@ class C new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.B")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").Constructors.Single()) + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").Constructors.Single()), + SemanticEdit(SemanticEditKind.Delete, c => c.GetParameterlessConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType | EditAndContinueCapabilities.AddMethodToExistingType); } @@ -14076,7 +15551,7 @@ class C new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true), }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } @@ -14099,7 +15574,7 @@ public void Field_Attribute_DeleteInsertUpdate_WithInitializer() 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) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); @@ -14361,7 +15836,7 @@ public void Field_Event_Partial_InsertDelete() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), DocumentResults(), @@ -14941,61 +16416,42 @@ public void PropertyTypeUpdate_WithBodies() } [Fact] - public void PropertyUpdate_AddAttribute() + public void Property_Update_AddAttribute() { var src1 = "class C { int P { get; set; } }"; var src2 = "class C { [System.Obsolete]int P { get; set; } }"; var edits = GetTopEdits(src1, src2); + edits.VerifySemantics( + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P")) }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + edits.VerifySemanticDiagnostics( new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "int P", FeaturesResources.property_) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Fact] - public void PropertyUpdate_AddAttribute_SupportedByRuntime() + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/68458")] + [WorkItem("https://github.com/dotnet/roslyn/issues/68458")] + public void Property_Update_AddAttribute_FieldTarget() { var src1 = "class C { int P { get; set; } }"; - var src2 = "class C { [System.Obsolete]int P { get; set; } }"; + var src2 = "class C { [field: System.Obsolete]int P { get; set; } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P")) - }, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P")) }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); - } - - [Fact] - public void PropertyAccessorUpdate_AddAttribute() - { - var src1 = "class C { int P { get; set; } }"; - var src2 = "class C { int P { [System.Obsolete]get; set; } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "get", CSharpFeaturesResources.property_getter) }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - } - - [Fact] - public void PropertyAccessorUpdate_AddAttribute2() - { - var src1 = "class C { int P { get; set; } }"; - var src2 = "class C { int P { get; [System.Obsolete]set; } }"; - - var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "set", CSharpFeaturesResources.property_setter) }, + new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "int P", FeaturesResources.property_) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void PropertyAccessorUpdate_AddAttribute_SupportedByRuntime() + public void PropertyAccessorUpdate_AddAttribute() { var src1 = "class C { int P { get; set; } }"; var src2 = "class C { int P { [System.Obsolete]get; set; } }"; @@ -15005,10 +16461,14 @@ public void PropertyAccessorUpdate_AddAttribute_SupportedByRuntime() edits.VerifySemantics( new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").GetMethod) }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "get", CSharpFeaturesResources.property_getter) }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void PropertyAccessorUpdate_AddAttribute_SupportedByRuntime2() + public void PropertyAccessorUpdate_AddAttribute2() { var src1 = "class C { int P { get; set; } }"; var src2 = "class C { int P { get; [System.Obsolete]set; } }"; @@ -15018,6 +16478,10 @@ public void PropertyAccessorUpdate_AddAttribute_SupportedByRuntime2() edits.VerifySemantics( new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P").SetMethod) }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "set", CSharpFeaturesResources.property_setter) }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] @@ -15566,7 +17030,7 @@ public void Property_AutoWithInitializer_Partial_InsertDelete() { 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) + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) }), DocumentResults(), @@ -15770,122 +17234,6 @@ public void IndexerWithExpressionBody_Update() }); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] - public void IndexerWithExpressionBody_Update_LiftedParameter() - { - var src1 = @" -using System; - -class C -{ - int this[int a] => new Func(() => a + 1)() + 10; -} -"; - var src2 = @" -using System; - -class C -{ - int this[int a] => new Func(() => 2)() + 11; // not capturing a anymore -}"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [int this[int a] => new Func(() => a + 1)() + 10;]@35 -> [int this[int a] => new Func(() => 2)() + 11;]@35", - "Update [=> new Func(() => a + 1)() + 10]@51 -> [=> new Func(() => 2)() + 11]@51"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.NotCapturingVariable, "int a", "a")); - } - - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] - public void IndexerWithExpressionBody_Update_LiftedParameter_2() - { - var src1 = @" -using System; - -class C -{ - int this[int a] => new Func(() => a + 1)(); -} -"; - var src2 = @" -using System; - -class C -{ - int this[int a] => new Func(() => 2)(); // not capturing a anymore -}"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [int this[int a] => new Func(() => a + 1)();]@35 -> [int this[int a] => new Func(() => 2)();]@35", - "Update [=> new Func(() => a + 1)()]@51 -> [=> new Func(() => 2)()]@51"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.NotCapturingVariable, "int a", "a")); - } - - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] - public void IndexerWithExpressionBody_Update_LiftedParameter_3() - { - var src1 = @" -using System; - -class C -{ - int this[int a] => new Func(() => { return a + 1; })(); -} -"; - var src2 = @" -using System; - -class C -{ - int this[int a] => new Func(() => { return 2; })(); // not capturing a anymore -}"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [int this[int a] => new Func(() => { return a + 1; })();]@35 -> [int this[int a] => new Func(() => { return 2; })();]@35", - "Update [=> new Func(() => { return a + 1; })()]@51 -> [=> new Func(() => { return 2; })()]@51"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.NotCapturingVariable, "int a", "a")); - } - - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] - public void IndexerWithExpressionBody_Update_LiftedParameter_4() - { - var src1 = @" -using System; - -class C -{ - int this[int a] => new Func(delegate { return a + 1; })(); -} -"; - var src2 = @" -using System; - -class C -{ - int this[int a] => new Func(delegate { return 2; })(); // not capturing a anymore -}"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [int this[int a] => new Func(delegate { return a + 1; })();]@35 -> [int this[int a] => new Func(delegate { return 2; })();]@35", - "Update [=> new Func(delegate { return a + 1; })()]@51 -> [=> new Func(delegate { return 2; })()]@51"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.NotCapturingVariable, "int a", "a")); - } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] public void Indexer_ExpressionBodyToBlockBody() { @@ -16545,58 +17893,26 @@ public void IndexerInit_Partial_InsertDelete() } [Fact] - public void AutoIndexer_Partial_InsertDelete() - { - var srcA1 = "partial class C { }"; - var srcB1 = "partial class C { int this[int x] { get; set; } }"; - - 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( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").SetMethod), - }), - DocumentResults(), - }); - } - - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51297")] - public void IndexerWithExpressionBody_Partial_InsertDeleteUpdate_LiftedParameter() - { - var srcA1 = @" -partial class C -{ -}"; - var srcB1 = @" -partial class C -{ - int this[int a] => new System.Func(() => a + 1); -}"; + public void AutoIndexer_Partial_InsertDelete() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { int this[int x] { get; set; } }"; - var srcA2 = @" -partial class C -{ - int this[int a] => new System.Func(() => 2); // no capture -}"; - var srcB2 = @" -partial class C -{ -}"; + 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(diagnostics: new[] { Diagnostic(RudeEditKind.NotCapturingVariable, "int a", "a") }), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").SetMethod), + }), DocumentResults(), }); } @@ -19162,328 +20478,5 @@ public void TopLevelStatements_Reorder() } #endregion - - #region Primary Constructors - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_01([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C() { void M() { } }"; - var src2 = keyword + " C() { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, keyword + " C", FeaturesResources.method)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_02([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C() { }"; - var src2 = keyword + " C() { void M() { } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "void M()", FeaturesResources.method)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_03([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C() { void M() { } }"; - var src2 = keyword + " C() { void M() { ToString(); } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "void M()", FeaturesResources.method)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_04([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C(int a) { }"; - var src2 = keyword + " C(int a, int b) { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "int b", FeaturesResources.parameter)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_05([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C(int a, int b) { }"; - var src2 = keyword + " C(int a) { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, keyword + " C", FeaturesResources.parameter)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_06([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C { }"; - var src2 = keyword + " C() { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics(); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_07([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C() { }"; - var src2 = keyword + " C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics(); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_08([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C() { }"; - var src2 = keyword + " C(int b) { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "int b", FeaturesResources.parameter)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_09([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C(int b) { }"; - var src2 = keyword + " C() { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, keyword + " C", FeaturesResources.parameter)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_10([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C { }"; - var src2 = keyword + " C(int b) { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "int b", FeaturesResources.parameter)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_11([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C(int b) { }"; - var src2 = keyword + " C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, keyword + " C", FeaturesResources.parameter)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_12([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = "partial " + keyword + " C(); partial " + keyword + " C { void M() { } }"; - var src2 = "partial " + keyword + " C(); partial " + keyword + " C { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "partial " + keyword + " C", FeaturesResources.method)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_13([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = "partial " + keyword + " C(); partial " + keyword + " C { }"; - var src2 = "partial " + keyword + " C(); partial " + keyword + " C { void M() { } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "void M()", FeaturesResources.method)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_14([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = "partial " + keyword + " C(); partial " + keyword + " C { void M() { } }"; - var src2 = "partial " + keyword + " C(); partial " + keyword + " C { void M() { ToString(); } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "void M()", FeaturesResources.method)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_15([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = "partial " + keyword + " C(); partial " + keyword + " C { void M() { } }"; - var src2 = "partial " + keyword + " C {} partial " + keyword + " C { void M() { ToString(); } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "void M()", FeaturesResources.method)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_16([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = "partial " + keyword + " C {} partial " + keyword + " C { void M() { } }"; - var src2 = "partial " + keyword + " C(); partial " + keyword + " C { void M() { ToString(); } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "void M()", FeaturesResources.method)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_17([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C(int a) { }"; - var src2 = keyword + " C(long a) { }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "long a", FeaturesResources.parameter)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_18([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C(int x, int y) { void M() { x++; } System.Func z = () => y; }"; - var src2 = keyword + " C(int x, int y) { void M() { x++; } System.Func z = () => x + y; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "z = () => x + y", FeaturesResources.field)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_19([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C(int x, int y) { void M() { x++; } System.Func z = () => x + y; }"; - var src2 = keyword + " C(int x, int y) { void M() { x++; } System.Func z = () => y; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "z = () => y", FeaturesResources.field)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_20([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C(int x) { void M() { x++; } }"; - var src2 = keyword + " C(int x) { void M() { x++; } System.Func z = () => x; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "z = () => x", FeaturesResources.field)); - } - - [Theory] - [CombinatorialData] - public void PrimaryConstructors_21([CombinatorialValues("class", "struct")] string keyword) - { - var src1 = keyword + " C(int x) { void M() { x++; } System.Func z = () => x; }"; - var src2 = keyword + " C(int x) { void M() { x++; } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, keyword + " C", FeaturesResources.field)); - } - - [Fact] - public void PrimaryConstructors_22() - { - var src1 = "class C2(int x, int y) : C1(() => y) { void M() { x++; } } class C1(System.Func x);"; - var src2 = "class C2(int x, int y) : C1(() => x + y) { void M() { x++; } } class C1(System.Func x);"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "class C2", FeaturesResources.class_)); - } - - [Fact] - public void PrimaryConstructors_23() - { - var src1 = "class C2(int x, int y) : C1(() => x + y) { void M() { x++; } } class C1(System.Func x);"; - var src2 = "class C2(int x, int y) : C1(() => y) { void M() { x++; } } class C1(System.Func x);"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "class C2", FeaturesResources.class_)); - } - - [Fact] - public void PrimaryConstructors_24() - { - var src1 = "class C2(int x) : C1(null) { void M() { x++; } } class C1(System.Func x);"; - var src2 = "class C2(int x) : C1(() => x) { void M() { x++; } } class C1(System.Func x);"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "class C2", FeaturesResources.class_)); - } - - [Fact] - public void PrimaryConstructors_25() - { - var src1 = "class C2(int x) : C1(() => x) { void M() { x++; } } class C1(System.Func x);"; - var src2 = "class C2(int x) : C1(null) { void M() { x++; } } class C1(System.Func x);"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Update, "class C2", FeaturesResources.class_)); - } - - #endregion } } diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index 80b46faac9fed..e7e1fb90f9763 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -294,58 +294,72 @@ private static void VerifySemanticEdits( Assert.Equal(editKind, actualSemanticEdit.Kind); - var expectedOldSymbol = (editKind is SemanticEditKind.Update or SemanticEditKind.Delete) ? expectedSemanticEdit.SymbolProvider(oldCompilation) : null; - var expectedNewSymbol = expectedSemanticEdit.SymbolProvider(newCompilation); var symbolKey = actualSemanticEdit.Symbol; + ISymbol? expectedOldSymbol = null, expectedNewSymbol = null; - if (editKind == SemanticEditKind.Update) + switch (editKind) { - Assert.Equal(expectedOldSymbol, symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true).Symbol); - Assert.Equal(expectedNewSymbol, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); - } - else if (editKind == SemanticEditKind.Delete) - { - // Symbol key will happily resolve to a definition part that has no implementation, so we validate that - // differently - if (expectedOldSymbol is IMethodSymbol { IsPartialDefinition: true } && - symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true).Symbol is IMethodSymbol resolvedMethod) - { - Assert.Equal(expectedOldSymbol, resolvedMethod.PartialDefinitionPart); - Assert.Equal(null, resolvedMethod.PartialImplementationPart); - } - else - { + case SemanticEditKind.Update: + expectedOldSymbol = expectedSemanticEdit.SymbolProvider(oldCompilation); + expectedNewSymbol = expectedSemanticEdit.SymbolProvider(newCompilation); + Assert.Equal(expectedOldSymbol, symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true).Symbol); + Assert.Equal(expectedNewSymbol, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); + break; + + case SemanticEditKind.Delete: + expectedOldSymbol = expectedSemanticEdit.SymbolProvider(oldCompilation); - // When we're deleting a symbol, and have a deleted symbol container, it means the symbol wasn't really deleted, - // but rather had its signature changed in some way. Some of those ways, like changing the return type, are not - // represented in the symbol key, so the check below would fail, so we skip it. - if (expectedSemanticEdit.DeletedSymbolContainerProvider is null) + // Symbol key will happily resolve to a definition part that has no implementation, so we validate that + // differently + if (expectedOldSymbol is IMethodSymbol { IsPartialDefinition: true } && + symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true).Symbol is IMethodSymbol resolvedMethod) { - Assert.Equal(null, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); + Assert.Equal(expectedOldSymbol, resolvedMethod.PartialDefinitionPart); + Assert.Equal(null, resolvedMethod.PartialImplementationPart); + } + else + { + Assert.Equal(expectedOldSymbol, symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true).Symbol); + + // When we're deleting a symbol, and have a deleted symbol container, it means the symbol wasn't really deleted, + // but rather had its signature changed in some way. Some of those ways, like changing the return type, are not + // represented in the symbol key, so the check below would fail, so we skip it. + if (expectedSemanticEdit.DeletedSymbolContainerProvider is null) + { + Assert.Equal(null, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); + } } - } - var deletedSymbolContainer = actualSemanticEdit.DeletedSymbolContainer?.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol; - Assert.Equal(deletedSymbolContainer, expectedSemanticEdit.DeletedSymbolContainerProvider?.Invoke(newCompilation)); - } - else if (editKind is SemanticEditKind.Insert or SemanticEditKind.Replace) - { - Assert.Equal(expectedNewSymbol, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); - } - else - { - Assert.False(true, "Only Update, Delete, Insert or Replace allowed"); + var deletedSymbolContainer = actualSemanticEdit.DeletedSymbolContainer?.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol; + AssertEx.AreEqual( + deletedSymbolContainer, + expectedSemanticEdit.DeletedSymbolContainerProvider?.Invoke(newCompilation), + message: $"{message}, {editKind}({expectedNewSymbol ?? expectedOldSymbol}): Incorrect deleted container"); + + break; + + case SemanticEditKind.Insert or SemanticEditKind.Replace: + expectedNewSymbol = expectedSemanticEdit.SymbolProvider(newCompilation); + Assert.Equal(expectedNewSymbol, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); + break; + + default: + throw ExceptionUtilities.UnexpectedValue(editKind); } // Partial types must match: - Assert.Equal( + AssertEx.AreEqual( expectedSemanticEdit.PartialType?.Invoke(newCompilation), - actualSemanticEdit.PartialType?.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); + actualSemanticEdit.PartialType?.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol, + message: $"{message}, {editKind}({expectedNewSymbol ?? expectedOldSymbol}): Partial types do not match"); // Edit is expected to have a syntax map: var actualSyntaxMap = actualSemanticEdit.SyntaxMap; - Assert.Equal(expectedSemanticEdit.HasSyntaxMap, actualSyntaxMap != null); + AssertEx.AreEqual( + expectedSemanticEdit.HasSyntaxMap, + actualSyntaxMap != null, + message: $"{message}, {editKind}({expectedNewSymbol ?? expectedOldSymbol}): Incorrect syntax map"); // If expected map is specified validate its mappings with the actual one: var expectedSyntaxMap = expectedSemanticEdit.SyntaxMap; diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index 4cb3be3c9c655..fd9c57598bb8a 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -49,6 +49,25 @@ End Namespace Return String.Format(FeaturesResources.member_kind_and_name, resource, symbolDisplayName) End Function + Public Shared Function GetResource(keyword As String, symbolDisplayName As String, containerKeyword As String, containerDisplayName As String) As String + Dim keywordResource = TryGetResource(keyword) + If keywordResource Is Nothing Then + Throw ExceptionUtilities.UnexpectedValue(keyword) + End If + + Dim containerResource = TryGetResource(containerKeyword) + If containerResource Is Nothing Then + Throw ExceptionUtilities.UnexpectedValue(containerKeyword) + End If + + Return String.Format( + FeaturesResources.symbol_kind_and_name_of_member_kind_and_name, + keywordResource, + symbolDisplayName, + containerResource, + containerDisplayName) + End Function + Public Shared Function GetResource(keyword As String) As String Dim result = TryGetResource(keyword) If result Is Nothing Then diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb index aa8ebdd0372c2..9ca248ca6da54 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb @@ -465,14 +465,14 @@ End Class" Public Sub Constructor_Recompile2() Dim src1 = "Class C - Shared Sub New() + Sub New() MyBase.New() End Sub End Class" Dim src2 = "Class C - Shared Sub _ + Sub _ New() MyBase.New() End Sub @@ -481,7 +481,7 @@ End Class" Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits( Array.Empty(Of SequencePointUpdates), - {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").SharedConstructors.Single())}) + {SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single())}) End Sub #End Region diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 3b97b8290b24c..b8665b2bd2c88 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -3915,7 +3915,11 @@ End Class EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { - DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True) + }), DocumentResults( semanticEdits:= { @@ -4198,7 +4202,10 @@ End Class" EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { - DocumentResults(), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True) + }), DocumentResults(semanticEdits:= { SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True) @@ -6620,7 +6627,7 @@ End Class Partial Class C ReadOnly Property B As Integer = F(Function(a) a + 1) - Sub New() ' new ctor + Sub New(x As Integer) ' new ctor F(Function(c) c + 1) End Sub End Class @@ -6630,7 +6637,7 @@ End Class Dim syntaxMap = GetSyntaxMap(src1, src2) edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, "Sub New()")}) + {Diagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, "Sub New(x As Integer)")}) End Sub @@ -7965,7 +7972,8 @@ End Class ActiveStatementsDescription.Empty, { SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.B")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single()) + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single()), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType Or EditAndContinueCapabilities.AddInstanceFieldToExistingType) End Sub @@ -8433,7 +8441,8 @@ End Class { DocumentResults( semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True)}), - DocumentResults() + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True)}) }) End Sub @@ -9512,7 +9521,8 @@ End Class semanticEdits:= { SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_a"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.set_a"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.set_a"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) }) End Sub @@ -10966,7 +10976,7 @@ End Class capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "Public Sub M()", DeletedSymbolDisplay(FeaturesResources.parameter, "a As Integer"))}, + {Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "Public Sub M()", GetResource("parameter", "a", "method", "M(a As Integer)"))}, capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -10990,7 +11000,7 @@ End Class capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "Public Sub M(b As Integer)", DeletedSymbolDisplay(FeaturesResources.parameter, "a As Integer"))}, + {Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "Public Sub M(b As Integer)", GetResource("parameter", "a", "method", "M(a As Integer, b As Integer)"))}, capabilities:=EditAndContinueCapabilities.Baseline) End Sub diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index a2c0267c3b7b3..4024207dd864e 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -571,6 +571,7 @@ protected override Match ComputeTopLevelMatch(SyntaxNode oldCompilat private static SyntaxNode? GetDeclarationParameterList(SyntaxNode declaration) => declaration switch { + ParameterListSyntax parameterList => parameterList, AccessorDeclarationSyntax { Parent.Parent: IndexerDeclarationSyntax { ParameterList: var list } } => list, ArrowExpressionClauseSyntax { Parent: { } memberDecl } => GetDeclarationParameterList(memberDecl), _ => declaration.GetParameterList() @@ -673,79 +674,6 @@ private static IEnumerable GetChildNodes(SyntaxNode root, SyntaxNode } } - internal override void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode, ISymbol oldSymbol, ISymbol newSymbol, EditAndContinueCapabilitiesGrantor capabilities, CancellationToken cancellationToken) - { - // Global statements have a declaring syntax reference to the compilation unit itself, which we can just ignore - // for the purposes of declaration rude edits - if (oldNode.IsKind(SyntaxKind.CompilationUnit) || newNode.IsKind(SyntaxKind.CompilationUnit)) - { - return; - } - - // Compiler generated methods of records have a declaring syntax reference to the record declaration itself - // but their explicitly implemented counterparts reference the actual member. Compiler generated properties - // of records reference the parameter that names them. - // - // Since there is no useful "old" syntax node for these members, we can't compute declaration or body edits - // using the standard tree comparison code. - // - // Based on this, we can detect a new explicit implementation of a record member by checking if the - // declaration kind has changed. If it hasn't changed, then our standard code will handle it. - if (oldNode.RawKind == newNode.RawKind) - { - base.ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldNode, newNode, oldSymbol, newSymbol, capabilities, cancellationToken); - return; - } - - // When explicitly implementing a property that is represented by a positional parameter - // what looks like an edit could actually be a rude delete, or something else - if (oldNode is ParameterSyntax && - newNode is PropertyDeclarationSyntax property) - { - if (property.AccessorList!.Accessors.Count == 1) - { - // Explicitly implementing a property with only one accessor is a delete of the init accessor, so a rude edit. - // Not implementing the get accessor would be a compile error - - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.ImplementRecordParameterAsReadOnly, - GetDiagnosticSpan(newNode, EditKind.Delete), - oldNode, - new[] { property.Identifier.ToString() })); - } - else if (property.AccessorList.Accessors.Any(a => a.IsKind(SyntaxKind.SetAccessorDeclaration))) - { - // The compiler implements the properties with an init accessor so explicitly implementing - // it with a set accessor is a rude accessor change edit - - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.ImplementRecordParameterWithSet, - GetDiagnosticSpan(newNode, EditKind.Delete), - oldNode, - new[] { property.Identifier.ToString() })); - } - } - else if (oldNode is RecordDeclarationSyntax && - newNode is MethodDeclarationSyntax && - !oldSymbol.GetParameters().Select(p => p.Name).SequenceEqual(newSymbol.GetParameters().Select(p => p.Name)) && - !capabilities.Grant(EditAndContinueCapabilities.UpdateParameters)) - { - // Explicitly implemented methods must have parameter names that match the compiler generated versions - // exactly if the runtime doesn't support updating parameters, otherwise the debugger would show incorrect - // parameter names. - // We don't need to worry about parameter types, because if they were different then we wouldn't get here - // as this wouldn't be the explicit implementation of a known method. - // We don't need to worry about access modifiers because the symbol matching still works, and most of the - // time changing access modifiers for these known methods is a compile error anyway. - - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, - GetDiagnosticSpan(newNode, EditKind.Update), - oldNode, - new[] { oldSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat) })); - } - } - protected override bool TryMatchActiveStatement( SyntaxNode oldStatement, int statementPart, @@ -1179,12 +1107,21 @@ internal override bool HasBackingField(SyntaxNode propertyOrIndexerDeclaration) internal override bool TryGetAssociatedMemberDeclaration(SyntaxNode node, EditKind editKind, [NotNullWhen(true)] out SyntaxNode? declaration) { - if (node.Kind() is SyntaxKind.Parameter or SyntaxKind.TypeParameter) + if (node is (kind: SyntaxKind.Parameter)) + { + Contract.ThrowIfFalse(node.Parent is (kind: SyntaxKind.ParameterList or SyntaxKind.BracketedParameterList)); + + // ParameterList represents the primary constructor: + declaration = node.Parent.Parent is TypeDeclarationSyntax ? node.Parent : node.Parent.Parent; + Contract.ThrowIfNull(declaration); + + return true; + } + + if (node is (kind: SyntaxKind.TypeParameter)) { - Contract.ThrowIfFalse(node.Parent is (kind: - SyntaxKind.ParameterList or - SyntaxKind.TypeParameterList or - SyntaxKind.BracketedParameterList)); + Contract.ThrowIfFalse(node.Parent is (kind: SyntaxKind.TypeParameterList)); + declaration = node.Parent.Parent!; return true; } @@ -1207,46 +1144,53 @@ SyntaxKind.IndexerDeclaration or internal override bool IsDeclarationWithInitializer(SyntaxNode declaration) => declaration is VariableDeclaratorSyntax { Initializer: not null } or PropertyDeclarationSyntax { Initializer: not null }; - internal override bool IsRecordPrimaryConstructorParameter(SyntaxNode declaration) - => declaration is ParameterSyntax { Parent: ParameterListSyntax { Parent: RecordDeclarationSyntax } }; + internal override bool IsPrimaryConstructorDeclaration(SyntaxNode declaration) + => declaration.Parent is TypeDeclarationSyntax { ParameterList: var parameterList } && parameterList == declaration; - private static bool IsPropertyDeclarationMatchingPrimaryConstructorParameter(SyntaxNode declaration, INamedTypeSymbol newContainingType) + internal override bool IsConstructorWithMemberInitializers(ISymbol symbol, CancellationToken cancellationToken) { - if (newContainingType.IsRecord && - declaration is PropertyDeclarationSyntax { Identifier.ValueText: var name }) + if (symbol is not IMethodSymbol { MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor } method) { - // We need to use symbol information to find the primary constructor, because it could be in another file if the type is partial - foreach (var reference in newContainingType.DeclaringSyntaxReferences) - { - // Since users can define as many constructors as they like, going back to syntax to find the parameter list - // in the record declaration is the simplest way to check if there is a matching parameter - if (reference.GetSyntax() is RecordDeclarationSyntax record && - record.ParameterList is not null && - record.ParameterList.Parameters.Any(p => p.Identifier.ValueText.Equals(name))) - { - return true; - } - } + return false; } - return false; - } - - internal override bool IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(SyntaxNode declaration, INamedTypeSymbol newContainingType, out bool isFirstAccessor) - { - isFirstAccessor = false; - if (declaration is AccessorDeclarationSyntax { Parent: AccessorListSyntax { Parent: PropertyDeclarationSyntax property } list } && - IsPropertyDeclarationMatchingPrimaryConstructorParameter(property, newContainingType)) + // static constructor has initializers: + if (method.IsStatic) { - isFirstAccessor = list.Accessors[0] == declaration; return true; } - return false; - } + // If primary constructor is present then no other constructor has member initializers: + if (GetPrimaryConstructor(method.ContainingType, cancellationToken) is { } primaryConstructor) + { + return symbol == primaryConstructor; + } + + // Copy-constructor of a record does not have member initializers: + if (method.ContainingType.IsRecord && method.IsCopyConstructor()) + { + return false; + } + + // Default constructor has initializers unless the type is a struct. + // Struct with member initializers is required to have an explicit constructor. + if (method.IsImplicitlyDeclared) + { + return method.ContainingType.TypeKind != TypeKind.Struct; + } - internal override bool IsConstructorWithMemberInitializers(SyntaxNode constructorDeclaration) - => constructorDeclaration is ConstructorDeclarationSyntax ctor && (ctor.Initializer == null || ctor.Initializer.IsKind(SyntaxKind.BaseConstructorInitializer)); + var ctorInitializer = ((ConstructorDeclarationSyntax)symbol.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken)).Initializer; + if (method.ContainingType.TypeKind == TypeKind.Struct) + { + // constructor of a struct with implicit or this() initializer has member initializers: + return ctorInitializer is null or { ThisOrBaseKeyword: (kind: SyntaxKind.ThisKeyword), ArgumentList.Arguments: [] }; + } + else + { + // constructor of a class with implicit or base initializer has member initializers: + return ctorInitializer is null or (kind: SyntaxKind.BaseConstructorInitializer); + } + } internal override bool IsPartial(INamedTypeSymbol type) { @@ -1255,8 +1199,26 @@ internal override bool IsPartial(INamedTypeSymbol type) || ((BaseTypeDeclarationSyntax)syntaxRefs.Single().GetSyntax()).Modifiers.Any(SyntaxKind.PartialKeyword); } - protected override SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference reference, CancellationToken cancellationToken) - => reference.GetSyntax(cancellationToken); + protected override SyntaxNode GetSymbolDeclarationSyntax(ISymbol symbol, Func, SyntaxReference> selector, CancellationToken cancellationToken) + { + var syntax = selector(symbol.DeclaringSyntaxReferences).GetSyntax(cancellationToken); + + // Use the parameter list to represent primary constructor declaration. + return symbol.Kind == SymbolKind.Method && syntax is TypeDeclarationSyntax { ParameterList: { } parameterList } ? parameterList : syntax; + } + + protected override ISymbol? GetDeclaredSymbol(SemanticModel model, SyntaxNode declaration, CancellationToken cancellationToken) + { + if (IsPrimaryConstructorDeclaration(declaration)) + { + Contract.ThrowIfNull(declaration.Parent); + var recordType = (INamedTypeSymbol?)model.GetDeclaredSymbol(declaration.Parent, cancellationToken); + Contract.ThrowIfNull(recordType); + return recordType.InstanceConstructors.Single(ctor => ctor.DeclaringSyntaxReferences is [var syntaxRef] && syntaxRef.GetSyntax(cancellationToken) == declaration.Parent); + } + + return model.GetDeclaredSymbol(declaration, cancellationToken); + } protected override OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol, EditKind editKind)> GetSymbolEdits( EditKind editKind, @@ -1371,12 +1333,12 @@ static bool DiffersInReadOnlyModifier(SyntaxTokenList oldModifiers, SyntaxTokenL { if (oldVariables.Count == 1 && newVariables.Count == 1) { - return OneOrMany.Create((oldModel.GetDeclaredSymbol(oldVariables[0], cancellationToken), newModel.GetDeclaredSymbol(newVariables[0], cancellationToken), EditKind.Update)); + return OneOrMany.Create((GetDeclaredSymbol(oldModel, oldVariables[0], cancellationToken), GetDeclaredSymbol(newModel, newVariables[0], cancellationToken), EditKind.Update)); } var result = from oldVariable in oldVariables join newVariable in newVariables on oldVariable.Identifier.Text equals newVariable.Identifier.Text - select (oldModel.GetDeclaredSymbol(oldVariable, cancellationToken), newModel.GetDeclaredSymbol(newVariable, cancellationToken), EditKind.Update); + select (GetDeclaredSymbol(oldModel, oldVariable, cancellationToken), GetDeclaredSymbol(newModel, newVariable, cancellationToken), EditKind.Update); return OneOrMany.Create(result.ToImmutableArray()); } @@ -1422,6 +1384,13 @@ join newVariable in newVariables on oldVariable.Identifier.Text equals newVariab return OneOrMany.Create(ImmutableArray.Create((oldSymbol, newSymbol, EditKind.Update))); } + // Inserting/deleting a primary constructor base initializer/base list is an update of the constructor/type, + // not a delete/insert of the constructor/type itself: + if (node is (kind: SyntaxKind.PrimaryConstructorBaseType or SyntaxKind.BaseList)) + { + return OneOrMany.Create(ImmutableArray.Create((oldSymbol, newSymbol, EditKind.Update))); + } + break; case EditKind.Move: @@ -1442,7 +1411,7 @@ join newVariable in newVariables on oldVariable.Identifier.Text equals newVariab OneOrMany<(ISymbol?, ISymbol?, EditKind)>.Empty : new OneOrMany<(ISymbol?, ISymbol?, EditKind)>((oldSymbol, newSymbol, editKind)); } - private static ISymbol? GetSymbolForEdit( + private ISymbol? GetSymbolForEdit( SyntaxNode node, SemanticModel model, CancellationToken cancellationToken) @@ -1465,7 +1434,18 @@ join newVariable in newVariables on oldVariable.Identifier.Text equals newVariab return model.GetEnclosingSymbol(node.SpanStart, cancellationToken); } - var symbol = model.GetDeclaredSymbol(node, cancellationToken); + if (node is PrimaryConstructorBaseTypeSyntax primaryCtorBase) + { + return model.GetEnclosingSymbol(primaryCtorBase.ArgumentList.SpanStart, cancellationToken); + } + + if (node.IsKind(SyntaxKind.BaseList)) + { + Contract.ThrowIfNull(node.Parent); + node = node.Parent; + } + + var symbol = GetDeclaredSymbol(model, node, cancellationToken); // TODO: this is incorrect (https://github.com/dotnet/roslyn/issues/54800) // Ignore partial method definition parts. @@ -1640,6 +1620,10 @@ private static bool GroupBySignatureComparer(ImmutableArray ol return GetDiagnosticSpan(typeDeclaration.Modifiers, typeDeclaration.Keyword, typeDeclaration.TypeParameterList ?? (SyntaxNodeOrToken)typeDeclaration.Identifier); + case SyntaxKind.BaseList: + var baseList = (BaseListSyntax)node; + return baseList.Types.Span; + case SyntaxKind.EnumDeclaration: var enumDeclaration = (EnumDeclarationSyntax)node; return GetDiagnosticSpan(enumDeclaration.Modifiers, enumDeclaration.EnumKeyword, enumDeclaration.Identifier); @@ -2105,6 +2089,9 @@ internal override string GetDisplayName(IMethodSymbol symbol) case SyntaxKind.Parameter: return FeaturesResources.parameter; + case SyntaxKind.ParameterList: + return node.Parent is TypeDeclarationSyntax ? FeaturesResources.constructor : null; + case SyntaxKind.AttributeList: return FeaturesResources.attribute; @@ -3088,60 +3075,5 @@ private static bool DeclareSameIdentifiers(SyntaxToken[] oldVariables, SyntaxTok } #endregion - - protected override bool IsRudeEditDueToPrimaryConstructor(ISymbol symbol, CancellationToken cancellationToken) - { - switch (symbol.Kind) - { - case SymbolKind.NamedType: - { - return IsTypeWithPrimaryConstructor(symbol, cancellationToken); - } - - case SymbolKind.Parameter: - { - var container = symbol.ContainingSymbol; - - if (container is IMethodSymbol { IsImplicitlyDeclared: false, MethodKind: MethodKind.Constructor }) - { - foreach (var syntaxReference in container.DeclaringSyntaxReferences) - { - if (syntaxReference.GetSyntax(cancellationToken) is - ClassDeclarationSyntax { ParameterList: not null } or - StructDeclarationSyntax { ParameterList: not null }) - { - return true; - } - } - } - } - break; - - default: - { - return IsTypeWithPrimaryConstructor(symbol.ContainingSymbol, cancellationToken); - } - } - - return false; - - static bool IsTypeWithPrimaryConstructor(ISymbol container, CancellationToken cancellationToken) - { - if (container is { Kind: SymbolKind.NamedType, IsImplicitlyDeclared: false }) - { - foreach (var syntaxReference in container.DeclaringSyntaxReferences) - { - if (syntaxReference.GetSyntax(cancellationToken) is - ClassDeclarationSyntax { ParameterList: not null } or - StructDeclarationSyntax { ParameterList: not null }) - { - return true; - } - } - } - - return false; - } - } } } diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs index 3691538e5f7f5..b4b89e2b19ed0 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs @@ -43,12 +43,14 @@ protected override bool IsLambdaBodyStatementOrExpression(SyntaxNode node) #region Labels - // Assumptions: - // - Each listed label corresponds to one or more syntax kinds. - // - Nodes with same labels might produce Update edits, nodes with different labels don't. - // - If IsTiedToParent(label) is true for a label then all its possible parent labels must precede the label. - // (i.e. both MethodDeclaration and TypeDeclaration must precede TypeParameter label). - // - All descendants of a node whose kind is listed here will be ignored regardless of their labels + /// + /// Assumptions: + /// - Each listed label corresponds to one or more syntax kinds. + /// - Nodes with same labels might produce Update edits, nodes with different labels don't. + /// - If is true for a label then all its possible parent labels must precede the label. + /// (i.e. both MethodDeclaration and TypeDeclaration must precede TypeParameter label). + /// - All descendants of a node whose kind is listed here will be ignored regardless of their labels + /// internal enum Label { // Top level syntax kinds @@ -62,6 +64,8 @@ internal enum Label TypeDeclaration, EnumDeclaration, + BaseList, // tied to parent + PrimaryConstructorBase, // tied to parent DelegateDeclaration, FieldDeclaration, // tied to parent @@ -185,6 +189,7 @@ private static int TiedToAncestor(Label label) case Label.IndexerDeclaration: case Label.EventDeclaration: case Label.EnumMemberDeclaration: + case Label.BaseList: case Label.AccessorDeclaration: case Label.AccessorList: case Label.TypeParameterList: @@ -592,6 +597,14 @@ private static Label ClassifyTopSyntax(SyntaxKind kind, SyntaxNode? node, out bo case SyntaxKind.RecordStructDeclaration: return Label.TypeDeclaration; + case SyntaxKind.BaseList: + return Label.BaseList; + + case SyntaxKind.PrimaryConstructorBaseType: + // For top syntax, primary constructor base initializer is a leaf node + isLeaf = true; + return Label.PrimaryConstructorBase; + case SyntaxKind.MethodDeclaration: return Label.MethodDeclaration; diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index a7ed0329ac0da..b50b3f3432121 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -323,6 +323,25 @@ protected virtual TextSpan GetBodyDiagnosticSpan(SyntaxNode node, EditKind editK internal abstract TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, int ordinal); // display names: + + internal string GetDisplayKindAndName(ISymbol symbol, string? displayName = null, bool fullyQualify = false) + { + displayName ??= GetDisplayName(symbol); + var format = fullyQualify ? s_fullyQualifiedMemberDisplayFormat : s_unqualifiedMemberDisplayFormat; + + return (symbol is IParameterSymbol { ContainingType: not { TypeKind: TypeKind.Delegate } }) ? + string.Format( + FeaturesResources.symbol_kind_and_name_of_member_kind_and_name, + displayName, + symbol.Name, + GetDisplayName(symbol.ContainingSymbol), + symbol.ContainingSymbol.ToDisplayString(format)) : + string.Format( + FeaturesResources.member_kind_and_name, + displayName, + symbol.ToDisplayString(format)); + } + internal string GetDisplayName(SyntaxNode node, EditKind editKind = EditKind.Update) => TryGetDisplayName(node, editKind) ?? throw ExceptionUtilities.UnexpectedValue(node.GetType().Name); @@ -465,7 +484,7 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind /// /// Returns the declaration of /// - a property, indexer or event declaration whose accessor is the specified , - /// - a method, an indexer or a type (delegate) if the is a parameter, + /// - a method, an indexer, a type (delegate), or primary constructor parameter list if the is a parameter, /// - a method or an type if the is a type parameter. /// internal abstract bool TryGetAssociatedMemberDeclaration(SyntaxNode node, EditKind editKind, [NotNullWhen(true)] out SyntaxNode? declaration); @@ -479,19 +498,19 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind internal abstract bool IsDeclarationWithInitializer(SyntaxNode declaration); /// - /// Return true if the declaration is a parameter that is part of a records primary constructor. + /// True if is a declaration node of a primary constructor (i.e. parameter list of a type declaration). /// - internal abstract bool IsRecordPrimaryConstructorParameter(SyntaxNode declaration); - - /// - /// Return true if the declaration is a property accessor for a property that represents one of the parameters in a records primary constructor. - /// - internal abstract bool IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(SyntaxNode declaration, INamedTypeSymbol newContainingType, out bool isFirstAccessor); + /// + /// of a primary constructor returns the type declaration syntax. + /// This is inconvenient for EnC analysis since it doesn't allow us to distinguish declaration of the type from the constructor. + /// E.g. delete/insert of a primary constructor is not the same as delete/insert of the entire type declaration. + /// + internal abstract bool IsPrimaryConstructorDeclaration(SyntaxNode declaration); /// - /// Return true if the declaration is a constructor declaration to which field/property initializers are emitted. + /// Return true if is a constructor to which field/property initializers are emitted. /// - internal abstract bool IsConstructorWithMemberInitializers(SyntaxNode declaration); + internal abstract bool IsConstructorWithMemberInitializers(ISymbol symbol, CancellationToken cancellationToken); internal abstract bool IsPartial(INamedTypeSymbol type); @@ -730,29 +749,6 @@ private void ReportTopLevelSyntacticRudeEdits(ArrayBuilder d } } - /// - /// Reports rude edits for a symbol that's been deleted in one location and inserted in another and the edit was not classified as - /// or . - /// The scenarios include moving a type declaration from one file to another and moving a member of a partial type from one partial declaration to another. - /// - internal virtual void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode, ISymbol oldSymbol, ISymbol newSymbol, EditAndContinueCapabilitiesGrantor capabilities, CancellationToken cancellationToken) - { - // When a method is moved to a different declaration and its parameters are changed at the same time - // the new method symbol key will not resolve to the old one since the parameters are different. - // As a result we will report separate delete and insert rude edits. - // - // For delegates, however, the symbol key will resolve to the old type so we need to report - // rude edits here. - if (oldSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var oldDelegateInvoke } && - newSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var newDelegateInvoke }) - { - if (!ParameterTypesEquivalent(oldDelegateInvoke.Parameters, newDelegateInvoke.Parameters, exact: false)) - { - ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingParameterTypes, newSymbol, newNode, cancellationToken); - } - } - } - internal Dictionary BuildEditMap(EditScript editScript) { var map = new Dictionary(editScript.Edits.Length); @@ -1098,7 +1094,7 @@ private void AnalyzeChangedMemberBody( if (!activeNodes.IsEmpty() || newStateMachineInfo.HasSuspensionPoints || newBodyHasLambdas || - IsConstructorWithMemberInitializers(newDeclaration) || + IsConstructorWithMemberInitializers(newMember, cancellationToken) || IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration)) { @@ -2370,28 +2366,32 @@ protected static bool MemberSignaturesEquivalent( } } - private readonly struct ConstructorEdit + /// + /// Aggregates information needed to emit updates of constructors that contain member initialization. + /// + private sealed class MemberInitializationUpdates { public readonly INamedTypeSymbol OldType; /// /// Contains syntax maps for all changed data member initializers or constructor declarations (of constructors emitting initializers) - /// in the currently analyzed document. The key is the declaration of the member. + /// in the currently analyzed document. The key is the new declaration of the member. /// public readonly Dictionary?> ChangedDeclarations; - public ConstructorEdit(INamedTypeSymbol oldType) + /// + /// True if a member initializer has been deleted + /// ( only contains syntax nodes of new declarations, which are not available for deleted members). + /// + public bool HasDeletedMemberInitializer; + + public MemberInitializationUpdates(INamedTypeSymbol oldType) { OldType = oldType; ChangedDeclarations = new Dictionary?>(); } } - protected virtual bool IsRudeEditDueToPrimaryConstructor(ISymbol symbol, CancellationToken cancellationToken) - { - return false; - } - private async Task> AnalyzeSemanticsAsync( EditScript editScript, IReadOnlyDictionary editMap, @@ -2417,8 +2417,8 @@ private async Task> AnalyzeSemanticsAsync( } // { new type -> constructor update } - PooledDictionary? instanceConstructorEdits = null; - PooledDictionary? staticConstructorEdits = null; + PooledDictionary? instanceConstructorEdits = null; + PooledDictionary? staticConstructorEdits = null; using var _1 = PooledHashSet.GetInstance(out var processedSymbols); using var _2 = ArrayBuilder.GetInstance(out var semanticEdits); @@ -2472,7 +2472,7 @@ private async Task> AnalyzeSemanticsAsync( !SymbolsEquivalent(oldSymbol.ContainingNamespace, newSymbol.ContainingNamespace)) { // pick the first declaration in the new file that contains the namespace change: - var newTypeDeclaration = GetSymbolDeclarationSyntax(newSymbol.DeclaringSyntaxReferences.First(r => r.SyntaxTree == edit.NewNode!.SyntaxTree), cancellationToken); + var newTypeDeclaration = GetSymbolDeclarationSyntax(newSymbol, refs => refs.First(r => r.SyntaxTree == edit.NewNode!.SyntaxTree), cancellationToken); diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.ChangingNamespace, @@ -2501,11 +2501,11 @@ private async Task> AnalyzeSemanticsAsync( // Treat the edit as Insert/Delete. This may happen e.g. when all C# global statements are removed, the first one is added or they are moved to another file. if (syntacticEditKind == EditKind.Update) { - if (oldSymbol == null || oldDeclaration != null && oldDeclaration.SyntaxTree != oldModel?.SyntaxTree) + if (oldSymbol == null || oldDeclaration == null || oldDeclaration != null && oldDeclaration.SyntaxTree != oldModel?.SyntaxTree) { syntacticEditKind = EditKind.Insert; } - else if (newSymbol == null || newDeclaration != null && newDeclaration.SyntaxTree != newModel.SyntaxTree) + else if (newSymbol == null || newDeclaration == null || newDeclaration != null && newDeclaration.SyntaxTree != newModel.SyntaxTree) { syntacticEditKind = EditKind.Delete; } @@ -2590,14 +2590,6 @@ private async Task> AnalyzeSemanticsAsync( Contract.ThrowIfNull(oldSymbol); Contract.ThrowIfNull(oldDeclaration); - if (IsRudeEditDueToPrimaryConstructor(oldSymbol, cancellationToken)) - { - // https://github.com/dotnet/roslyn/issues/67108: Disable edits for now - diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.Delete, GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldDeclaration), - oldDeclaration, new[] { GetDisplayName(oldDeclaration, EditKind.Delete) })); - continue; - } - var activeStatementIndices = GetOverlappingActiveStatements(oldDeclaration, oldActiveStatements); var hasActiveStatement = activeStatementIndices.Any(); @@ -2622,7 +2614,7 @@ private async Task> AnalyzeSemanticsAsync( editKind = SemanticEditKind.Delete; // Check if the declaration has been moved from one document to another. - if (newSymbol != null && !(newSymbol is IMethodSymbol newMethod && newMethod.IsPartialDefinition)) + if (newSymbol is { } and not IMethodSymbol { IsPartialDefinition: true }) { // Symbol has actually not been deleted but rather moved to another document, another partial type declaration // or replaced with an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.) @@ -2640,46 +2632,40 @@ private async Task> AnalyzeSemanticsAsync( continue; } - if (!newSymbol.IsImplicitlyDeclared) + // Ignore the delete if there is going to be an insert corresponding to the new symbol that will create an update edit. + if (HasInsertMatchingDelete(newSymbol, oldCompilation, cancellationToken)) { - // Ignore the delete. The new symbol is explicitly declared and thus there will be an insert edit that will issue a semantic update. - // Note that this could also be the case for deleting properties of records, but they will be handled when we see - // their accessors below. continue; } - if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(oldDeclaration, newSymbol.ContainingType, out var isFirst)) + if (IsDeclarationWithInitializer(oldDeclaration)) { - // Defer a constructor edit to cover the property initializer changing - DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); - - // If there was no body deleted then we are done since the compiler generated property also has no body - if (TryGetDeclarationBody(oldDeclaration) is null) - { - continue; - } - - // If there was a body, then the backing field of the property will be affected so we - // need to issue edits for the synthezied members. - // We only need to do this once though. - if (isFirst) - { - AddEditsForSynthesizedRecordMembers(newCompilation, newSymbol.ContainingType, semanticEdits, cancellationToken); - } + // Note initializer deletion. + DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, isMemberWithDeletedInitializer: true); } - // If a constructor is deleted and replaced by an implicit one the update needs to aggregate updates to all data member initializers, - // or if a property is deleted that is part of a records primary constructor, which is effectivelly moving from an explicit to implicit - // initializer. - if (IsConstructorWithMemberInitializers(oldDeclaration)) + if (IsConstructorWithMemberInitializers(oldSymbol, cancellationToken)) { + // If a constructor is deleted and replaced by an implicit one the update needs to aggregate updates (syntax maps) of all data member initializers. + DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, isMemberWithDeletedInitializer: false); + processedSymbols.Remove(oldSymbol); - DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); continue; } + if (oldSymbol.ContainingType.IsRecord && + newSymbol.ContainingType.IsRecord && + newSymbol is IPropertySymbol newProperty && + IsPrimaryConstructorParameterMatchingSymbol(newSymbol, cancellationToken)) + { + AnalyzeRecordPropertyReplacement((IPropertySymbol)oldSymbol, newProperty, isDeleteEdit: true); + editKind = SemanticEditKind.Update; + break; + } + // there is no insert edit for an implicit declaration, therefore we need to issue an update: editKind = SemanticEditKind.Update; + break; } else { @@ -2710,9 +2696,9 @@ private async Task> AnalyzeSemanticsAsync( } // We allow deleting parameters, by issuing delete and insert edits for the old and new method - if (oldSymbol is IParameterSymbol) + if (oldSymbol is IParameterSymbol oldParameter) { - if (TryAddParameterInsertOrDeleteEdits(semanticEdits, oldSymbol.ContainingSymbol, newModel, capabilities, syntaxMap, editScript, processedSymbols, cancellationToken, out var notSupportedByRuntime)) + if (TryAddParameterInsertOrDeleteEdits(semanticEdits, oldParameter, oldModel, newModel, capabilities, syntaxMap, editScript, processedSymbols, cancellationToken, out var notSupportedByRuntime)) { continue; } @@ -2727,8 +2713,8 @@ private async Task> AnalyzeSemanticsAsync( { // Check if the symbol being deleted is a member of a type that's also being deleted. // If so, skip the member deletion and only report the containing symbol deletion. - var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken); - var newContainingSymbol = containingSymbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; + var containingTypeKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken); + var newContainingSymbol = containingTypeKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; if (newContainingSymbol == null) { continue; @@ -2736,7 +2722,42 @@ private async Task> AnalyzeSemanticsAsync( if (!hasActiveStatement && AllowsDeletion(oldSymbol)) { - AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken); + var newContainingType = (INamedTypeSymbol)newContainingSymbol; + + // If a property or field is deleted from a record the synthesized members may change + // (PrintMembers print all properties and fields, Equals and GHC compare all data members, etc.) + if (SymbolPresenceAffectsSynthesizedRecordMembers(oldSymbol)) + { + // If the deleted member has been replaced by another member (of a different kind, otherwise newSymbol would be non-null) of the same name + // we should not update record members as they will be updated by an insertion edit of the other member. + // An insert edit must exist for the other member, otherwise we would have two members in the old type of the same name but different kind (field/property). + var newMatchingSymbol = newContainingType.GetMembers(oldSymbol.Name).FirstOrDefault(m => m is IPropertySymbol or IFieldSymbol); + if (newMatchingSymbol is null) + { + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newContainingType, cancellationToken); + } + } + + if (IsDeclarationWithInitializer(oldDeclaration)) + { + DeferConstructorEdit(oldSymbol.ContainingType, newContainingType, oldDeclaration, syntaxMap, oldSymbol.IsStatic, isMemberWithDeletedInitializer: true); + } + + AddDeleteEditsForMemberAndAccessors(semanticEdits, oldSymbol, containingTypeKey, syntaxMap, partialType: null, cancellationToken); + + // Note: Delete of a constructor does not need to be deferred since it does not affect other constructors. + // We do need to handle deletion of a primary record constructor though. + if (oldSymbol.ContainingType.IsRecord && IsPrimaryConstructor(oldSymbol, cancellationToken)) + { + var oldPrimaryConstructor = (IMethodSymbol)oldSymbol; + + // Deconstructor delete: + AddDeconstructorEdits(semanticEdits, oldPrimaryConstructor, otherConstructor: null, containingTypeKey, oldCompilation, newCompilation, syntaxMap: null, processedSymbols, isParameterDelete: true, cancellationToken); + + // Synthesized method updates: + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newContainingType, cancellationToken); + } + continue; } } @@ -2747,33 +2768,18 @@ private async Task> AnalyzeSemanticsAsync( rudeEditKind, diagnosticSpan, oldDeclaration, - new[] - { - string.Format(FeaturesResources.member_kind_and_name, - GetDisplayName(oldDeclaration, EditKind.Delete), - oldSymbol.ToDisplayString(diagnosticSpan.IsEmpty ? s_fullyQualifiedMemberDisplayFormat : s_unqualifiedMemberDisplayFormat)) - })); + new[] { GetDisplayKindAndName(oldSymbol, GetDisplayName(oldDeclaration, EditKind.Delete), fullyQualify: diagnosticSpan.IsEmpty) })); continue; } } - break; - case EditKind.Insert: { Contract.ThrowIfNull(newModel); Contract.ThrowIfNull(newSymbol); Contract.ThrowIfNull(newDeclaration); - if (IsRudeEditDueToPrimaryConstructor(newSymbol, cancellationToken)) - { - // https://github.com/dotnet/roslyn/issues/67108: Disable edits for now - diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.Insert, GetDiagnosticSpan(newDeclaration, EditKind.Insert), - newDeclaration, new[] { GetDisplayName(newDeclaration, EditKind.Insert) })); - continue; - } - syntaxMap = null; editKind = SemanticEditKind.Insert; @@ -2783,53 +2789,46 @@ private async Task> AnalyzeSemanticsAsync( // Check if the declaration has been moved from one document to another. if (oldSymbol != null) { + editKind = SemanticEditKind.Update; + // Symbol has actually not been inserted but rather moved between documents or partial type declarations, // or is replacing an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.) oldContainingType = oldSymbol.ContainingType; - if (oldSymbol.IsImplicitlyDeclared) + if (oldSymbol is IPropertySymbol { ContainingType.IsRecord: true, GetMethod.IsImplicitlyDeclared: true, SetMethod.IsImplicitlyDeclared: true } oldRecordProperty && + IsPrimaryConstructorParameterMatchingSymbol(oldSymbol, cancellationToken)) { - // 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) - { - Contract.ThrowIfNull(oldDeclaration); - ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol, capabilities, cancellationToken); - - if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(newDeclaration, newContainingType, out var isFirst)) - { - // If there is no body declared we can skip it entirely because for a property accessor - // it matches what the compiler would have previously implicitly implemented. - if (TryGetDeclarationBody(newDeclaration) is null) - { - continue; - } - - // If there was a body, then the backing field of the property will be affected so we - // need to issue edits for the synthezied members. Only need to do it once. - if (isFirst) - { - AddEditsForSynthesizedRecordMembers(newCompilation, newContainingType, semanticEdits, cancellationToken); - } - } + AnalyzeRecordPropertyReplacement(oldRecordProperty, (IPropertySymbol)newSymbol, isDeleteEdit: false); + break; + } - editKind = SemanticEditKind.Update; + // When a method is moved to a different declaration and its parameters are changed at the same time + // the new method symbol key will not resolve to the old one since the parameters are different. + // As a result we will report separate delete and insert rude edits. + // + // For delegates, however, the symbol key will resolve to the old type so we need to report + // rude edits here. + if (oldSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var oldDelegateInvoke } && + newSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var newDelegateInvoke }) + { + if (!ParameterTypesEquivalent(oldDelegateInvoke.Parameters, newDelegateInvoke.Parameters, exact: false)) + { + ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingParameterTypes, newSymbol, newDeclaration, cancellationToken); } } - else if (oldSymbol.DeclaringSyntaxReferences.Length == 1 && newSymbol.DeclaringSyntaxReferences.Length == 1) + + if (oldSymbol.DeclaringSyntaxReferences.Length == 1 && newSymbol.DeclaringSyntaxReferences.Length == 1) { Contract.ThrowIfNull(oldDeclaration); // Handles partial methods and explicitly implemented properties that implement positional parameters of records + // TODO: update, should this require declaring syntax refs? https://github.com/dotnet/roslyn/issues/68738 // We ignore partial method definition parts when processing edits (GetSymbolForEdit). // The only declaration in compilation without syntax errors that can have multiple declaring references is a type declaration. // We can therefore ignore any symbols that have more than one declaration. ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newDeclaration, newModel, ref lazyLayoutAttribute); - // Compare the old declaration syntax of the symbol with its new declaration and report rude edits - // if it changed in any way that's not allowed. - ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol, capabilities, cancellationToken); - var oldBody = TryGetDeclarationBody(oldDeclaration); if (oldBody != null) { @@ -2863,11 +2862,10 @@ 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); + var isNewConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newSymbol, cancellationToken); var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration); - var isRecordPrimaryConstructorParameter = IsRecordPrimaryConstructorParameter(oldDeclaration); - if (isNewConstructorWithMemberInitializers || isDeclarationWithInitializer || isRecordPrimaryConstructorParameter) + if (isNewConstructorWithMemberInitializers || isDeclarationWithInitializer) { if (isNewConstructorWithMemberInitializers) { @@ -2879,25 +2877,21 @@ private async Task> AnalyzeSemanticsAsync( AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, processedSymbols, cancellationToken); } - DeferConstructorEdit(oldSymbol.ContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + DeferConstructorEdit(oldSymbol.ContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, + isMemberWithDeletedInitializer: IsDeclarationWithInitializer(oldDeclaration) && !IsDeclarationWithInitializer(newDeclaration)); // Don't add a separate semantic edit. // Updates of data members with initializers and constructors that emit initializers will be aggregated and added later. continue; } - - editKind = SemanticEditKind.Update; - } - else - { - editKind = SemanticEditKind.Update; } } else if (TryGetAssociatedMemberDeclaration(newDeclaration, EditKind.Insert, out var newAssociatedMemberDeclaration) && HasEdit(editMap, newAssociatedMemberDeclaration, EditKind.Insert)) { - // If the symbol is an accessor and the containing property/indexer/event declaration has also been inserted skip + // If the symbol is an accessor and the containing property/indexer/event declaration has also been inserted // the insert of the accessor as it will be inserted by the property/indexer/event. + // Similarly for (type) parameters and their containing symbol. continue; } else if (newSymbol is ITypeParameterSymbol) @@ -2910,9 +2904,9 @@ private async Task> AnalyzeSemanticsAsync( continue; } - else if (newSymbol is IParameterSymbol) + else if (newSymbol is IParameterSymbol newParameter) { - if (!TryAddParameterInsertOrDeleteEdits(semanticEdits, newSymbol.ContainingSymbol, oldModel, capabilities, syntaxMap, editScript, processedSymbols, cancellationToken, out var notSupportedByRuntime)) + if (!TryAddParameterInsertOrDeleteEdits(semanticEdits, newParameter, newModel, oldModel, capabilities, syntaxMap, editScript, processedSymbols, cancellationToken, out var notSupportedByRuntime)) { diagnostics.Add(new RudeEditDiagnostic( notSupportedByRuntime ? RudeEditKind.InsertNotSupportedByRuntime : RudeEditKind.Insert, @@ -2954,13 +2948,11 @@ private async Task> AnalyzeSemanticsAsync( // We could compare the exact order of the members but the scenario is unlikely to occur. ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newDeclaration, newModel, ref lazyLayoutAttribute); - // If a property or field is added to a record then the implicit constructors change, - // and we need to mark a number of other synthesized members as having changed. - if (newSymbol is IPropertySymbol or IFieldSymbol && newContainingType.IsRecord) + // If a property or field is inserted into a record the synthesized members may change + // (PrintMembers print all properties and fields, Equals and GHC compare all data members, etc.) + if (SymbolPresenceAffectsSynthesizedRecordMembers(newSymbol)) { - DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); - - AddEditsForSynthesizedRecordMembers(newCompilation, newContainingType, semanticEdits, cancellationToken); + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newContainingType, cancellationToken); } } else @@ -2982,7 +2974,7 @@ private async Task> AnalyzeSemanticsAsync( ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: false); } - var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration); + var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newSymbol, cancellationToken); if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(newDeclaration)) { Contract.ThrowIfNull(newContainingType); @@ -2999,7 +2991,7 @@ private async Task> AnalyzeSemanticsAsync( break; } - DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, isMemberWithDeletedInitializer: false); if (isConstructorWithMemberInitializers) { @@ -3039,15 +3031,6 @@ private async Task> AnalyzeSemanticsAsync( Contract.ThrowIfNull(oldDeclaration); Contract.ThrowIfNull(newDeclaration); - if (IsRudeEditDueToPrimaryConstructor(oldSymbol, cancellationToken) || - IsRudeEditDueToPrimaryConstructor(newSymbol, cancellationToken)) - { - // https://github.com/dotnet/roslyn/issues/67108: Disable edits for now - diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.Update, GetDiagnosticSpan(newDeclaration, EditKind.Update), - newDeclaration, new[] { GetDisplayName(newDeclaration, EditKind.Update) })); - continue; - } - var oldBody = TryGetDeclarationBody(oldDeclaration); if (oldBody != null) { @@ -3075,7 +3058,7 @@ 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 isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newSymbol, cancellationToken); var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration); if (isConstructorWithMemberInitializers || isDeclarationWithInitializer) @@ -3090,7 +3073,8 @@ private async Task> AnalyzeSemanticsAsync( AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, processedSymbols, cancellationToken); } - DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, + isMemberWithDeletedInitializer: IsDeclarationWithInitializer(oldDeclaration) && !IsDeclarationWithInitializer(newDeclaration)); // Don't add a separate semantic edit. // Updates of data members with initializers and constructors that emit initializers will be aggregated and added later. @@ -3104,6 +3088,40 @@ private async Task> AnalyzeSemanticsAsync( throw ExceptionUtilities.UnexpectedValue(edit.Kind); } + void AnalyzeRecordPropertyReplacement(IPropertySymbol oldProperty, IPropertySymbol newProperty, bool isDeleteEdit) + { + Debug.Assert(newDeclaration != null); + Debug.Assert(oldProperty.ContainingType.IsRecord); + Debug.Assert(newProperty.ContainingType.IsRecord); + + // The synthesized auto-property is `T P { get; init; } = P`. + // If the initializer is different from `P` the primary constructor needs to be updated. + // Note: we update the constructor regardless of the initializer exact shape, but we could check for it. + DeferConstructorEdit(oldProperty.ContainingType, newProperty.ContainingType, newDeclaration: null, syntaxMap, oldProperty.IsStatic, isMemberWithDeletedInitializer: true); + + var (customProperty, synthesizedProperty) = isDeleteEdit ? (oldProperty, newProperty) : (newProperty, oldProperty); + + // new property is synthesized + Debug.Assert(synthesizedProperty.SetMethod is { IsInitOnly: true }); + + if (customProperty.SetMethod == null) + { + // Custom read-only property replaced with synthesized auto-property + if (isDeleteEdit) + { + AddInsertEditsForMemberAndAccessors(semanticEdits, synthesizedProperty.SetMethod, syntaxMap, partialType: null, processedSymbols, cancellationToken); + } + else + { + AddDeleteEditsForMemberAndAccessors(semanticEdits, synthesizedProperty.SetMethod, SymbolKey.Create(oldProperty.ContainingType, cancellationToken), syntaxMap, partialType: null, cancellationToken); + } + } + + // The synthesized property replacing the deleted one will be an auto-property. + // If the accessor had body or the property changed accessibility then synthesized record members might be affected. + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newProperty.ContainingType, cancellationToken); + } + Contract.ThrowIfFalse(editKind is SemanticEditKind.Update or SemanticEditKind.Insert); if (editKind == SemanticEditKind.Update) @@ -3172,8 +3190,8 @@ private async Task> AnalyzeSemanticsAsync( { var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken); - AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken); - AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Insert, newSymbol, containingSymbolKey: null, syntaxMap, + AddDeleteEditsForMemberAndAccessors(semanticEdits, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, cancellationToken); + AddInsertEditsForMemberAndAccessors(semanticEdits, newSymbol, syntaxMap, partialType: IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null, processedSymbols, cancellationToken); } @@ -3246,10 +3264,10 @@ private async Task> AnalyzeSemanticsAsync( var syntaxMap = isActiveMember ? CreateSyntaxMapForEquivalentNodes(oldDeclaration, newDeclaration) : null; // only trivia changed: - Contract.ThrowIfFalse(IsConstructorWithMemberInitializers(oldDeclaration) == IsConstructorWithMemberInitializers(newDeclaration)); - Contract.ThrowIfFalse(IsDeclarationWithInitializer(oldDeclaration) == IsDeclarationWithInitializer(newDeclaration)); + Debug.Assert(IsConstructorWithMemberInitializers(oldSymbol, cancellationToken) == IsConstructorWithMemberInitializers(newSymbol, cancellationToken)); + Debug.Assert(IsDeclarationWithInitializer(oldDeclaration) == IsDeclarationWithInitializer(newDeclaration)); - var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration); + var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newSymbol, cancellationToken); var isDeclarationWithInitializer = IsDeclarationWithInitializer(newDeclaration); if (isConstructorWithMemberInitializers || isDeclarationWithInitializer) @@ -3262,7 +3280,7 @@ private async Task> AnalyzeSemanticsAsync( processedSymbols.Remove(newSymbol); } - DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, isMemberWithDeletedInitializer: false); // Don't add a separate semantic edit. // Updates of data members with initializers and constructors that emit initializers will be aggregated and added later. @@ -3313,6 +3331,40 @@ private async Task> AnalyzeSemanticsAsync( diagnostics, cancellationToken); } + + // Called when a body of a constructor or an initializer of a member is updated or inserted. + // newDeclaration is the declaration node of an updated/inserted constructor or a member with an initializer, + // or null if the constructor or member has been deleted. + void DeferConstructorEdit( + INamedTypeSymbol oldType, + INamedTypeSymbol newType, + SyntaxNode? newDeclaration, + Func? syntaxMap, + bool isStatic, + bool isMemberWithDeletedInitializer) + { + Dictionary constructorEdits; + if (isStatic) + { + constructorEdits = staticConstructorEdits ??= PooledDictionary.GetInstance(); + } + else + { + constructorEdits = instanceConstructorEdits ??= PooledDictionary.GetInstance(); + } + + if (!constructorEdits.TryGetValue(newType, out var constructorEdit)) + { + constructorEdits.Add(newType, constructorEdit = new MemberInitializationUpdates(oldType)); + } + + if (newDeclaration != null && !constructorEdit.ChangedDeclarations.ContainsKey(newDeclaration)) + { + constructorEdit.ChangedDeclarations.Add(newDeclaration, syntaxMap); + } + + constructorEdit.HasDeletedMemberInitializer |= isMemberWithDeletedInitializer; + } } catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken)) { @@ -3334,91 +3386,218 @@ private async Task> AnalyzeSemanticsAsync( { return ( (oldSymbol != null && oldSymbol.DeclaringSyntaxReferences.Length == 1) ? - GetSymbolDeclarationSyntax(oldSymbol.DeclaringSyntaxReferences.Single(), cancellationToken) : oldNode, + GetSymbolDeclarationSyntax(oldSymbol, cancellationToken) : oldNode, (newSymbol != null && newSymbol.DeclaringSyntaxReferences.Length == 1) ? - GetSymbolDeclarationSyntax(newSymbol.DeclaringSyntaxReferences.Single(), cancellationToken) : newNode); + GetSymbolDeclarationSyntax(newSymbol, cancellationToken) : newNode); } } /// - /// Adds a delete and insert edit for the old and new symbols that have had a parameter inserted or deleted + /// Adds a delete and insert edit for the old and new symbols that have had a parameter inserted or deleted. + /// Not used if the symbol containing deleted/inserted parameters was itself deleted/inserted. /// - /// The symbol that contains the parameter that has been added or deleted (either IMethodSymbol or IPropertySymbol) - /// The semantic model from the old compilation, for parameter inserts, or new compilation, for deletes + /// The parameter that has been added or deleted + /// The semantic model from the compilation without the (i.e the old compilation for insert, or new compilation for deletes) /// Whether the edit should be rejected because the runtime doesn't support inserting new methods. Otherwise a normal rude edit is appropriate. /// Returns whether semantic edits were added, or if not then a rude edit should be created - private bool TryAddParameterInsertOrDeleteEdits(ArrayBuilder semanticEdits, ISymbol containingSymbol, SemanticModel? otherModel, EditAndContinueCapabilitiesGrantor capabilities, Func? syntaxMap, EditScript editScript, HashSet processedSymbols, CancellationToken cancellationToken, out bool notSupportedByRuntime) + private bool TryAddParameterInsertOrDeleteEdits( + ArrayBuilder semanticEdits, + IParameterSymbol parameterSymbol, + SemanticModel model, + SemanticModel? otherModel, + EditAndContinueCapabilitiesGrantor capabilities, + Func? syntaxMap, + EditScript editScript, + HashSet processedSymbols, + CancellationToken cancellationToken, + out bool notSupportedByRuntime) { - Debug.Assert(containingSymbol is IPropertySymbol or IMethodSymbol); + var member = parameterSymbol.ContainingSymbol; + Debug.Assert(member is IPropertySymbol or IMethodSymbol); notSupportedByRuntime = false; // Since we're inserting (or deleting) a parameter node, oldSymbol (or newSymbol) would have been null, - // and a symbolkey won't map to the other compilation because the parameters are different, so we have to go back to the edit map + // and a symbol key won't map to the other compilation because the parameters are different, so we have to go back to the edit map // to find the declaration that contains the parameter, and its partner, and then its symbol, so we need to be sure we can get // to syntax, and have a semantic model to get back to symbols. if (otherModel is null || - containingSymbol.DeclaringSyntaxReferences.Length != 1) + member.DeclaringSyntaxReferences.Length != 1) { return false; } // We can ignore parameter inserts and deletes for partial method definitions, as we'll report them on the implementation. // We return true here so no rude edit is raised. - if (containingSymbol is IMethodSymbol { IsPartialDefinition: true }) + if (member is IMethodSymbol { IsPartialDefinition: true }) { return true; } // We don't support delegate parameters - if (containingSymbol.ContainingType.IsDelegateType()) + if (member.ContainingType.IsDelegateType()) { return false; } // Find the node that matches this declaration - SyntaxNode otherContainingNode; - var containingNode = containingSymbol.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); - if (editScript.Match.TryGetOldNode(containingNode, out var oldNode)) + SyntaxNode otherContainingDeclaration; + bool isParameterDelete; + var containingDeclaration = GetSymbolDeclarationSyntax(member, cancellationToken); + if (editScript.Match.TryGetOldNode(containingDeclaration, out var oldNode)) { - otherContainingNode = oldNode; + otherContainingDeclaration = oldNode; + isParameterDelete = false; } - else if (editScript.Match.TryGetNewNode(containingNode, out var newNode)) + else if (editScript.Match.TryGetNewNode(containingDeclaration, out var newNode)) { - otherContainingNode = newNode; + otherContainingDeclaration = newNode; + isParameterDelete = true; } else { return false; } - var otherContainingSymbol = otherModel.GetDeclaredSymbol(otherContainingNode, cancellationToken); - if (otherContainingSymbol is null || !AllowsDeletion(otherContainingSymbol)) + // Member from the other compilation that does not have the parameter and matches the member that does. + var otherMember = GetRequiredDeclaredSymbol(otherModel, otherContainingDeclaration, cancellationToken); + Debug.Assert(otherMember is IPropertySymbol or IMethodSymbol); + + if (!AllowsDeletion(otherMember)) { return false; } // Now we can work out which is the old and which is the new, depending on which map we found // the match in - var oldSymbol = (otherContainingNode == oldNode) ? otherContainingSymbol : containingSymbol; - var newSymbol = (otherContainingNode == oldNode) ? containingSymbol : otherContainingSymbol; + var (oldContainingSymbol, newContainingSymbol) = isParameterDelete ? (member, otherMember) : (otherMember, member); - if (!CanRenameOrChangeSignature(oldSymbol, newSymbol, capabilities, cancellationToken)) + if (!CanRenameOrChangeSignature(oldContainingSymbol, newContainingSymbol, capabilities, cancellationToken)) { notSupportedByRuntime = true; return false; } - var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken); + var containingTypeKey = SymbolKey.Create(oldContainingSymbol.ContainingType, cancellationToken); + AddSynthesizedMemberEditsForParameterChange(semanticEdits, parameterSymbol, otherMember, containingTypeKey, model, otherModel, syntaxMap, processedSymbols, isParameterDelete, cancellationToken); - AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken); + AddDeleteAndInsertEditsForMemberAndAccessors(semanticEdits, oldContainingSymbol, newContainingSymbol, containingTypeKey, syntaxMap, processedSymbols, cancellationToken); + return true; + } - var symbolKey = SymbolKey.Create(newSymbol, cancellationToken); - AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Insert, newSymbol, containingSymbolKey: null, syntaxMap, - partialType: IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null, processedSymbols, - cancellationToken); + /// + /// Adds edits of synthesized members that may be affected by a change. + /// + /// Is the member in the other compilation corresponding to the member whose is being changed. + private void AddSynthesizedMemberEditsForParameterChange( + ArrayBuilder semanticEdits, + IParameterSymbol parameterSymbol, + ISymbol otherMember, + SymbolKey containingTypeKey, + SemanticModel model, + SemanticModel otherModel, + Func? syntaxMap, + HashSet processedSymbols, + bool isParameterDelete, + CancellationToken cancellationToken) + { + var member = parameterSymbol.ContainingSymbol; + Debug.Assert(member is IPropertySymbol or IMethodSymbol); - return true; + if (!member.ContainingType.IsRecord || + !otherMember.ContainingType.IsRecord || + !IsPrimaryConstructor(member, cancellationToken) || + !IsPrimaryConstructor(otherMember, cancellationToken)) + { + return; + } + + // Parameter deleted from or inserted into a primary constructor of a record type. + // + // Note that although the compiler emits auto-properties and deconstructor automatically + // given just an insert or udpate edit of the primary constructor, it will not emit the necessary deletes. + // We could only add delete edits and avoid adding updates and inserts. It would make the code somewhat simpler + // but also asymetric (delete vs insert). Adding inserts and updates to the edits explicitly also avoids + // dependency on the compiler implementation details. + var primaryConstructor = (IMethodSymbol)member; + var otherPrimaryConstructor = (IMethodSymbol)otherMember; + + // Delete/insert/update synthesized properties and their accessors. + + // If deleting a parameter from or inserting a parameter to primary constructor of a record + // that does not have a corresponding synthesized property (has a custom property of field) + // has no effect on the property. + + var synthesizedProperty = GetPropertySynthesizedForRecordPrimaryConstructorParameter(parameterSymbol); + if (synthesizedProperty != null) + { + var otherMembersOfParameterName = otherMember.ContainingType.GetMembers(parameterSymbol.Name); + if (otherMembersOfParameterName.Any(static m => m is IPropertySymbol)) + { + // Replace a synthesized auto-property with a custom implementation: + AddUpdateEditsForMemberAndAccessors(semanticEdits, synthesizedProperty, syntaxMap, partialType: null, cancellationToken); + } + else if (isParameterDelete) + { + // Delete synthesized property: + AddDeleteEditsForMemberAndAccessors(semanticEdits, synthesizedProperty, deletedSymbolContainer: containingTypeKey, syntaxMap, partialType: null, cancellationToken); + } + else + { + // Insert synthesized property: + AddInsertEditsForMemberAndAccessors(semanticEdits, synthesizedProperty, syntaxMap, partialType: null, processedSymbols, cancellationToken); + } + } + + // Deconstructor - we can add deconstructor edit for each changed parameter, they will get deduplicated. + AddDeconstructorEdits(semanticEdits, primaryConstructor, otherPrimaryConstructor, containingTypeKey, model.Compilation, otherModel.Compilation, syntaxMap, processedSymbols, isParameterDelete, cancellationToken); + + // Synthesized method updates - we can add edits for each changed parameter, they will get deduplicated. + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, otherModel.Compilation, primaryConstructor.ContainingType, cancellationToken); + } + + /// + /// Adds edits deleting/inserting deconstructor no longer matching of a record + /// and inserting/deleting deconstructor the one that maches its signature. + /// + private void AddDeconstructorEdits( + ArrayBuilder semanticEdits, + IMethodSymbol? constructor, + IMethodSymbol? otherConstructor, + SymbolKey containingTypeKey, + Compilation compilation, + Compilation otherCompilation, + Func? syntaxMap, + HashSet processedSymbols, + bool isParameterDelete, + CancellationToken cancellationToken) + { + AddEdits(constructor, otherCompilation, isParameterDelete); + AddEdits(otherConstructor, compilation, !isParameterDelete); + + void AddEdits(IMethodSymbol? constructor, Compilation otherCompilation, bool isDelete) + { + if (constructor != null && + IsPrimaryConstructor(constructor, cancellationToken) && + constructor.GetMatchingDeconstructor() is { IsImplicitlyDeclared: true } deconstructor) + { + if (SymbolKey.Create(deconstructor, cancellationToken).Resolve(otherCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol != null) + { + // Update for transition from synthesized to declared deconstructor + AddUpdateEditsForMemberAndAccessors(semanticEdits, deconstructor, syntaxMap, partialType: null, cancellationToken); + } + else if (isDelete) + { + // Delete synthesized deconstructor: + AddDeleteEditsForMemberAndAccessors(semanticEdits, deconstructor, deletedSymbolContainer: containingTypeKey, syntaxMap, partialType: null, cancellationToken); + } + else + { + // Insert synthesized deconstructor: + AddInsertEditsForMemberAndAccessors(semanticEdits, deconstructor, syntaxMap, partialType: null, processedSymbols, cancellationToken); + } + } + } } /// @@ -3463,82 +3642,155 @@ MethodKind.PropertyGet or } /// - /// Add semantic edits for the specified symbol, or the associated members of the specified symbol, - /// for example, edits for each accessor if a property symbol is passed in. + /// Add edit for the specified symbol and its accessors. /// - private static void AddMemberOrAssociatedMemberSemanticEdits(ArrayBuilder semanticEdits, SemanticEditKind editKind, ISymbol symbol, SymbolKey? containingSymbolKey, Func? syntaxMap, SymbolKey? partialType, HashSet? processedSymbols, CancellationToken cancellationToken) + private static void AddUpdateEditsForMemberAndAccessors( + ArrayBuilder semanticEdits, ISymbol symbol, Func? syntaxMap, SymbolKey? partialType, CancellationToken cancellationToken) { - Debug.Assert(symbol is IMethodSymbol or IPropertySymbol or IEventSymbol); - - // We store the containing symbol in NewSymbol of the edit for later use. - if (symbol is IMethodSymbol) + switch (symbol) { - AddEdit(symbol); + case IMethodSymbol: + AddUpdate(symbol); + break; + + case IPropertySymbol propertySymbol: + AddUpdate(propertySymbol); + AddUpdate(propertySymbol.GetMethod); + AddUpdate(propertySymbol.SetMethod); + break; + + case IEventSymbol eventSymbol: + AddUpdate(eventSymbol); + AddUpdate(eventSymbol.AddMethod); + AddUpdate(eventSymbol.RemoveMethod); + AddUpdate(eventSymbol.RaiseMethod); + break; + + default: + throw ExceptionUtilities.UnexpectedValue(symbol.Kind); } - else if (symbol is IPropertySymbol propertySymbol) - { - if (editKind == SemanticEditKind.Delete) - { - // When deleting a property, we delete the get and set method individually, because we actually just update them - // to be throwing - AddEdit(propertySymbol.GetMethod); - AddEdit(propertySymbol.SetMethod); - } - else - { - Contract.ThrowIfNull(processedSymbols); - // When inserting a new property as part of a property change however, we need to insert the entire property, so - // that the field, property and method semantics metadata tables can all be updated if/as necessary - AddEdit(propertySymbol); - if (propertySymbol.GetMethod is not null) - { - processedSymbols.Add(propertySymbol.GetMethod); - } + void AddUpdate(ISymbol? symbol) + { + if (symbol is null) + return; - if (propertySymbol.SetMethod is not null) - { - processedSymbols.Add(propertySymbol.SetMethod); - } - } + var symbolKey = SymbolKey.Create(symbol, cancellationToken); + semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null, partialType, deletedSymbolContainer: null)); } - else if (symbol is IEventSymbol eventSymbol) + } + + /// + /// Add edit for the specified symbol and its accessors. + /// + private static void AddDeleteEditsForMemberAndAccessors( + ArrayBuilder semanticEdits, ISymbol oldSymbol, SymbolKey deletedSymbolContainer, Func? syntaxMap, SymbolKey? partialType, CancellationToken cancellationToken) + { + switch (oldSymbol) { - if (editKind == SemanticEditKind.Delete) - { - AddEdit(eventSymbol.AddMethod); - AddEdit(eventSymbol.RemoveMethod); - AddEdit(eventSymbol.RaiseMethod); - } - else - { - Contract.ThrowIfNull(processedSymbols); + case IMethodSymbol: + AddDelete(oldSymbol); + break; - AddEdit(eventSymbol); - if (eventSymbol.AddMethod is not null) - { - processedSymbols.Add(eventSymbol.AddMethod); - } + case IPropertySymbol propertySymbol: + // Delete accessors individually, because we actually just update them to be throwing. + AddDelete(propertySymbol.GetMethod); + AddDelete(propertySymbol.SetMethod); + break; - if (eventSymbol.RemoveMethod is not null) - { - processedSymbols.Add(eventSymbol.RemoveMethod); - } + case IEventSymbol eventSymbol: + // Delete accessors individually, because we actually just update them to be throwing. + AddDelete(eventSymbol.AddMethod); + AddDelete(eventSymbol.RemoveMethod); + AddDelete(eventSymbol.RaiseMethod); + break; - if (eventSymbol.RaiseMethod is not null) - { - processedSymbols.Add(eventSymbol.RaiseMethod); - } - } + default: + throw ExceptionUtilities.UnexpectedValue(oldSymbol.Kind); } - void AddEdit(ISymbol? symbol) + void AddDelete(ISymbol? symbol) { if (symbol is null) return; var symbolKey = SymbolKey.Create(symbol, cancellationToken); - semanticEdits.Add(new SemanticEditInfo(editKind, symbolKey, syntaxMap, syntaxMapTree: null, partialType, deletedSymbolContainer: containingSymbolKey)); + semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Delete, symbolKey, syntaxMap, syntaxMapTree: null, partialType, deletedSymbolContainer: deletedSymbolContainer)); + } + } + + /// + /// Add edit for the specified symbol and its accessors. + /// + private static void AddInsertEditsForMemberAndAccessors( + ArrayBuilder semanticEdits, ISymbol newSymbol, Func? syntaxMap, SymbolKey? partialType, HashSet processedSymbols, CancellationToken cancellationToken) + { + switch (newSymbol) + { + case IMethodSymbol: + AddInsert(newSymbol); + break; + + case IPropertySymbol propertySymbol: + // When inserting a new property, we need to insert the entire property, so + // that the backing field (if any), property and method semantics metadata tables can all be updated if/as necessary. + AddInsert(propertySymbol); + + // Mark processed to suppress adding update edits of the accessors based on syntactic edits: + MarkProcessed(propertySymbol.GetMethod); + MarkProcessed(propertySymbol.SetMethod); + break; + + case IEventSymbol eventSymbol: + // When inserting a new event we need to insert the entire event, so + // pevent and method semantics metadata tables can all be updated if/as necessary. + AddInsert(eventSymbol); + + // Mark processed to suppress adding update edits of the accessors based on syntactic edits: + MarkProcessed(eventSymbol.AddMethod); + MarkProcessed(eventSymbol.RemoveMethod); + MarkProcessed(eventSymbol.RaiseMethod); + break; + + default: + throw ExceptionUtilities.UnexpectedValue(newSymbol.Kind); + } + + void AddInsert(ISymbol symbol) + { + var symbolKey = SymbolKey.Create(symbol, cancellationToken); + semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Insert, symbolKey, syntaxMap, syntaxMapTree: null, partialType)); + } + + void MarkProcessed(ISymbol? symbol) + { + if (symbol != null) + { + processedSymbols.Add(symbol); + } + } + } + + private static void AddDeleteAndInsertEditsForMemberAndAccessors( + ArrayBuilder semanticEdits, + ISymbol? oldSymbol, + ISymbol? newSymbol, + SymbolKey containingSymbolKey, + Func? syntaxMap, + HashSet processedSymbols, + CancellationToken cancellationToken) + { + Debug.Assert(oldSymbol is not INamedTypeSymbol); + Debug.Assert(newSymbol is not INamedTypeSymbol); + + if (oldSymbol != null) + { + AddDeleteEditsForMemberAndAccessors(semanticEdits, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, cancellationToken); + } + + if (newSymbol != null) + { + AddInsertEditsForMemberAndAccessors(semanticEdits, newSymbol, syntaxMap, partialType: null, processedSymbols, cancellationToken); } } @@ -3563,9 +3815,7 @@ void AddEdit(ISymbol? symbol) var oldRoot = oldModel.SyntaxTree.GetRoot(cancellationToken); foreach (var oldTypeDeclaration in GetTopLevelTypeDeclarations(oldRoot)) { - var oldType = (INamedTypeSymbol?)oldModel.GetDeclaredSymbol(oldTypeDeclaration, cancellationToken); - Contract.ThrowIfNull(oldType); - + var oldType = (INamedTypeSymbol)GetRequiredDeclaredSymbol(oldModel, oldTypeDeclaration, cancellationToken); if (!processedTypes.Add(oldType)) { continue; @@ -3596,9 +3846,7 @@ void AddEdit(ISymbol? symbol) var newRoot = newModel.SyntaxTree.GetRoot(cancellationToken); foreach (var newTypeDeclaration in GetTopLevelTypeDeclarations(newRoot)) { - var newType = (INamedTypeSymbol?)newModel.GetDeclaredSymbol(newTypeDeclaration, cancellationToken); - Contract.ThrowIfNull(newType); - + var newType = (INamedTypeSymbol)GetRequiredDeclaredSymbol(newModel, newTypeDeclaration, cancellationToken); if (!processedTypes.Add(newType)) { continue; @@ -4237,7 +4485,7 @@ private void AnalyzeSymbolUpdate( ArrayBuilder diagnostics, ArrayBuilder semanticEdits, Func? syntaxMap, - HashSet? processedSymbols, + HashSet processedSymbols, CancellationToken cancellationToken) { // TODO: fails in VB on delegate parameter https://github.com/dotnet/roslyn/issues/53337 @@ -4256,31 +4504,32 @@ private void AnalyzeSymbolUpdate( // In VB, when the type of a custom event changes, the parameters on the add and remove handlers also change // but we can ignore them because we have already done what we need to the event declaration itself. if (newSymbol.ContainingSymbol is IMethodSymbol { AssociatedSymbol: IEventSymbol associatedSymbol } && - processedSymbols?.Contains(associatedSymbol) == true) + processedSymbols.Contains(associatedSymbol)) { return; } - AddParameterUpdateSemanticEdit(semanticEdits, (IParameterSymbol)oldSymbol, (IParameterSymbol)newSymbol, syntaxMap, reportDeleteAndInsertEdits: hasParameterTypeChange, processedSymbols, cancellationToken); + AddParameterUpdateSemanticEdit(semanticEdits, (IParameterSymbol)oldSymbol, (IParameterSymbol)newSymbol, newCompilation, syntaxMap, reportDeleteAndInsertEdits: hasParameterTypeChange, processedSymbols, cancellationToken); } else if (hasReturnTypeChange) { var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingSymbol, cancellationToken); - AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken); - AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Insert, newSymbol, containingSymbolKey: null, syntaxMap, partialType: null, processedSymbols, cancellationToken); + AddDeleteAndInsertEditsForMemberAndAccessors(semanticEdits, oldSymbol, newSymbol, containingSymbolKey, syntaxMap, processedSymbols, cancellationToken); } else if (hasAttributeChange || hasReturnTypeAttributeChange) { - AddCustomAttributeSemanticEdits(semanticEdits, oldSymbol, newSymbol, topMatch, syntaxMap, hasAttributeChange, hasReturnTypeAttributeChange, cancellationToken); + AddCustomAttributeSemanticEdits(semanticEdits, oldSymbol, newSymbol, newCompilation, topMatch, syntaxMap, processedSymbols, hasAttributeChange, hasReturnTypeAttributeChange, cancellationToken); } } - private static void AddCustomAttributeSemanticEdits( + private void AddCustomAttributeSemanticEdits( ArrayBuilder semanticEdits, ISymbol oldSymbol, ISymbol newSymbol, + Compilation newCompilation, Match topMatch, Func? syntaxMap, + HashSet processedSymbols, bool hasAttributeChange, bool hasReturnTypeAttributeChange, CancellationToken cancellationToken) @@ -4318,28 +4567,59 @@ private static void AddCustomAttributeSemanticEdits( } else if (newSymbol is IParameterSymbol newParameterSymbol) { - AddParameterUpdateSemanticEdit(semanticEdits, (IParameterSymbol)oldSymbol, newParameterSymbol, syntaxMap, reportDeleteAndInsertEdits: false, processedSymbols: null, cancellationToken); + AddParameterUpdateSemanticEdit(semanticEdits, (IParameterSymbol)oldSymbol, newParameterSymbol, newCompilation, syntaxMap, reportDeleteAndInsertEdits: false, processedSymbols, cancellationToken); } } - private static void AddParameterUpdateSemanticEdit(ArrayBuilder semanticEdits, IParameterSymbol oldParameterSymbol, IParameterSymbol newParameterSymbol, Func? syntaxMap, bool reportDeleteAndInsertEdits, HashSet? processedSymbols, CancellationToken cancellationToken) + private void AddParameterUpdateSemanticEdit( + ArrayBuilder semanticEdits, + IParameterSymbol oldParameterSymbol, + IParameterSymbol newParameterSymbol, + Compilation newCompilation, + Func? syntaxMap, + bool reportDeleteAndInsertEdits, + HashSet processedSymbols, + CancellationToken cancellationToken) { - var newContainingSymbol = newParameterSymbol.ContainingSymbol; + var newContainingMember = newParameterSymbol.ContainingSymbol; if (reportDeleteAndInsertEdits) { - var oldContainingSymbol = oldParameterSymbol.ContainingSymbol; - var containingSymbolKey = SymbolKey.Create(oldContainingSymbol.ContainingSymbol, cancellationToken); - AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldContainingSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken); - AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Insert, newContainingSymbol, containingSymbolKey: null, syntaxMap, partialType: null, processedSymbols, cancellationToken); + var oldContainingMember = oldParameterSymbol.ContainingSymbol; + var containingSymbolKey = SymbolKey.Create(oldContainingMember.ContainingSymbol, cancellationToken); + + AddDeleteAndInsertEditsForMemberAndAccessors(semanticEdits, oldContainingMember, newContainingMember, containingSymbolKey, syntaxMap, processedSymbols, cancellationToken); + + // If primary constructor was replaced with non-primary (or vice versa) we wouldn't be processing a parameter update, + // but rather a delete and insert of the constructor symbol. + Debug.Assert(IsPrimaryConstructor(oldContainingMember, cancellationToken) == IsPrimaryConstructor(newContainingMember, cancellationToken)); + + if (oldContainingMember.ContainingType.IsRecord && newContainingMember.ContainingType.IsRecord && IsPrimaryConstructor(oldContainingMember, cancellationToken)) + { + var oldPrimaryConstructor = (IMethodSymbol)oldContainingMember; + var newPrimaryConstructor = (IMethodSymbol)newContainingMember; + + // add delete and insert edits of synthesized properties: + var oldSynthesizedProperty = GetPropertySynthesizedForRecordPrimaryConstructorParameter(oldParameterSymbol); + var newSynthesizedProperty = GetPropertySynthesizedForRecordPrimaryConstructorParameter(newParameterSymbol); + AddDeleteAndInsertEditsForMemberAndAccessors(semanticEdits, oldSynthesizedProperty, newSynthesizedProperty, containingSymbolKey, syntaxMap, processedSymbols, cancellationToken); + + // add delete and insert edits of synthesized deconstructor: + var oldSynthesizedDeconstructor = oldPrimaryConstructor.GetMatchingDeconstructor(); + var newSynthesizedDeconstructor = newPrimaryConstructor.GetMatchingDeconstructor(); + AddDeleteAndInsertEditsForMemberAndAccessors(semanticEdits, oldSynthesizedDeconstructor, newSynthesizedDeconstructor, containingSymbolKey, syntaxMap, processedSymbols, cancellationToken); + + // add updates of synthesized methods: + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newContainingMember.ContainingType, cancellationToken); + } } else { - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(newContainingSymbol, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null)); + semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(newContainingMember, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null)); } // attributes applied on parameters of a delegate are applied to both Invoke and BeginInvoke methods - if (newContainingSymbol.ContainingSymbol is INamedTypeSymbol { TypeKind: TypeKind.Delegate } newContainingDelegateType) + if (newContainingMember.ContainingType is INamedTypeSymbol { TypeKind: TypeKind.Delegate } newContainingDelegateType) { Debug.Assert(reportDeleteAndInsertEdits == false); AddDelegateBeginInvokeEdit(semanticEdits, newContainingDelegateType, syntaxMap, cancellationToken); @@ -4571,7 +4851,7 @@ private bool CanAddNewMemberToExistingType(ISymbol newSymbol, EditAndContinueCap requiredCapabilities |= EditAndContinueCapabilities.AddMethodToExistingType; } - if (newSymbol is IFieldSymbol || newSymbol is IPropertySymbol { DeclaringSyntaxReferences: [var syntaxRef] } && HasBackingField(syntaxRef.GetSyntax(cancellationToken))) + if (newSymbol is IFieldSymbol || newSymbol is IPropertySymbol { DeclaringSyntaxReferences: [_] } && HasBackingField(GetSymbolDeclarationSyntax(newSymbol, cancellationToken))) { requiredCapabilities |= newSymbol.IsStatic ? EditAndContinueCapabilities.AddStaticFieldToExistingType : EditAndContinueCapabilities.AddInstanceFieldToExistingType; } @@ -4595,58 +4875,66 @@ private static bool CanUpdateMemberBody(ISymbol oldSymbol, ISymbol newSymbol, Ed return true; } - private static void AddEditsForSynthesizedRecordMembers(Compilation compilation, INamedTypeSymbol recordType, ArrayBuilder semanticEdits, CancellationToken cancellationToken) + /// + /// Adds edits for synthesized record members. + /// + private static void AddSynthesizedRecordMethodUpdatesForPropertyChange( + ArrayBuilder semanticEdits, + Compilation compilation, + INamedTypeSymbol recordType, + CancellationToken cancellationToken) { - foreach (var member in GetRecordUpdatedSynthesizedMembers(compilation, recordType)) + Debug.Assert(recordType.IsRecord); + + foreach (var member in GetRecordUpdatedSynthesizedMethods(compilation, recordType)) { var symbolKey = SymbolKey.Create(member, cancellationToken); semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap: null, syntaxMapTree: null, partialType: null)); } } - private static IEnumerable GetRecordUpdatedSynthesizedMembers(Compilation compilation, INamedTypeSymbol record) + private static IEnumerable GetRecordUpdatedSynthesizedMethods(Compilation compilation, INamedTypeSymbol record) { + Debug.Assert(record.IsRecord); + // All methods that are updated have well known names, and calling GetMembers(string) is // faster than enumerating. - // When a new field or property is added the PrintMembers, Equals(R) and GetHashCode() methods are updated - // We don't need to worry about Deconstruct because it only changes when a new positional parameter - // is added, and those are rude edits (due to adding a constructor parameter). - // We don't need to worry about the constructors as they are reported elsewhere. - // We have to use SingleOrDefault and check IsImplicitlyDeclared because the user can provide their - // own implementation of these methods, and edits to them are handled by normal processing. + // When a new field or property is added methods PrintMembers, Equals, GetHashCode and the copy-constructor may be updated. + // Some updates might not be strictly necessary but for simplicity we update all that are not explicitly declared. + + // The primary constructor is deferred since it needs to be aggregated with initializer updates. + var result = record.GetMembers(WellKnownMemberNames.PrintMembersMethodName) - .OfType() - .SingleOrDefault(m => - m.IsImplicitlyDeclared && - m.Parameters.Length == 1 && - SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, compilation.GetTypeByMetadataName(typeof(StringBuilder).FullName!)) && - SymbolEqualityComparer.Default.Equals(m.ReturnType, compilation.GetTypeByMetadataName(typeof(bool).FullName!))); + .FirstOrDefault(static (m, compilation) => m is IMethodSymbol { IsImplicitlyDeclared: true } method && HasPrintMembersSignature(method, compilation), compilation); if (result is not null) { yield return result; } result = record.GetMembers(WellKnownMemberNames.ObjectEquals) - .OfType() - .SingleOrDefault(m => - m.IsImplicitlyDeclared && - m.Parameters.Length == 1 && - SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType)); + .FirstOrDefault(static m => m is IMethodSymbol { IsImplicitlyDeclared: true } method && HasIEquatableEqualsSignature(method)); if (result is not null) { yield return result; } result = record.GetMembers(WellKnownMemberNames.ObjectGetHashCode) - .OfType() - .SingleOrDefault(m => - m.IsImplicitlyDeclared && - m.Parameters.Length == 0); + .FirstOrDefault(static m => m is IMethodSymbol { IsImplicitlyDeclared: true } method && HasGetHashCodeSignature(method)); if (result is not null) { yield return result; } + + // copy constructor + if (record.TypeKind == TypeKind.Class) + { + result = record.InstanceConstructors.SingleOrDefault(m => m.IsImplicitlyDeclared && m.IsCopyConstructor()); + if (result is not null) + { + yield return result; + } + } } private void ReportDeletedMemberRudeEdit( @@ -4661,10 +4949,7 @@ private void ReportDeletedMemberRudeEdit( diagnostics.Add(new RudeEditDiagnostic( rudeEditKind, GetDiagnosticSpan(newNode, EditKind.Delete), - arguments: new[] - { - string.Format(FeaturesResources.member_kind_and_name, GetDisplayName(oldSymbol), oldSymbol.ToDisplayString(s_unqualifiedMemberDisplayFormat)) - })); + arguments: new[] { GetDisplayKindAndName(oldSymbol, fullyQualify: false) })); } private void ReportUpdateRudeEdit(ArrayBuilder diagnostics, RudeEditKind rudeEdit, SyntaxNode newNode) @@ -4692,7 +4977,14 @@ RudeEditKind.GenericMethodUpdate or RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime => new[] { CreateNewOnMetadataUpdateAttributeName }, - _ => new[] { GetDisplayName(newSymbol) } + _ => new[] + { + // Include member name if it is implicitly declared, otherwise it might not be obvious which member is being referred to. + // TODO: newSymbol.ContainingSymbol.IsImplicitlyDeclared should not be needed https://github.com/dotnet/roslyn/issues/68510 + newSymbol.IsImplicitlyDeclared || newSymbol.ContainingSymbol is { IsImplicitlyDeclared: true, Kind: not SymbolKind.Namespace } ? + GetDisplayKindAndName(newSymbol, fullyQualify: false) : + GetDisplayName(newSymbol) + } }; diagnostics.Add(new RudeEditDiagnostic(rudeEdit, span, node, arguments)); @@ -4710,14 +5002,15 @@ private void ReportUpdateRudeEdit(ArrayBuilder diagnostics, } } - private static SyntaxNode GetRudeEditDiagnosticNode(ISymbol symbol, CancellationToken cancellationToken) + private SyntaxNode GetRudeEditDiagnosticNode(ISymbol symbol, CancellationToken cancellationToken) { var container = symbol; while (container != null) { - if (container.DeclaringSyntaxReferences.Length > 0) + // TODO: only one condition should be sufficient https://github.com/dotnet/roslyn/issues/68510 + if (container.DeclaringSyntaxReferences.Length > 0 && !container.IsImplicitlyDeclared) { - return container.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); + return GetSymbolDeclarationSyntax(container, cancellationToken); } container = container.ContainingSymbol; @@ -4726,7 +5019,7 @@ private static SyntaxNode GetRudeEditDiagnosticNode(ISymbol symbol, Cancellation throw ExceptionUtilities.Unreachable(); } - private static SyntaxNode GetDeleteRudeEditDiagnosticNode(ISymbol oldSymbol, Compilation newCompilation, CancellationToken cancellationToken) + private SyntaxNode GetDeleteRudeEditDiagnosticNode(ISymbol oldSymbol, Compilation newCompilation, CancellationToken cancellationToken) { var oldContainer = oldSymbol.ContainingSymbol; while (oldContainer != null) @@ -4896,46 +5189,13 @@ private static bool HasExplicitOrSequentialLayout( #region Constructors and Initializers - /// - /// Called when a body of a constructor or an initializer of a member is updated or inserted. - /// - private static void DeferConstructorEdit( - INamedTypeSymbol oldType, - INamedTypeSymbol newType, - SyntaxNode? newDeclaration, - Func? syntaxMap, - bool isStatic, - ref PooledDictionary? instanceConstructorEdits, - ref PooledDictionary? staticConstructorEdits) - { - Dictionary constructorEdits; - if (isStatic) - { - constructorEdits = staticConstructorEdits ??= PooledDictionary.GetInstance(); - } - else - { - constructorEdits = instanceConstructorEdits ??= PooledDictionary.GetInstance(); - } - - if (!constructorEdits.TryGetValue(newType, out var constructorEdit)) - { - constructorEdits.Add(newType, constructorEdit = new ConstructorEdit(oldType)); - } - - if (newDeclaration != null && !constructorEdit.ChangedDeclarations.ContainsKey(newDeclaration)) - { - constructorEdit.ChangedDeclarations.Add(newDeclaration, syntaxMap); - } - } - private void AddConstructorEdits( - IReadOnlyDictionary updatedTypes, + IReadOnlyDictionary updatedTypes, Match topMatch, SemanticModel? oldModel, Compilation oldCompilation, Compilation newCompilation, - Roslyn.Utilities.IReadOnlySet processedSymbols, + HashSet processedSymbols, EditAndContinueCapabilitiesGrantor capabilities, bool isStatic, [Out] ArrayBuilder semanticEdits, @@ -4949,8 +5209,10 @@ private void AddConstructorEdits( { var oldType = updatesInCurrentDocument.OldType; - var anyInitializerUpdatesInCurrentDocument = updatesInCurrentDocument.ChangedDeclarations.Keys.Any(IsDeclarationWithInitializer); + var anyInitializerUpdatesInCurrentDocument = updatesInCurrentDocument.ChangedDeclarations.Keys.Any(IsDeclarationWithInitializer) || updatesInCurrentDocument.HasDeletedMemberInitializer; var isPartialEdit = IsPartialEdit(oldType, newType, oldSyntaxTree, newSyntaxTree); + var typeKey = SymbolKey.Create(newType, cancellationToken); + var partialType = isPartialEdit ? typeKey : (SymbolKey?)null; // Create a syntax map that aggregates syntax maps of the constructor body and all initializers in this document. // Use syntax maps stored in update.ChangedDeclarations and fallback to 1:1 map for unchanged members. @@ -4973,33 +5235,36 @@ private void AddConstructorEdits( continue; } + if (newType.TypeKind != oldType.TypeKind || oldType.IsRecord != newType.IsRecord) + { + // rude edit has been reported when changing type kinds + continue; + } + + // 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(newCtor, cancellationToken)) + { + continue; + } + var newCtorKey = SymbolKey.Create(newCtor, cancellationToken); var syntaxMapToUse = aggregateSyntaxMap; SyntaxNode? newDeclaration = null; - ISymbol? oldCtor; + IMethodSymbol? oldCtor; if (!newCtor.IsImplicitlyDeclared) { // Constructors have to have a single declaration syntax, they can't be partial - newDeclaration = GetSymbolDeclarationSyntax(newCtor.DeclaringSyntaxReferences.Single(), cancellationToken); - - // Implicit record constructors are represented by the record declaration itself. - var isPrimaryRecordConstructor = 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)) - { - continue; - } + newDeclaration = GetSymbolDeclarationSyntax(newCtor, cancellationToken); // 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)) + if (!anyInitializerUpdatesInCurrentDocument && !updatesInCurrentDocument.ChangedDeclarations.ContainsKey(newDeclaration)) { continue; } @@ -5007,14 +5272,14 @@ private void AddConstructorEdits( // 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, take the slow path. - if (!isPrimaryRecordConstructor && topMatch.TryGetOldNode(newDeclaration, out var oldDeclaration)) + if (topMatch.TryGetOldNode(newDeclaration, out var oldDeclaration)) { Contract.ThrowIfNull(oldModel); - oldCtor = oldModel.GetDeclaredSymbol(oldDeclaration, cancellationToken); - Contract.ThrowIfFalse(oldCtor is IMethodSymbol { MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor }); + + oldCtor = (IMethodSymbol)GetRequiredDeclaredSymbol(oldModel, oldDeclaration, cancellationToken); + Contract.ThrowIfFalse(oldCtor is { MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor }); } - else if (!isPrimaryRecordConstructor && newCtor.Parameters.Length == 0) + else if (newCtor.Parameters.Length == 0) { oldCtor = TryGetParameterlessConstructor(oldType, isStatic); } @@ -5025,7 +5290,7 @@ private void AddConstructorEdits( // There may be semantic errors in the compilation that result in multiple candidates. // Pick the first candidate. - oldCtor = resolution.Symbol; + oldCtor = (IMethodSymbol?)resolution.Symbol; } if (oldCtor == null && HasMemberInitializerContainingLambda(oldType, isStatic, ref lazyOldTypeHasMemberInitializerContainingLambda, cancellationToken)) @@ -5038,88 +5303,55 @@ private void AddConstructorEdits( // Report an error if the updated constructor's declaration is in the current document // and its body edit is disallowed (e.g. contains stackalloc). - if (oldCtor != null && newDeclaration.SyntaxTree == newSyntaxTree && anyInitializerUpdatesInCurrentDocument && !isPrimaryRecordConstructor) + if (oldCtor != null && newDeclaration.SyntaxTree == newSyntaxTree && anyInitializerUpdatesInCurrentDocument) { - // attribute rude edit to one of the modified members - var firstSpan = updatesInCurrentDocument.ChangedDeclarations.Keys.Where(IsDeclarationWithInitializer).Aggregate( - (min: int.MaxValue, span: default(TextSpan)), - (accumulate, node) => (node.SpanStart < accumulate.min) ? (node.SpanStart, node.Span) : accumulate).span; - var newBody = TryGetDeclarationBody(newDeclaration); - Contract.ThrowIfNull(newBody); - Contract.ThrowIfTrue(firstSpan.IsEmpty); - ReportMemberOrLambdaBodyUpdateRudeEditsImpl(diagnostics, newDeclaration, newBody, firstSpan); - } + // If the declaration represents a primary constructor the body will be null. + if (newBody != null) + { + // attribute rude edit to one of the modified members + var firstSpan = updatesInCurrentDocument.ChangedDeclarations.Keys.Where(IsDeclarationWithInitializer).Aggregate( + (min: int.MaxValue, span: default(TextSpan)), + (accumulate, node) => (node.SpanStart < accumulate.min) ? (node.SpanStart, node.Span) : accumulate).span; - // When explicitly implementing the copy constructor of a record the parameter name if the runtime doesn't support - // updating parameters, otherwise the debugger would show the incorrect name in the autos/locals/watch window - if (oldCtor != null && - !isPrimaryRecordConstructor && - oldCtor.DeclaringSyntaxReferences.Length == 0 && - newCtor.Parameters.Length == 1 && - newType.IsRecord && - oldCtor.GetParameters().First().Name != newCtor.GetParameters().First().Name && - !capabilities.Grant(EditAndContinueCapabilities.UpdateParameters)) - { - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, - GetDiagnosticSpan(newDeclaration, EditKind.Update), - arguments: new[] { oldCtor.ToDisplayString(SymbolDisplayFormats.NameFormat) })); + // span may be empty if the only edits are deletes of members with initializers + if (firstSpan.IsEmpty) + { + Debug.Assert(updatesInCurrentDocument.HasDeletedMemberInitializer); + firstSpan = newDeclaration.Span; + } - continue; + ReportMemberOrLambdaBodyUpdateRudeEditsImpl(diagnostics, newDeclaration, newBody, firstSpan); + } } } else { - if (newCtor.Parameters.Length == 1) - { - // New constructor is implicitly declared with a parameter, so its the copy constructor of a record - Debug.Assert(oldType.IsRecord); - Debug.Assert(newType.IsRecord); - - // We only need an edit for this if the number of properties or fields on the record has changed. Changes to - // initializers, or whether the property is part of the primary constructor, will still come through this code - // path because they need an edit to the other constructor, but not the copy construcor. - if (oldType.GetMembers().OfType().Count() == newType.GetMembers().OfType().Count() && - oldType.GetMembers().OfType().Count() == newType.GetMembers().OfType().Count()) - { - continue; - } + // New constructor contains initializers and is implicitly declared so it must be parameterless. + Debug.Assert(newCtor.Parameters.IsEmpty); - oldCtor = oldType.InstanceConstructors.Single(c => c.Parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(c.Parameters[0].Type, c.ContainingType)); - // The copy constructor does not have a syntax map - syntaxMapToUse = null; - // Since there is no syntax map, we don't need to handle anything special to merge them for partial types. - // The easiest way to do this is just to pretend this isn't a partial edit. - isPartialEdit = false; - } - else + oldCtor = TryGetParameterlessConstructor(oldType, isStatic); + + // Skip update if both old and new are implicitly declared and no initializer updates were made in current document. + // 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. + if (!anyInitializerUpdatesInCurrentDocument && oldCtor is { IsImplicitlyDeclared: true }) { - // New constructor is implicitly declared so it must be parameterless. - // - // Instance constructor: - // Its presence indicates there are no other instance constructors in the new type and therefore - // there must be a single parameterless instance constructor in the old type (constructors with parameters can't be removed). - // - // Static constructor: - // Static constructor is always parameterless and not implicitly generated if there are no static initializers. - oldCtor = TryGetParameterlessConstructor(oldType, isStatic); + continue; } - - Contract.ThrowIfFalse(isStatic || oldCtor != null); } if (oldCtor != null) { - AnalyzeSymbolUpdate(oldCtor, newCtor, newDeclaration, newCompilation, topMatch, capabilities, diagnostics, semanticEdits, syntaxMapToUse, processedSymbols: null, cancellationToken); + AnalyzeSymbolUpdate(oldCtor, newCtor, newDeclaration, newCompilation, topMatch, capabilities, diagnostics, semanticEdits, syntaxMapToUse, processedSymbols, cancellationToken); semanticEdits.Add(new SemanticEditInfo( SemanticEditKind.Update, newCtorKey, syntaxMapToUse, syntaxMapTree: isPartialEdit ? newSyntaxTree : null, - partialType: isPartialEdit ? SymbolKey.Create(newType, cancellationToken) : null)); + partialType: partialType)); } else { @@ -5130,6 +5362,41 @@ private void AddConstructorEdits( syntaxMapTree: null, partialType: null)); } + + // primary record constructor updated to non-primary and vice versa: + if (newType.IsRecord) + { + var oldCtorIsPrimary = oldCtor != null && IsPrimaryConstructor(oldCtor, cancellationToken); + var newCtorIsPrimary = IsPrimaryConstructor(newCtor, cancellationToken); + + if (oldCtorIsPrimary != newCtorIsPrimary) + { + // Deconstructor: + AddDeconstructorEdits(semanticEdits, oldCtor, newCtor, typeKey, oldCompilation, newCompilation, syntaxMap: null, processedSymbols, isParameterDelete: newCtorIsPrimary, cancellationToken); + + // Synthesized method updates: + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newCtor.ContainingType, cancellationToken); + } + } + } + + if (!isStatic && oldType.TypeKind == TypeKind.Class && newType.TypeKind == TypeKind.Class) + { + // Adding the first instance constructor with parameters suppresses synthesized default constructor. + if (oldType.HasSynthesizedDefaultConstructor() && !newType.HasSynthesizedDefaultConstructor()) + { + semanticEdits.Add(new SemanticEditInfo( + SemanticEditKind.Delete, + SymbolKey.Create(oldType.InstanceConstructors.Single(c => c.Parameters is []), cancellationToken), + syntaxMap: null, + syntaxMapTree: null, + partialType: partialType, + deletedSymbolContainer: typeKey)); + } + + // Removing the last instance constructor with parameters inserts synthesized default constructor. + // We don't need to add an explicit semantic edit for synthesized members though since the compiler + // emits them automatically. } } } @@ -5152,7 +5419,7 @@ private bool HasMemberInitializerContainingLambda(INamedTypeSymbol type, bool is (member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Property) && member.DeclaringSyntaxReferences.Length > 0) // skip generated fields (e.g. VB auto-property backing fields) { - var syntax = GetSymbolDeclarationSyntax(member.DeclaringSyntaxReferences.Single(), cancellationToken); + var syntax = GetSymbolDeclarationSyntax(member, cancellationToken); if (IsDeclarationWithInitializer(syntax) && ContainsLambda(syntax)) { return true; @@ -5163,7 +5430,7 @@ private bool HasMemberInitializerContainingLambda(INamedTypeSymbol type, bool is return false; } - private static ISymbol? TryGetParameterlessConstructor(INamedTypeSymbol type, bool isStatic) + private static IMethodSymbol? TryGetParameterlessConstructor(INamedTypeSymbol type, bool isStatic) { var oldCtors = isStatic ? type.StaticConstructors : type.InstanceConstructors; if (isStatic) @@ -5187,7 +5454,7 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) newSymbol?.Kind == SymbolKind.NamedType && newSymbol.DeclaringSyntaxReferences.Length > 1 && newSymbol.DeclaringSyntaxReferences.Any(IsNotInDocument, newSyntaxTree); } - #endregion +#endregion #region Lambdas and Closures @@ -5332,16 +5599,16 @@ select clausesByQuery.First()) // { old capture index -> old closure scope or null for "this" } using var _3 = ArrayBuilder.GetInstance(oldCaptures.Length, fillWithValue: null, out var oldCapturesToClosureScopes); - var parameterMap = ComputeParameterMap(oldDeclaration, newDeclaration); - CalculateCapturedVariablesMaps( oldCaptures, + oldMember, + oldDeclaration, oldMemberBody, newCaptures, newMember, + newDeclaration, newMemberBody, map, - parameterMap, reverseCapturesMap, newCapturesToClosureScopes, oldCapturesToClosureScopes, @@ -5680,26 +5947,35 @@ private static void BuildIndex(Dictionary index, ImmutableArray } /// - /// Returns node that represents a declaration of the symbol whose is passed in. + /// Returns node that represents a declaration of the symbol. /// - protected abstract SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference reference, CancellationToken cancellationToken); + protected abstract SyntaxNode GetSymbolDeclarationSyntax(ISymbol symbol, Func, SyntaxReference> selector, CancellationToken cancellationToken); + + protected SyntaxNode GetSymbolDeclarationSyntax(ISymbol symbol, CancellationToken cancellationToken) + => GetSymbolDeclarationSyntax(symbol, selector: System.Linq.ImmutableArrayExtensions.First, cancellationToken); + + protected abstract ISymbol? GetDeclaredSymbol(SemanticModel model, SyntaxNode declaration, CancellationToken cancellationToken); + + protected ISymbol GetRequiredDeclaredSymbol(SemanticModel model, SyntaxNode declaration, CancellationToken cancellationToken) + { + var symbol = GetDeclaredSymbol(model, declaration, cancellationToken); + Contract.ThrowIfNull(symbol); + return symbol; + } private TextSpan GetSymbolLocationSpan(ISymbol symbol, CancellationToken cancellationToken) { // Note that in VB implicit value parameter in property setter doesn't have a location. // In C# its location is the location of the setter. // See https://github.com/dotnet/roslyn/issues/14273 - return IsGlobalMain(symbol) ? GetGlobalStatementDiagnosticSpan(symbol.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), EditKind.Update) : - symbol is IParameterSymbol && IsGlobalMain(symbol.ContainingSymbol) ? GetGlobalStatementDiagnosticSpan(symbol.ContainingSymbol.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), EditKind.Update) : + return IsGlobalMain(symbol) ? GetGlobalStatementDiagnosticSpan(GetSymbolDeclarationSyntax(symbol, cancellationToken), EditKind.Update) : + symbol is IParameterSymbol && IsGlobalMain(symbol.ContainingSymbol) ? GetGlobalStatementDiagnosticSpan(GetSymbolDeclarationSyntax(symbol.ContainingSymbol, cancellationToken), EditKind.Update) : symbol.Locations.FirstOrDefault()?.SourceSpan ?? symbol.ContainingSymbol.Locations.First().SourceSpan; } - private static CapturedParameterKey GetParameterKey(IParameterSymbol parameter, CancellationToken cancellationToken) + private CapturedParameterKey GetParameterKey(IParameterSymbol parameter, CancellationToken cancellationToken) { - if (parameter.IsThis) - { - return new CapturedParameterKey(ParameterKind.This); - } + Debug.Assert(!parameter.IsThis); if (parameter.IsImplicitValueParameter()) { @@ -5712,7 +5988,7 @@ private static CapturedParameterKey GetParameterKey(IParameterSymbol parameter, } var lambda = parameter.ContainingSymbol is IMethodSymbol { MethodKind: MethodKind.LambdaMethod or MethodKind.LocalFunction } containingLambda ? - containingLambda.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken) : null; + GetSymbolDeclarationSyntax(containingLambda, cancellationToken) : null; // Indexer parameters in the getter/setter are implicitly declared. // We need to find the corresponding parameter of the indexer itself. @@ -5721,7 +5997,7 @@ private static CapturedParameterKey GetParameterKey(IParameterSymbol parameter, parameter = associatedSymbol.GetParameters().Single(p => p.Name == parameter.Name); } - return new CapturedParameterKey(ParameterKind.Explicit, parameter.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), lambda); + return new CapturedParameterKey(ParameterKind.Explicit, GetSymbolDeclarationSyntax(parameter, cancellationToken), lambda); } private static bool TryMapParameter( @@ -5761,7 +6037,6 @@ private enum ParameterKind { Explicit, Value, - This, TopLevelMainArgs } @@ -5769,12 +6044,14 @@ private enum ParameterKind private void CalculateCapturedVariablesMaps( ImmutableArray oldCaptures, + ISymbol oldMember, + SyntaxNode oldDeclaration, SyntaxNode oldMemberBody, ImmutableArray newCaptures, ISymbol newMember, + SyntaxNode newDeclaration, SyntaxNode newMemberBody, BidirectionalMap bodyMap, - BidirectionalMap? parameterMap, [Out] ArrayBuilder reverseCapturesMap, // {new capture index -> old capture index} [Out] ArrayBuilder newCapturesToClosureScopes, // {new capture index -> new closure scope} [Out] ArrayBuilder oldCapturesToClosureScopes, // {old capture index -> old closure scope} @@ -5784,6 +6061,21 @@ private void CalculateCapturedVariablesMaps( { hasErrors = false; + var parameterMap = ComputeParameterMap(oldDeclaration, newDeclaration); + + var oldEncompassingPrimaryConstructor = GetEncompassingPrimaryConstructor(oldDeclaration, oldMember, cancellationToken); + var newEncompassingPrimaryConstructor = GetEncompassingPrimaryConstructor(newDeclaration, newMember, cancellationToken); + if (oldEncompassingPrimaryConstructor != null && newEncompassingPrimaryConstructor != null) + { + var primaryParameterMap = ComputeParameterMap( + GetSymbolDeclarationSyntax(oldEncompassingPrimaryConstructor, cancellationToken), + GetSymbolDeclarationSyntax(newEncompassingPrimaryConstructor, cancellationToken)); + + Contract.ThrowIfNull(primaryParameterMap); + + parameterMap = (parameterMap != null) ? parameterMap.Value.With(primaryParameterMap.Value) : primaryParameterMap; + } + // Validate that all variables that are/were captured in the new/old body were captured in // the old/new one and their type and scope haven't changed. // @@ -5813,20 +6105,31 @@ private void CalculateCapturedVariablesMaps( // the closure tree of the previous version and then map // closure scopes in the new version to the previous ones, keeping empty closures around. - using var _1 = PooledDictionary.GetInstance(out var oldLocalCapturesBySyntax); + using var _1 = PooledDictionary.GetInstance(out var oldLocalCaptures); using var _2 = PooledDictionary.GetInstance(out var oldParameterCaptures); + IParameterSymbol? oldCapturedThisParameter = null; + IParameterSymbol? newCapturedThisParameter = null; + for (var i = 0; i < oldCaptures.Length; i++) { var oldCapture = oldCaptures[i]; - if (oldCapture.Kind == SymbolKind.Parameter) + if (oldCapture is IParameterSymbol oldParameterCapture) { - oldParameterCaptures.Add(GetParameterKey((IParameterSymbol)oldCapture, cancellationToken), i); + // Primary constructor parameter is accessed via "this" from non-primary constructor. + if (oldParameterCapture.IsThis || oldEncompassingPrimaryConstructor is null && IsPrimaryConstructor(oldCapture.ContainingSymbol, cancellationToken)) + { + oldCapturedThisParameter ??= oldParameterCapture; + } + else + { + oldParameterCaptures.Add(GetParameterKey(oldParameterCapture, cancellationToken), i); + } } else { - oldLocalCapturesBySyntax.Add(oldCapture.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), i); + oldLocalCaptures.Add(GetSymbolDeclarationSyntax(oldCapture, cancellationToken), i); } } @@ -5835,9 +6138,14 @@ private void CalculateCapturedVariablesMaps( var newCapture = newCaptures[newCaptureIndex]; int oldCaptureIndex; - if (newCapture.Kind == SymbolKind.Parameter) + if (newCapture is IParameterSymbol newParameterCapture) { - var newParameterCapture = (IParameterSymbol)newCapture; + if (newParameterCapture.IsThis || newEncompassingPrimaryConstructor is null && IsPrimaryConstructor(newCapture.ContainingSymbol, cancellationToken)) + { + newCapturedThisParameter ??= newParameterCapture; + continue; + } + var newParameterKey = GetParameterKey(newParameterCapture, cancellationToken); if (!TryMapParameter(newParameterKey, parameterMap?.Reverse, bodyMap.Reverse, out var oldParameterKey) || !oldParameterCaptures.TryGetValue(oldParameterKey, out oldCaptureIndex)) @@ -5859,16 +6167,16 @@ private void CalculateCapturedVariablesMaps( } else { - var newCaptureSyntax = newCapture.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); + var newCaptureSyntax = GetSymbolDeclarationSyntax(newCapture, cancellationToken); // variable doesn't exists in the old method or has not been captured prior the edit: if (!bodyMap.Reverse.TryGetValue(newCaptureSyntax, out var mappedOldSyntax) || - !oldLocalCapturesBySyntax.TryGetValue(mappedOldSyntax, out oldCaptureIndex)) + !oldLocalCaptures.TryGetValue(mappedOldSyntax, out oldCaptureIndex)) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.CapturingVariable, GetSymbolLocationSpan(newCapture, cancellationToken), - null, + node: null, new[] { newCapture.Name })); hasErrors = true; @@ -5877,7 +6185,7 @@ private void CalculateCapturedVariablesMaps( // Remove the old capture so that at the end we can use this hashset // to identify old captures that don't have a corresponding capture in the new version: - oldLocalCapturesBySyntax.Remove(mappedOldSyntax); + oldLocalCaptures.Remove(mappedOldSyntax); } reverseCapturesMap[newCaptureIndex] = oldCaptureIndex; @@ -5954,7 +6262,31 @@ private void CalculateCapturedVariablesMaps( } } - // What's left in oldCapturesBySyntax are captured variables in the previous version + if (oldCapturedThisParameter is null != newCapturedThisParameter is null) + { + if (oldCapturedThisParameter is null) + { + Contract.ThrowIfNull(newCapturedThisParameter); + + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.CapturingVariable, + GetSymbolLocationSpan(newCapturedThisParameter, cancellationToken), + node: null, + new[] { newCapturedThisParameter.Name })); + } + else + { + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.NotCapturingVariable, + GetSymbolLocationSpan(newMember, cancellationToken), + node: null, + new[] { oldCapturedThisParameter.Name })); + } + + hasErrors = true; + } + + // What's left in old*Captures are captured variables in the previous version // that have no corresponding captured variables in the new version. // Report a rude edit for all such variables. @@ -5983,10 +6315,10 @@ private void CalculateCapturedVariablesMaps( hasErrors = true; } - if (oldLocalCapturesBySyntax.Count > 0) + if (oldLocalCaptures.Count > 0) { // uncaptured or deleted variables: - foreach (var entry in oldLocalCapturesBySyntax) + foreach (var entry in oldLocalCaptures) { var oldCaptureNode = entry.Key; var oldCaptureIndex = entry.Value; @@ -6100,9 +6432,9 @@ private SyntaxNode GetCapturedVariableScope(ISymbol localOrParameter, SyntaxNode var member = localOrParameter.ContainingSymbol; // lambda parameters and C# constructor parameters are lifted to their own scope: - if ((member as IMethodSymbol)?.MethodKind == MethodKind.AnonymousFunction || HasParameterClosureScope(member)) + if (member is IMethodSymbol { MethodKind: MethodKind.AnonymousFunction } || HasParameterClosureScope(member)) { - var result = localOrParameter.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); + var result = GetSymbolDeclarationSyntax(localOrParameter, cancellationToken); Debug.Assert(IsLambda(result)); return result; } @@ -6110,7 +6442,7 @@ private SyntaxNode GetCapturedVariableScope(ISymbol localOrParameter, SyntaxNode return memberBody; } - var node = localOrParameter.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken); + var node = GetSymbolDeclarationSyntax(localOrParameter, cancellationToken); while (true) { RoslynDebug.Assert(node is object); @@ -6173,7 +6505,7 @@ private void ReportMissingStateMachineAttribute( #endregion - #endregion +#endregion #region Helpers @@ -6277,6 +6609,98 @@ private bool InGenericLocalContext(SyntaxNode node, SyntaxNode containingMemberB return false; } + public IMethodSymbol? GetPrimaryConstructor(INamedTypeSymbol typeSymbol, CancellationToken cancellationToken) + => typeSymbol.InstanceConstructors.FirstOrDefault(IsPrimaryConstructor, cancellationToken); + + // TODO: should be compiler API: https://github.com/dotnet/roslyn/issues/53092 + public bool IsPrimaryConstructor(ISymbol symbol, CancellationToken cancellationToken) + => symbol is IMethodSymbol { IsStatic: false, MethodKind: MethodKind.Constructor, DeclaringSyntaxReferences: [_] } && IsPrimaryConstructorDeclaration(GetSymbolDeclarationSyntax(symbol, cancellationToken)); + + /// + /// True if is a property or a field whose name matches one of the primary constructor parameter names. + /// TODO: should be compiler API: https://github.com/dotnet/roslyn/issues/54286 + /// + public bool IsPrimaryConstructorParameterMatchingSymbol(ISymbol symbol, CancellationToken cancellationToken) + => symbol is { IsStatic: false } and (IPropertySymbol or IFieldSymbol) && + GetPrimaryConstructor(symbol.ContainingType, cancellationToken) is { } primaryCtor && + primaryCtor.Parameters.Any(static (parameter, name) => parameter.Name == name, symbol.Name); + + /// + /// Primary constructor that the participates in (if any), + /// i.e. the itself if it is a primary constructor, + /// or the primary constructor the member initializer of contributes to. + /// + public IMethodSymbol? GetEncompassingPrimaryConstructor(SyntaxNode declaration, ISymbol symbol, CancellationToken cancellationToken) + => IsPrimaryConstructorDeclaration(declaration) ? (IMethodSymbol)symbol : + IsDeclarationWithInitializer(declaration) ? symbol.ContainingType.InstanceConstructors.FirstOrDefault(IsPrimaryConstructor, cancellationToken) : + null; + + private static IPropertySymbol? GetPropertySynthesizedForRecordPrimaryConstructorParameter(IParameterSymbol parameter) + => (IPropertySymbol?)parameter.ContainingType.GetMembers(parameter.Name) + .FirstOrDefault(static m => m is IPropertySymbol { IsImplicitlyDeclared: false, GetMethod.IsImplicitlyDeclared: true, SetMethod.IsImplicitlyDeclared: true }); + + private static bool SymbolPresenceAffectsSynthesizedRecordMembers(ISymbol symbol) + => symbol is (IPropertySymbol { GetMethod: not null } or IFieldSymbol) and { IsStatic: false, ContainingType.IsRecord: true }; + + /// + /// True if a syntactic delete edit of an old symbol has a matching syntactic insert edit. + /// + /// The ld symbol has to be explicitly declared, otherwise it couldn't have been deleted via syntactic delete edit. + /// + private bool HasInsertMatchingDelete(ISymbol newSymbol, Compilation oldCompilation, CancellationToken cancellationToken) + { + if (!newSymbol.IsSynthesized()) + { + return true; + } + + // new symbol is synthesized - check if there is an insert of another symbol that triggers the synthesis + + // Primary deconstructor is synthesized based on presence of primary constructor: + if (newSymbol is IMethodSymbol { IsStatic: false, ContainingType.IsRecord: true, ReturnsVoid: true, Name: WellKnownMemberNames.DeconstructMethodName } method && + GetPrimaryConstructor(newSymbol.ContainingType, cancellationToken) is { } newPrimaryConstructor && + method.HasDeconstructorSignature(newPrimaryConstructor)) + { + var oldConstructor = SymbolKey.Create(newPrimaryConstructor, cancellationToken).Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; + + // An insert exists if the new primary constructor is explicitly declared and + // the old one doesn't exist, is synthesized, or is not a primary constructor parameter. + return !newPrimaryConstructor.IsSynthesized() && + (oldConstructor == null || oldConstructor.IsSynthesized() || !IsPrimaryConstructor(oldConstructor, cancellationToken)); + } + + // Primary property is synthesized based on presence of primary constructor parameter: + if (newSymbol is IPropertySymbol { IsStatic: false, ContainingType.IsRecord: true } && + GetPrimaryConstructor(newSymbol.ContainingType, cancellationToken)?.Parameters.FirstOrDefault( + static (parameter, name) => parameter.Name == name, newSymbol.Name) is { } newPrimaryParameter) + { + var oldParameter = SymbolKey.Create(newPrimaryParameter, cancellationToken).Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; + + // An insert exists if the new primary parameter is explicitly declared and + // the old one doesn't exist, is synthesized, or is not a primary constructor parameter. + return !newPrimaryParameter.IsSynthesized() && + (oldParameter == null || oldParameter.IsSynthesized() || !IsPrimaryConstructor(oldParameter.ContainingSymbol, cancellationToken)); + } + + // Accessor of a property is synthesized based on presence of the property: + if (newSymbol is IMethodSymbol { AssociatedSymbol: IPropertySymbol { } newProperty }) + { + // An insert exists if an insert exists for the new property + return HasInsertMatchingDelete(newProperty, oldCompilation, cancellationToken); + } + + return false; + } + + private static bool HasPrintMembersSignature(IMethodSymbol method, Compilation compilation) + => method.Parameters is [var parameter] && SymbolEqualityComparer.Default.Equals(parameter.Type, compilation.GetTypeByMetadataName(typeof(StringBuilder).FullName!)); + + private static bool HasIEquatableEqualsSignature(IMethodSymbol method) + => method.Parameters is [var parameter] && SymbolEqualityComparer.Default.Equals(parameter.Type, method.ContainingType); + + private static bool HasGetHashCodeSignature(IMethodSymbol method) + => method.Parameters is []; + #endregion #region Testing diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index 3a78223f022f6..aed9bd94ab3ae 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -133,9 +133,6 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.MemberBodyInternalError, nameof(FeaturesResources.Modifying_body_of_0_requires_restarting_the_application_due_to_internal_error_1)); AddRudeEdit(RudeEditKind.MemberBodyTooBig, nameof(FeaturesResources.Modifying_body_of_0_requires_restarting_the_application_because_the_body_has_too_many_statements)); AddRudeEdit(RudeEditKind.SourceFileTooBig, nameof(FeaturesResources.Modifying_source_file_0_requires_restarting_the_application_because_the_file_is_too_big)); - AddRudeEdit(RudeEditKind.ImplementRecordParameterAsReadOnly, nameof(FeaturesResources.Implementing_a_record_positional_parameter_0_as_read_only_requires_restarting_the_application)); - AddRudeEdit(RudeEditKind.ImplementRecordParameterWithSet, nameof(FeaturesResources.Implementing_a_record_positional_parameter_0_with_a_set_accessor_requires_restarting_the_application)); - AddRudeEdit(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, nameof(FeaturesResources.Explicitly_implemented_methods_of_records_must_have_parameter_names_that_match_the_compiler_generated_equivalent_0)); AddRudeEdit(RudeEditKind.NotSupportedByRuntime, nameof(FeaturesResources.Applying_source_changes_while_the_application_is_running_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.MakeMethodAsyncNotSupportedByRuntime, nameof(FeaturesResources.Making_a_method_asynchronous_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.MakeMethodIteratorNotSupportedByRuntime, nameof(FeaturesResources.Making_a_method_an_iterator_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime)); diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index 56b6fad495d43..d1acac4458f14 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -118,11 +118,11 @@ internal enum RudeEditKind : ushort MemberBodyTooBig = 90, // InsertIntoGenericType = 91, - ImplementRecordParameterAsReadOnly = 92, - ImplementRecordParameterWithSet = 93, + //ImplementRecordParameterAsReadOnly = 92, + //ImplementRecordParameterWithSet = 93, //AddRecordPositionalParameter = 94, //DeleteRecordPositionalParameter = 95, - ExplicitRecordMethodParameterNamesMustMatch = 96, + //ExplicitRecordMethodParameterNamesMustMatch = 96, NotSupportedByRuntime = 97, MakeMethodAsyncNotSupportedByRuntime = 98, MakeMethodIteratorNotSupportedByRuntime = 99, diff --git a/src/Features/Core/Portable/EditAndContinue/Utilities/BidirectionalMap.cs b/src/Features/Core/Portable/EditAndContinue/Utilities/BidirectionalMap.cs index d7995889d9c8c..18041a2eb972d 100644 --- a/src/Features/Core/Portable/EditAndContinue/Utilities/BidirectionalMap.cs +++ b/src/Features/Core/Portable/EditAndContinue/Utilities/BidirectionalMap.cs @@ -2,42 +2,55 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using Microsoft.CodeAnalysis.Differencing; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.EditAndContinue { internal readonly struct BidirectionalMap + where T: notnull { public readonly IReadOnlyDictionary Forward; public readonly IReadOnlyDictionary Reverse; public BidirectionalMap(IReadOnlyDictionary forward, IReadOnlyDictionary reverse) { + Contract.ThrowIfFalse(forward.Count == reverse.Count); Forward = forward; Reverse = reverse; } - public BidirectionalMap(IEnumerable> entries) + public BidirectionalMap With(BidirectionalMap map) { - var map = new Dictionary(); - var reverseMap = new Dictionary(); + if (map.Forward.Count == 0) + { + return this; + } - foreach (var entry in entries) + var count = Forward.Count + map.Forward.Count; + var forward = new Dictionary(count); + var reverse = new Dictionary(count); + + foreach (var entry in Forward) { - map.Add(entry.Key, entry.Value); - reverseMap.Add(entry.Value, entry.Key); + forward.Add(entry.Key, entry.Value); + reverse.Add(entry.Value, entry.Key); } - Forward = map; - Reverse = reverseMap; + foreach (var entry in map.Forward) + { + forward.Add(entry.Key, entry.Value); + reverse.Add(entry.Value, entry.Key); + } + + return new BidirectionalMap(forward, reverse); } + public BidirectionalMap WithMatch(Match match) + => With(BidirectionalMap.FromMatch(match)); + public static BidirectionalMap FromMatch(Match match) => new(match.Matches, match.ReverseMatches); - - public bool IsDefaultOrEmpty => Forward == null || Forward.Count == 0; } } diff --git a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs index d9eb7ecc5cfdb..b7f4cd163f618 100644 --- a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs +++ b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs @@ -8,6 +8,9 @@ using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; +using System.Threading; +using System.Linq; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.EditAndContinue { @@ -147,5 +150,31 @@ public static ManagedHotReloadDiagnostic ToHotReloadDiagnostic(this DiagnosticDa fileSpan.Path ?? "", fileSpan.Span.ToSourceSpan()); } + + public static bool IsSynthesized(this ISymbol symbol) + => symbol.IsImplicitlyDeclared || symbol.IsSynthesizedAutoProperty(); + + public static bool IsSynthesizedAutoProperty(this ISymbol property) + => property is IPropertySymbol { GetMethod.IsImplicitlyDeclared: true, SetMethod.IsImplicitlyDeclared: true }; + + public static bool HasSynthesizedDefaultConstructor(this INamedTypeSymbol type) + => !type.InstanceConstructors.Any(static c => !(c.Parameters is [] || c.ContainingType.IsRecord && c.IsCopyConstructor())); + + public static bool IsCopyConstructor(this ISymbol symbol) + => symbol is IMethodSymbol { Parameters: [var parameter] } && SymbolEqualityComparer.Default.Equals(parameter.Type, symbol.ContainingType); + + public static bool HasDeconstructorSignature(this IMethodSymbol method, IMethodSymbol constructor) + => method.Parameters.Length > 0 && + method.Parameters.Length == constructor.Parameters.Length && + method.Parameters.All( + static (param, constructor) => param.RefKind == RefKind.Out && param.Type.Equals(constructor.Parameters[param.Ordinal].Type, SymbolEqualityComparer.Default), + constructor); + + /// + /// Returns a deconstructor that matches the parameters of the given , or null if there is none. + /// + public static IMethodSymbol? GetMatchingDeconstructor(this IMethodSymbol constructor) + => (IMethodSymbol?)constructor.ContainingType.GetMembers(WellKnownMemberNames.DeconstructMethodName).FirstOrDefault( + static (symbol, constructor) => symbol is IMethodSymbol method && HasDeconstructorSignature(method, constructor), constructor); } } diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 1137eac7109a1..f396160d91fb1 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -2709,6 +2709,10 @@ Zero-width positive lookbehind assertions are typically used at the beginning of {0} '{1}' e.g. "method 'M'" + + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + code @@ -2730,15 +2734,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of and update call sites directly - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - Convert to record struct diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 46580dcad125b..63d8023f73ddf 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -700,11 +700,6 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Příklady: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - Explicitně implementované metody záznamů musí mít názvy parametrů, které odpovídají kompilátorem vygenerovanému ekvivalentu {0}. - - Extract base class... Extrahovat základní třídu... @@ -920,16 +915,6 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Implementovat přes {0} - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - Implementace parametru pozičního záznamu {0} jako jen pro čtení vyžaduje restartování aplikace. - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - Implementace parametru pozičního záznamu {0} s přístupovým objektem set vyžaduje restartování aplikace. - - Incomplete \p{X} character escape Neúplné uvození znaků \p{X} @@ -4335,6 +4320,11 @@ Když se tento specifikátor standardního formátu použije, operace formátov 'Symbol nemůže být obor názvů. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator oddělovač času diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 737a73bfd7015..2b9c1435b0ca2 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -700,11 +700,6 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Beispiele: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - Explizit implementierte Methoden von Datensätzen müssen Parameternamen aufweisen, die mit dem vom Compiler generierten Äquivalent "{0}" übereinstimmen. - - Extract base class... Basisklasse extrahieren... @@ -920,16 +915,6 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Über "{0}" implementieren - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - Das Implementieren eines Positionsparameters „{0}“ als schreibgeschützt erfordert einen Neustart der Anwendung, - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - Das Implementieren eines Datensatz-Positionsparameters „{0}“ mit einer festgelegten Zugriffsmethode erfordert einen Neustart der Anwendung. - - Incomplete \p{X} character escape Unvollständiges \p{X}-Escapezeichen. @@ -4335,6 +4320,11 @@ Bei Verwendung dieses Standardformatbezeichners wird zur Formatierung oder Analy '"symbol" kann kein Namespace sein. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator Zeittrennzeichen diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 31a3e06e4ed31..7d7d2c6dc689c 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -700,11 +700,6 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Ejemplos: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - Los métodos de registros implementados explícitamente deben tener nombres de parámetro que coincidan con el equivalente generado por el programa "{0}" - - Extract base class... Extraer clase base... @@ -920,16 +915,6 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Implementar a través de "{0}" - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - Para implementar un parámetro posicional de registro "{0}" como solo lectura se requiere reiniciar la aplicación. - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - Para implementar un parámetro posicional de registro "{0}" con un descriptor de acceso set se requiere reiniciar la aplicación. - - Incomplete \p{X} character escape Escape de carácter incompleto \p{X} @@ -4335,6 +4320,11 @@ Cuando se usa este especificador de formato estándar, la operación de formato 'símbolo' no puede ser un espacio de nombres. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator separador de la hora diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index cf8114f077aee..69d1aab0ec0c0 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -700,11 +700,6 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Exemples : Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - Les méthodes d'enregistrement implémentées explicitement doivent avoir des noms de paramètres qui correspondent à l'équivalent « {0} » généré par le compilateur. - - Extract base class... Extraire la classe de base... @@ -920,16 +915,6 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Implémenter via '{0}' - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - L’implémentation d’un paramètre positionnel « {0} » en lecture seule requiert le redémarrage de l’application, - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - L’implémentation d’un paramètre positionnel d’enregistrement « {0} » avec un accesseur set requiert le redémarrage de l’application. - - Incomplete \p{X} character escape Caractère d'échappement \p{X} incomplet @@ -4335,6 +4320,11 @@ Quand ce spécificateur de format standard est utilisé, l'opération qui consis 'symbol' ne peut pas être un espace de noms. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator séparateur d'heure diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 2b246d4449b79..c546cc7432dfb 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -700,11 +700,6 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Esempi: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - I metodi dei record implementati in modo esplicito devono avere nomi di parametro corrispondenti all'equivalente generato dal compilatore '{0}' - - Extract base class... Estrai classe di base... @@ -920,16 +915,6 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Implementa tramite '{0}' - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - Se si implementa un parametro posizionale di record '{0}' come di sola lettura, è necessario riavviare l'applicazione. - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - Se si implementa un parametro posizionale di record '{0}' con una funzione di accesso set, è necessario riavviare l'applicazione. - - Incomplete \p{X} character escape Sequenza di caratteri di escape \p{X} incompleta @@ -4335,6 +4320,11 @@ Quando si usa questo identificatore di formato standard, la formattazione o l'op 'L'elemento 'symbol' non può essere uno spazio dei nomi. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator Separatore di ora diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 92420cbac7f32..af9f866e11cad 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -700,11 +700,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 例: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - 明示的に実装されたレコードのメソッドには、コンパイラ生成と同等の '{0}' と一致するパラメーター名が必要です - - Extract base class... 基底クラスの抽出... @@ -920,16 +915,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' を通じて実装します - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - レコードの位置パラメーター '{0}' を読み取り専用として実装するには、アプリケーションを再起動する必要があります。 - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - set アクセサーを含むレコード位置パラメーター '{0}' を実装するには、アプリケーションを再起動する必要があります。 - - Incomplete \p{X} character escape 不完全な \p{X} 文字エスケープです @@ -4335,6 +4320,11 @@ When this standard format specifier is used, the formatting or parsing operation 'symbol' は名前空間にすることはできません。 + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator 時刻の区切り記号 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index b8861af78960f..fe6541815205d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -700,11 +700,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 예: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - 명시적으로 구현된 레코드 메서드에는 컴파일러에서 생성된 것과 일치하는 매개 변수 이름 '{0}'이 있어야 합니다. - - Extract base class... 기본 클래스 추출... @@ -920,16 +915,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}'을(를) 통해 구현 - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - 레코드 위치 매개 변수 '{0}'을(를) 구현하려면 읽기 전용으로 애플리케이션을 다시 시작해야 합니다. - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - 설정된 접근자를 사용하여 레코드 위치 매개 변수 '{0}'를 구현하려면 응용 프로그램을 다시 시작해야 합니다. - - Incomplete \p{X} character escape 불완전한 \p{X} 문자 이스케이프 @@ -4335,6 +4320,11 @@ When this standard format specifier is used, the formatting or parsing operation '기호'는 네임스페이스일 수 없습니다. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator 시간 구분 기호 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 66154c26f796c..bde33325f74e4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -700,11 +700,6 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Przykłady: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - Jawnie zaimplementowane metody rekordów muszą mieć nazwy parametrów pasujące do wygenerowanego odpowiednika kompilatora "{0}" - - Extract base class... Wyodrębnij klasę bazową... @@ -920,16 +915,6 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Implementuj za pomocą elementu „{0}” - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - Wdrożenie parametru pozycyjnyego rekordu „{0}" jako tylko do odczytu wymaga ponownego uruchomienia aplikacji - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - Wdrożenie parametru pozycyjnego rekordu "{0}" za pomocą ustawionej metody dostępu wymaga ponownego uruchomienia aplikacji. - - Incomplete \p{X} character escape Niekompletna sekwencja ucieczki znaku \p{X} @@ -4335,6 +4320,11 @@ Gdy jest używany ten standardowy specyfikator formatu, operacja formatowania lu 'Element „symbol” nie może być przestrzenią nazw. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator separator godziny diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index c2f18e8e9bced..0b46a6238ef19 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -700,11 +700,6 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Exemplos: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - Métodos explicitamente implementados de registros devem ter nomes de parâmetro que correspondem ao compilador gerado '{0}' equivalente - - Extract base class... Extrair a classe base... @@ -920,16 +915,6 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Implementar por meio de '{0}' - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - Implementar um parâmetro posicional de registro '{0}' como somente leitura requer reiniciar o aplicativo, - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - Implementar um parâmetro posicional de registro '{0}' com um acessador set requer a reinicialização do aplicativo. - - Incomplete \p{X} character escape Escape de caractere incompleto \p{X} @@ -4335,6 +4320,11 @@ Quando esse especificador de formato padrão é usado, a operação de análise "símbolo" não pode ser um namespace. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator separador de hora diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index c411eacf87320..6331e409a1c78 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -700,11 +700,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Примеры: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - Явно реализованные методы записей должны иметь имена параметров, соответствующие компилятору, созданному эквивалентом '{0}' - - Extract base class... Извлечь базовый класс... @@ -920,16 +915,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Реализовать через "{0}" - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - Для реализации позиционного параметра записи "{0}" в режиме только для чтения требуется перезапустить приложение. - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - Для реализации позиционного параметра записи "{0}" с заданным методом доступа требуется перезапустить приложение. - - Incomplete \p{X} character escape Незавершенная escape-последовательность \p{X} @@ -4335,6 +4320,11 @@ When this standard format specifier is used, the formatting or parsing operation '"символ" не может быть пространством имен. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator разделитель компонентов времени diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 56d91c49848e0..6f5a4ac3bbb2f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -700,11 +700,6 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Örnekler: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - Açık olarak uygulanan kayıt yöntemlerinin derleyici tarafından oluşturulan '{0}' eşdeğeri ile eşleşen parametre adlarına sahip olması gerekir - - Extract base class... Temel sınıfı ayıkla... @@ -920,16 +915,6 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' aracılığıyla uygula - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - '{0}' kayıt konum parametresinin salt okunur olarak uygulanması, uygulamanın yeniden başlatılmasını gerektirir. - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - Küme erişimcisi ile '{0}' kayıt konum parametresi uygulamak, uygulamanın yeniden başlatılmasını gerektirir. - - Incomplete \p{X} character escape Tamamlanmamış \p{X} karakter kaçış @@ -4335,6 +4320,11 @@ Bu standart biçim belirticisi kullanıldığında, biçimlendirme veya ayrışt 'symbol' bir ad alanı olamaz. + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator zaman ayırıcısı diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 1f13efbab6882..66018aa5c62b5 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -700,11 +700,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 示例: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - 记录的显示实施方法参数名必须匹配编辑器生成的等效“{0}” - - Extract base class... 提取基类... @@ -920,16 +915,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 通过“{0}”实现 - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - 将记录位置参数“{0}”实现为只读需要重启应用程序, - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - 实现带有集取值函数的记录位置参数“{0}”需要重新启动应用程序。 - - Incomplete \p{X} character escape \p{X} 字符转义不完整 @@ -4335,6 +4320,11 @@ When this standard format specifier is used, the formatting or parsing operation '“symbol” 不能为命名空间。 + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator 时间分隔符 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index e559df50b426b..87ac0571a17fe 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -700,11 +700,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 範例: Plural form when we have multiple examples to show. - - Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' - 記錄的明確實作方法,必須有參數名稱符合編譯器產生的相等 '{0}' - - Extract base class... 擷取基底類別... @@ -920,16 +915,6 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 透過 '{0}' 實作 - - Implementing a record positional parameter '{0}' as read only requires restarting the application, - 實作記錄位置參數 '{0}' 作爲唯讀需要重新啟動應用程式, - - - - Implementing a record positional parameter '{0}' with a set accessor requires restarting the application. - 使用集合存取子實作記錄位置參數 '{0}' 需要重新啟動應用程式。 - - Incomplete \p{X} character escape 不完整的 \p{X} 字元逸出 @@ -4335,6 +4320,11 @@ When this standard format specifier is used, the formatting or parsing operation 'symbol' 不可為命名空間。 + + {0} '{1}' of {2} '{3}' + {0} '{1}' of {2} '{3}' + e.g. "parameter 'T param' of method 'M'" + time separator 時間分隔符號 diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index f030c45286789..16782f5a799b3 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -4,6 +4,7 @@ Imports System.Collections.Immutable Imports System.Composition +Imports System.Reflection.Metadata Imports System.Runtime.CompilerServices Imports System.Runtime.InteropServices Imports System.Threading @@ -668,15 +669,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return Nothing End If - If primaryMatch Is Nothing Then - Return BidirectionalMap(Of SyntaxNode).FromMatch(secondaryMatch) - End If + Dim map = BidirectionalMap(Of SyntaxNode).FromMatch(If(primaryMatch, secondaryMatch)) - If secondaryMatch Is Nothing Then - Return BidirectionalMap(Of SyntaxNode).FromMatch(primaryMatch) + If primaryMatch IsNot Nothing AndAlso secondaryMatch IsNot Nothing Then + map = map.WithMatch(secondaryMatch) End If - Return New BidirectionalMap(Of SyntaxNode)(primaryMatch.Matches.Concat(secondaryMatch.Matches)) + Return map End Function Private Shared Function GetTopLevelMatch(oldNode As SyntaxNode, newNode As SyntaxNode) As Match(Of SyntaxNode) @@ -1140,11 +1139,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function - Friend Overrides Function IsRecordPrimaryConstructorParameter(declaration As SyntaxNode) As Boolean - Return False - End Function - - Friend Overrides Function IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(declaration As SyntaxNode, newContainingType As INamedTypeSymbol, ByRef isFirstAccessor As Boolean) As Boolean + Friend Overrides Function IsPrimaryConstructorDeclaration(declaration As SyntaxNode) As Boolean Return False End Function @@ -1166,8 +1161,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ''' For example, a method with a body is represented by a SubBlock/FunctionBlock while a method without a body ''' is represented by its declaration statement. ''' - Protected Overrides Function GetSymbolDeclarationSyntax(reference As SyntaxReference, cancellationToken As CancellationToken) As SyntaxNode - Dim syntax = reference.GetSyntax(cancellationToken) + Protected Overrides Function GetSymbolDeclarationSyntax(symbol As ISymbol, selector As Func(Of ImmutableArray(Of SyntaxReference), SyntaxReference), cancellationToken As CancellationToken) As SyntaxNode + Dim syntax = selector(symbol.DeclaringSyntaxReferences).GetSyntax(cancellationToken) Dim parent = syntax.Parent Select Case syntax.Kind @@ -1242,7 +1237,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue ' declarations that never have a block Case SyntaxKind.ModifiedIdentifier - Contract.ThrowIfFalse(parent.Parent.IsKind(SyntaxKind.FieldDeclaration)) + Contract.ThrowIfFalse( + parent.Parent.IsKind(SyntaxKind.FieldDeclaration) OrElse parent.Parent.IsKind(SyntaxKind.LocalDeclarationStatement)) Dim variableDeclaration = CType(parent, VariableDeclaratorSyntax) Return If(variableDeclaration.Names.Count = 1, parent, syntax) @@ -1255,8 +1251,28 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function - Friend Overrides Function IsConstructorWithMemberInitializers(declaration As SyntaxNode) As Boolean - Dim ctor = TryCast(declaration, ConstructorBlockSyntax) + Protected Overrides Function GetDeclaredSymbol(model As SemanticModel, declaration As SyntaxNode, cancellationToken As CancellationToken) As ISymbol + Return model.GetDeclaredSymbol(declaration, cancellationToken) + End Function + + Friend Overrides Function IsConstructorWithMemberInitializers(symbol As ISymbol, cancellationToken As CancellationToken) As Boolean + Dim method = TryCast(symbol, IMethodSymbol) + If method Is Nothing OrElse (method.MethodKind <> MethodKind.Constructor AndAlso method.MethodKind <> MethodKind.SharedConstructor) Then + Return False + End If + + ' static constructor has initializers: + If method.IsStatic Then + Return True + End If + + ' Default constructor has initializers unless the type is a struct. + ' Instance member initializers in a struct are not supported in VB. + If method.IsImplicitlyDeclared Then + Return method.ContainingType.TypeKind <> TypeKind.Struct + End If + + Dim ctor = TryCast(symbol.DeclaringSyntaxReferences(0).GetSyntax(cancellationToken).Parent, ConstructorBlockSyntax) If ctor Is Nothing Then Return False End If @@ -1382,15 +1398,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return OneOrMany(Of (ISymbol, ISymbol, EditKind)).Empty End If - Dim oldSymbol = oldModel.GetDeclaredSymbol(oldNode, cancellationToken) - Dim newSymbol = newModel.GetDeclaredSymbol(newNode, cancellationToken) + Dim oldSymbol = GetDeclaredSymbol(oldModel, oldNode, cancellationToken) + Dim newSymbol = GetDeclaredSymbol(newModel, newNode, cancellationToken) Return OneOrMany.Create((oldSymbol, newSymbol, editKind)) End Select Throw ExceptionUtilities.UnexpectedValue(editKind) End Function - Private Shared Function TryGetSyntaxNodesForEdit( + Private Function TryGetSyntaxNodesForEdit( editKind As EditKind, node As SyntaxNode, model As SemanticModel, @@ -1406,7 +1422,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Case SyntaxKind.VariableDeclarator Dim variableDeclarator = CType(node, VariableDeclaratorSyntax) If variableDeclarator.Names.Count > 1 Then - symbols = OneOrMany.Create(variableDeclarator.Names.SelectAsArray(Function(n) model.GetDeclaredSymbol(n, cancellationToken))) + symbols = OneOrMany.Create(variableDeclarator.Names.SelectAsArray(Function(n) GetDeclaredSymbol(model, n, cancellationToken))) Return True End If @@ -1421,7 +1437,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue symbols = OneOrMany.Create( (From declarator In field.Declarators From name In declarator.Names - Select model.GetDeclaredSymbol(name, cancellationToken)).ToImmutableArray()) + Select GetDeclaredSymbol(model, name, cancellationToken)).ToImmutableArray()) Return True End If @@ -1429,7 +1445,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select - Dim symbol = model.GetDeclaredSymbol(node, cancellationToken) + Dim symbol = GetDeclaredSymbol(model, node, cancellationToken) If symbol Is Nothing Then Return False End If