From 886773fffaa50edaea339a4bd85c80b2b7a97b5c Mon Sep 17 00:00:00 2001 From: tmat Date: Wed, 16 Aug 2023 18:22:28 -0700 Subject: [PATCH 01/14] Refactor parameter and property analysis in EnC analyzer --- SpellingExclusions.dic | 3 +- ...essaryPragmaSuppressionsCodeFixProvider.cs | 1 + .../EditAndContinue/CSharpSymbolMatcher.cs | 8 +- .../EditAndContinue/EditAndContinueTests.cs | 455 +-- .../EditAndContinue/DeltaMetadataWriter.cs | 32 +- .../Emit/EditAndContinue/SymbolChanges.cs | 114 +- .../Core/Portable/Emit/SemanticEdit.cs | 16 +- .../Portable/InternalUtilities/OneOrMany.cs | 3 + .../Portable/PEWriter/ReferenceIndexerBase.cs | 7 + .../VisualBasicSymbolMatcher.vb | 7 +- .../EditAndContinueClosureTests.vb | 2 +- .../EditAndContinue/EditAndContinueTests.vb | 736 ++++- .../ActiveStatementTests.Methods.cs | 135 +- .../EditAndContinue/ActiveStatementTests.cs | 207 +- .../Helpers/EditingTestBase.cs | 1 + .../EditAndContinue/LineEditTests.cs | 22 +- .../EditAndContinue/StatementEditingTests.cs | 59 +- .../EditAndContinue/TopLevelEditingTests.cs | 2751 +++++++++++------ .../EditAndContinueTestHelpers.cs | 12 +- .../EditAndContinue/ActiveStatementTests.vb | 19 +- .../EditAndContinue/LineEditTests.vb | 6 +- .../EditAndContinue/TopLevelEditingTests.vb | 675 +++- .../CSharpEditAndContinueAnalyzer.cs | 366 ++- .../AbstractEditAndContinueAnalyzer.cs | 1548 +++++----- .../EditAndContinueDiagnosticDescriptors.cs | 5 +- .../Portable/EditAndContinue/EditSession.cs | 23 +- .../Portable/EditAndContinue/RudeEditKind.cs | 3 +- .../EditAndContinue/SemanticEditInfo.cs | 53 +- .../EditAndContinue/Utilities/Extensions.cs | 28 +- .../Core/Portable/FeaturesResources.resx | 7 +- .../Portable/xlf/FeaturesResources.cs.xlf | 11 +- .../Portable/xlf/FeaturesResources.de.xlf | 11 +- .../Portable/xlf/FeaturesResources.es.xlf | 11 +- .../Portable/xlf/FeaturesResources.fr.xlf | 11 +- .../Portable/xlf/FeaturesResources.it.xlf | 11 +- .../Portable/xlf/FeaturesResources.ja.xlf | 11 +- .../Portable/xlf/FeaturesResources.ko.xlf | 11 +- .../Portable/xlf/FeaturesResources.pl.xlf | 11 +- .../Portable/xlf/FeaturesResources.pt-BR.xlf | 11 +- .../Portable/xlf/FeaturesResources.ru.xlf | 11 +- .../Portable/xlf/FeaturesResources.tr.xlf | 11 +- .../xlf/FeaturesResources.zh-Hans.xlf | 11 +- .../xlf/FeaturesResources.zh-Hant.xlf | 11 +- .../VisualBasicEditAndContinueAnalyzer.vb | 260 +- src/Workspaces/CoreTest/SymbolKeyTests.cs | 48 + .../SymbolKey/SymbolKey.ParameterSymbolKey.cs | 21 +- .../Compiler/Core/SymbolKey/SymbolKey.cs | 2 +- .../Core/Utilities/IDictionaryExtensions.cs | 12 + ....SignatureTypeSymbolEquivalenceComparer.cs | 4 +- .../Utilities/SymbolEquivalenceComparer.cs | 11 +- 50 files changed, 5356 insertions(+), 2449 deletions(-) diff --git a/SpellingExclusions.dic b/SpellingExclusions.dic index 1e27739a43869..44098153565f7 100644 --- a/SpellingExclusions.dic +++ b/SpellingExclusions.dic @@ -1,3 +1,4 @@ -awaitable +stackalloc +awaitable Refactorings Infos diff --git a/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessaryPragmaSuppressionsCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessaryPragmaSuppressionsCodeFixProvider.cs index 5b3d705ff6a14..e80e803decba5 100644 --- a/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessaryPragmaSuppressionsCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessaryPragmaSuppressionsCodeFixProvider.cs @@ -37,6 +37,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var syntaxFacts = context.Document.GetRequiredLanguageService(); + foreach (var diagnostic in context.Diagnostics) { // Defensive check that we are operating on the diagnostic on a pragma. diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs index b44adb0a10524..ae3f2555ef453 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs @@ -795,7 +795,10 @@ private bool AreArrayTypesEqual(ArrayTypeSymbol type, ArrayTypeSymbol other) private bool AreEventsEqual(EventSymbol @event, EventSymbol other) { Debug.Assert(StringOrdinalComparer.Equals(@event.Name, other.Name)); - return _comparer.Equals(@event.Type, other.Type); + + // Events can't be overloaded on type. + // ECMA: Within the rows owned by a given row in the TypeDef table, there shall be no duplicates based upon Name [ERROR] + return true; } private bool AreFieldsEqual(FieldSymbol field, FieldSymbol other) @@ -927,6 +930,9 @@ static bool verifyRefModifiers(ImmutableArray modifiers, RefKind private bool ArePropertiesEqual(PropertySymbol property, PropertySymbol other) { Debug.Assert(StringOrdinalComparer.Equals(property.MetadataName, other.MetadataName)); + + // Properties may be overloaded on their signature. + // ECMA: Within the rows owned by a given row in the TypeDef table, there shall be no duplicates based upon Name+Type [ERROR] return _comparer.Equals(property.Type, other.Type) && property.RefKind.Equals(other.RefKind) && property.Parameters.SequenceEqual(other.Parameters, AreParametersEqual); diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 16db8ef6a1a63..d724f9ab7f6ca 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -3007,7 +3007,7 @@ public void Property_Accessor_Update() var reader1 = md1.Reader; var readers = new[] { reader0, reader1 }; - CheckNames(readers, reader1.GetPropertyDefNames(), "P"); + CheckNames(readers, reader1.GetPropertyDefNames()); CheckNames(readers, reader1.GetMethodDefNames(), "get_P"); CheckEncLog(reader1, @@ -3015,22 +3015,22 @@ public void Property_Accessor_Update() Row(7, TableIndex.TypeRef, EditAndContinueOperation.Default), Row(8, TableIndex.TypeRef, EditAndContinueOperation.Default), Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), - Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(1, TableIndex.Property, EditAndContinueOperation.Default), - Row(2, TableIndex.MethodSemantics, EditAndContinueOperation.Default)); + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default)); CheckEncMap(reader1, Handle(7, TableIndex.TypeRef), Handle(8, TableIndex.TypeRef), Handle(1, TableIndex.MethodDef), Handle(2, TableIndex.StandAloneSig), - Handle(1, TableIndex.Property), - Handle(2, TableIndex.MethodSemantics), Handle(2, TableIndex.AssemblyRef)); } - [Fact] - public void Property_Add() + /// + /// Validate that explicitly listing property accessors in the edit list produces the same results as listing just the property. + /// + [Theory] + [CombinatorialData] + public void Property_Add(bool explicitAccessorEdits) { var source0 = @" class C @@ -3069,7 +3069,9 @@ class C var diff1 = compilation1.EmitDifference( generation0, - ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Insert, null, r1))); + explicitAccessorEdits + ? ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Insert, null, r1), SemanticEdit.Create(SemanticEditKind.Insert, null, r1.GetMethod)) + : ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Insert, null, r1))); using var md1 = diff1.GetMetadata(); var reader1 = md1.Reader; @@ -3102,8 +3104,13 @@ class C var diff2 = compilation2.EmitDifference( diff1.NextGeneration, - ImmutableArray.Create( - SemanticEdit.Create(SemanticEditKind.Insert, null, q2))); + explicitAccessorEdits + ? ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Insert, null, q2), + SemanticEdit.Create(SemanticEditKind.Insert, null, q2.GetMethod), + SemanticEdit.Create(SemanticEditKind.Insert, null, q2.SetMethod)) + : ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Insert, null, q2))); using var md2 = diff2.GetMetadata(); var reader2 = md2.Reader; @@ -3163,7 +3170,6 @@ class C { g.VerifyTypeDefNames("", "C"); g.VerifyMethodDefNames("get_P", "set_P", ".ctor"); - g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor", ".ctor", ".ctor"); }) .AddGeneration( @@ -3175,33 +3181,31 @@ class C edits: new[] { Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.get_P"), newSymbolProvider: c => c.GetMember("C")), Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.P"), newSymbolProvider: c => c.GetMember("C")), }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("get_P", "set_P"); - g.VerifyMemberRefNames(/* MissingMethodException */ ".ctor", ".ctor"); + + // Set the property name to "_deleted" + // TODO: https://github.com/dotnet/roslyn/issues/69834 g.VerifyEncLogDefinitions(new[] { Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(1, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), Row(1, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default) }); + g.VerifyEncMapDefinitions(new[] { Handle(1, TableIndex.MethodDef), Handle(2, TableIndex.MethodDef), Handle(1, TableIndex.Param), Handle(1, TableIndex.CustomAttribute), - Handle(7, TableIndex.CustomAttribute), - Handle(1, TableIndex.Property), - Handle(3, TableIndex.MethodSemantics), - Handle(4, TableIndex.MethodSemantics), + Handle(7, TableIndex.CustomAttribute) }); var expectedIL = """ @@ -3230,7 +3234,6 @@ class C { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("get_P", "set_P"); - g.VerifyMemberRefNames(/* MissingMethodException */ ".ctor", ".ctor"); g.VerifyEncLogDefinitions(new[] { Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), @@ -3239,8 +3242,8 @@ class C Row(1, TableIndex.Param, EditAndContinueOperation.Default), Row(1, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default) }); g.VerifyEncMapDefinitions(new[] { @@ -3250,8 +3253,8 @@ class C Handle(1, TableIndex.CustomAttribute), Handle(7, TableIndex.CustomAttribute), Handle(1, TableIndex.Property), - Handle(5, TableIndex.MethodSemantics), - Handle(6, TableIndex.MethodSemantics), + Handle(3, TableIndex.MethodSemantics), + Handle(4, TableIndex.MethodSemantics), }); var expectedIL = """ @@ -3293,7 +3296,6 @@ class C { g.VerifyTypeDefNames("", "C"); g.VerifyMethodDefNames("get_P", "set_P", ".ctor"); - g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor"); }) .AddGeneration( @@ -3302,32 +3304,30 @@ class C { } """, - edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.get_P"), newSymbolProvider: c => c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), + edits: new[] + { + Edit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.P"), newSymbolProvider: c => c.GetMember("C")), }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("get_P", "set_P"); - g.VerifyMemberRefNames(/* MissingMethodException */ ".ctor"); + + // Set the property name to "_deleted" + // TODO: https://github.com/dotnet/roslyn/issues/69834 g.VerifyEncLogDefinitions(new[] { Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(1, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), - Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), }); g.VerifyEncMapDefinitions(new[] { Handle(1, TableIndex.MethodDef), Handle(2, TableIndex.MethodDef), Handle(1, TableIndex.Param), - Handle(1, TableIndex.Property), - Handle(3, TableIndex.MethodSemantics), - Handle(4, TableIndex.MethodSemantics), }); var expectedIL = """ @@ -3369,8 +3369,8 @@ class C Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), }); g.VerifyEncMapDefinitions(new[] { @@ -3383,8 +3383,8 @@ class C Handle(6, TableIndex.CustomAttribute), Handle(7, TableIndex.CustomAttribute), Handle(1, TableIndex.Property), - Handle(5, TableIndex.MethodSemantics), - Handle(6, TableIndex.MethodSemantics) + Handle(3, TableIndex.MethodSemantics), + Handle(4, TableIndex.MethodSemantics) }); var expectedIL = """ @@ -3426,7 +3426,6 @@ class C { g.VerifyTypeDefNames("", "C"); g.VerifyMethodDefNames("get_P", "set_P", ".ctor"); - g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor"); }) .AddGeneration( @@ -3436,31 +3435,28 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.get_P"), newSymbolProvider: c => c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.P"), newSymbolProvider: c => c.GetMember("C")), }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("get_P", "set_P"); - g.VerifyMemberRefNames(/* MissingMethodException */ ".ctor"); + + // Set the property name to "_deleted" + // TODO: https://github.com/dotnet/roslyn/issues/69834 g.VerifyEncLogDefinitions(new[] { Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(1, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), - Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), }); g.VerifyEncMapDefinitions(new[] { Handle(1, TableIndex.MethodDef), Handle(2, TableIndex.MethodDef), Handle(1, TableIndex.Param), - Handle(1, TableIndex.Property), - Handle(3, TableIndex.MethodSemantics), - Handle(4, TableIndex.MethodSemantics), }); var expectedIL = """ @@ -3482,8 +3478,11 @@ class C public string P { get { return "2"; } set { } } } """, - edits: new[] { - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.P")), + edits: new[] + { + Edit(SemanticEditKind.Insert, c => c.GetMember("C.P")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.get_P")), // the compiler does not need this edit, but the IDE adds it for simplicity + Edit(SemanticEditKind.Insert, c => c.GetMember("C.set_P")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { @@ -3497,8 +3496,8 @@ class C Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(1, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), - Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), }); g.VerifyEncMapDefinitions(new[] { @@ -3507,8 +3506,8 @@ class C Handle(1, TableIndex.Param), Handle(2, TableIndex.StandAloneSig), Handle(1, TableIndex.Property), - Handle(5, TableIndex.MethodSemantics), - Handle(6, TableIndex.MethodSemantics), + Handle(3, TableIndex.MethodSemantics), + Handle(4, TableIndex.MethodSemantics), }); var expectedIL = """ @@ -3551,7 +3550,6 @@ class C { g.VerifyTypeDefNames("", "C"); g.VerifyMethodDefNames("get_P", "set_P", ".ctor"); - g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor"); }) .AddGeneration( @@ -3561,8 +3559,9 @@ class C public string P { get { return "1"; } } } """, - edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), + edits: new[] + { + Edit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), }, validator: g => { @@ -3599,14 +3598,16 @@ class C public string P { get { return "2"; } set { } } } """, - edits: new[] { - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.P")), + edits: new[] + { + Edit(SemanticEditKind.Insert, c => c.GetMember("C.P")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.get_P")), // the compiler does not need this edit, but the IDE adds it for simplicity + Edit(SemanticEditKind.Insert, c => c.GetMember("C.set_P")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("get_P", "set_P"); - g.VerifyMemberRefNames(); g.VerifyEncLogDefinitions(new[] { Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), @@ -3676,10 +3677,14 @@ class C int P { get; set; } } """, - edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMembers("C.get_P").FirstOrDefault(m => m.GetTypeOrReturnType().SpecialType == SpecialType.System_String)?.ISymbol, newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMembers("C.set_P").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_String)?.ISymbol, newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMembers("C.P").FirstOrDefault(m => m.GetTypeOrReturnType().SpecialType == SpecialType.System_Int32)?.ISymbol), + edits: new[] + { + Edit(SemanticEditKind.Delete, c => c.GetMember("C.P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.P")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.get_P")), // the compiler does not need this edit, but the IDE adds it for simplicity + Edit(SemanticEditKind.Insert, c => c.GetMember("C.set_P")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { @@ -3697,7 +3702,6 @@ class C Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(1, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.PropertyMap, EditAndContinueOperation.AddProperty), Row(2, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), @@ -3711,8 +3715,6 @@ class C Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default) }); g.VerifyEncMapDefinitions(new[] { @@ -3729,12 +3731,9 @@ class C Handle(9, TableIndex.CustomAttribute), Handle(10, TableIndex.CustomAttribute), Handle(11, TableIndex.CustomAttribute), - Handle(1, TableIndex.Property), Handle(2, TableIndex.Property), Handle(3, TableIndex.MethodSemantics), - Handle(4, TableIndex.MethodSemantics), - Handle(5, TableIndex.MethodSemantics), - Handle(6, TableIndex.MethodSemantics) + Handle(4, TableIndex.MethodSemantics) }); var expectedIL = """ @@ -3772,9 +3771,12 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMembers("C.get_P").FirstOrDefault(m => m.GetTypeOrReturnType().SpecialType == SpecialType.System_Int32)?.ISymbol, newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMembers("C.set_P").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_Int32)?.ISymbol, newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMembers("C.P").FirstOrDefault(m => m.GetTypeOrReturnType().SpecialType == SpecialType.System_String)?.ISymbol), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.P")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.get_P")), // the compiler does not need this edit, but the IDE adds it for simplicity + Edit(SemanticEditKind.Insert, c => c.GetMember("C.set_P")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { @@ -3789,17 +3791,14 @@ class C Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(1, TableIndex.Property, EditAndContinueOperation.Default), - Row(2, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), Row(2, TableIndex.Param, EditAndContinueOperation.Default), Row(1, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(7, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(8, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(9, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(10, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default), }); g.VerifyEncMapDefinitions(new[] { @@ -3814,11 +3813,8 @@ class C Handle(10, TableIndex.CustomAttribute), Handle(11, TableIndex.CustomAttribute), Handle(1, TableIndex.Property), - Handle(2, TableIndex.Property), - Handle(7, TableIndex.MethodSemantics), - Handle(8, TableIndex.MethodSemantics), - Handle(9, TableIndex.MethodSemantics), - Handle(10, TableIndex.MethodSemantics) + Handle(5, TableIndex.MethodSemantics), + Handle(6, TableIndex.MethodSemantics), }); var expectedIL = """ @@ -3875,15 +3871,18 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.get_P"), newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.set_P"), newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.Q")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.P"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.Q")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.get_Q")), // the compiler does not need this edit, but the IDE adds it for simplicity + Edit(SemanticEditKind.Insert, c => c.GetMember("C.set_Q")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("get_P", "set_P", "get_Q", "set_Q"); - g.VerifyDeletedMembers("C: {P, get_P, set_P}"); + g.VerifyDeletedMembers("C: {get_P, set_P, P}"); g.VerifyEncLogDefinitions(new[] { @@ -3895,7 +3894,6 @@ class C Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(1, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.PropertyMap, EditAndContinueOperation.AddProperty), Row(2, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), @@ -3909,8 +3907,6 @@ class C Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default) }); g.VerifyEncMapDefinitions(new[] { @@ -3927,12 +3923,9 @@ class C Handle(9, TableIndex.CustomAttribute), Handle(10, TableIndex.CustomAttribute), Handle(11, TableIndex.CustomAttribute), - Handle(1, TableIndex.Property), Handle(2, TableIndex.Property), Handle(3, TableIndex.MethodSemantics), Handle(4, TableIndex.MethodSemantics), - Handle(5, TableIndex.MethodSemantics), - Handle(6, TableIndex.MethodSemantics) }); var expectedIL = """ @@ -3970,15 +3963,18 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.get_Q"), newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.set_Q"), newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.P")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.get_Q"), newSymbolProvider: c=>c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.set_Q"), newSymbolProvider: c=>c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.Q"), newSymbolProvider: c=>c.GetMember("C")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.P")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.get_P")), // the compiler does not need this edit, but the IDE adds it for simplicity + Edit(SemanticEditKind.Insert, c => c.GetMember("C.set_P")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("get_P", "set_P", "get_Q", "set_Q"); - g.VerifyDeletedMembers("C: {Q, get_Q, set_Q}"); + g.VerifyDeletedMembers("C: {get_Q, set_Q, Q}"); g.VerifyEncLogDefinitions(new[] { @@ -3987,17 +3983,14 @@ class C Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(1, TableIndex.Property, EditAndContinueOperation.Default), - Row(2, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), Row(2, TableIndex.Param, EditAndContinueOperation.Default), Row(1, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(7, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(8, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(9, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(10, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default), }); g.VerifyEncMapDefinitions(new[] { @@ -4012,11 +4005,8 @@ class C Handle(10, TableIndex.CustomAttribute), Handle(11, TableIndex.CustomAttribute), Handle(1, TableIndex.Property), - Handle(2, TableIndex.Property), - Handle(7, TableIndex.MethodSemantics), - Handle(8, TableIndex.MethodSemantics), - Handle(9, TableIndex.MethodSemantics), - Handle(10, TableIndex.MethodSemantics) + Handle(5, TableIndex.MethodSemantics), + Handle(6, TableIndex.MethodSemantics) }); var expectedIL = """ @@ -4064,7 +4054,6 @@ class C { g.VerifyTypeDefNames("", "C"); g.VerifyMethodDefNames("get_Item", "set_Item", ".ctor"); - g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor", ".ctor"); }) .AddGeneration( @@ -4074,24 +4063,25 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.get_Item"), newSymbolProvider: c => c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.set_Item"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.set_Item"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), newSymbolProvider: c => c.GetMember("C")), }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("get_Item", "set_Item"); - g.VerifyMemberRefNames(/* MissingMethodException */ ".ctor"); + g.VerifyDeletedMembers("C: {get_Item, set_Item, this[]}"); + + // Set the property name to "_deleted" + // TODO: https://github.com/dotnet/roslyn/issues/69834 g.VerifyEncLogDefinitions(new[] { Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(1, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), Row(2, TableIndex.Param, EditAndContinueOperation.Default), - Row(3, TableIndex.Param, EditAndContinueOperation.Default), - Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(3, TableIndex.Param, EditAndContinueOperation.Default) }); g.VerifyEncMapDefinitions(new[] { @@ -4100,9 +4090,6 @@ class C Handle(1, TableIndex.Param), Handle(2, TableIndex.Param), Handle(3, TableIndex.Param), - Handle(1, TableIndex.Property), - Handle(3, TableIndex.MethodSemantics), - Handle(4, TableIndex.MethodSemantics), }); var expectedIL = """ @@ -4144,8 +4131,10 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMembers("C.get_Item").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_Int32)?.ISymbol, newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMembers("C.this[]").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_String)?.ISymbol), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.this[]")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.get_Item")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { @@ -4159,14 +4148,12 @@ class C Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(1, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.PropertyMap, EditAndContinueOperation.AddProperty), Row(2, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), Row(3, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), Row(2, TableIndex.Param, EditAndContinueOperation.Default), Row(2, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default) }); g.VerifyEncMapDefinitions(new[] { @@ -4175,10 +4162,8 @@ class C Handle(1, TableIndex.Param), Handle(2, TableIndex.Param), Handle(2, TableIndex.StandAloneSig), - Handle(1, TableIndex.Property), Handle(2, TableIndex.Property), Handle(2, TableIndex.MethodSemantics), - Handle(3, TableIndex.MethodSemantics) }); var expectedIL = """ @@ -4211,8 +4196,10 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMembers("C.get_Item").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_String)?.ISymbol, newSymbolProvider: c=>c.GetMember("C")), - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMembers("C.this[]").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_Int32)?.ISymbol), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), newSymbolProvider: c=>c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), newSymbolProvider: c=>c.GetMember("C")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.this[]")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.get_Item")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { @@ -4226,11 +4213,9 @@ class C Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(1, TableIndex.Property, EditAndContinueOperation.Default), - Row(2, TableIndex.Property, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), Row(2, TableIndex.Param, EditAndContinueOperation.Default), - Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default) }); g.VerifyEncMapDefinitions(new[] { @@ -4240,9 +4225,7 @@ class C Handle(2, TableIndex.Param), Handle(3, TableIndex.StandAloneSig), Handle(1, TableIndex.Property), - Handle(2, TableIndex.Property), - Handle(4, TableIndex.MethodSemantics), - Handle(5, TableIndex.MethodSemantics) + Handle(3, TableIndex.MethodSemantics), }); var expectedIL = """ @@ -4266,6 +4249,128 @@ .maxstack 8 // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations g.VerifyIL(expectedIL); + }).AddGeneration( + source: $$""" + class C + { + int this[int x] { get { return 2; } } + int this[string y] { get { return 3; } } + } + """, + edits: new[] + { + Edit(SemanticEditKind.Insert, c => c.GetMembers("C.this[]").Single(p => p.Parameters is [{ Name: "y" }])), + Edit(SemanticEditKind.Insert, c => c.GetMembers("C.this[]").Single(p => p.Parameters is [{ Name: "y" }]).GetMethod), // the compiler does not need this edit, but the IDE adds it for simplicity + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("get_Item"); + g.VerifyDeletedMembers("C: {this[], get_Item}"); + + // existing property and getter are updated: + g.VerifyEncLogDefinitions(new[] + { + Row(4, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(2, TableIndex.Property, EditAndContinueOperation.Default), + Row(2, TableIndex.Param, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(3, TableIndex.MethodDef), + Handle(2, TableIndex.Param), + Handle(4, TableIndex.StandAloneSig), + Handle(2, TableIndex.Property), + Handle(4, TableIndex.MethodSemantics) + }); + + var expectedIL = """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.3 + IL_0002: stloc.0 + IL_0003: br.s IL_0005 + IL_0005: ldloc.0 + IL_0006: ret + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + }) + .Verify(); + } + + [Fact] + public void Indexer_ChangeParameterName() + { + using var _ = new EditAndContinueTest() + .AddBaseline( + source: $$""" + class C + { + int this[int x] { get => 1; set { } } + } + """, + validator: g => + { + g.VerifyTypeDefNames("", "C"); + g.VerifyMethodDefNames("get_Item", "set_Item", ".ctor"); + }) + .AddGeneration( + source: $$""" + class C + { + int this[int y] { get => 1; set { } } + } + """, + edits: new[] + { + // Both accessors must be updated, the indexer itself does not: + Edit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")), + Edit(SemanticEditKind.Update, c => c.GetMember("C.set_Item")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("get_Item", "set_Item"); + + g.VerifyEncLogDefinitions(new[] + { + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(2, TableIndex.Param, EditAndContinueOperation.Default), + Row(3, TableIndex.Param, EditAndContinueOperation.Default), + }); + + g.VerifyEncMapDefinitions(new[] + { + Handle(1, TableIndex.MethodDef), + Handle(2, TableIndex.MethodDef), + Handle(1, TableIndex.Param), + Handle(2, TableIndex.Param), + Handle(3, TableIndex.Param), + }); + + g.VerifyIL(""" + { + // Code size 2 (0x2) + .maxstack 8 + IL_0000: ldc.i4.1 + IL_0001: ret + } + { + // Code size 2 (0x2) + .maxstack 8 + IL_0000: nop + IL_0001: ret + } + """); }) .Verify(); } @@ -4424,7 +4529,6 @@ class C { g.VerifyTypeDefNames("", "C"); g.VerifyMethodDefNames("add_E", "remove_E", ".ctor"); - g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor", ".ctor", "Combine", "CompareExchange", "Remove", ".ctor"); }) .AddGeneration( @@ -4434,25 +4538,25 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.add_E"), newSymbolProvider: c => c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.remove_E"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.E"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.add_E"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.remove_E"), newSymbolProvider: c => c.GetMember("C")), }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("add_E", "remove_E"); - g.VerifyMemberRefNames(/* MissingMethodException */ ".ctor", ".ctor"); + + // Set the property name to "_deleted" + // TODO: https://github.com/dotnet/roslyn/issues/69834 g.VerifyEncLogDefinitions(new[] { - Row(1, TableIndex.Event, EditAndContinueOperation.Default), Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default), Row(2, TableIndex.Param, EditAndContinueOperation.Default), Row(1, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default) }); g.VerifyEncMapDefinitions(new[] { @@ -4462,9 +4566,6 @@ class C Handle(2, TableIndex.Param), Handle(1, TableIndex.CustomAttribute), Handle(7, TableIndex.CustomAttribute), - Handle(1, TableIndex.Event), - Handle(3, TableIndex.MethodSemantics), - Handle(4, TableIndex.MethodSemantics) }); var expectedIL = """ @@ -4497,7 +4598,6 @@ class C { g.VerifyTypeDefNames("", "C"); g.VerifyMethodDefNames("add_E", "remove_E", ".ctor"); - g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor", ".ctor", "Combine", "CompareExchange", "Remove", ".ctor"); }) .AddGeneration( @@ -4508,19 +4608,20 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.add_E"), newSymbolProvider: c => c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.remove_E"), newSymbolProvider: c => c.GetMember("C")), - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.F")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.E"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.add_E"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.remove_E"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.F")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.add_F")), // the compiler does not need this edit, but the IDE adds it for simplicity + Edit(SemanticEditKind.Insert, c => c.GetMember("C.remove_F")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("add_E", "remove_E", "add_F", "remove_F"); - g.VerifyMemberRefNames(/* MissingMethodException */ ".ctor", ".ctor", ".ctor", "Combine", "CompareExchange", "Remove"); g.VerifyEncLogDefinitions(new[] { Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), - Row(1, TableIndex.Event, EditAndContinueOperation.Default), Row(1, TableIndex.EventMap, EditAndContinueOperation.AddEvent), Row(2, TableIndex.Event, EditAndContinueOperation.Default), Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddField), @@ -4545,8 +4646,6 @@ class C Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default) }); g.VerifyEncMapDefinitions(new[] { @@ -4566,12 +4665,9 @@ class C Handle(10, TableIndex.CustomAttribute), Handle(11, TableIndex.CustomAttribute), Handle(2, TableIndex.StandAloneSig), - Handle(1, TableIndex.Event), Handle(2, TableIndex.Event), Handle(3, TableIndex.MethodSemantics), Handle(4, TableIndex.MethodSemantics), - Handle(5, TableIndex.MethodSemantics), - Handle(6, TableIndex.MethodSemantics) }); var expectedIL = """ @@ -4643,20 +4739,21 @@ class C } """, edits: new[] { - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.add_F"), newSymbolProvider: c => c.GetMember("C")), - Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.remove_F"), newSymbolProvider: c => c.GetMember("C")), - Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.E")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.F"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.add_F"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Delete, c => c.GetMember("C.remove_F"), newSymbolProvider: c => c.GetMember("C")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.E")), + Edit(SemanticEditKind.Insert, c => c.GetMember("C.add_E")), // the compiler does not need this edit, but the IDE adds it for simplicity + Edit(SemanticEditKind.Insert, c => c.GetMember("C.remove_E")), // the compiler does not need this edit, but the IDE adds it for simplicity }, validator: g => { g.VerifyTypeDefNames(); g.VerifyMethodDefNames("add_E", "remove_E", "add_F", "remove_F"); - g.VerifyMemberRefNames(/* MissingMethodException */ ".ctor", ".ctor", "Combine", "CompareExchange", "Remove", ".ctor"); g.VerifyEncLogDefinitions(new[] { Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), Row(1, TableIndex.Event, EditAndContinueOperation.Default), - Row(2, TableIndex.Event, EditAndContinueOperation.Default), Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), @@ -4669,10 +4766,8 @@ class C Row(7, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(10, TableIndex.CustomAttribute, EditAndContinueOperation.Default), Row(11, TableIndex.CustomAttribute, EditAndContinueOperation.Default), - Row(7, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(8, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(9, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(10, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default), }); g.VerifyEncMapDefinitions(new[] { @@ -4690,11 +4785,8 @@ class C Handle(11, TableIndex.CustomAttribute), Handle(3, TableIndex.StandAloneSig), Handle(1, TableIndex.Event), - Handle(2, TableIndex.Event), - Handle(7, TableIndex.MethodSemantics), - Handle(8, TableIndex.MethodSemantics), - Handle(9, TableIndex.MethodSemantics), - Handle(10, TableIndex.MethodSemantics) + Handle(5, TableIndex.MethodSemantics), + Handle(6, TableIndex.MethodSemantics), }); var expectedIL = """ @@ -6131,8 +6223,6 @@ interface J { } CheckEncLog(reader2, Row(4, TableIndex.AssemblyRef, EditAndContinueOperation.Default), Row(11, TableIndex.TypeRef, EditAndContinueOperation.Default), - Row(2, TableIndex.Event, EditAndContinueOperation.Default), - Row(3, TableIndex.Event, EditAndContinueOperation.Default), Row(1, TableIndex.Field, EditAndContinueOperation.Default), Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), @@ -6145,22 +6235,12 @@ interface J { } Row(11, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(12, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(13, TableIndex.MethodDef, EditAndContinueOperation.Default), - Row(1, TableIndex.Property, EditAndContinueOperation.Default), - Row(2, TableIndex.Property, EditAndContinueOperation.Default), Row(3, TableIndex.Param, EditAndContinueOperation.Default), Row(4, TableIndex.Param, EditAndContinueOperation.Default), Row(5, TableIndex.Param, EditAndContinueOperation.Default), Row(6, TableIndex.Param, EditAndContinueOperation.Default), Row(7, TableIndex.Param, EditAndContinueOperation.Default), - Row(8, TableIndex.Param, EditAndContinueOperation.Default), - Row(11, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(12, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(13, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(14, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(15, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(16, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(17, TableIndex.MethodSemantics, EditAndContinueOperation.Default), - Row(18, TableIndex.MethodSemantics, EditAndContinueOperation.Default)); + Row(8, TableIndex.Param, EditAndContinueOperation.Default)); diff2.VerifyIL(@" { @@ -16135,8 +16215,8 @@ void M() var compilation1 = compilation0.WithSource(new[] { source1_gen1.Tree, source0_gen1.Tree }); var c1_gen0 = compilation0.GetMember("C"); - var c1_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[0]); - var c2_gen1 = ((NamedTypeSymbol)compilation1.GetMembers("C")[1]); + var c1_gen1 = (NamedTypeSymbol)compilation1.GetMembers("C")[0]; + var c2_gen1 = (NamedTypeSymbol)compilation1.GetMembers("C")[1]; var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); @@ -16159,7 +16239,6 @@ .maxstack 1 var diff = compilation1.EmitDifference( generation0, ImmutableArray.Create( - SemanticEdit.Create(SemanticEditKind.Delete, c1_gen0, null, syntaxMap: null, preserveLocalVariables: true), SemanticEdit.Create(SemanticEditKind.Insert, null, c1_gen1, syntaxMap: null, preserveLocalVariables: true), SemanticEdit.Create(SemanticEditKind.Insert, null, c2_gen1, syntaxMap: null, preserveLocalVariables: true))); @@ -16249,8 +16328,6 @@ .maxstack 1 var diff = compilation1.EmitDifference( generation0, ImmutableArray.Create( - SemanticEdit.Create(SemanticEditKind.Delete, c1_gen0, null, syntaxMap: null, preserveLocalVariables: true), - SemanticEdit.Create(SemanticEditKind.Delete, c2_gen0, null, syntaxMap: null, preserveLocalVariables: true), SemanticEdit.Create(SemanticEditKind.Insert, null, c2_gen1, syntaxMap: null, preserveLocalVariables: true))); // There should be no diagnostics from rude edits diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs index 3c02ec1da9ff7..9268a693d4ca9 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs @@ -569,21 +569,6 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) this.AddDefIfNecessary(_eventDefs, eventDef, eventChange); } - foreach (var eventDef in _changes.GetDeletedEvents(typeDef)) - { - RoslynDebug.AssertNotNull(deletedMethodDefinitions); - - var oldEventDef = (IEventDefinition)eventDef.GetCciAdapter(); - - // Because deleted event information comes from the associated symbol of the deleted accessors, its safe - // to assume that everything will be in the dictionary. We wouldn't be here it if wasn't. - var adder = deletedMethodDefinitions[(IMethodDefinition)oldEventDef.Adder]; - var remover = deletedMethodDefinitions[(IMethodDefinition)oldEventDef.Remover]; - var caller = oldEventDef.Caller is null ? null : deletedMethodDefinitions[(IMethodDefinition)oldEventDef.Caller]; - var newEventDef = new DeletedEventDefinition(oldEventDef, adder, remover, caller, typeDef, _typesUsedByDeletedMembers); - _eventDefs.AddUpdated(newEventDef); - } - foreach (var fieldDef in typeDef.GetFields(this.Context)) { var fieldChange = _changes.GetChangeForPossibleReAddedMember(fieldDef, DefinitionExistsInAnyPreviousGeneration); @@ -619,20 +604,6 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) this.AddDefIfNecessary(_propertyDefs, propertyDef, propertyChange); } - foreach (var propertyDef in _changes.GetDeletedProperties(typeDef)) - { - RoslynDebug.AssertNotNull(deletedMethodDefinitions); - - var oldPropertyDef = (IPropertyDefinition)propertyDef.GetCciAdapter(); - - // Because deleted property information comes from the associated symbol of the deleted accessors, its safe - // to assume that everything will be in the dictionary. We wouldn't be here it if wasn't. - var getter = oldPropertyDef.Getter is null ? null : deletedMethodDefinitions[(IMethodDefinition)oldPropertyDef.Getter]; - var setter = oldPropertyDef.Setter is null ? null : deletedMethodDefinitions[(IMethodDefinition)oldPropertyDef.Setter]; - var newPropertyDef = new DeletedPropertyDefinition(oldPropertyDef, getter, setter, typeDef, _typesUsedByDeletedMembers); - _propertyDefs.AddUpdated(newPropertyDef); - } - var implementingMethods = ArrayBuilder.GetInstance(); // First, visit all MethodImplementations and add to this.methodImplList. @@ -765,8 +736,7 @@ private bool AddDefIfNecessary(DefinitionIndex defIndex, T def, SymbolChan defIndex.AddUpdated(def); return false; case SymbolChange.ContainsChanges: - Debug.Assert(def is INestedTypeDefinition); - // Changes to members within nested type only. + Debug.Assert(def is INestedTypeDefinition or IPropertyDefinition or IEventDefinition); return false; default: // No changes to member or container. diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs index fa912cd0be55a..6ded492c794d3 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs @@ -37,7 +37,7 @@ internal abstract class SymbolChanges /// keyed by the containing type from the new compilation. /// Populated based on semantic edits with . /// - private readonly IReadOnlyDictionary> _deletedMembers; + private readonly ImmutableDictionary> _deletedMembers; private readonly Func _isAddedSymbol; @@ -61,7 +61,7 @@ public ImmutableDictionary> Get continue; } - var internalSymbols = GetDeletedMemberInternalSymbols(deletedMembers, includeMethods: true, includeProperties: true, includeEvents: true); + var internalSymbols = GetDeletedMemberInternalSymbols(deletedMembers); builder.Add(typeSymbol, internalSymbols); @@ -70,7 +70,7 @@ public ImmutableDictionary> Get return builder.ToImmutable(); } - private ImmutableArray GetDeletedMemberInternalSymbols(IDefinition containingType, bool includeMethods, bool includeProperties, bool includeEvents) + private ImmutableArray GetDeletedMemberInternalSymbols(IDefinition containingType, Func? predicate = null) { var containingSymbol = containingType.GetInternalSymbol()?.GetISymbol(); if (containingSymbol is null) @@ -83,44 +83,18 @@ private ImmutableArray GetDeletedMemberInternalSymbols(IDefinit return ImmutableArray.Empty; } - return GetDeletedMemberInternalSymbols(deleted, includeMethods, includeProperties, includeEvents); + return GetDeletedMemberInternalSymbols(deleted, predicate); } - private ImmutableArray GetDeletedMemberInternalSymbols(ISet deletedMembers, bool includeMethods, bool includeProperties, bool includeEvents) + private ImmutableArray GetDeletedMemberInternalSymbols(ImmutableArray deletedMembers, Func? predicate = null) { var internalSymbols = ArrayBuilder.GetInstance(); foreach (var symbol in deletedMembers) { - if (GetISymbolInternalOrNull(symbol) is { } internalSymbol) + if ((predicate == null || predicate(symbol)) && GetISymbolInternalOrNull(symbol) is { } internalSymbol) { - if (includeProperties && - symbol is IMethodSymbol { AssociatedSymbol: IPropertySymbol propertySymbol } && - (propertySymbol.GetMethod is null || propertySymbol.GetMethod == symbol)) - { - var internalPropertySymbol = GetISymbolInternalOrNull(propertySymbol); - if (internalPropertySymbol is not null) - { - internalSymbols.Add(internalPropertySymbol); - } - } - - if (includeEvents && - symbol is IMethodSymbol { AssociatedSymbol: IEventSymbol eventSymbol } && - eventSymbol.AddMethod == symbol) - { - var internalEventSymbol = GetISymbolInternalOrNull(eventSymbol); - if (internalEventSymbol is not null) - { - internalSymbols.Add(internalEventSymbol); - } - } - - if (includeMethods && - symbol is IMethodSymbol) - { - internalSymbols.Add(internalSymbol); - } + internalSymbols.Add(internalSymbol); } } @@ -128,13 +102,7 @@ private ImmutableArray GetDeletedMemberInternalSymbols(ISet GetDeletedMethods(IDefinition containingType) - => GetDeletedMemberInternalSymbols(containingType, includeMethods: true, includeProperties: false, includeEvents: false); - - public ImmutableArray GetDeletedProperties(IDefinition containingType) - => GetDeletedMemberInternalSymbols(containingType, includeMethods: false, includeProperties: true, includeEvents: false); - - public ImmutableArray GetDeletedEvents(IDefinition containingType) - => GetDeletedMemberInternalSymbols(containingType, includeMethods: false, includeProperties: false, includeEvents: true); + => GetDeletedMemberInternalSymbols(containingType, static m => m is IMethodSymbol); public bool IsReplaced(IDefinition definition, bool checkEnclosingTypes = false) => definition.GetInternalSymbol() is { } internalSymbol && IsReplaced(internalSymbol.GetISymbol(), checkEnclosingTypes); @@ -209,7 +177,7 @@ public SymbolChange GetChange(IDefinition def) { var symbol = def.GetInternalSymbol(); - if (symbol is ISynthesizedGlobalMethodSymbol synthesizedGlobalMethod) + if (symbol is ISynthesizedGlobalMethodSymbol) { // Global methods are not reused, we always generate a new one. return SymbolChange.Added; @@ -426,11 +394,11 @@ public IEnumerable GetTopLevelSourceTypeDefinitions(Em /// Note that these changes only include user-defined source symbols, not synthesized symbols since those will be /// generated during lowering of the changed user-defined symbols. /// - private static void CalculateChanges(IEnumerable edits, out IReadOnlyDictionary changes, out ISet replaceSymbols, out IReadOnlyDictionary> deletedMembers) + private static void CalculateChanges(IEnumerable edits, out IReadOnlyDictionary changes, out ISet replaceSymbols, out ImmutableDictionary> deletedMembers) { var changesBuilder = new Dictionary(); HashSet? lazyReplaceSymbolsBuilder = null; - Dictionary>? lazyDeletedMembersBuilder = null; + Dictionary>? lazyDeletedMembersBuilder = null; foreach (var edit in edits) { @@ -453,26 +421,31 @@ private static void CalculateChanges(IEnumerable edits, out IReadO break; case SemanticEditKind.Delete: - // We allow method deletions only at the moment. + Debug.Assert(edit.OldSymbol is IMethodSymbol or IPropertySymbol or IEventSymbol); + Debug.Assert(edit.NewSymbol != null); + // For deletions NewSymbol is actually containing symbol - if (edit.OldSymbol is IMethodSymbol && edit.NewSymbol is { } newContainingSymbol) + var newContainingSymbol = edit.NewSymbol; + + lazyDeletedMembersBuilder ??= new(); + if (!lazyDeletedMembersBuilder.TryGetValue(newContainingSymbol, out var set)) + { + set = ArrayBuilder.GetInstance(); + lazyDeletedMembersBuilder.Add(newContainingSymbol, set); + } + + // edited symbols must be unique: + Debug.Assert(!set.Contains(edit.OldSymbol)); + set.Add(edit.OldSymbol); + + // We need to make sure we track the containing type of the member being + // deleted, from the new compilation, in case the deletion is the only change. + if (!changesBuilder.ContainsKey(newContainingSymbol)) { - Debug.Assert(edit.OldSymbol != null); - lazyDeletedMembersBuilder ??= new(); - if (!lazyDeletedMembersBuilder.TryGetValue(newContainingSymbol, out var set)) - { - set = new HashSet(); - lazyDeletedMembersBuilder.Add(newContainingSymbol, set); - } - set.Add(edit.OldSymbol); - // We need to make sure we track the containing type of the member being - // deleted, from the new compilation, in case the deletion is the only change. - if (!changesBuilder.ContainsKey(newContainingSymbol)) - { - changesBuilder.Add(newContainingSymbol, SymbolChange.ContainsChanges); - AddContainingTypesAndNamespaces(changesBuilder, newContainingSymbol); - } + changesBuilder.Add(newContainingSymbol, SymbolChange.ContainsChanges); + AddContainingSymbolChanges(changesBuilder, newContainingSymbol); } + continue; default: @@ -490,7 +463,7 @@ private static void CalculateChanges(IEnumerable edits, out IReadO // Partial methods should be implementations, not definitions. Debug.Assert(method.PartialImplementationPart == null); - Debug.Assert((edit.OldSymbol == null) || (((IMethodSymbol)edit.OldSymbol).PartialImplementationPart == null)); + Debug.Assert(edit.OldSymbol == null || ((IMethodSymbol)edit.OldSymbol).PartialImplementationPart == null); var definitionPart = method.PartialDefinitionPart; if (definitionPart != null) @@ -499,13 +472,12 @@ private static void CalculateChanges(IEnumerable edits, out IReadO } } - AddContainingTypesAndNamespaces(changesBuilder, member); + AddContainingSymbolChanges(changesBuilder, member); - // If we saw an edit for a symbol that is a nested type of this symbol, we might already have a dictionary entry - // for it, flagging that it contains changes. If so we "upgrade" the change to the real one. + // If we saw an edit for a symbol that is contained in the current symbol, we would have already flagged it as "containing changes". + // If so we "upgrade" the change to the one requested by semantic edit. if (changesBuilder.TryGetValue(member, out var existingChange) && existingChange == SymbolChange.ContainsChanges) { - Debug.Assert(member is INamedTypeSymbol); changesBuilder[member] = change; } else @@ -516,10 +488,13 @@ private static void CalculateChanges(IEnumerable edits, out IReadO changes = changesBuilder; replaceSymbols = lazyReplaceSymbolsBuilder ?? SpecializedCollections.EmptySet(); - deletedMembers = lazyDeletedMembersBuilder ?? SpecializedCollections.EmptyReadOnlyDictionary>(); + + deletedMembers = lazyDeletedMembersBuilder?.ToImmutableDictionary( + keySelector: static e => e.Key, + elementSelector: static e => e.Value.ToImmutableAndFree()) ?? ImmutableDictionary>.Empty; } - private static void AddContainingTypesAndNamespaces(Dictionary changes, ISymbol symbol) + private static void AddContainingSymbolChanges(Dictionary changes, ISymbol symbol) { while (true) { @@ -529,10 +504,7 @@ private static void AddContainingTypesAndNamespaces(Dictionary /// - /// The symbol from the later compilation, or null if the edit represents a deletion. + /// The symbol from the later compilation, or the symbol of the containing type + /// from the later compilation if is . /// /// /// A map from syntax node in the later compilation to syntax node in the previous compilation, @@ -103,11 +105,21 @@ public SemanticEdit(SemanticEditKind kind, ISymbol? oldSymbol, ISymbol? newSymbo throw new ArgumentNullException(nameof(oldSymbol)); } - if (newSymbol == null && kind != SemanticEditKind.Delete) + if (newSymbol == null) { throw new ArgumentNullException(nameof(newSymbol)); } + // Syntax map is only meaningful for update edits that preserve local variables. + Debug.Assert(syntaxMap == null || kind == SemanticEditKind.Update && preserveLocalVariables); + + // Partial methods should be implementations, not definitions. + Debug.Assert(oldSymbol is not IMethodSymbol { PartialImplementationPart: not null }); + Debug.Assert(newSymbol is not IMethodSymbol { PartialImplementationPart: not null }); + + // Check symbol kinds that can be deleted: + Debug.Assert(kind != SemanticEditKind.Delete || oldSymbol is IMethodSymbol or IPropertySymbol or IEventSymbol); + if (instrumentation.IsDefault) { instrumentation = MethodInstrumentation.Empty; diff --git a/src/Compilers/Core/Portable/InternalUtilities/OneOrMany.cs b/src/Compilers/Core/Portable/InternalUtilities/OneOrMany.cs index 71c9217239fe9..10c7ac2543e5f 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/OneOrMany.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/OneOrMany.cs @@ -120,6 +120,9 @@ public OneOrMany Select(Func selector, public T First() => this[0]; + public T? FirstOrDefault() + => HasOneItem ? _one : _many.FirstOrDefault(); + public T? FirstOrDefault(Func predicate) { if (HasOneItem) diff --git a/src/Compilers/Core/Portable/PEWriter/ReferenceIndexerBase.cs b/src/Compilers/Core/Portable/PEWriter/ReferenceIndexerBase.cs index b240b8b0b5b9a..0a84aebbeeae8 100644 --- a/src/Compilers/Core/Portable/PEWriter/ReferenceIndexerBase.cs +++ b/src/Compilers/Core/Portable/PEWriter/ReferenceIndexerBase.cs @@ -17,6 +17,12 @@ internal abstract class ReferenceIndexerBase : MetadataVisitor { private readonly HashSet _alreadySeen = new(); private readonly HashSet _alreadyHasToken = new(); + + /// + /// Set true before a type reference is visited but only if a token needs to be created for the type reference. + /// E.g. not set before return type of a method is visited since the type reference is encoded in the signature blob of the method. + /// On the other hand, it is true before the type of an event definition is visited since Event table stores the type of the event as a coded token (TypeDef/Ref/Spec). + /// protected bool typeReferenceNeedsToken; internal ReferenceIndexerBase(EmitContext context) @@ -38,6 +44,7 @@ public override void Visit(ICustomModifier customModifier) { this.typeReferenceNeedsToken = true; this.Visit(customModifier.GetModifier(Context)); + Debug.Assert(!this.typeReferenceNeedsToken); } public override void Visit(IEventDefinition eventDefinition) diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb index 88de7e0a8a6ae..7a22573ba47b3 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb @@ -555,7 +555,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private Function AreEventsEqual([event] As EventSymbol, other As EventSymbol) As Boolean Debug.Assert(s_nameComparer.Equals([event].Name, other.Name)) - Return Me._comparer.Equals([event].Type, other.Type) + ' Events can't be overloaded on type. + ' ECMA: Within the rows owned by a given row in the TypeDef table, there shall be no duplicates based upon Name [ERROR] + Return True End Function Private Function AreFieldsEqual(field As FieldSymbol, other As FieldSymbol) As Boolean @@ -611,6 +613,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private Function ArePropertiesEqual([property] As PropertySymbol, other As PropertySymbol) As Boolean Debug.Assert(s_nameComparer.Equals([property].Name, other.Name)) + + ' Properties may be overloaded on their signature. + ' ECMA: Within the rows owned by a given row in the TypeDef table, there shall be no duplicates based upon Name+Type [ERROR] Return Me._comparer.Equals([property].Type, other.Type) AndAlso [property].Parameters.SequenceEqual(other.Parameters, AddressOf Me.AreParametersEqual) End Function diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.vb index 920b8416e1c46..24a5a85ecdb8e 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueClosureTests.vb @@ -1761,7 +1761,7 @@ End Class generation0, ImmutableArray.Create( New SemanticEdit(SemanticEditKind.Insert, Nothing, b1), - New SemanticEdit(SemanticEditKind.Insert, Nothing, ctor1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables:=True))) + New SemanticEdit(SemanticEditKind.Insert, Nothing, ctor1))) Const bug2504IsFixed = False diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb index e688b98e03e75..c5110614c320c 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb @@ -1341,6 +1341,324 @@ End Class ") End Sub + + + Public Sub Event_Delete() + Using New EditAndContinueTest(). + AddBaseline( + source:=" +Class C + Custom Event E As System.Action(Of Integer) + AddHandler(value As System.Action(Of Integer)) + System.Console.WriteLine(1) + End AddHandler + RemoveHandler(value As System.Action(Of Integer)) + System.Console.WriteLine(2) + End RemoveHandler + RaiseEvent() + System.Console.WriteLine(3) + End RaiseEvent + End Event +End Class +", + validator:= + Sub(g) + End Sub). + AddGeneration( + source:=" +Class C +End Class +", + edits:= + { + Edit(SemanticEditKind.Delete, symbolProvider:=Function(c) c.GetMember("C.raise_E"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, symbolProvider:=Function(c) c.GetMember("C.add_E"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, symbolProvider:=Function(c) c.GetMember("C.remove_E"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, symbolProvider:=Function(c) c.GetMember("C.E"), newSymbolProvider:=Function(c) c.GetMember("C")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + g.VerifyMethodDefNames("add_E", "remove_E", "raise_E") + g.VerifyDeletedMembers("C: {raise_E, add_E, remove_E, E}") + + ' We should update the Event table entry to indicate that the event has been deleted: + ' TODO: https://github.com/dotnet/roslyn/issues/69834 + g.VerifyEncLogDefinitions( + { + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(2, TableIndex.Param, EditAndContinueOperation.Default) + }) + + g.VerifyIL(" +{ + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000006 + IL_0005: throw +} +") + End Sub). + AddGeneration( + source:=" +Class C + Custom Event E As System.Action(Of Integer) + AddHandler(value As System.Action(Of Integer)) + System.Console.WriteLine(10) + End AddHandler + RemoveHandler(value As System.Action(Of Integer)) + System.Console.WriteLine(20) + End RemoveHandler + RaiseEvent() + System.Console.WriteLine(30) + End RaiseEvent + End Event +End Class +", + edits:= + { + Edit(SemanticEditKind.Insert, symbolProvider:=Function(c) c.GetMember("C.E")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + g.VerifyMethodDefNames("add_E", "remove_E", "raise_E") + g.VerifyDeletedMembers("C: {raise_E, add_E, remove_E, E}") + + g.VerifyEncLogDefinitions( + { + Row(1, TableIndex.Event, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(2, TableIndex.Param, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + }) + + g.VerifyIL(" +{ + // Code size 10 (0xa) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4.s 10 + IL_0003: call 0x0A000007 + IL_0008: nop + IL_0009: ret +} +{ + // Code size 10 (0xa) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4.s 20 + IL_0003: call 0x0A000007 + IL_0008: nop + IL_0009: ret +} +{ + // Code size 10 (0xa) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4.s 30 + IL_0003: call 0x0A000007 + IL_0008: nop + IL_0009: ret +} +") + End Sub). + Verify() + End Using + End Sub + + + Public Sub Event_TypeChange() + Using New EditAndContinueTest(). + AddBaseline( + source:=" +Class C + Custom Event E As System.Action(Of Integer) + AddHandler(value As System.Action(Of Integer)) + System.Console.WriteLine(1) + End AddHandler + RemoveHandler(value As System.Action(Of Integer)) + System.Console.WriteLine(2) + End RemoveHandler + RaiseEvent() + End RaiseEvent + End Event +End Class +", + validator:= + Sub(g) + End Sub). + AddGeneration( + source:=" +Class C + Custom Event E As System.Action(Of Boolean) + AddHandler(value As System.Action(Of Boolean)) + System.Console.WriteLine(10) + End AddHandler + RemoveHandler(value As System.Action(Of Boolean)) + System.Console.WriteLine(20) + End RemoveHandler + RaiseEvent() + End RaiseEvent + End Event +End Class +", + edits:= + { + Edit(SemanticEditKind.Delete, symbolProvider:=Function(c) c.GetMember("C.add_E"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, symbolProvider:=Function(c) c.GetMember("C.remove_E"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Update, symbolProvider:=Function(c) c.GetMember("C.E")), + Edit(SemanticEditKind.Insert, symbolProvider:=Function(c) c.GetMember("C.add_E")), + Edit(SemanticEditKind.Insert, symbolProvider:=Function(c) c.GetMember("C.remove_E")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + + ' We do not update raise_E since its signature has not changed. + g.VerifyMethodDefNames("add_E", "remove_E", "add_E", "remove_E") + g.VerifyDeletedMembers("C: {add_E, remove_E}") + + ' New event is added to the Event table associated with the new accessors. + ' Events can't be overloaded on type so we will update the existing Event table entry. + g.VerifyEncLogDefinitions( + { + Row(1, TableIndex.Event, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(6, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(2, TableIndex.Param, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(3, TableIndex.Param, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(4, TableIndex.Param, EditAndContinueOperation.Default), + Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + }) + + g.VerifyIL(" +{ + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000006 + IL_0005: throw +} +{ + // Code size 10 (0xa) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4.s 10 + IL_0003: call 0x0A000007 + IL_0008: nop + IL_0009: ret +} +{ + // Code size 10 (0xa) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4.s 20 + IL_0003: call 0x0A000007 + IL_0008: nop + IL_0009: ret +} +") + End Sub). + AddGeneration( + source:=" +Class C + Custom Event E As System.Action(Of Integer) + AddHandler(value As System.Action(Of Integer)) + System.Console.WriteLine(100) + End AddHandler + RemoveHandler(value As System.Action(Of Integer)) + System.Console.WriteLine(200) + End RemoveHandler + RaiseEvent() + End RaiseEvent + End Event +End Class +", + edits:= + { + Edit(SemanticEditKind.Delete, symbolProvider:=Function(c) c.GetMember("C.add_E"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, symbolProvider:=Function(c) c.GetMember("C.remove_E"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Update, symbolProvider:=Function(c) c.GetMember("C.E")), + Edit(SemanticEditKind.Insert, symbolProvider:=Function(c) c.GetMember("C.add_E")), + Edit(SemanticEditKind.Insert, symbolProvider:=Function(c) c.GetMember("C.remove_E")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + + ' We do not update raise_E since its signature has not changed. + g.VerifyMethodDefNames("add_E", "remove_E", "add_E", "remove_E") + g.VerifyDeletedMembers("C: {add_E, remove_E}") + + ' Updating existing members, no new additions. + g.VerifyEncLogDefinitions( + { + Row(1, TableIndex.Event, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(6, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(2, TableIndex.Param, EditAndContinueOperation.Default), + Row(3, TableIndex.Param, EditAndContinueOperation.Default), + Row(4, TableIndex.Param, EditAndContinueOperation.Default), + Row(7, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(8, TableIndex.MethodSemantics, EditAndContinueOperation.Default), + Row(9, TableIndex.MethodSemantics, EditAndContinueOperation.Default) + }) + + g.VerifyIL(" +{ + // Code size 10 (0xa) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4.s 100 + IL_0003: call 0x0A000008 + IL_0008: nop + IL_0009: ret +} +{ + // Code size 13 (0xd) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4 0xc8 + IL_0006: call 0x0A000008 + IL_000b: nop + IL_000c: ret +} +{ + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000009 + IL_0005: throw +} +") + End Sub). + Verify() + End Using + End Sub + Public Sub UpdateType_AddAttributes() Dim source0 = " @@ -1686,7 +2004,7 @@ End Class End Sub - Public Sub AddingSetAccessor() + Public Sub Property_Insert_Setter() Dim source0 = @@ -1764,7 +2082,7 @@ End Module End Sub - Public Sub PropertyGetterReturnValueVariable() + Public Sub Property_GetterReturnValueVariable() Dim source0 = @@ -1821,6 +2139,420 @@ End Module End Using End Sub + + + Public Sub Property_TypeChange() + Using New EditAndContinueTest(). + AddBaseline( + source:=" +Imports System + +Class C + Property P As Action(Of Integer) + Get + Console.WriteLine(1) + Return Nothing + End Get + Set(value As Action(Of Integer)) + Console.WriteLine(2) + End Set + End Property +End Class +", + validator:= + Sub(g) + g.VerifyTypeDefNames("", "C") + g.VerifyFieldDefNames() + g.VerifyMethodDefNames(".ctor", "get_P", "set_P") + End Sub). + AddGeneration( + source:=" +Imports System + +Class C + Property P As Action(Of Boolean) + Get + Console.WriteLine(10) + Return Nothing + End Get + Set(value As Action(Of Boolean)) + Console.WriteLine(20) + End Set + End Property +End Class +", + edits:= + { + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.P"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.set_P"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Insert, Function(c) c.GetMember("C.P")), + Edit(SemanticEditKind.Insert, Function(c) c.GetMember("C.get_P")), + Edit(SemanticEditKind.Insert, Function(c) c.GetMember("C.set_P")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + + ' old accessors are updated to throw, new accessors are added: + g.VerifyMethodDefNames("get_P", "set_P", "get_P", "set_P") + g.VerifyDeletedMembers("C: {P, get_P, set_P}") + + ' New property is added to the Property table associated with the new accessors. + ' Properties can be overloaded on name and signature, so we need to insert a new entry for the new signature. + ' + ' We keep the existing entry as is, which is not ideal since reflection now returns both the old and the new properties rather than just the new one. + ' Consider updating the existing Property table entry to change the property name to _deleted. + ' TODO: https://github.com/dotnet/roslyn/issues/69834 + g.VerifyEncLogDefinitions( + { + Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), ' Action get_P + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), ' set_P(Action) + Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), ' Action get_P + Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), ' set_P(Action) + Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.PropertyMap, EditAndContinueOperation.AddProperty), ' Action P + Row(2, TableIndex.Property, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(2, TableIndex.Param, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodSemantics, EditAndContinueOperation.Default), ' Action P <-> Action get_P + Row(4, TableIndex.MethodSemantics, EditAndContinueOperation.Default) ' Action P <-> set_P(Action) + }) + + g.VerifyIL(" +{ + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000006 + IL_0005: throw +} +{ + // Code size 15 (0xf) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.s 10 + IL_0003: call 0x0A000007 + IL_0008: nop + IL_0009: ldnull + IL_000a: stloc.0 + IL_000b: br.s IL_000d + IL_000d: ldloc.0 + IL_000e: ret +} +{ + // Code size 10 (0xa) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4.s 20 + IL_0003: call 0x0A000007 + IL_0008: nop + IL_0009: ret +}") + End Sub). + AddGeneration( + source:=" +Imports System + +Class C + Property P As Action(Of Integer) + Get + Console.WriteLine(100) + Return Nothing + End Get + Set(value As Action(Of Integer)) + Console.WriteLine(200) + End Set + End Property +End Class +", + edits:= + { + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.P"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.set_P"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Insert, Function(c) c.GetMember("C.P")), + Edit(SemanticEditKind.Insert, Function(c) c.GetMember("C.get_P")), + Edit(SemanticEditKind.Insert, Function(c) c.GetMember("C.set_P")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + + ' old accessors are updated to throw, new accessors are added: + g.VerifyMethodDefNames("get_P", "set_P", "get_P", "set_P") + g.VerifyDeletedMembers("C: {P, get_P, set_P}") + + ' Changing the signature back updates the the original property and accessors. + ' No new property/method is added. + g.VerifyEncLogDefinitions( + { + Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), ' Action get_P + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), ' set_P(Action) + Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), ' Action get_P + Row(5, TableIndex.MethodDef, EditAndContinueOperation.Default), ' set_P(Action) + Row(1, TableIndex.Property, EditAndContinueOperation.Default), ' Action P + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(2, TableIndex.Param, EditAndContinueOperation.Default), + Row(5, TableIndex.MethodSemantics, EditAndContinueOperation.Default), ' Action P <-> Action get_P + Row(6, TableIndex.MethodSemantics, EditAndContinueOperation.Default) ' Action P <-> set_P(Action) + }) + + g.VerifyIL(" +{ + // Code size 15 (0xf) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.s 100 + IL_0003: call 0x0A000008 + IL_0008: nop + IL_0009: ldnull + IL_000a: stloc.0 + IL_000b: br.s IL_000d + IL_000d: ldloc.0 + IL_000e: ret +} +{ + // Code size 13 (0xd) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4 0xc8 + IL_0006: call 0x0A000008 + IL_000b: nop + IL_000c: ret +} +{ + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000009 + IL_0005: throw +}") + End Sub). + Verify() + End Using + End Sub + + + Public Sub Property_Delete() + Using New EditAndContinueTest(). + AddBaseline( + source:=" +Imports System + +Class C + Property P As Integer + Get + Return 1 + End Get + Set(value As Integer) + End Set + End Property +End Class +", + validator:= + Sub(g) + g.VerifyTypeDefNames("", "C") + g.VerifyFieldDefNames() + g.VerifyMethodDefNames(".ctor", "get_P", "set_P") + End Sub). + AddGeneration( + source:=" +Imports System + +Class C +End Class +", + edits:= + { + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.P"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), newSymbolProvider:=Function(c) c.GetMember("C")), + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.set_P"), newSymbolProvider:=Function(c) c.GetMember("C")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + + ' deleted getter is updated to throw: + g.VerifyMethodDefNames("get_P", "set_P") + g.VerifyDeletedMembers("C: {P, get_P, set_P}") + + g.VerifyEncLogDefinitions( + { + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default) + }) + + g.VerifyIL(" +{ + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000005 + IL_0005: throw +}") + End Sub). + AddGeneration( + source:=" +Imports System + +Class C + Property P As Integer + Get + Return 2 + End Get + Set(value As Integer) + End Set + End Property +End Class +", + edits:= + { + Edit(SemanticEditKind.Insert, symbolProvider:=Function(c) c.GetMember("C.get_P")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + + g.VerifyMethodDefNames("get_P") + g.VerifyDeletedMembers("C: {P, get_P, set_P}") + + g.VerifyEncLogDefinitions( + { + Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default) + }) + + g.VerifyIL(" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: stloc.0 + IL_0003: br.s IL_0005 + IL_0005: ldloc.0 + IL_0006: ret +}") + End Sub). + Verify() + End Using + End Sub + + + Public Sub Property_DeleteGetter() + Using New EditAndContinueTest(). + AddBaseline( + source:=" +Imports System + +Class C + Property P As Integer + Get + Return 1 + End Get + Set(value As Integer) + End Set + End Property +End Class +", + validator:= + Sub(g) + g.VerifyTypeDefNames("", "C") + g.VerifyFieldDefNames() + g.VerifyMethodDefNames(".ctor", "get_P", "set_P") + End Sub). + AddGeneration( + source:=" +Imports System + +Class C + WriteOnly Property P As Integer + Set(value As Integer) + End Set + End Property +End Class +", + edits:= + { + Edit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), newSymbolProvider:=Function(c) c.GetMember("C")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + g.VerifyDeletedMembers("C: {get_P}") + + ' deleted getter is updated to throw: + g.VerifyMethodDefNames("get_P") + + g.VerifyEncLogDefinitions( + { + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default) + }) + + g.VerifyIL(" +{ + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000005 + IL_0005: throw +}") + End Sub). + AddGeneration( + source:=" +Imports System + +Class C + Property P As Integer + Get + Return 2 + End Get + Set(value As Integer) + End Set + End Property +End Class +", + edits:= + { + Edit(SemanticEditKind.Insert, symbolProvider:=Function(c) c.GetMember("C.get_P")) + }, + validator:= + Sub(g) + g.VerifyTypeDefNames() + g.VerifyFieldDefNames() + g.VerifyDeletedMembers("C: {get_P}") + + g.VerifyMethodDefNames("get_P") + + g.VerifyEncLogDefinitions( + { + Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default) + }) + + g.VerifyIL(" +{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.2 + IL_0002: stloc.0 + IL_0003: br.s IL_0005 + IL_0005: ldloc.0 + IL_0006: ret +}") + End Sub). + Verify() + End Using + End Sub + #Region "Local Slots" Public Sub CatchClause() diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs index be913f6d4f4b4..fd053fd568528 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs @@ -4,6 +4,7 @@ #nullable disable +using System.Linq; using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Emit; @@ -53,7 +54,7 @@ static void Main(string[] args) { DocumentResults( active, - diagnostics: new[] { Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", DeletedSymbolDisplay(FeaturesResources.method, "Goo(int a)")) }) + diagnostics: new[] { Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", DeletedSymbolDisplay(FeaturesResources.method, "C.Goo(int)")) }) }); } @@ -81,14 +82,9 @@ static void Boo(int a) var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - EditAndContinueValidation.VerifySemantics( - new[] { edits }, - new[] - { - DocumentResults( - active, - diagnostics: new[] {Diagnostic(RudeEditKind.UpdateAroundActiveStatement, "static void Boo(int a)", FeaturesResources.method) }) - }); + edits.VerifySemanticDiagnostics( + active, + diagnostics: [Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "static void Boo(int a)", GetResource("method"))]); } [Fact] @@ -496,6 +492,126 @@ static void Main(string[] args) new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.Main"), syntaxMap[0]) }); } + [Fact] + public void Method_Partial_Update_Attribute_Definition() + { + var attribute = """ + public class A : System.Attribute { public A(int x) {} } + """; + + var src1 = attribute + + """ + partial class C { [A(1)]partial void F(); } + partial class C { partial void F() { } } + """; + + var src2 = attribute + + """ + partial class C { [A(2)]partial void F(); } + partial class C { partial void F() { } } + """; + + var active = GetActiveStatements(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + EditAndContinueValidation.VerifySemantics( + GetTopEdits(src1, src2), + active, + [SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C")], + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Fact] + public void Method_Partial_Update_Attribute_DefinitionAndImplementation() + { + var attribute = """ + public class A : System.Attribute { public A(int x) {} } + public class B : System.Attribute { public B(int x) {} } + """; + + var srcA1 = attribute + + """ + partial class C { [A(1)]partial void F(); } + """; + + var srcB1 = + """ + partial class C { [B(1)]partial void F() { var x = 1; } } + """; + + var srcA2 = attribute + + """ + partial class C { [A(2)]partial void F(); } + """; + + var srcB2 = + """ + partial class C { [B(2)]partial void F() { var x = 1; } } + """; + + var syntaxMapB = GetSyntaxMap(srcB1, srcB2)[0]; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults( + activeStatements: GetActiveStatements(srcA1, srcA2), + semanticEdits: [SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C")]), + DocumentResults( + activeStatements: GetActiveStatements(srcB1, srcB2), + semanticEdits: [SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C", syntaxMap: syntaxMapB)]), + ], + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Fact] + public void Method_Partial_SignatureChangeInsertDelete1() + { + var srcA1 = "partial class C { void F(byte x) { } }"; + var srcB1 = "partial class C { void F(char x) {} }"; + var srcA2 = "partial class C { void F(char x) {} }"; + var srcB2 = "partial class C { void F(byte x) {} }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + activeStatements: GetActiveStatements(srcA1, srcA2), + diagnostics: [Diagnostic(RudeEditKind.DeleteActiveStatement, "char x", GetResource("method"))]), + + DocumentResults( + activeStatements: GetActiveStatements(srcB1, srcB2), + diagnostics: []) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + } + + [Fact] + public void Method_Partial_SignatureChangeInsertDelete2() + { + var srcA1 = "partial class C { void F(byte x) { } }"; + var srcB1 = "partial class C { void F(char x) {} }"; + var srcA2 = "partial class C { void F(char x) {} }"; + var srcB2 = "partial class C { void F(byte x) {} }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + activeStatements: GetActiveStatements(srcA1, srcA2), + diagnostics: []), + + DocumentResults( + activeStatements: GetActiveStatements(srcB1, srcB2), + diagnostics: [Diagnostic(RudeEditKind.DeleteActiveStatement, "byte x", GetResource("method"))]) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + } + #endregion #region Constuctors @@ -749,6 +865,7 @@ public void Indexer_BlockBodyToExpressionBody2() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 2e89e28b509c8..4d22390b5cd3b 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -799,7 +799,144 @@ namespace N var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", GetResource("method", "Main()"))); + Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", GetResource("method", "N.C.Main()"))); + } + + #endregion + + #region Parameters & Returns + + [Fact] + public void Parameter_Rename() + { + var src1 = "class C { int F(int a) => 1; }"; + var src2 = "class C { int F(int b) => 1; }"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifySemanticDiagnostics( + active, + capabilities: EditAndContinueCapabilities.UpdateParameters); + } + + [Theory] + [InlineData("int")] + [InlineData("in byte")] + [InlineData("ref byte")] + [InlineData("out byte")] + [InlineData("ref readonly byte")] + public void Parameter_Update_TypeOrRefKind_RuntimeTypeChanged(string oldType) + { + var src1 = "class C { int F(" + oldType + " a) => throw null!; }"; + var src2 = "class C { int F(byte a) => throw null!; }"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + // Can't remap to the new version of the method. + // Even if we emitted trampoline we would not be able to remap to the exact instruction the active statement is at in the old version. + + edits.VerifySemanticDiagnostics(active, + diagnostics: new[] { Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "int F(byte a)", GetResource("method")) }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory] + [CombinatorialData] + public void Parameter_Update_RefKind_RuntimeTypeUnchanged( + [CombinatorialValues("ref", "out", "in", "ref readonly")] string oldModifiers, + [CombinatorialValues("ref", "out", "in", "ref readonly")] string newModifiers) + { + if (oldModifiers == newModifiers) + { + return; + } + + var src1 = "class C { int F(" + oldModifiers + " int a) => throw null!; }"; + var src2 = "class C { int F(" + newModifiers + " int a) => throw null!; }"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + // We don't require a runtime capability to update attributes. + // All runtimes support changing the attributes in metadata, some just don't reflect the changes in the Reflection model. + // Having compiler-generated attributes visible via Reflaction API is not that important. + // The same for [in] and [out] metadata flags. + edits.VerifySemantics(active, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Theory] + [InlineData("params")] + [InlineData("this")] + public void Parameter_Update_Modifiers_RuntimeTypeUnchanged(string newModifiers) + { + var src1 = "static class C { static int F(int[] a) => throw null!; }"; + var src2 = "static class C { static int F(" + newModifiers + " int[] a) => throw null!; }"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + // We don't require a runtime capability to update attributes. + // All runtimes support changing the attributes in metadata, some just don't reflect the changes in the Reflection model. + // Having compiler-generated attributes visible via Reflaction API is not that important. + // The same for [in] and [out] metadata flags. + edits.VerifySemantics(active, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Return_TypeChange() + { + var src1 = "class C { int F(int a) => 1; }"; + var src2 = "class C { byte F(int a) => 1; }"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + // Can't remap to the new version of the method. + // Even if we emitted trampoline we would not be able to remap to the exact instruction the active statement is at in the old version. + + edits.VerifySemanticDiagnostics(active, + diagnostics: new[] { Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "byte F(int a)", GetResource("method")) }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Return_TypeChange_Ref() + { + var src1 = "class C { int X; int F(int a) => 1; }"; + var src2 = "class C { int X; ref int F(int a) => ref X; }"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + // Can't remap to the new version of the method. + // Even if we emitted trampoline we would not be able to remap to the exact instruction the active statement is at in the old version. + + edits.VerifySemanticDiagnostics(active, + diagnostics: [Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "ref int F(int a)", GetResource("method"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Return_TypeChange_RefReadonly() + { + var src1 = "class C { int X; ref int F(int a) => ref X; }"; + var src2 = "class C { int X; ref readonly int F(int a) => ref X; }"; + + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + // Can't remap to the new version of the method. + // Even if we emitted trampoline we would not be able to remap to the exact instruction the active statement is at in the old version. + + edits.VerifySemanticDiagnostics(active, + diagnostics: new[] { Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "ref readonly int F(int a)", GetResource("method")) }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } #endregion @@ -949,7 +1086,7 @@ class Goo ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("Goo..ctor")) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("Goo..ctor"), preserveLocalVariables: true) }, capabilities: EditAndContinueTestHelpers.Net6RuntimeCapabilities); } @@ -1497,9 +1634,9 @@ public void Constructor_Instance_ImplicitInitializer_ParameterChange() var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - // TODO: should be rude edit (https://github.com/dotnet/roslyn/issues/68708) edits.VerifySemanticDiagnostics( active, + diagnostics: [Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "C(byte P)", GetResource("constructor"))], capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } @@ -1924,6 +2061,63 @@ public record C #region Properties + [Fact] + public void Property_Update_Type_ActiveAccessors() + { + // only type is changed, no changes to the accessors (not even whitespace) + var src1 = @" +class C +{ + public byte P { get => 1; set { } } +} +"; + var src2 = @" +class C +{ + public long P { get => 1; set { } } +} +"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifySemanticDiagnostics( + active, + diagnostics: + [ + Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "get", GetResource("property getter")), + Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "set", GetResource("property setter")) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Property_Rename_ActiveAccessors() + { + var src1 = @" +class C +{ + public int P { get => 1; set { } } +} +"; + var src2 = @" +class C +{ + public int Q { get => 1; set { } } +} +"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifySemanticDiagnostics( + active, + diagnostics: + [ + Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "get", GetResource("property getter")), + Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "set", GetResource("property setter")) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + [Fact] public void Property_Auto_Record_ReplacingNonPrimaryWithPrimary_Getter() { @@ -2802,7 +2996,7 @@ public C() {} var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", GetResource("field", "a")), + Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", GetResource("field", "C.a")), Diagnostic(RudeEditKind.Delete, "class C", GetResource("field", "a"))); } @@ -2938,7 +3132,7 @@ public C() {} edits.VerifySemanticDiagnostics(active, Diagnostic(RudeEditKind.Move, "int c", GetResource("field")), - Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", GetResource("field", "a")), + Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", GetResource("field", "C.a")), Diagnostic(RudeEditKind.Delete, "class C", GetResource("field", "a"))); } @@ -2989,7 +3183,7 @@ class C var active = GetActiveStatements(src1, src2); edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", GetResource("field", "a")), + Diagnostic(RudeEditKind.DeleteActiveStatement, "class C", GetResource("field", "C.a")), Diagnostic(RudeEditKind.Delete, "class C", GetResource("field", "a"))); } @@ -3016,6 +3210,7 @@ class C { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.a")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_a"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.a"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C..ctor"), preserveLocalVariables: true), }, capabilities: EditAndContinueCapabilities.AddInstanceFieldToExistingType); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index 87484866beb36..eb3cb951a2e8a 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -79,6 +79,7 @@ public static string GetResource(string keyword) "auto-property" => FeaturesResources.auto_property, "event" => FeaturesResources.event_, "event field" => CSharpFeaturesResources.event_field, + "event accessor" => FeaturesResources.event_accessor, "indexer" => CSharpFeaturesResources.indexer, "indexer getter" => CSharpFeaturesResources.indexer_getter, "indexer setter" => CSharpFeaturesResources.indexer_setter, diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs index af00c3cfae84c..1afbe077d53eb 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs @@ -516,7 +516,7 @@ class C { static void Bar() { - Console.ReadLine(2); +/******/Console.ReadLine(2); } } "; @@ -525,7 +525,7 @@ class C { static void Bar() { - /*edit*/Console.ReadLine(2); +/******//*edit*/Console.ReadLine(2); } }"; @@ -533,7 +533,7 @@ static void Bar() edits.VerifyLineEdits( Array.Empty(), - diagnostics: new[] { Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "\r\n /*edit*/", FeaturesResources.method) }, + diagnostics: new[] { Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "/******//*edit*/", FeaturesResources.method) }, capabilities: EditAndContinueCapabilities.Baseline); edits.VerifyLineEdits( @@ -550,7 +550,7 @@ class C { static void Bar() { - /*edit*/Console.ReadLine(2); +/******//*edit*/Console.ReadLine(2); } } "; @@ -559,14 +559,14 @@ class C { static void Bar() { - Console.ReadLine(2); +/******/Console.ReadLine(2); } }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - diagnostics: new[] { Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "\r\n ", FeaturesResources.method) }, + diagnostics: new[] { Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "/******/", FeaturesResources.method) }, capabilities: EditAndContinueCapabilities.Baseline); edits.VerifyLineEdits( @@ -986,7 +986,6 @@ record C(int P); SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), }); - } [Fact] @@ -1153,7 +1152,7 @@ public C(int a) var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - diagnostics: new[] { Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "public C(int a)", GetResource("constructor")) }, + diagnostics: [Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "base", GetResource("constructor"))], capabilities: EditAndContinueCapabilities.Baseline); edits.VerifyLineEdits( @@ -1618,16 +1617,13 @@ class C var src2 = @" class C { - static int Goo = 1 + 1; + static int Goo = 1 +/**/1; }"; var edits = GetTopEdits(src1, src2); edits.VerifyLineEdits( Array.Empty(), - diagnostics: new[] - { - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "class C", GetResource("static constructor", "C()")) - }, + diagnostics: [Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "/**/", GetResource("field"))], capabilities: EditAndContinueCapabilities.Baseline); edits.VerifyLineEdits( diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index 38ddd81d2ddb2..c87f1a2baddb0 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -3558,8 +3558,7 @@ void F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingLambdaParameters, "(out int a)", CSharpFeaturesResources.lambda)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } // Add corresponding test to VB @@ -6079,6 +6078,56 @@ static void F(byte x) Diagnostic(RudeEditKind.ChangingCapturedVariableType, "x", "x", "int")); } + [Fact] + public void Lambdas_CapturedParameter_ChangeType_Indexer1() + { + var src1 = @" +using System; + +class C +{ + Func this[int a] { get => () => a; } +} +"; + var src2 = @" +using System; + +class C +{ + Func this[byte a] => () => a; +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ChangingCapturedVariableType, "a", "a", "int")); + } + + [Fact] + public void Lambdas_CapturedParameter_ChangeType_Indexer_NoBodyChange() + { + var src1 = @" +using System; + +class C +{ + Func this[int a ] => () => a; +} +"; + var src2 = @" +using System; + +class C +{ + Func this[byte a] => () => a; +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ChangingCapturedVariableType, "a", "a", "int")); + } + [Fact] public void Lambdas_ReorderCapturedParameters() { @@ -7428,8 +7477,7 @@ void F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingLambdaParameters, "f", CSharpFeaturesResources.local_function)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] @@ -7459,8 +7507,7 @@ void F() "; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingLambdaParameters, "f", CSharpFeaturesResources.local_function)); + edits.VerifySemantics(SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"), preserveLocalVariables: true)); } [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 40fd03314c659..dae7a21cafbf4 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests public class TopLevelEditingTests : EditingTestBase { private static readonly string s_attributeSource = @" -[System.AttributeUsage(System.AttributeTargets.All)]class A : System.Attribute { } +[System.AttributeUsage(System.AttributeTargets.All)]class A : System.Attribute { public A() {} public A(int x) { } } "; #region Usings @@ -1217,18 +1217,21 @@ public void Type_Attribute_ReloadableBase() } [Fact] - public void Type_Attribute_Add() + public void Type_Update_Attribute_Insert() { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; + var attributes = + """ + class A : System.Attribute { } + class B : System.Attribute { } + """; - var src1 = attribute + "[A]class C { }"; - var src2 = attribute + "[A, B]class C { }"; + var src1 = attributes + "[A]class C { }"; + var src2 = attributes + "[A, B]class C { }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [[A]class C { }]@96 -> [[A, B]class C { }]@96"); + "Update [[A]class C { }]@62 -> [[A, B]class C { }]@62"); edits.VerifySemantics( ActiveStatementsDescription.Empty, @@ -1237,7 +1240,38 @@ public void Type_Attribute_Add() } [Fact] - public void Type_Attribute_Add_NotSupportedByRuntime1() + public void Type_Update_Attribute_Insert_Reloadable() + { + var attributes = ReloadableAttributeSrc + + """ + class A : System.Attribute { } + class B : System.Attribute { } + """; + + var srcA1 = attributes + "[CreateNewOnMetadataUpdate]partial class C { }"; + var srcB1 = "partial class C { }"; + + var srcA2 = attributes + "[CreateNewOnMetadataUpdate][A]partial class C { }"; + var srcB2 = "[B]partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C"), partialType: "C") + }), + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C"), partialType: "C") + }), + }, + capabilities: EditAndContinueCapabilities.NewTypeDefinition); + } + + [Fact] + public void Type_Update_Attribute_Insert_NotSupportedByRuntime1() { var attribute = "public class AAttribute : System.Attribute { }\n\n" + "public class BAttribute : System.Attribute { }\n\n"; @@ -1256,7 +1290,7 @@ public void Type_Attribute_Add_NotSupportedByRuntime1() } [Fact] - public void Type_Attribute_Add_NotSupportedByRuntime2() + public void Type_Update_Attribute_Insert_NotSupportedByRuntime2() { var attribute = "public class AAttribute : System.Attribute { }\n\n"; @@ -1274,7 +1308,7 @@ public void Type_Attribute_Add_NotSupportedByRuntime2() } [Fact] - public void Type_Attribute_Reorder1() + public void Type_Update_Attribute_Reorder1() { var src1 = "[A(1), B(2), C(3)]class C { }"; var src2 = "[C(3), A(1), B(2)]class C { }"; @@ -1288,7 +1322,7 @@ public void Type_Attribute_Reorder1() } [Fact] - public void Type_Attribute_Reorder2() + public void Type_Update_Attribute_Reorder2() { var src1 = "[A, B, C]class C { }"; var src2 = "[B, C, A]class C { }"; @@ -1578,6 +1612,9 @@ public void Type_BaseType_Update_RuntimeTypeUnchanged(string oldType, string new var edits = GetTopEdits(src1, src2); + // We don't require a runtime capability to update attributes. + // All runtimes support changing the attributes in metadata, some just don't reflect the changes in the Reflection model. + // Having compiler-generated attributes visible via Reflaction API is not that important. edits.VerifySemantics( SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C"))); } @@ -2331,14 +2368,17 @@ interface I { void F() {} } DocumentResults(), DocumentResults( - diagnostics: new[] + semanticEdits: new[] { - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "void F()", GetResource("method")), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "void F()", GetResource("method")), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "void F()", GetResource("method")) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I.F")) }) }, - capabilities: EditAndContinueCapabilities.Baseline); + capabilities: EditAndContinueCapabilities.GenericUpdateMethod); EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, @@ -2347,14 +2387,14 @@ interface I { void F() {} } DocumentResults(), DocumentResults( - semanticEdits: new[] + diagnostics: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.F")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I.F")) + Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "void F()", GetResource("method")), + Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "void F()", GetResource("method")), + Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "void F()", GetResource("method")) }) }, - capabilities: EditAndContinueCapabilities.GenericUpdateMethod); + capabilities: EditAndContinueCapabilities.Baseline); } [Fact(Skip = "https://github.com/dotnet/roslyn/issues/54881")] @@ -3104,8 +3144,8 @@ public void Record_Method_Insert_ReplacingSynthesizedWithCustom_SemanticError() var src1 = "record C { }"; var src2 = @"record C { -protected virtual bool PrintMembers(System.Text.StringBuilder sb) => false; -protected virtual bool PrintMembers(System.Text.StringBuilder sb) => false; + protected virtual bool PrintMembers(System.Text.StringBuilder sb) => false; + protected virtual bool PrintMembers(System.Text.StringBuilder sb) => false; }"; var edits = GetTopEdits(src1, src2); @@ -3383,6 +3423,7 @@ public void Record_Property_Delete_Writable(string setter) SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")) }, @@ -3406,6 +3447,7 @@ public void Record_Property_Delete_ReadOnly(string getter) SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, capabilities: EditAndContinueCapabilities.Baseline); @@ -3445,8 +3487,7 @@ public void Record_Property_Delete_ReadOnly_ReplacingCustomWithSynthesized_Gener new[] { Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "int X", GetResource("property")), - Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "record C", GetResource("property getter", "X.get")), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "(int X)", GetResource("constructor")) + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "record C", GetResource("property getter", "X.get")) }, capabilities: EditAndContinueCapabilities.Baseline); } @@ -3473,6 +3514,7 @@ public void Record_Property_Delete_WriteOnly() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, capabilities: EditAndContinueCapabilities.Baseline); @@ -3489,6 +3531,7 @@ public void Record_Property_Delete_Static() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), }, @@ -3521,6 +3564,7 @@ public C(bool b) : this(1) { } SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Y"), deletedSymbolContainerProvider: c => c.GetMember("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), @@ -3892,14 +3936,14 @@ public void Record_Property_Insert_ReplacingSynthesizedWithCustom_WithSetter_Wri var edits = GetTopEdits(src1, src2); edits.VerifySemantics(new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), - SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), - 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), - }); + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + 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), + }); } [Fact] @@ -4074,8 +4118,8 @@ public void Record_Property_Insert_ReplacingSynthesizedWithCustom_WithBody(strin SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("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.get_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_X")), SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true)); edits.VerifySemanticDiagnostics(); @@ -4104,8 +4148,8 @@ public void Record_Property_Insert_ReplacingSynthesizedWithCustom_WithBody_Parti SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("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.get_X")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_X")), SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), partialType: "C", preserveLocalVariables: true) }) }); @@ -4785,7 +4829,7 @@ public void Delegate_Accessibility_Update() } [Fact] - public void Delegate_ReturnType_Update() + public void Delegate_ReturnType_Update_RuntimeTypeChanged() { var src1 = "public delegate int D();"; var src2 = "public delegate void D();"; @@ -4799,6 +4843,24 @@ public void Delegate_ReturnType_Update() Diagnostic(RudeEditKind.TypeUpdate, "public delegate void D()", FeaturesResources.delegate_)); } + [Fact] + public void Delegate_ReturnType_Update_RuntimeTypeUnchanged() + { + var src1 = "public delegate object D();"; + var src2 = "public delegate dynamic D();"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.Invoke")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.EndInvoke")) + }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + [Fact] public void Delegate_ReturnType_AddAttribute() { @@ -4815,8 +4877,9 @@ public void Delegate_ReturnType_AddAttribute() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.Invoke")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.BeginInvoke")) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.EndInvoke")) }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } @@ -4833,7 +4896,7 @@ public void Delegate_Parameter_Insert() "Insert [int a]@22"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "int a", FeaturesResources.parameter)); + Diagnostic(RudeEditKind.TypeUpdate, "int a", GetResource("delegate"))); } [Fact] @@ -4861,7 +4924,7 @@ public void Delegate_Parameter_Delete() "Delete [int a]@22"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "public delegate int D()", DeletedSymbolDisplay(FeaturesResources.parameter, "int a"))); + Diagnostic(RudeEditKind.TypeUpdate, "public delegate int D()", GetResource("delegate"))); } [Fact] @@ -4889,7 +4952,7 @@ public void Delegate_Parameter_Rename() } [Fact] - public void Delegate_Parameter_Update() + public void Delegate_Parameter_Update_Type_RuntimeTypeChanged() { var src1 = "public delegate int D(int a);"; var src2 = "public delegate int D(byte a);"; @@ -4900,11 +4963,24 @@ public void Delegate_Parameter_Update() "Update [int a]@22 -> [byte a]@22"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.TypeUpdate, "byte a", GetResource("parameter"))); + Diagnostic(RudeEditKind.TypeUpdate, "byte a", GetResource("delegate"))); + } + + [Fact] + public void Delegate_Parameter_Update_Type_RuntimeTypeUnchanged() + { + var src1 = "public delegate int D(object a);"; + var src2 = "public delegate int D(dynamic a);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.BeginInvoke")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.Invoke"))); } [Fact] - public void Delegate_Parameter_AddAttribute() + public void Delegate_Parameter_Update_Attribute() { var attribute = "public class A : System.Attribute { }\n\n"; @@ -5092,7 +5168,7 @@ public void Delegate_Attribute_Add() [Fact] public void Delegate_Attribute_Add_WithReturnTypeAttribute() { - var attribute = "public class AAttribute : System.Attribute { }\n\n"; + var attribute = "public class A : System.Attribute { }\n\n"; var src1 = attribute + "public delegate int D(int a);"; var src2 = attribute + "[return: A][A]public delegate int D(int a);"; @@ -5100,14 +5176,14 @@ public void Delegate_Attribute_Add_WithReturnTypeAttribute() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [public delegate int D(int a);]@48 -> [[return: A][A]public delegate int D(int a);]@48"); + "Update [public delegate int D(int a);]@39 -> [[return: A][A]public delegate int D(int a);]@39"); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.Invoke")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.BeginInvoke")) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D.EndInvoke")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("D")), }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } @@ -5141,7 +5217,7 @@ public void Delegate_ReadOnlyRef_Parameter_InsertParameter() "Insert [in int b]@22"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "in int b", FeaturesResources.parameter)); + Diagnostic(RudeEditKind.TypeUpdate, "in int b", GetResource("delegate"))); } [Fact] @@ -5156,7 +5232,7 @@ public void Delegate_ReadOnlyRef_Parameter_Update() "Update [int b]@22 -> [in int b]@22"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "in int b", GetResource("parameter"))); + Diagnostic(RudeEditKind.TypeUpdate, "in int b", GetResource("delegate"))); } [Fact] @@ -5832,10 +5908,7 @@ public void NestedDelegateInPartialType_InsertDeleteAndChangeParameters() new[] { DocumentResults( - diagnostics: new[] - { - Diagnostic(RudeEditKind.ChangingParameterTypes, "delegate void D(int x)", FeaturesResources.delegate_) - }), + diagnostics: [Diagnostic(RudeEditKind.TypeUpdate, "delegate void D(int x)", GetResource("delegate"))]), DocumentResults() }); @@ -5915,7 +5988,7 @@ public void NestedPartialTypeInPartialType_InsertDeleteAndChange() } [Fact] - public void Type_Partial_AddMultiple() + public void Type_Insert_Partial_Multiple() { var srcA1 = ""; var srcB1 = ""; @@ -5939,6 +6012,24 @@ public void Type_Partial_AddMultiple() capabilities: EditAndContinueCapabilities.NewTypeDefinition); } + [Fact] + public void Type_Delete_Partial_Multiple() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { }"; + + var srcA2 = ""; + var srcB2 = ""; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults(diagnostics: [Diagnostic(RudeEditKind.Delete, null, GetResource("class", "C"))]), + DocumentResults(diagnostics: [Diagnostic(RudeEditKind.Delete, null, GetResource("class", "C"))]), + ], + capabilities: EditAndContinueCapabilities.NewTypeDefinition); + } + [Fact] public void Type_Partial_InsertDeleteAndChange_Attribute() { @@ -6866,17 +6957,19 @@ event Action E { add {} remove {} } DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("M"), preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P1").GetMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P1").SetMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P2").GetMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("P2").SetMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Int32").GetMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Int32").SetMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Byte").GetMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("this[]").Cast().Single(m => m.GetParameters().Single().Type.Name == "Byte").SetMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("E").AddMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("E").RemoveMethod, preserveLocalVariables: false), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.M")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P1").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P1").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P2").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.P2").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters[0].Type.Name == "Int32").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters[0].Type.Name == "Int32").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters[0].Type.Name == "Int32")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters[0].Type.Name == "Byte").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters[0].Type.Name == "Byte").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters[0].Type.Name == "Byte")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.E").AddMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.E").RemoveMethod), }) }); } @@ -7050,18 +7143,101 @@ public void PartialMember_RenameInsertDelete() { DocumentResults(semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F2")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F2")), }), DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F1")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F1")), }) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51011")] + public void PartialMember_RenameInsertDelete_SameFile() + { + var src1 = """ + partial class C { void F1(int a) {} void F4(int d) {} } + partial class C { void F3(int c) {} void F2(int b) {} } + partial class C { } + """; + var src2 = """ + partial class C { void F2(int b) {} void F4(int d) {} } + partial class C { void F1(int a) {} } + partial class C { void F3(int c) {} } + """; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [void F1(int a) {}]@18 -> [void F2(int b) {}]@18", + "Update [void F3(int c) {}]@75 -> [void F1(int a) {}]@75", + "Insert [void F3(int c) {}]@114", + "Insert [(int c)]@121", + "Update [int a]@26 -> [int b]@26", + "Update [int c]@83 -> [int a]@83", + "Insert [int c]@122", + "Delete [void F2(int b) {}]@93", + "Delete [(int b)]@100", + "Delete [int b]@101"); + + edits.VerifySemantics( + [ + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F2")), + ]); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51011")] + public void PartialMember_SignatureChangeInsertDelete() + { + var srcA1 = "partial class C { void F(byte x) {} }"; + var srcB1 = "partial class C { void F(char x) {} }"; + var srcA2 = "partial class C { void F(char x) {} }"; + var srcB2 = "partial class C { void F(byte x) {} }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.F").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Char }]))]), + + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.F").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Byte }]))]), + }); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/51011")] + public void PartialMember_SignatureChangeInsertDelete_Indexer() + { + var srcA1 = "partial class C { int this[byte x] { get => 1; set {} } }"; + var srcB1 = "partial class C { int this[char x] { get => 1; set {} } }"; + var srcA2 = "partial class C { int this[char x] { get => 1; set {} } }"; + var srcB2 = "partial class C { int this[byte y] { get => 1; set {} } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(semanticEdits: + [ + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Char }])), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Char }]).GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Char }]).SetMethod), + ]), + + DocumentResults( + semanticEdits: + [ + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Byte }])), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Byte }]).GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Byte }]).SetMethod), + ]) + }); + } + [Fact] public void PartialMember_DeleteInsert_UpdateMethodBodyError() { @@ -7374,8 +7550,8 @@ struct S public readonly int M() => 1; }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly int M()", FeaturesResources.method)); + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.M"))); } [Fact] @@ -7500,7 +7676,7 @@ public async Task WaitAsync() [InlineData("string", "string?")] [InlineData("object", "dynamic")] [InlineData("(int a, int b)", "(int a, int c)")] - public void Method_ReturnType_Update_RuntimeTypeUnchanged(string oldType, string newType) + public void Method_Update_ReturnType_RuntimeTypeUnchanged(string oldType, string newType) { var src1 = "class C { " + oldType + " M() => default; }"; var src2 = "class C { " + newType + " M() => default; }"; @@ -7515,7 +7691,7 @@ public void Method_ReturnType_Update_RuntimeTypeUnchanged(string oldType, string [InlineData("int", "string")] [InlineData("int", "int?")] [InlineData("(int a, int b)", "(int a, double b)")] - public void Method_ReturnType_Update_RuntimeTypeChanged(string oldType, string newType) + public void Method_Update_ReturnType_RuntimeTypeChanged(string oldType, string newType) { var src1 = "class C { " + oldType + " M() => default; }"; var src2 = "class C { " + newType + " M() => default; }"; @@ -7531,12 +7707,12 @@ public void Method_ReturnType_Update_RuntimeTypeChanged(string oldType, string n capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, newType + " M()", FeaturesResources.method) }, + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, newType + " M()", FeaturesResources.method) }, capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Method_ReturnType_Update_AndBodyChange() + public void Method_Update_ReturnType_WithBodyChange() { var src1 = "class C { int M() => 1; }"; var src2 = "class C { char M() => 'a'; }"; @@ -7552,7 +7728,79 @@ public void Method_ReturnType_Update_AndBodyChange() capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "char M()", FeaturesResources.method) }, + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "char M()", FeaturesResources.method) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Method_Update_ReturnType_Tuple() + { + var src1 = "class C { (int, int) M() { throw new System.Exception(); } }"; + var src2 = "class C { (string, int) M() { throw new System.Exception(); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [(int, int) M() { throw new System.Exception(); }]@10 -> [(string, int) M() { throw new System.Exception(); }]@10"); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "(string, int) M()", FeaturesResources.method) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Method_Update_ReturnType_Tuple_ElementDelete() + { + var src1 = "class C { (int, int, int a) M() { return (1, 2, 3); } }"; + var src2 = "class C { (int, int) M() { return (1, 2); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [(int, int, int a) M() { return (1, 2, 3); }]@10 -> [(int, int) M() { return (1, 2); }]@10"); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "(int, int) M()", FeaturesResources.method) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Method_Update_ReturnType_Tuple_ElementAdd() + { + var src1 = "class C { (int, int) M() { return (1, 2); } }"; + var src2 = "class C { (int, int, int a) M() { return (1, 2, 3); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [(int, int) M() { return (1, 2); }]@10 -> [(int, int, int a) M() { return (1, 2, 3); }]@10"); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "(int, int, int a) M()", FeaturesResources.method) }, capabilities: EditAndContinueCapabilities.Baseline); } @@ -8206,7 +8454,7 @@ public void MethodInsertDelete1() } [Fact] - public void MethodUpdate_AddParameter() + public void Method_Update_Parameter_Insert() { var src1 = @" class C @@ -8232,18 +8480,18 @@ static void Main(string[] args) edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.Main").FirstOrDefault(m => m.GetParameterCount() == 0)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C.Main").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Main"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Main")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "string[] args", FeaturesResources.parameter) }, + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "string[] args", GetResource("method"))], capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void MethodUpdate_AddParameters() + public void Method_Update_Parameter_Insert_Multiple() { var src1 = @" class C @@ -8264,24 +8512,20 @@ void M(int a, int b, int c) edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterCount() == 3)?.ISymbol) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] - { - Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int b", FeaturesResources.parameter), - Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int c", FeaturesResources.parameter) - }, + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "int b", GetResource("method"))], capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void MethodUpdate_AddParameter_Partial() + public void Method_Update_Parameter_Partial() { - var src1 = @" + var src1 = @" class C { partial void M(int a); @@ -8293,9 +8537,9 @@ partial void M(int a) var src2 = @" class C { - partial void M(int a, int b, int c); + partial void M(int a, int/*1*/b, int c); - partial void M(int a, int b, int c) + partial void M(int a, int/*2*/b, int c) { } }"; @@ -8304,22 +8548,21 @@ partial void M(int a, int b, int c) edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => ((IMethodSymbol)c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterCount() == 3)?.ISymbol)?.PartialImplementationPart) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M").PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C"), partialType: "C"), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M").PartialImplementationPart, partialType: "C") }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] - { - Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int b", FeaturesResources.parameter), - Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int c", FeaturesResources.parameter) - }, + [ + Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "int/*1*/b", GetResource("method")), + Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "int/*2*/b", GetResource("method")) + ], capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void MethodUpdate_ChangeParameterType() + public void Method_Update_Type() { var src1 = @" class C @@ -8351,12 +8594,12 @@ static void Main(int x) capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "int x", FeaturesResources.parameter) }, + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "int x", GetResource("method"))], capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void MethodUpdate_ChangeParameterTypeAndRename() + public void Method_Update_Parameter_Type_WithRename() { var src1 = @" class C @@ -8389,7 +8632,7 @@ static void Main(int someInt) } [Fact] - public void MethodUpdate_DeleteParameter() + public void Method_Update_Parameter_Delete() { var src1 = @" class C @@ -8415,18 +8658,18 @@ static void Main() edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.Main").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C.Main").FirstOrDefault(m => m.GetParameterCount() == 0)?.ISymbol) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Main"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Main")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "static void Main()", GetResource("parameter", "args", "method", "Main(string[] args)")) }, + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "static void Main()", GetResource("method"))], capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void MethodUpdate_DeleteParameters() + public void Method_Update_Parameter_Delete_Multiple() { var src1 = @" class C @@ -8447,22 +8690,21 @@ void M(int a) edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterCount() == 3)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( new[] { - 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)")) + Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "void M(int a)", GetResource("method")) }, capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void MethodUpdate_UpdateParameter() + public void Method_Update_Parameter_Rename() { var src1 = @" class C @@ -8498,7 +8740,7 @@ static void Main(string[] b) } [Fact] - public void MethodUpdate_UpdateParameterAndBody() + public void Method_Update_Parameter_Rename_WithBodyUpdate() { var src1 = @" class C @@ -9667,40 +9909,45 @@ public void Method_ReadOnlyRef_Parameter_InsertWhole() [Fact] public void Method_ReadOnlyRef_Parameter_InsertParameter() { - var src1 = "class Test { int M() => throw null; }"; - var src2 = "class Test { int M(in int b) => throw null; }"; + var src1 = "class C { int M() => throw null; }"; + var src2 = "class C { int M(in int b) => throw null; }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Insert [in int b]@19"); + "Insert [in int b]@16"); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("Test.M").FirstOrDefault(m => m.GetParameterCount() == 0)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("Test")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("Test.M").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "in int b", FeaturesResources.parameter) }, + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "in int b", GetResource("method"))], capabilities: EditAndContinueCapabilities.Baseline); } [Fact] public void Method_ReadOnlyRef_Parameter_Update() { - var src1 = "class Test { int M(int b) => throw null; }"; - var src2 = "class Test { int M(in int b) => throw null; }"; + var src1 = "class C { int M(int b) => throw null; }"; + var src2 = "class C { int M(in int b) => throw null; }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int b]@19 -> [in int b]@19"); + "Update [int b]@16 -> [in int b]@16"); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "in int b", FeaturesResources.parameter)); + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] @@ -9739,7 +9986,7 @@ public void Method_ReadOnlyRef_ReturnType_Update() capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "ref readonly int M()", FeaturesResources.method) }, + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "ref readonly int M()", FeaturesResources.method) }, capabilities: EditAndContinueCapabilities.Baseline); } @@ -9822,7 +10069,8 @@ public void Method_Partial_DeleteInsert_DefinitionPart() { DocumentResults(), DocumentResults(), - DocumentResults(), + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C")]), }); } @@ -9844,7 +10092,7 @@ public void Method_Partial_DeleteInsert_ImplementationPart() DocumentResults(), DocumentResults(), DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F").PartialImplementationPart) }), + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F").PartialImplementationPart, partialType: "C") }), }); } @@ -9862,8 +10110,9 @@ public void Method_Partial_Swap_ImplementationAndDefinitionParts() new[] { DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F").PartialImplementationPart) }), - DocumentResults(), + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F").PartialImplementationPart, partialType: "C") }), + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F").PartialImplementationPart, partialType: "C") }), }); } @@ -9885,7 +10134,7 @@ public void Method_Partial_DeleteImplementation() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").GetMember("F"), deletedSymbolContainerProvider: c => c.GetMember("C")) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.F"), deletedSymbolContainerProvider: c => c.GetMember("C"), partialType: "C") }), }); } @@ -9903,13 +10152,10 @@ public void Method_Partial_DeleteBoth() new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - DocumentResults(), - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").GetMember("F")?.PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C")) - }), + semanticEdits: [SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.F").PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C"), partialType: "C")]), + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.F").PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C"), partialType: "C")]), }); } @@ -9932,9 +10178,10 @@ public void Method_Partial_DeleteInsertBoth() { DocumentResults(), DocumentResults(), - DocumentResults(), DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F").PartialImplementationPart) }) + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C") }), + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C") }) }); } @@ -9952,9 +10199,8 @@ public void Method_Partial_Insert() new[] { DocumentResults(), - DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").GetMember("F").PartialImplementationPart) }), + semanticEdits: [SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F").PartialImplementationPart)]), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } @@ -9972,13 +10218,158 @@ public void Method_Partial_Insert_Reloadable() new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - DocumentResults(), + DocumentResults( + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C"), partialType: "C") }), DocumentResults( semanticEdits: new[] { SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C"), partialType: "C") }), }, capabilities: EditAndContinueCapabilities.NewTypeDefinition); } + [Fact] + public void Method_Partial_Update_Attribute_Definition() + { + var attribute = """ + public class A : System.Attribute { public A(int x) {} } + """; + + var srcA1 = attribute + + "partial class C { [A(1)]partial void F(); }"; + var srcB1 = "partial class C { partial void F() { } }"; + + var srcA2 = attribute + + "partial class C { [A(2)]partial void F(); }"; + var srcB2 = "partial class C { partial void F() { } }"; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C")]), + DocumentResults(), + ], + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Fact] + public void Method_Partial_Update_Attribute_Implementation() + { + var attribute = """ + public class A : System.Attribute { public A(int x) {} } + """; + + var srcA1 = attribute + + "partial class C { partial void F(); }"; + var srcB1 = "partial class C { [A(1)]partial void F() { } }"; + + var srcA2 = attribute + + "partial class C { partial void F(); }"; + var srcB2 = "partial class C { [A(2)]partial void F() { } }"; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults(), + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C")]), + ], + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Fact] + public void Method_Partial_Update_Attribute_DefinitionAndImplementation() + { + var attribute = """ + public class A : System.Attribute { public A(int x) {} } + """; + + var srcA1 = attribute + + "partial class C { [A(1)]partial void F(); }"; + var srcB1 = "partial class C { [A(1)]partial void F() { } }"; + + var srcA2 = attribute + + "partial class C { [A(2)]partial void F(); }"; + var srcB2 = "partial class C { [A(2)]partial void F() { } }"; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C")]), + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C")]), + ], + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Fact] + public void Method_Partial_DeleteInsert_DefinitionWithAttributeChange() + { + var attribute = """ + public class A : System.Attribute {} + """; + + var srcA1 = attribute + + "partial class C { [A]partial void F(); }"; + var srcB1 = "partial class C { partial void F() { } }"; + + var srcA2 = attribute + + "partial class C { }"; + var srcB2 = "partial class C { partial void F() { } partial void F(); }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C") + }), + }, + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Fact] + public void Method_Partial_Parameter_TypeChange() + { + var srcA1 = "partial class C { partial void F(long x); }"; + var srcB1 = "partial class C { partial void F(long x) { } }"; + + var srcA2 = "partial class C { partial void F(byte x); }"; + var srcB2 = "partial class C { partial void F(byte x) { } }"; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults( + semanticEdits: + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.F").PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C"), partialType: "C"), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C") + ]), + DocumentResults( + semanticEdits: + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.F").PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C"), partialType: "C"), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C") + ]), + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults( + diagnostics: [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "byte x", GetResource("method"))]), + DocumentResults( + diagnostics: [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "byte x", GetResource("method"))]), + ], + capabilities: EditAndContinueCapabilities.Baseline); + } + #endregion #region Operators @@ -10318,21 +10709,6 @@ public void Operator_ReadOnlyRef_Parameter_InsertWhole() Diagnostic(RudeEditKind.InsertOperator, "public static bool operator !(in Test b)", FeaturesResources.operator_)); } - [Fact] - public void Operator_ReadOnlyRef_Parameter_Update() - { - var src1 = "class Test { public static bool operator !(Test b) => throw null; }"; - var src2 = "class Test { public static bool operator !(in Test b) => throw null; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [Test b]@43 -> [in Test b]@43"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "in Test b", FeaturesResources.parameter)); - } - [Fact] public void Operator_Delete() { @@ -10362,7 +10738,7 @@ public void Constructor_Parameter_AddAttribute() ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C..ctor")) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C..ctor"), preserveLocalVariables: true) }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } @@ -10379,30 +10755,49 @@ public void Constructor_Parameter_AddAttribute_Primary() ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C..ctor")) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C..ctor"), preserveLocalVariables: true) }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } - [Theory] - [InlineData("")] - [InlineData("param:")] - [WorkItem("https://github.com/dotnet/roslyn/issues/68458")] - public void Constructor_Parameter_AddAttribute_Record(string target) + [Fact] + public void Constructor_Parameter_Update_Attribute_Record_ParamTarget() { - var src1 = "record C(int P);" + s_attributeSource; - var src2 = "record C([" + target + "A]int P);" + s_attributeSource; + var src1 = "record C([param: A(1)] int P);" + s_attributeSource; + var src2 = "record C([param: A(2)] int P);" + s_attributeSource; var edits = GetTopEdits(src1, src2); + // Attribute is only applied to the parameter. + // Currently we don't filter the property update out. edits.VerifySemantics( ActiveStatementsDescription.Empty, - new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C")), + [ + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_P")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_P")), - }, + ], + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + } + + [Fact] + public void Constructor_Parameter_Update_Attribute_Record_PropertyTarget() + { + var src1 = "record C([property: A(1)] int P);" + s_attributeSource; + var src2 = "record C([property: A(2)] int P);" + s_attributeSource; + + var edits = GetTopEdits(src1, src2); + + // Attribute is only applied to the parameter. + // Currently we don't filter the constructor update out. + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + [ + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), + 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.ChangeCustomAttributes); } @@ -10451,6 +10846,7 @@ public void Constructor_Parameter_AddAttribute_Record_ReplacingSynthesizedWithCu var edits = GetTopEdits(src1, src2); + // We update more members than strictly necessary to avoid more complex analysis. edits.VerifySemantics( ActiveStatementsDescription.Empty, new[] @@ -10459,9 +10855,9 @@ public void Constructor_Parameter_AddAttribute_Record_ReplacingSynthesizedWithCu SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("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")), + SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), // TOOD: Should include update of P: https://github.com/dotnet/roslyn/issues/68458 }, capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); @@ -10479,36 +10875,45 @@ public void Constructor_Parameter_AddAttribute_Record_ReplacingCustomPropertyWit ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("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); } - [Fact] - public void Constructor_Parameter_ChangeType() + [Theory] + [CombinatorialData] + public void Constructor_Parameter_Update_TypeOrRefKind_RuntimeTypeChanged( + [CombinatorialValues("int", "in byte", "ref byte", "out byte", "ref readonly byte")] string type, + bool direction) { - var src1 = "class C { public C(bool x) { } }"; - var src2 = "class C { public C(int x) { } }"; + var (oldType, newType) = direction ? (type, "byte") : ("byte", type); + + var src1 = "class C { C(" + oldType + " a) => throw null!; }"; + var src2 = "class C { C(" + newType + " a) => throw null!; }"; + var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - new[] - { + 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); + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "int x", FeaturesResources.parameter) }, + new[] + { + Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, newType + " a", GetResource("constructor")) + }, capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Constructor_Parameter_ChangeType_Primary() + public void Constructor_Parameter_Update_Type_Primary() { var src1 = @"class C(bool x);"; var src2 = @"class C(int x);"; @@ -10523,12 +10928,12 @@ public void Constructor_Parameter_ChangeType_Primary() capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "int x", FeaturesResources.parameter) }, + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "int x", GetResource("constructor"))], capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Constructor_Parameter_ChangeType_Primary_PartialMove() + public void Constructor_Parameter_Update_Type_Primary_PartialMove() { var srcA1 = "partial class C(bool a);"; var srcB1 = "partial class C;"; @@ -10549,14 +10954,14 @@ public void Constructor_Parameter_ChangeType_Primary_PartialMove() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")) + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C"), partialType: "C") }), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void Constructor_Parameter_ChangeType_Record() + public void Constructor_Parameter_Update_Type_Record() { var src1 = @"record C(bool x);"; var src2 = @"record C(int x);"; @@ -10565,31 +10970,45 @@ public void Constructor_Parameter_ChangeType_Record() edits.VerifySemantics( new[] { - 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.x"), deletedSymbolContainerProvider: c => c.GetMember("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.Delete, c => c.GetMember("C.set_x"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.x")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_x")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_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.GetSpecializedEqualsOverload("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.AddInstanceFieldToExistingType); edits.VerifySemanticDiagnostics( - new[] - { - Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "int x", GetResource("parameter")), - Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "int x", GetResource("auto-property")) - }, + [ + Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "int x", GetResource("auto-property")), + Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "int x", GetResource("constructor")) + ], capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Constructor_Parameter_ChangeType_ReplacingClassWithRecord() + public void Constructor_Parameter_Update_Type_Record_TypeLayout() + { + var src1 = @"record struct C(bool x);"; + var src2 = @"record struct C(int x);"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.InsertIntoStruct, "int x", GetResource("auto-property"), GetResource("record struct"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + + [Fact] + public void Constructor_Parameter_Update_Type_ReplacingClassWithRecord() { var src1 = @"class C(bool x);"; var src2 = @"record C(int x);"; @@ -10650,6 +11069,7 @@ public void Constructor_Parameter_Delete_Primary_Record() edits.VerifySemantics( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Y"), deletedSymbolContainerProvider: c => c.GetMember("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")), @@ -10685,6 +11105,7 @@ record C(int X); edits.VerifySemantics( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Y"), deletedSymbolContainerProvider: c => c.GetMember("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")), @@ -10712,6 +11133,7 @@ public void Constructor_Parameter_Delete_Primary_Record_Struct() edits.VerifySemantics( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Y"), deletedSymbolContainerProvider: c => c.GetMember("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")), @@ -10841,10 +11263,11 @@ public void Constructor_Parameter_Delete_Primary_Record_WithCustomPropertyDelete SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("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.Y"), deletedSymbolContainerProvider: c => c.GetMember("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.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } @@ -10860,6 +11283,7 @@ public void Constructor_Parameter_Delete_Primary_Record_WithCustomDeconstructor_ edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Y"), deletedSymbolContainerProvider: c => c.GetMember("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")), @@ -10885,6 +11309,7 @@ public void Constructor_Parameter_Delete_Primary_Record_WithCustomDeconstructor_ new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("C.Deconstruct").Single(m => m.Parameters is [_])), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Y"), deletedSymbolContainerProvider: c => c.GetMember("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")), @@ -10910,6 +11335,7 @@ public void Constructor_Parameter_Delete_Primary_Record_WithCustomDeconstructor_ new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryDeconstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Y"), deletedSymbolContainerProvider: c => c.GetMember("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")), @@ -10934,6 +11360,7 @@ public void Constructor_Parameter_Delete_Primary_Record_WithCustomDeconstructor_ edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Y"), deletedSymbolContainerProvider: c => c.GetMember("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.Insert, c => c.GetPrimaryDeconstructor("C")), @@ -10941,9 +11368,9 @@ public void Constructor_Parameter_Delete_Primary_Record_WithCustomDeconstructor_ SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("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); } @@ -11025,7 +11452,7 @@ public void Constructor_Parameter_Insert() capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int b", FeaturesResources.parameter) }, + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "int b", GetResource("constructor"))], capabilities: EditAndContinueCapabilities.Baseline); } @@ -11051,7 +11478,7 @@ public void Constructor_Parameter_Insert_Primary_Uncaptured(string keyword) capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int b", FeaturesResources.parameter) }, + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "(int a, int b)", GetResource("constructor"))], capabilities: EditAndContinueCapabilities.Baseline); } @@ -11107,10 +11534,10 @@ public void Constructor_Parameter_Insert_Primary_Record() SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("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")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } @@ -11177,7 +11604,7 @@ public void Constructor_Parameter_Insert_In() capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "in int b", FeaturesResources.parameter) }, + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "in int b", GetResource("constructor"))], capabilities: EditAndContinueCapabilities.Baseline); } @@ -11292,13 +11719,47 @@ public void Constructor_Parameter_Insert_Primary_Record_WithCustomDeconstructor_ SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("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 | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } + [Fact] + public void Constructor_Parameter_Insert_Primary_Record_WithCustomDeconstructor_Delete_Partial() + { + var srcA1 = "partial record C(int X ) { public partial void Deconstruct(out int X) => X = 1; }"; + var srcB1 = "partial record C { public partial void Deconstruct(out int X); }"; + + var srcA2 = "partial record C(int X, int Y);"; + var srcB2 = "partial record C;"; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults( + semanticEdits: + [ + 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.GetSpecializedEqualsOverload("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C").PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C"), partialType: "C"), + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + ]), + DocumentResults( + semanticEdits: + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryDeconstructor("C").PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C"), partialType: "C"), + ]), + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + [Fact] public void Constructor_Parameter_Insert_Primary_IntoLayoutClass_NotLifted() { @@ -11575,21 +12036,6 @@ class C(int x, int y) Diagnostic(RudeEditKind.NotCapturingPrimaryConstructorParameter, "M2", GetResource("class with explicit or sequential layout"), "x")); } - [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("partial class")] [InlineData("partial struct")] @@ -11707,14 +12153,13 @@ public void Constructor_Parameter_DeleteInsert_ReplacingNonPrimaryWithPrimary_Re var srcB2 = "partial record C(int P);"; EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - new[] - { + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ DocumentResults(), DocumentResults( - semanticEdits: new[] - { + semanticEdits: + [ 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), @@ -11723,8 +12168,8 @@ public void Constructor_Parameter_DeleteInsert_ReplacingNonPrimaryWithPrimary_Re SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), - }), - }); + ]), + ]); } [Fact] @@ -11772,6 +12217,7 @@ public void Constructor_Parameter_DeleteInsert_ReplacingNonPrimaryWithPrimary_Wi { DocumentResults(semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), }), @@ -11807,6 +12253,7 @@ public void Constructor_Parameter_DeleteInsert_SwappingNonPrimaryWithPrimary_Rec DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), } @@ -11841,6 +12288,7 @@ public void Constructor_Parameter_DeleteInsert_ReplacingPropertyWithField_Record DocumentResults( semanticEdits: new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), }), @@ -12093,7 +12541,7 @@ public void Constructor_Instance_Update_AnonymousTypeInFieldInitializer() [Theory] [InlineData("class ")] [InlineData("record")] - public void Constructor_Instance_Update_AnonymousTypeInFieldInitializer_Primary(string keyword) + public void Constructor_Instance_Update_AnonymousTypeInMemberInitializer_Field_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 }); }"; @@ -12265,7 +12713,7 @@ partial void C(int x) var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("C").PartialImplementationPart)); + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("C").PartialImplementationPart, partialType: "C")); } [Fact] @@ -12327,10 +12775,9 @@ public void Constructor_Instance_Insert_Struct_Primary_Record() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - new[] - { + [ Diagnostic(RudeEditKind.InsertIntoStruct, "int X", GetResource("auto-property"), GetResource("record struct")) - }, + ], capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } @@ -12421,11 +12868,10 @@ public void Constructor_Instance_Insert_ReplacingDefault_Class_Primary_Record_Ge EditAndContinueCapabilities.GenericUpdateMethod); edits.VerifySemanticDiagnostics( - new[] - { + [ Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "(int P)", GetResource("constructor")), - Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int P", GetResource("parameter")), - }, + Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "int P", GetResource("parameter")) + ], capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } @@ -12508,6 +12954,35 @@ public void Constructor_Instance_Insert_ReplacingDefault_WithStackAllocInMemberI }); } + [Fact] + public void Constructor_Instance_Delete_ReplacingDefault_Partial() + { + var srcA1 = "partial class C { }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { public C(int a) { } }"; + var srcB2 = "partial class C { public C(int a, int b) { } }"; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults( + semanticEdits: + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetParameterlessConstructor("C"), partialType: "C", deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(m => m.Parameters.Length == 1), partialType: "C") + ]), + + DocumentResults( + semanticEdits: + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetParameterlessConstructor("C"), partialType: "C", deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").InstanceConstructors.Single(m => m.Parameters.Length == 2), partialType: "C") + ]) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + [Fact] public void Constructor_Instance_Insert_ReplacingExplicitWithDefault_WithStackAllocInMemberInitializer_Partial() { @@ -12631,7 +13106,7 @@ public void Constructor_Instance_Insert_AddingParameterless_Partial( DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Insert, c => c.GetParameterlessConstructor("C")) + SemanticEdit(SemanticEditKind.Insert, c => c.GetParameterlessConstructor("C"), partialType: "C") }), // no change in document B @@ -12786,6 +13261,7 @@ public void Constructor_Instance_Delete_Struct_Primary_Record() SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), }, @@ -12840,12 +13316,13 @@ public void Constructor_Instance_Delete_ReplacingWithDefault_Class_Primary_Recor 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.GetSpecializedEqualsOverload("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.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), }, @@ -12970,12 +13447,13 @@ public void Constructor_Instance_Delete_Primary_Record() 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.GetSpecializedEqualsOverload("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.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), }, @@ -13080,6 +13558,26 @@ public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_AbstractT capabilities: EditAndContinueCapabilities.Baseline); } + [Fact] + public void Constructor_Instance_Delete_ReplacingCustomWithSynthesized_Partial() + { + var srcA1 = "partial class C { public C(int a) { } }"; + var srcB1 = "partial class C { public C(int a, int b) { } }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { }"; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(m => m.Parameters.Length == 1), deletedSymbolContainerProvider: c => c.GetMember("C"))]), + + DocumentResults( + semanticEdits: [SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").InstanceConstructors.Single(m => m.Parameters.Length == 2), deletedSymbolContainerProvider: c => c.GetMember("C"))]) + ]); + } + [Fact] public void Constructor_Instance_Delete_Primary_ReplacingWithSynthesized() { @@ -13705,7 +14203,7 @@ partial class C SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Single().Type.Name == "Int32"), partialType: "C", syntaxMap: syntaxMapB), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Single().Type.Name == "Boolean"), partialType: "C", syntaxMap: syntaxMapB), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Single().Type.Name == "UInt32"), partialType: "C", syntaxMap: syntaxMapB), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Single().Type.Name == "Byte"), syntaxMap: null), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Single().Type.Name == "Byte"), partialType: "C", syntaxMap: null), }) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); @@ -14062,10 +14560,10 @@ public class C #endregion - #region Fields and Properties with Initializers + #region Members with Initializers [Fact] - public void FieldInitializer_Update1() + public void MemberInitializer_Update_Field() { var src1 = "class C { int a = 0; }"; var src2 = "class C { int a = 1; }"; @@ -14081,7 +14579,20 @@ public void FieldInitializer_Update1() } [Fact] - public void PropertyInitializer_Update1() + public void MemberInitializer_Update_Field_Event() + { + var src1 = "class C { event System.Action a = F(0); static System.Action F(int a) => null; }"; + var src2 = "class C { event System.Action a = F(1); static System.Action F(int a) => null; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + ActiveStatementsDescription.Empty, + new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true) }); + } + + [Fact] + public void MemberInitializer_Update_Property() { var src1 = "class C { int a { get; } = 0; }"; var src2 = "class C { int a { get; } = 1; }"; @@ -14097,7 +14608,7 @@ public void PropertyInitializer_Update1() } [Fact] - public void FieldInitializer_Update2() + public void MemberInitializer_Update_Remove_Field() { var src1 = "class C { int a = 0; }"; var src2 = "class C { int a; }"; @@ -14111,24 +14622,112 @@ public void FieldInitializer_Update2() } [Fact] - public void PropertyInitializer_Update2() + public void MemberInitializer_Update_Remove_Partial_Field() { - var src1 = "class C { int a { get; } = 0; }"; - var src2 = "class C { int a { get { return 1; } } }"; - - var edits = GetTopEdits(src1, src2); + var srcA1 = "partial class C { int F = 1; }"; + var srcB1 = "partial class C { }"; - edits.VerifyEdits( - "Update [int a { get; } = 0;]@10 -> [int a { get { return 1; } }]@10", - "Update [get;]@18 -> [get { return 1; }]@18"); + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int F ; }"; - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), preserveLocalVariables: true)); - } + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) + }), + }); + } + + [Fact] + public void MemberInitializer_Update_Remove_Partial_Property() + { + var srcA1 = "partial class C { int F { get; } = 1; }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int F { get; } }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_F")) + }), + }); + } + + [Fact] + public void MemberInitializer_Update_DeleteInsert_Field() + { + var srcA1 = "partial class C { int F = 1; }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int F = 2; }"; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults(), + DocumentResults( + semanticEdits: + [ + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true), + ]), + ]); + } + + [Fact] + public void MemberInitializer_Update_DeleteInsert_Property() + { + var srcA1 = "partial class C { int F { get; } = 1; }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int F { get; } = 2; }"; + + EditAndContinueValidation.VerifySemantics( + [GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)], + [ + DocumentResults(), + DocumentResults( + semanticEdits: + [ + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true), + ]), + ]); + } + + [Fact] + public void MemberInitializer_PropertyUpdate2() + { + var src1 = "class C { int a { get; } = 0; }"; + var src2 = "class C { int a { get { return 1; } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [int a { get; } = 0;]@10 -> [int a { get { return 1; } }]@10", + "Update [get;]@18 -> [get { return 1; }]@18"); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.a").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(), preserveLocalVariables: true)); + } [Fact] - public void PropertyInitializer_InsertDelete() + public void MemberInitializer_PropertyInsertDelete() { var srcA1 = "partial class C { }"; var srcB1 = "partial class C { int a { get; } = 1; }"; @@ -14151,7 +14750,7 @@ public void PropertyInitializer_InsertDelete() } [Fact] - public void FieldInitializer_Update3() + public void MemberInitializer_Field_Update3() { var src1 = "class C { int a; }"; var src2 = "class C { int a = 0; }"; @@ -14167,7 +14766,7 @@ public void FieldInitializer_Update3() } [Fact] - public void PropertyInitializer_Update3() + public void MemberInitializer_PropertyUpdate3() { var src1 = "class C { int a { get { return 1; } } }"; var src2 = "class C { int a { get; } = 0; }"; @@ -14202,7 +14801,7 @@ public void FieldInitializer_Update_AccessPrimaryConstructorParameter(string key } [Fact] - public void FieldInitializer_Delete() + public void MemberInitializer_Field_Delete() { var src1 = "class C { int a = 1; }"; var src2 = "class C { }"; @@ -14217,7 +14816,7 @@ public void FieldInitializer_Delete() [InlineData("")] [InlineData("public C() { }")] [InlineData("public C(int x) { }")] - public void PropertyInitializer_Delete(string ctor) + public void MemberInitializer_PropertyDelete(string ctor) { var src1 = "class C { " + ctor + " int a { get; set; } = 1; }"; var src2 = "class C { " + ctor + " }"; @@ -14228,6 +14827,7 @@ public void PropertyInitializer_Delete(string ctor) ActiveStatementsDescription.Empty, new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.a"), deletedSymbolContainerProvider: c => c.GetMember("C")), 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) @@ -14235,7 +14835,7 @@ public void PropertyInitializer_Delete(string ctor) } [Fact] - public void FieldInitializerUpdate_StaticCtorUpdate1() + public void MemberInitializer_Update_Field_StaticCtorUpdate1() { var src1 = "class C { static int a; static C() { } }"; var src2 = "class C { static int a = 0; }"; @@ -14253,7 +14853,7 @@ public void FieldInitializerUpdate_StaticCtorUpdate1() } [Fact] - public void PropertyInitializerUpdate_StaticCtorUpdate1() + public void MemberInitializer_Update_Property_StaticCtorUpdate1() { var src1 = "class C { static int a { get; } = 1; static C() { } }"; var src2 = "class C { static int a { get; } = 2;}"; @@ -14266,7 +14866,7 @@ public void PropertyInitializerUpdate_StaticCtorUpdate1() } [Fact] - public void FieldInitializerUpdate_InstanceCtorUpdate_Private() + public void MemberInitializer_Update_Field_InstanceCtorUpdate_Private() { var src1 = "class C { int a; [System.Obsolete]C() { } }"; var src2 = "class C { int a = 0; }"; @@ -14283,7 +14883,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate_Private() } [Fact] - public void PropertyInitializerUpdate_InstanceCtorUpdate_Private() + public void MemberInitializer_Update_Property_InstanceCtorUpdate_Private() { var src1 = "class C { int a { get; } = 1; C() { } }"; var src2 = "class C { int a { get; } = 2; }"; @@ -14295,7 +14895,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate_Private() } [Fact] - public void FieldInitializerUpdate_InstanceCtorUpdate_Public() + public void MemberInitializer_Update_Field_InstanceCtorUpdate_Public() { var src1 = "class C { int a; public C() { } }"; var src2 = "class C { int a = 0; }"; @@ -14308,7 +14908,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate_Public() } [Fact] - public void PropertyInitializerUpdate_InstanceCtorUpdate_Public() + public void MemberInitializer_Update_Property_InstanceCtorUpdate_Public() { var src1 = "class C { int a { get; } = 1; public C() { } }"; var src2 = "class C { int a { get; } = 2; }"; @@ -14321,7 +14921,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate_Public() } [Fact] - public void FieldInitializerUpdate_StaticCtorUpdate2() + public void MemberInitializer_Update_Field_StaticCtorUpdate2() { var src1 = "class C { static int a; static C() { } }"; var src2 = "class C { static int a = 0; static C() { } }"; @@ -14337,7 +14937,7 @@ public void FieldInitializerUpdate_StaticCtorUpdate2() } [Fact] - public void PropertyInitializerUpdate_StaticCtorUpdate2() + public void MemberInitializer_Update_Property_StaticCtorUpdate2() { var src1 = "class C { static int a { get; } = 1; static C() { } }"; var src2 = "class C { static int a { get; } = 2; static C() { } }"; @@ -14352,7 +14952,7 @@ public void PropertyInitializerUpdate_StaticCtorUpdate2() [Theory] [InlineData("class ")] [InlineData("struct")] - public void FieldInitializerUpdate_InstanceCtorUpdate2(string typeKind) + public void MemberInitializer_Update_Field_InstanceCtorUpdate2(string typeKind) { var src1 = typeKind + " C { int a; public C() { } }"; var src2 = typeKind + " C { int a = 0; public C() { } }"; @@ -14371,7 +14971,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate2(string typeKind) [InlineData("class")] [InlineData("struct")] [InlineData("readonly struct")] - public void PropertyInitializerUpdate_InstanceCtorUpdate2(string typeKind) + public void MemberInitializer_Update_Property_InstanceCtorUpdate2(string typeKind) { var src1 = typeKind + " C { int a { get; } = 1; public C() { } }"; var src2 = typeKind + " C { int a { get; } = 2; public C() { } }"; @@ -14384,7 +14984,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate2(string typeKind) } [Fact] - public void FieldInitializerUpdate_InstanceCtorUpdate3() + public void MemberInitializer_Update_Field_InstanceCtorUpdate3() { var src1 = "class C { int a; }"; var src2 = "class C { int a = 0; }"; @@ -14400,7 +15000,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate3() } [Fact] - public void PropertyInitializerUpdate_InstanceCtorUpdate3() + public void MemberInitializer_Update_Property_InstanceCtorUpdate3() { var src1 = "class C { int a { get; } = 1; }"; var src2 = "class C { int a { get; } = 2; }"; @@ -14413,7 +15013,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate3() } [Fact] - public void FieldInitializerUpdate_InstanceCtorUpdate4() + public void MemberInitializer_Update_Field_InstanceCtorUpdate4() { var src1 = "class C { int a = 0; }"; var src2 = "class C { int a; }"; @@ -14429,7 +15029,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate4() } [Fact] - public void FieldInitializerUpdate_InstanceCtorUpdate_Class() + public void MemberInitializer_Update_Field_InstanceCtorUpdate_Class() { 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() { } }"; @@ -14449,7 +15049,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate_Class() } [Fact] - public void FieldInitializerUpdate_InstanceCtorUpdate_Struct() + public void MemberInitializer_Update_Field_InstanceCtorUpdate_Struct() { 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) { } }"; @@ -14469,7 +15069,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate_Struct() } [Fact] - public void PropertyInitializerUpdate_InstanceCtorUpdate5() + public void MemberInitializer_Update_Property_InstanceCtorUpdate5() { var src1 = "class C { int a { get; } = 1; private C(int a) { } private C(bool a) { } }"; var src2 = "class C { int a { get; } = 10000; private C(int a) { } private C(bool a) { } }"; @@ -14486,7 +15086,7 @@ public void PropertyInitializerUpdate_InstanceCtorUpdate5() } [Fact] - public void PropertyInitializerUpdate_Struct_InstanceCtorUpdate5() + public void MemberInitializer_Update_Property_Struct_InstanceCtorUpdate5() { var src1 = "struct C { int a { get; } = 1; private C(int a) { } private C(bool a) { } }"; var src2 = "struct C { int a { get; } = 10000; private C(int a) { } private C(bool a) { } }"; @@ -14503,7 +15103,7 @@ public void PropertyInitializerUpdate_Struct_InstanceCtorUpdate5() } [Fact] - public void FieldInitializerUpdate_InstanceCtorUpdate6() + public void MemberInitializer_Update_Field_InstanceCtorUpdate6() { var src1 = "class C { int a; private C(int a) : this(true) { } private C(bool a) { } }"; var src2 = "class C { int a = 0; private C(int a) : this(true) { } private C(bool a) { } }"; @@ -14522,7 +15122,7 @@ public void FieldInitializerUpdate_InstanceCtorUpdate6() } [Fact] - public void FieldInitializerUpdate_StaticCtorInsertImplicit() + public void MemberInitializer_Update_Field_StaticCtorInsertImplicit() { var src1 = "class C { static int a; }"; var src2 = "class C { static int a = 0; }"; @@ -14538,7 +15138,7 @@ public void FieldInitializerUpdate_StaticCtorInsertImplicit() } [Fact] - public void FieldInitializerUpdate_StaticCtorInsertExplicit() + public void MemberInitializer_Update_Field_StaticCtorInsertExplicit() { var src1 = "class C { static int a; }"; var src2 = "class C { static int a = 0; static C() { } }"; @@ -14559,7 +15159,7 @@ public void FieldInitializerUpdate_StaticCtorInsertExplicit() [InlineData("class")] [InlineData("struct")] [InlineData("readonly struct")] - public void FieldInitializerUpdate_Constructor_Instance_InsertExplicit(string typeKind) + public void MemberInitializer_Update_Field_Constructor_Instance_InsertExplicit(string typeKind) { var src1 = typeKind + " C { int a; }"; var src2 = typeKind + " C { int a = 0; public C() { } }"; @@ -14575,7 +15175,7 @@ public void FieldInitializerUpdate_Constructor_Instance_InsertExplicit(string ty [InlineData("class")] [InlineData("struct")] [InlineData("readonly struct")] - public void PropertyInitializerUpdate_Constructor_Instance_InsertExplicit(string typeKind) + public void MemberInitializer_Update_Property_Constructor_Instance_InsertExplicit(string typeKind) { var src1 = typeKind + " C { int a { get; } = 1; }"; var src2 = typeKind + " C { int a { get; } = 2; public C() { } }"; @@ -14588,7 +15188,7 @@ public void PropertyInitializerUpdate_Constructor_Instance_InsertExplicit(string } [Fact] - public void FieldInitializerUpdate_GenericType() + public void MemberInitializer_Update_Field_GenericType() { var src1 = "class C { int a = 1; }"; var src2 = "class C { int a = 2; }"; @@ -14602,7 +15202,6 @@ public void FieldInitializerUpdate_GenericType() new[] { Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "a = 2", GetResource("field")), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "class C", GetResource("constructor", "C()")) }, capabilities: EditAndContinueCapabilities.Baseline); @@ -14615,7 +15214,7 @@ public void FieldInitializerUpdate_GenericType() } [Fact] - public void PropertyInitializerUpdate_GenericType() + public void MemberInitializer_Update_Property_GenericType() { var src1 = "class C { int a { get; } = 1; }"; var src2 = "class C { int a { get; } = 2; }"; @@ -14623,11 +15222,7 @@ public void PropertyInitializerUpdate_GenericType() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - new[] - { - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "int a", GetResource("auto-property")), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "class C", GetResource("constructor", "C()")), - }, + [Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "int a", GetResource("auto-property"))], capabilities: EditAndContinueCapabilities.Baseline); edits.VerifySemantics( @@ -14639,7 +15234,7 @@ public void PropertyInitializerUpdate_GenericType() } [Fact] - public void FieldInitializerUpdate_StackAllocInConstructor() + public void MemberInitializer_Update_StackAllocInConstructor() { var src1 = "unsafe class C { int a = 1; public C() { int* a = stackalloc int[10]; } }"; var src2 = "unsafe class C { int a = 2; public C() { int* a = stackalloc int[10]; } }"; @@ -14654,7 +15249,7 @@ public void FieldInitializerUpdate_StackAllocInConstructor() } [Fact] - public void FieldInitializerUpdate_StackAllocInConstructor_ThisInitializer() + public void MemberInitializer_Update_StackAllocInConstructor_ThisInitializer() { var src1 = "class C { int a = 1; C() : this(stackalloc int[1]) { } C(System.Span s) { } }"; var src2 = "class C { int a = 2; C() : this(stackalloc int[1]) { } C(System.Span s) { } }"; @@ -14666,13 +15261,15 @@ public void FieldInitializerUpdate_StackAllocInConstructor_ThisInitializer() } [Fact] - public void FieldInitializerUpdate_StackAllocInConstructor_BaseInitializer() + [WorkItem("https://github.com/dotnet/roslyn/issues/68731")] + public void MemberInitializer_Update_StackAllocInConstructor_Initializer_Field() { var src1 = "class C : B { int a = 1; C() : base(stackalloc int[1]) { } } class B(System.Span s);"; var src2 = "class C : B { int a = 2; C() : base(stackalloc int[1]) { } } class B(System.Span s);"; var edits = GetTopEdits(src1, src2); + // TODO: allow https://github.com/dotnet/roslyn/issues/68731 edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("constructor"))); } @@ -14690,20 +15287,21 @@ public void FieldInitializerUpdate_StackAllocInConstructor_PrimaryBaseInitialize [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/67307")] - public void FieldInitializerUpdate_StackAllocInOtherInitializer() + public void MemberInitializer_Update_StackAllocInOtherInitializer() { var src1 = "class C { int a = 1; int b = G(stackalloc int[10]); static int G(Span span) => 1; }"; var src2 = "class C { int a = 2; int b = G(stackalloc int[10]); static int G(Span span) => 1; }"; var edits = GetTopEdits(src1, src2); + // TODO: allow https://github.com/dotnet/roslyn/issues/67307 edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.StackAllocUpdate, "class C", GetResource("constructor", "C()"))); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37172")] [WorkItem("https://github.com/dotnet/roslyn/issues/43099")] - public void FieldInitializerUpdate_SwitchExpressionInConstructor() + public void MemberInitializer_Update_SwitchExpressionInConstructor() { var src1 = "class C { int a = 1; public C() { var b = a switch { 0 => 0, _ => 1 }; } }"; var src2 = "class C { int a = 2; public C() { var b = a switch { 0 => 0, _ => 1 }; } }"; @@ -14714,7 +15312,7 @@ public void FieldInitializerUpdate_SwitchExpressionInConstructor() } [Fact] - public void PropertyInitializerUpdate_StackAlloc_Update() + public void MemberInitializer_Update_StackAlloc_Update() { var src1 = "class C { int a { get; } = G(stackalloc int[10]); static int G(System.Span span) => 1; }"; var src2 = "class C { int a { get; } = G(stackalloc int[20]); static int G(System.Span span) => 1; }"; @@ -14722,14 +15320,14 @@ public void PropertyInitializerUpdate_StackAlloc_Update() var edits = GetTopEdits(src1, src2); // Note: One edit is for the initializer and the other for implicit constructor. - // We don't attempt to avoid duplicates reported for different memebers. + // We don't attempt to avoid duplicates reported for different members. edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[20]", GetResource("auto-property")), Diagnostic(RudeEditKind.StackAllocUpdate, "class C", GetResource("constructor", "C()"))); } [Fact] - public void PropertyInitializerUpdate_StackAlloc_Delete() + public void MemberInitializer_Update_StackAlloc_Delete() { var src1 = "class C { int a { get; } = G(stackalloc int[10]); static int G(System.Span span) => 1; }"; var src2 = "class C { int a { get; } = G(default); static int G(System.Span span) => 1; }"; @@ -14744,7 +15342,7 @@ public void PropertyInitializerUpdate_StackAlloc_Delete() } [Fact] - public void PropertyInitializerUpdate_StackAlloc_Insert() + public void MemberInitializer_Update_StackAlloc_Insert() { var src1 = "class C { int a { get; } = G(default); static int G(System.Span span) => 1; }"; var src2 = "class C { int a { get; } = G(stackalloc int[10]); static int G(System.Span span) => 1; }"; @@ -14756,34 +15354,69 @@ public void PropertyInitializerUpdate_StackAlloc_Insert() } [Fact] - public void PropertyInitializerUpdate_StackAllocInConstructor1() + public void PropertyInitializerUpdate_StackAlloc_Delete() { - var src1 = "unsafe class C { int a { get; } = 1; public C() { int* a = stackalloc int[10]; } }"; - var src2 = "unsafe class C { int a { get; } = 2; public C() { int* a = stackalloc int[10]; } }"; + var src1 = "class C { int a { get; } = G(stackalloc int[10]); static int G(System.Span span) => 1; }"; + var src2 = "class C { int a { get; } = G(default); static int G(System.Span span) => 1; }"; var edits = GetTopEdits(src1, src2); - // TODO (tomat): diagnostic should point to the property initializer + // Note: One edit is for the initializer and the other for implicit constructor. + // We don't attempt to avoid duplicates reported for different memebers. edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[10]", FeaturesResources.constructor)); + Diagnostic(RudeEditKind.StackAllocUpdate, "int a", GetResource("auto-property")), + Diagnostic(RudeEditKind.StackAllocUpdate, "class C", GetResource("constructor", "C()"))); } [Fact] - public void PropertyInitializerUpdate_StackAllocInConstructor2() + public void PropertyInitializerUpdate_StackAlloc_Insert() { - var src1 = "unsafe class C { int a { get; } = 1; public C() : this(1) { int* a = stackalloc int[10]; } public C(int a) { } }"; - var src2 = "unsafe class C { int a { get; } = 2; public C() : this(1) { int* a = stackalloc int[10]; } public C(int a) { } }"; + var src1 = "class C { int a { get; } = G(default); static int G(System.Span span) => 1; }"; + var src2 = "class C { int a { get; } = G(stackalloc int[10]); static int G(System.Span span) => 1; }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(); + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[10]", GetResource("auto-property"))); } - [Fact] - public void PropertyInitializerUpdate_StackAllocInConstructor3() + [Theory] + [InlineData("")] + [InlineData("{ get; }")] + public void MemberInitializer_Update_StackAlloc(string accessor) + { + var src1 = "unsafe class C { int a " + accessor + " = G(stackalloc int[10]); public G(Span span) => 1; }"; + var src2 = "unsafe class C { int a " + accessor + " = G(stackalloc int[20]); public G(Span span) => 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[20]", GetResource(accessor == "" ? "field" : "auto-property")), + Diagnostic(RudeEditKind.StackAllocUpdate, "public G(Span span)", GetResource("constructor"))); + } + + [Theory] + [InlineData("")] + [InlineData("{ get; }")] + public void MemberInitializer_Update_StackAlloc_InConstructorWithInitializers1(string accessor) + { + var src1 = "unsafe class C { int a " + accessor + " = 1; public C() { int* a = stackalloc int[10]; } }"; + var src2 = "unsafe class C { int a " + accessor + " = 2; public C() { int* a = stackalloc int[10]; } }"; + + var edits = GetTopEdits(src1, src2); + + // TODO (tomat): diagnostic should point to the property initializer + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[10]", GetResource("constructor"))); + } + + [Theory] + [InlineData("")] + [InlineData("{ get; }")] + public void MemberInitializer_Update_StackAlloc_InConstructorWithInitializers2(string accessor) { - var src1 = "unsafe class C { int a { get; } = 1; public C() { } public C(int b) { int* a = stackalloc int[10]; } }"; - var src2 = "unsafe class C { int a { get; } = 2; public C() { } public C(int b) { int* a = stackalloc int[10]; } }"; + var src1 = "unsafe class C { int a " + accessor + " = 1; public C() { } public C(int b) { int* a = stackalloc int[10]; } }"; + var src2 = "unsafe class C { int a " + accessor + " = 2; public C() { } public C(int b) { int* a = stackalloc int[10]; } }"; var edits = GetTopEdits(src1, src2); @@ -14792,36 +15425,58 @@ public void PropertyInitializerUpdate_StackAllocInConstructor3() Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[10]", FeaturesResources.constructor)); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37172")] + [Theory] + [InlineData("")] + [InlineData("{ get; }")] + public void MemberInitializer_Update_StackAlloc_InConstructorWithoutInitializers(string accessor) + { + var src1 = "unsafe class C { int a " + accessor + " = 1; public C() : this(1) { int* a = stackalloc int[10]; } public C(int a) { } }"; + var src2 = "unsafe class C { int a " + accessor + " = 2; public C() : this(1) { int* a = stackalloc int[10]; } public C(int a) { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics(); + } + + [Theory] + [InlineData("")] + [InlineData("{ get; }")] [WorkItem("https://github.com/dotnet/roslyn/issues/43099")] - public void PropertyInitializerUpdate_SwitchExpressionInConstructor1() + [WorkItem("https://github.com/dotnet/roslyn/issues/37172")] + public void MemberInitializer_Update_SwitchExpression_InConstructorWithInitializers(string accessor) { - var src1 = "class C { int a { get; } = 1; public C() { var b = a switch { 0 => 0, _ => 1 }; } }"; - var src2 = "class C { int a { get; } = 2; public C() { var b = a switch { 0 => 0, _ => 1 }; } }"; + var src1 = "class C { int a " + accessor + " = 1; public C() { var b = a switch { 0 => 0, _ => 1 }; } }"; + var src2 = "class C { int a " + accessor + " = 2; public C() { var b = a switch { 0 => 0, _ => 1 }; } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics(); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37172")] + [Theory] + [InlineData("")] + [InlineData("{ get; }")] [WorkItem("https://github.com/dotnet/roslyn/issues/43099")] - public void PropertyInitializerUpdate_SwitchExpressionInConstructor2() + [WorkItem("https://github.com/dotnet/roslyn/issues/37172")] + public void MemberInitializer_Update_SwitchExpression_InConstructorWithInitializers2(string accessor) { - var src1 = "class C { int a { get; } = 1; public C() : this(1) { var b = a switch { 0 => 0, _ => 1 }; } public C(int a) { } }"; - var src2 = "class C { int a { get; } = 2; public C() : this(1) { var b = a switch { 0 => 0, _ => 1 }; } public C(int a) { } }"; + var src1 = "class C { int a " + accessor + " = 1; public C() { } public C(int b) { var b = a switch { 0 => 0, _ => 1 }; } }"; + var src2 = "class C { int a " + accessor + " = 2; public C() { } public C(int b) { var b = a switch { 0 => 0, _ => 1 }; } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics(); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/37172")] + [Theory] + [InlineData("")] + [InlineData("{ get; }")] + [WorkItem("https://github.com/dotnet/roslyn/issues/37172")] [WorkItem("https://github.com/dotnet/roslyn/issues/43099")] - public void PropertyInitializerUpdate_SwitchExpressionInConstructor3() + public void MemberInitializer_Update_SwitchExpression_InConstructorWithoutInitializers(string accessor) { - var src1 = "class C { int a { get; } = 1; public C() { } public C(int b) { var b = a switch { 0 => 0, _ => 1 }; } }"; - var src2 = "class C { int a { get; } = 2; public C() { } public C(int b) { var b = a switch { 0 => 0, _ => 1 }; } }"; + var src1 = "class C { int a " + accessor + " = 1; public C() : this(1) { var b = a switch { 0 => 0, _ => 1 }; } public C(int a) { } }"; + var src2 = "class C { int a " + accessor + " = 2; public C() : this(1) { var b = a switch { 0 => 0, _ => 1 }; } public C(int a) { } }"; var edits = GetTopEdits(src1, src2); @@ -14829,7 +15484,7 @@ public void PropertyInitializerUpdate_SwitchExpressionInConstructor3() } [Fact] - public void FieldInitializerUpdate_LambdaInConstructor() + public void MemberInitializer_Update_LambdaInConstructor_Field() { var src1 = "class C { int a = 1; public C() { F(() => {}); } static void F(System.Action a) {} }"; var src2 = "class C { int a = 2; public C() { F(() => {}); } static void F(System.Action a) {} }"; @@ -14843,7 +15498,7 @@ public void FieldInitializerUpdate_LambdaInConstructor() } [Fact] - public void PropertyInitializerUpdate_LambdaInConstructor() + public void MemberInitializer_Update_LambdaInConstructor_Property() { var src1 = "class C { int a { get; } = 1; public C() { F(() => {}); } static void F(System.Action a) {} }"; var src2 = "class C { int a { get; } = 2; public C() { F(() => {}); } static void F(System.Action a) {} }"; @@ -14854,7 +15509,7 @@ public void PropertyInitializerUpdate_LambdaInConstructor() } [Fact] - public void FieldInitializerUpdate_QueryInConstructor() + public void MemberInitializer_Update_QueryInConstructor_Field() { var src1 = "using System.Linq; class C { int a = 1; public C() { F(from a in new[] {1,2,3} select a + 1); } static void F(System.Collections.Generic.IEnumerable x) {} }"; var src2 = "using System.Linq; class C { int a = 2; public C() { F(from a in new[] {1,2,3} select a + 1); } static void F(System.Collections.Generic.IEnumerable x) {} }"; @@ -14868,7 +15523,7 @@ public void FieldInitializerUpdate_QueryInConstructor() } [Fact] - public void PropertyInitializerUpdate_QueryInConstructor() + public void MemberInitializer_Update_QueryInConstructor_Property() { var src1 = "using System.Linq; class C { int a { get; } = 1; public C() { F(from a in new[] {1,2,3} select a + 1); } static void F(System.Collections.Generic.IEnumerable x) {} }"; var src2 = "using System.Linq; class C { int a { get; } = 2; public C() { F(from a in new[] {1,2,3} select a + 1); } static void F(System.Collections.Generic.IEnumerable x) {} }"; @@ -14879,7 +15534,7 @@ public void PropertyInitializerUpdate_QueryInConstructor() } [Fact] - public void FieldInitializerUpdate_AnonymousTypeInConstructor() + public void MemberInitializer_Update_AnonymousTypeInConstructor_Field() { var src1 = "class C { int a = 1; C() { F(new { A = 1, B = 2 }); } static void F(object x) {} }"; var src2 = "class C { int a = 2; C() { F(new { A = 1, B = 2 }); } static void F(object x) {} }"; @@ -14890,7 +15545,7 @@ public void FieldInitializerUpdate_AnonymousTypeInConstructor() } [Fact] - public void PropertyInitializerUpdate_AnonymousTypeInConstructor() + public void MemberInitializer_Update_AnonymousTypeInConstructor_Property() { var src1 = "class C { int a { get; } = 1; C() { F(new { A = 1, B = 2 }); } static void F(object x) {} }"; var src2 = "class C { int a { get; } = 2; C() { F(new { A = 1, B = 2 }); } static void F(object x) {} }"; @@ -14901,7 +15556,7 @@ public void PropertyInitializerUpdate_AnonymousTypeInConstructor() } [Fact] - public void FieldInitializerUpdate_PartialTypeWithSingleDeclaration() + public void MemberInitializer_Update_PartialTypeWithSingleDeclaration_Field() { var src1 = "partial class C { int a = 1; }"; var src2 = "partial class C { int a = 2; }"; @@ -14920,7 +15575,7 @@ public void FieldInitializerUpdate_PartialTypeWithSingleDeclaration() } [Fact] - public void PropertyInitializerUpdate_PartialTypeWithSingleDeclaration() + public void MemberInitializer_Update_PartialTypeWithSingleDeclaration_Property() { var src1 = "partial class C { int a { get; } = 1; }"; var src2 = "partial class C { int a { get; } = 2; }"; @@ -14936,7 +15591,7 @@ public void PropertyInitializerUpdate_PartialTypeWithSingleDeclaration() } [Fact] - public void FieldInitializerUpdate_PartialTypeWithMultipleDeclarations() + public void MemberInitializer_Update_PartialTypeWithMultipleDeclarations_Field() { var src1 = "partial class C { int a = 1; } partial class C { }"; var src2 = "partial class C { int a = 2; } partial class C { }"; @@ -14955,7 +15610,7 @@ public void FieldInitializerUpdate_PartialTypeWithMultipleDeclarations() } [Fact] - public void PropertyInitializerUpdate_PartialTypeWithMultipleDeclarations() + public void MemberInitializer_Update_PartialTypeWithMultipleDeclarations_Property() { var src1 = "partial class C { int a { get; } = 1; } partial class C { }"; var src2 = "partial class C { int a { get; } = 2; } partial class C { }"; @@ -14970,87 +15625,51 @@ public void PropertyInitializerUpdate_PartialTypeWithMultipleDeclarations() }); } - [Fact] - public void FieldInitializerUpdate_ParenthesizedLambda() - { - var src1 = "class C { int a = F(1, (x, y) => x + y); }"; - var src2 = "class C { int a = F(2, (x, y) => x + y); }"; - - var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(); - } - - [Fact] - public void PropertyInitializerUpdate_ParenthesizedLambda() - { - var src1 = "class C { int a { get; } = F(1, (x, y) => x + y); }"; - var src2 = "class C { int a { get; } = F(2, (x, y) => x + y); }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics(); - } - - [Fact] - public void FieldInitializerUpdate_SimpleLambda() - { - var src1 = "class C { int a = F(1, x => x); }"; - var src2 = "class C { int a = F(2, x => x); }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics(); - } - - [Fact] - public void PropertyInitializerUpdate_SimpleLambda() - { - var src1 = "class C { int a { get; } = F(1, x => x); }"; - var src2 = "class C { int a { get; } = F(2, x => x); }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemanticDiagnostics(); - } - - [Fact] - public void FieldInitializerUpdate_Query() + [Theory] + [InlineData("")] + [InlineData("{ get; }")] + public void MemberInitializer_Update_ParenthesizedLambda(string accessor) { - var src1 = "class C { int a = F(1, from goo in bar select baz); }"; - var src2 = "class C { int a = F(2, from goo in bar select baz); }"; + var src1 = "class C { int a " + accessor + " = F(1, (x, y) => x + y); }"; + var src2 = "class C { int a " + accessor + " = F(2, (x, y) => x + y); }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics(); } - [Fact] - public void PropertyInitializerUpdate_Query() + [Theory] + [InlineData("")] + [InlineData("{ get; }")] + public void MemberInitializer_Update_AnonymousType(string accessor) { - var src1 = "class C { int a { get; } = F(1, from goo in bar select baz); }"; - var src2 = "class C { int a { get; } = F(2, from goo in bar select baz); }"; + var src1 = "class C { int a " + accessor + " = F(1, new { A = 1, B = 2 }); }"; + var src2 = "class C { int a " + accessor + " = F(2, new { A = 1, B = 2 }); }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics(); } - [Fact] - public void FieldInitializerUpdate_AnonymousType() + [Theory] + [InlineData("")] + [InlineData("{ get; }")] + public void MemberInitializer_Update_Query_Field(string accessor) { - var src1 = "class C { int a = F(1, new { A = 1, B = 2 }); }"; - var src2 = "class C { int a = F(2, new { A = 1, B = 2 }); }"; + var src1 = "class C { int a " + accessor + " = F(1, from goo in bar select baz); }"; + var src2 = "class C { int a " + accessor + " = F(2, from goo in bar select baz); }"; var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics(); } - [Fact] - public void PropertyInitializerUpdate_AnonymousType() + [Theory] + [InlineData("")] + [InlineData("{ get; }")] + public void MemberInitializer_Update_Lambda(string accessor) { - var src1 = "class C { int a { get; } = F(1, new { A = 1, B = 2 }); }"; - var src2 = "class C { int a { get; } = F(2, new { A = 1, B = 2 }); }"; + var src1 = "class C { int a " + accessor + " = F(1, x => x); }"; + var src2 = "class C { int a " + accessor + " = F(2, x => x); }"; var edits = GetTopEdits(src1, src2); @@ -15058,7 +15677,7 @@ public void PropertyInitializerUpdate_AnonymousType() } [Fact] - public void FieldInitializerUpdate_Lambdas_ImplicitCtor_EditInitializerWithLambda1() + public void MemberInitializer_Update_Lambda_ImplicitCtor_EditInitializerWithLambda1() { var src1 = @" using System; @@ -15091,7 +15710,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_ImplicitCtor_EditInitializerWithoutLambda1() + public void MemberInitializer_Update_Lambda_ImplicitCtor_EditInitializerWithoutLambda1() { var src1 = @" using System; @@ -15124,7 +15743,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_CtorIncludingInitializers_EditInitializerWithLambda1() + public void MemberInitializer_Update_Lambda_CtorIncludingInitializers_EditInitializerWithLambda1() { var src1 = @" using System; @@ -15161,7 +15780,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_CtorIncludingInitializers_EditInitializerWithoutLambda1() + public void MemberInitializer_Update_Lambda_CtorIncludingInitializers_EditInitializerWithoutLambda1() { var src1 = @" using System; @@ -15198,7 +15817,7 @@ public C() {} } [Fact] - public void FieldInitializerUpdate_Lambdas_MultipleCtorsIncludingInitializers_EditInitializerWithLambda1() + public void MemberInitializer_Update_Lambda_MultipleCtorsIncludingInitializers_EditInitializerWithLambda1() { var src1 = @" using System; @@ -15241,7 +15860,7 @@ public C(bool b) {} } [Fact] - public void FieldInitializerUpdate_Lambdas_MultipleCtorsIncludingInitializersContainingLambdas_EditInitializerWithLambda1() + public void MemberInitializer_Update_Lambda_MultipleCtorsIncludingInitializersContainingLambdas_EditInitializerWithLambda1() { var src1 = @" using System; @@ -15284,7 +15903,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_MultipleCtorsIncludingInitializersContainingLambdas_EditInitializerWithLambda_Trivia1() + public void MemberInitializer_Update_Lambda_MultipleCtorsIncludingInitializersContainingLambdas_EditInitializerWithLambda_Trivia1() { var src1 = @" using System; @@ -15327,7 +15946,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_MultipleCtorsIncludingInitializersContainingLambdas_EditConstructorWithLambda1() + public void MemberInitializer_Update_Lambda_MultipleCtorsIncludingInitializersContainingLambdas_EditConstructorWithLambda1() { var src1 = @" using System; @@ -15369,7 +15988,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_MultipleCtorsIncludingInitializersContainingLambdas_EditConstructorWithLambda_Trivia1() + public void MemberInitializer_Update_Lambda_MultipleCtorsIncludingInitializersContainingLambdas_EditConstructorWithLambda_Trivia1() { var src1 = @" using System; @@ -15411,7 +16030,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_MultipleCtorsIncludingInitializersContainingLambdas_EditConstructorWithoutLambda1() + public void MemberInitializer_Update_Lambda_MultipleCtorsIncludingInitializersContainingLambdas_EditConstructorWithoutLambda1() { var src1 = @" using System; @@ -15453,7 +16072,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_EditConstructorNotIncludingInitializers() + public void MemberInitializer_Update_Lambda_EditConstructorNotIncludingInitializers() { var src1 = @" using System; @@ -15495,7 +16114,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_RemoveCtorInitializer1() + public void MemberInitializer_Update_Lambda_RemoveCtorInitializer1() { var src1 = @" using System; @@ -15537,7 +16156,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_AddCtorInitializer1() + public void MemberInitializer_Update_Lambda_AddCtorInitializer1() { var src1 = @" using System; @@ -15578,7 +16197,7 @@ class C } [Fact] - public void FieldInitializerUpdate_Lambdas_UpdateBaseCtorInitializerWithLambdas1() + public void MemberInitializer_Update_Lambda_UpdateBaseCtorInitializerWithLambdas1() { var src1 = @" using System; @@ -15638,7 +16257,7 @@ public C(bool b) [Theory] [InlineData("")] [InlineData(" : base()")] - public void FieldInitializerUpdate_Lambdas_ReplacingCustomWithSynthesized_ConstructorWithMemberInitializers(string initializer) + public void MemberInitializer_Update_Lambda_ConstructorWithMemberInitializers_ReplacingCustomWithSynthesized(string initializer) { var src1 = $$""" using System; @@ -15677,7 +16296,7 @@ class C : B [Theory] [CombinatorialData] - public void FieldInitializerUpdate_Lambdas_ReplacingCustomWithSynthesized_ConstructorWithMemberInitializers_Primary( + public void MemberInitializer_Update_Lambda_ConstructorWithMemberInitializers_ReplacingCustomWithSynthesized_Primary( [CombinatorialValues("", "()")] string initializer, bool isInsert) { var src1 = $$""" @@ -15717,7 +16336,7 @@ class C : B } [Fact] - public void FieldInitializerUpdate_Lambdas_PartialDeclarationDelete_SingleDocument() + public void MemberInitializer_Update_Lambda_PartialDeclarationDelete_SingleDocument() { var src1 = @" partial class C @@ -15778,7 +16397,7 @@ public void FieldInitializerUpdate_Lambdas_InsertPrimaryConstructorParameterUse( } [Fact] - public void FieldInitializerUpdate_ActiveStatements1() + public void MemberInitializer_Update_ActiveStatements1() { var src1 = @" using System; @@ -15818,7 +16437,7 @@ class C } [Fact] - public void PropertyWithInitializer_SemanticError_Partial() + public void MemberInitializer_Update_Partial_SemanticError() { var src1 = @" partial class C @@ -15852,47 +16471,48 @@ public C() { } } [Fact] - public void Field_Partial_DeleteInsert_InitializerRemoval() + public void MemberInitializer_Rename_Property() { - var srcA1 = "partial class C { int F = 1; }"; - var srcB1 = "partial class C { }"; + var src1 = "class C { int A { get; } = 1; }"; + var src2 = "class C { int B { get; } = 2; }"; - var srcA2 = "partial class C { }"; - var srcB2 = "partial class C { int F; }"; + var edits = GetTopEdits(src1, src2); - EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - new[] - { - DocumentResults(), - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) - }), - }); + edits.VerifySemantics( + semanticEdits: + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.A"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_A"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.B")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_B")), + SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), preserveLocalVariables: true), + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } [Fact] - public void Field_Partial_DeleteInsert_InitializerUpdate() + public void MemberInitializer_Update_Reloadable_Partial() { - var srcA1 = "partial class C { int F = 1; }"; - var srcB1 = "partial class C { }"; + var srcA1 = ReloadableAttributeSrc + "[CreateNewOnMetadataUpdate]partial class C { int x = 1; }"; + var srcB1 = "partial class C { int y = 1; }"; - var srcA2 = "partial class C { }"; - var srcB2 = "partial class C { int F = 2; }"; + var srcA2 = ReloadableAttributeSrc + "[CreateNewOnMetadataUpdate]partial class C { int x = 2; }"; + var srcB2 = "partial class C { int y = 2; }"; EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { - DocumentResults(), - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetParameterlessConstructor("C"), partialType: "C", preserveLocalVariables: true) - }), - }); + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C"), partialType: "C") + }), + DocumentResults(semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Replace, c => c.GetMember("C"), partialType: "C") + }), + }, + capabilities: EditAndContinueCapabilities.NewTypeDefinition); } #endregion @@ -16667,198 +17287,36 @@ public void Field_Type_Update_RuntimeTypeChanged(string oldType, string newType) var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.TypeUpdate, newType + " F, G", FeaturesResources.field), - Diagnostic(RudeEditKind.TypeUpdate, newType + " F, G", FeaturesResources.field)); + Diagnostic(RudeEditKind.TypeUpdate, newType + " F, G", GetResource("field")), + Diagnostic(RudeEditKind.TypeUpdate, newType + " F, G", GetResource("field"))); } - [Theory] - [InlineData("string", "string?")] - [InlineData("object", "dynamic")] - [InlineData("(int a, int b)", "(int a, int c)")] - public void Field_Event_Type_Update_RuntimeTypeUnchanged(string oldType, string newType) + [Fact] + public void Field_Type_Update_ReorderRemoveAdd() { - var src1 = "class C { event System.Action<" + oldType + "> F, G; }"; - var src2 = "class C { event System.Action<" + newType + "> F, G; }"; + var src1 = "class C { int F, G, H; bool U; }"; + var src2 = "class C { string G, F; double V, U; }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.G"))); + edits.VerifyEdits( + "Update [int F, G, H]@10 -> [string G, F]@10", + "Reorder [G]@17 -> @17", + "Update [bool U]@23 -> [double V, U]@23", + "Insert [V]@30", + "Delete [H]@20"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Move, "G", FeaturesResources.field), + Diagnostic(RudeEditKind.TypeUpdate, "string G, F", FeaturesResources.field), + Diagnostic(RudeEditKind.TypeUpdate, "string G, F", FeaturesResources.field), + Diagnostic(RudeEditKind.TypeUpdate, "double V, U", FeaturesResources.field), + Diagnostic(RudeEditKind.Delete, "string G, F", DeletedSymbolDisplay(FeaturesResources.field, "H"))); } - [Theory] - [InlineData("int", "string")] - [InlineData("int", "int?")] - public void Field_Event_Type_Update_RuntimeTypeChanged(string oldType, string newType) - { - var src1 = "class C { event System.Action<" + oldType + "> E; }"; - var src2 = "class C { event System.Action<" + newType + "> E; }"; + #endregion - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.add_E").FirstOrDefault(m => m.GetParameterTypes()[0].Type.GetMemberTypeArgumentsNoUseSiteDiagnostics()[0].SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.remove_E").FirstOrDefault(m => m.GetParameterTypes()[0].Type.GetMemberTypeArgumentsNoUseSiteDiagnostics()[0].SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.E")), - }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - - edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "event System.Action<" + newType + "> E", GetResource("event field")) }, - capabilities: EditAndContinueCapabilities.Baseline); - } - - [Fact] - public void Field_Event_Type_Update_TupleType() - { - var src1 = "class C { event System.Action<(int a, int b)> E; }"; - var src2 = "class C { event System.Action<(int a, double b)> E; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.add_E").FirstOrDefault(m => m.GetParameterTypes()[0].Type.GetMemberTypeArgumentsNoUseSiteDiagnostics()[0].GetMemberTypeArgumentsNoUseSiteDiagnostics()[1].SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.remove_E").FirstOrDefault(m => m.GetParameterTypes()[0].Type.GetMemberTypeArgumentsNoUseSiteDiagnostics()[0].GetMemberTypeArgumentsNoUseSiteDiagnostics()[1].SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.E")), - }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - - edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "event System.Action<(int a, double b)> E", GetResource("event field")) }, - capabilities: EditAndContinueCapabilities.Baseline); - } - - [Fact] - public void Field_Type_Update_ReorderRemoveAdd() - { - var src1 = "class C { int F, G, H; bool U; }"; - var src2 = "class C { string G, F; double V, U; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [int F, G, H]@10 -> [string G, F]@10", - "Reorder [G]@17 -> @17", - "Update [bool U]@23 -> [double V, U]@23", - "Insert [V]@30", - "Delete [H]@20"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Move, "G", FeaturesResources.field), - Diagnostic(RudeEditKind.TypeUpdate, "string G, F", FeaturesResources.field), - Diagnostic(RudeEditKind.TypeUpdate, "string G, F", FeaturesResources.field), - Diagnostic(RudeEditKind.TypeUpdate, "double V, U", FeaturesResources.field), - Diagnostic(RudeEditKind.Delete, "string G, F", DeletedSymbolDisplay(FeaturesResources.field, "H"))); - } - - [Fact] - public void Event_Rename1() - { - var src1 = "class C { event int E { remove { } add { } } }"; - var src2 = "class C { event int F { remove { } add { } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.add_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.remove_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F")), - }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - - edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "event int F", FeaturesResources.event_) }, - capabilities: EditAndContinueCapabilities.Baseline); - } - - [Fact] - public void Event_Rename2() - { - var src1 = "class C { event int E; }"; - var src2 = "class C { event int F; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.add_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.remove_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F")), - }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - } - - [Fact] - public void Event_UpdateType() - { - var src1 = "class C { event int E { remove { } add { } } }"; - var src2 = "class C { event string E { remove { } add { } } }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.add_E").FirstOrDefault(m => m.GetParameterTypes()[0].Type.SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.remove_E").FirstOrDefault(m => m.GetParameterTypes()[0].Type.SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.E")), - }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - - edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "event string E", FeaturesResources.event_) }, - capabilities: EditAndContinueCapabilities.Baseline); - } - - [Fact] - public void Field_Event_Reorder() - { - var src1 = "class C { int a = 0; int b = 1; event int c = 2; }"; - var src2 = "class C { event int c = 2; int a = 0; int b = 1; }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Reorder [event int c = 2;]@32 -> @10"); - - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Move, "event int c = 2", CSharpFeaturesResources.event_field)); - } - - [Fact] - public void Field_Event_Partial_InsertDelete() - { - var srcA1 = "partial class C { }"; - var srcB1 = "partial class C { event int E = 2; }"; - - var srcA2 = "partial class C { event int E = 2; }"; - var srcB2 = "partial class C { }"; - - 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(), - }); - } - - #endregion - - #region Properties + #region Properties [Theory] [InlineData("static")] @@ -16900,7 +17358,9 @@ public void Property_ExpressionBody_Rename() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Q")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Q")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); @@ -17171,7 +17631,9 @@ public void Property_Rename1() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Q")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Q")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); @@ -17215,8 +17677,11 @@ public void Property_Rename3() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Q")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_Q")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Q")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); @@ -17233,13 +17698,32 @@ public void Property_Rename4() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Q")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_Q")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Q")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } + [Fact] + public void Property_Rename_SemanticError_NoAccessors() + { + var src1 = "class C { System.Action E { } }"; + var src2 = "class C { System.Action F { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.E"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F")) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + [Fact] public void Property_RenameAndUpdate() { @@ -17251,12 +17735,31 @@ public void Property_RenameAndUpdate() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Q")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Q")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } + [Fact] + public void Property_Rename_Stackalloc() + { + var src1 = "class C { int G(Span s) => 0; int P { get => G(stackalloc int[1]); set => G(stackalloc int[1]); } }"; + var src2 = "class C { int G(Span s) => 0; int Q { get => G(stackalloc int[1]); set => G(stackalloc int[1]); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + new[] + { + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("property getter")), + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("property setter")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + [Theory] [InlineData("class")] [InlineData("struct")] @@ -17270,6 +17773,7 @@ public void Property_Delete(string keyword) edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), }, @@ -17287,6 +17791,7 @@ public void Property_Delete_GetOnly() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")) }, capabilities: EditAndContinueCapabilities.Baseline); @@ -17303,6 +17808,7 @@ public void Property_Delete_Auto_Class() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), }, @@ -17323,6 +17829,7 @@ public void Property_Delete_Auto_Struct() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("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")), }, @@ -17436,32 +17943,53 @@ public void PropertyAccessorReorder_GetInit() } [Fact] - public void PropertyTypeUpdate() + public void Property_Update_Type() { - var src1 = "class C { int P { get; set; } }"; + var src1 = "class C { byte P { get; set; } }"; var src2 = "class C { char P { get; set; } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [int P { get; set; }]@10 -> [char P { get; set; }]@10"); - edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.set_P").FirstOrDefault(p => p.GetParameters()[0].Type.SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.P")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_P")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "char P", GetResource("auto-property")) }, + new[] + { + Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "char P", GetResource("auto-property")) + }, capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void PropertyTypeUpdate_WithBodies() + public void Property_Update_Type_Stackalloc() + { + // only type is changed, no changes to the accessors (not even whitespace) + var src1 = "class C { byte G(Span s) => 0; byte P { get => G(stackalloc int[1]); set => G(stackalloc int[1]); } }"; + var src2 = "class C { byte G(Span s) => 0; long P { get => G(stackalloc int[1]); set => G(stackalloc int[1]); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + new[] + { + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("property getter")), + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("property setter")) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + + [Fact] + public void Property_Update_Type_WithBodies() { var src1 = "class C { int P { get { return 1; } set { } } }"; var src2 = "class C { char P { get { return 'a'; } set { } } }"; @@ -17471,17 +17999,33 @@ public void PropertyTypeUpdate_WithBodies() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.P"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.set_P").FirstOrDefault(p => p.GetParameters()[0].Type.SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_P"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_P")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.P")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "char P", FeaturesResources.property_) }, + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "char P", FeaturesResources.property_) }, capabilities: EditAndContinueCapabilities.Baseline); } + [Fact] + public void Property_Update_Type_TypeLayout() + { + var src1 = "struct C { byte P { get; } }"; + var src2 = "struct C { long P { get; } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.InsertIntoStruct, "long P", GetResource("auto-property"), GetResource("struct"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + [Fact] public void Property_Update_AddAttribute() { @@ -18172,13 +18716,15 @@ public void Property_ReadOnlyRef_Update() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("Test.P"), deletedSymbolContainerProvider: c => c.GetMember("Test")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("Test.get_P"), deletedSymbolContainerProvider: c => c.GetMember("Test")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("Test.P")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("Test.get_P")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "ref readonly int P", GetResource("auto-property")) }, + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "ref readonly int P", GetResource("auto-property")) }, capabilities: EditAndContinueCapabilities.Baseline); } @@ -18341,12 +18887,15 @@ struct S readonly int P4 { get => 1; set {}} }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly int P1", CSharpFeaturesResources.property_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly int P4", CSharpFeaturesResources.property_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly int P4", CSharpFeaturesResources.property_setter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.property_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.property_setter)); + edits.VerifySemantics( + [ + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.get_P1")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.get_P2")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.get_P4")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.set_P2")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.set_P3")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.set_P4")), + ]); } [Fact] @@ -18408,9 +18957,12 @@ class C(int A, int B) edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.B"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_B"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_B"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.D")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_D")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_D")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } @@ -18445,9 +18997,12 @@ class C(int A, int B) new[] { SemanticEdit(SemanticEditKind.Update, c => c.GetPrimaryConstructor("C"), preserveLocalVariables: true), - //SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_B"), deletedSymbolContainerProvider: c => c.GetMember("C")), - //SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_B"), deletedSymbolContainerProvider: c => c.GetMember("C")), - //SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.D")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.B"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_B"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_B"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.D")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_D")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_D")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } @@ -18549,7 +19104,8 @@ public void IndexerWithExpressionBody_Update() edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item"), preserveLocalVariables: false) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")) }); } @@ -18569,7 +19125,8 @@ public void Indexer_ExpressionBodyToBlockBody() edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item"), preserveLocalVariables: false) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")) }); } @@ -18589,7 +19146,8 @@ public void Indexer_BlockBodyToExpressionBody() edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item"), preserveLocalVariables: false) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")) }); } @@ -18636,7 +19194,8 @@ public void Indexer_GetterExpressionBodyToExpressionBody() edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item"), preserveLocalVariables: false) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")) }); } @@ -18655,7 +19214,8 @@ public void Indexer_ExpressionBodyToGetterExpressionBody() edits.VerifySemantics(ActiveStatementsDescription.Empty, new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item"), preserveLocalVariables: false) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")) }); } @@ -18743,6 +19303,7 @@ public void Indexer_GetterAndSetterBlockBodiesToExpressionBody() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, @@ -18767,8 +19328,9 @@ public void Indexer_ExpressionBodyToGetterAndSetterBlockBodies() edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item"), preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_Item"), preserveLocalVariables: false) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_Item")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } @@ -18796,6 +19358,84 @@ interface J Diagnostic(RudeEditKind.Renamed, "int J.this[int a]", GetResource("indexer", "I.this[int a]"))); } + [Fact] + public void Indexer_Rename_ExpressionBody() + { + var interfaces = """ + interface I + { + int this[int a] { get; } + } + + interface J + { + int this[int a] { get; } + } + """; + var src1 = "class C { int I.this[int a] => 1; } " + interfaces; + var src2 = "class C { int J.this[int a] => 1; } " + interfaces; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Renamed, "int J.this[int a]", GetResource("indexer", "I.this[int a]"))); + } + + [Fact] + public void Indexer_Rename_Stackalloc() + { + var interfaces = """ + interface I + { + int this[int a] { get; } + } + + interface J + { + int this[int a] { get; } + } + """; + + // only type is changed, no changes to the accessors (not even whitespace) + var src1 = "class C { byte G(Span s) => 0; byte I.this[int a] { get => G(stackalloc int[1]); set => G(stackalloc int[1]); } }" + interfaces; + var src2 = "class C { byte G(Span s) => 0; long J.this[int a] { get => G(stackalloc int[1]); set => G(stackalloc int[1]); } }" + interfaces; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [ + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("indexer getter")), + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("indexer setter")) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + + [Fact] + public void Indexer_Rename_Stackalloc_ExpressionBody() + { + var interfaces = """ + interface I + { + int this[int a] { get; } + } + + interface J + { + int this[int a] { get; } + } + """; + + // only type is changed, no changes to the body (not even whitespace) + var src1 = "class C { byte G(Span s) => 0; byte I.this[int a] => G(stackalloc int[1]); } " + interfaces; + var src2 = "class C { byte G(Span s) => 0; long J.this[int a] => G(stackalloc int[1]); } " + interfaces; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("indexer getter"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + [Fact] public void Indexer_Reorder1() { @@ -18825,156 +19465,292 @@ public void Indexer_AccessorReorder() } [Fact] - public void Indexer_TypeUpdate() + public void Indexer_Update_Attribute() { - var src1 = "class C { int this[int a] { get; set; } }"; - var src2 = "class C { string this[int a] { get; set; } }"; + var attribute = """ + public class A : System.Attribute { public A(int x) {} } + """; - var edits = GetTopEdits(src1, src2); + var src1 = attribute + "class C { [A(1)]int this[int a] { get; set; } }"; + var src2 = attribute + "class C { [A(2)]int this[int a] { get; set; } }"; - edits.VerifyEdits( - "Update [int this[int a] { get; set; }]@10 -> [string this[int a] { get; set; }]@10"); + var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - new[] + [SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]"))], + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "int this[int a]", GetResource("indexer"))], + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Indexer_Update_Type() + { + var src1 = "class C { byte this[int a] { get => 1; set {} } }"; + var src2 = "class C { long this[int a] { get => 1; set {} } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.set_Item").FirstOrDefault(p => p.GetParameters()[1].Type.SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Item")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_Item")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "string this[int a]", CSharpFeaturesResources.indexer) }, + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "long this[int a]", CSharpFeaturesResources.indexer) }, capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Tuple_TypeUpdate() + public void Indexer_Update_Type_WithExpressionBody() { - var src1 = "class C { (int, int) M() { throw new System.Exception(); } }"; - var src2 = "class C { (string, int) M() { throw new System.Exception(); } }"; + var src1 = "class C { byte this[int a] => 1; }"; + var src2 = "class C { long this[int a] => 1; }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [(int, int) M() { throw new System.Exception(); }]@10 -> [(string, int) M() { throw new System.Exception(); }]@10"); - edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Item")), }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "(string, int) M()", FeaturesResources.method) }, + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "long this[int a]", CSharpFeaturesResources.indexer) }, capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void TupleElementDelete() + public void Indexer_Update_Type_Stackalloc() { - var src1 = "class C { (int, int, int a) M() { return (1, 2, 3); } }"; - var src2 = "class C { (int, int) M() { return (1, 2); } }"; + // only type is changed, no changes to the accessors (not even whitespace) + var src1 = "class C { byte G(Span s) => 0; byte this[int x] { get => G(stackalloc int[1]); set => G(stackalloc int[1]); } }"; + var src2 = "class C { byte G(Span s) => 0; long this[int x] { get => G(stackalloc int[1]); set => G(stackalloc int[1]); } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [(int, int, int a) M() { return (1, 2, 3); }]@10 -> [(int, int) M() { return (1, 2); }]@10"); + edits.VerifySemanticDiagnostics( + [ + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("indexer getter")), + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("indexer setter")) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + + [Fact] + public void Indexer_Update_Type_Stackalloc_WithExpressionBody() + { + // only type is changed, no changes to the body (not even whitespace) + var src1 = "class C { byte G(Span s) => 0; byte this[int x] => G(stackalloc int[1]); }"; + var src2 = "class C { byte G(Span s) => 0; long this[int x] => G(stackalloc int[1]); }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("indexer getter"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + + [Fact] + public void Indexer_Parameter_Update_Type() + { + var src1 = "class C { int this[byte a] { get => 1; set { } } }"; + var src2 = "class C { int this[long a] { get => 1; set { } } }"; + + var edits = GetTopEdits(src1, src2); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Item")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_Item")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - - edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "(int, int) M()", FeaturesResources.method) }, - capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void TupleElementAdd() + public void Indexer_Parameter_Rename() { - var src1 = "class C { (int, int) M() { return (1, 2); } }"; - var src2 = "class C { (int, int, int a) M() { return (1, 2, 3); } }"; + var src1 = "class C { int this[int a] { get => 1; set { } } }"; + var src2 = "class C { int this[int b] { get => 1; set { } } }"; var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [(int, int) M() { return (1, 2); }]@10 -> [(int, int, int a) M() { return (1, 2, 3); }]@10"); - edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_Item")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_Item")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + capabilities: EditAndContinueCapabilities.UpdateParameters); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "(int, int, int a) M()", FeaturesResources.method) }, + [Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "int b", GetResource("parameter"))], capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Indexer_ParameterUpdate() + public void Indexer_Parameter_Update_Attribute() { - var src1 = "class C { int this[int a] { get; set; } }"; - var src2 = "class C { int this[string a] { get; set; } }"; + var attribute = """ + public class A : System.Attribute { public A(int x) {} } + """; + + var src1 = attribute + "class C { int this[[A(1)]int a] { get => 1; set { } } }"; + var src2 = attribute + "class C { int this[[A(2)]int a] { get => 1; set { } } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.get_Item").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.set_Item").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_Int32)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C.this[]").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_String)?.ISymbol), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")) }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "int a", GetResource("parameter"))], + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] - public void Indexer_ParameterInsert() + public void Indexer_Parameter_Insert() { - var src1 = "class C { int this[int a] { get; set; } }"; - var src2 = "class C { int this[int a, string b] { get; set; } }"; + var src1 = "class C { int this[int a] { get => 1; set { } } }"; + var src2 = "class C { int this[int a, string b] { get => 1; set { } } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.get_Item").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.set_Item").FirstOrDefault(m => m.GetParameterCount() == 2)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C.this[]").FirstOrDefault(m => m.GetParameterCount() == 2)?.ISymbol), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Item")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_Item")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void Indexer_ParameterDelete() + public void Indexer_Parameter_Delete() { - var src1 = "class C { int this[int a, string b] { get; set; } }"; - var src2 = "class C { int this[int a] { get; set; } }"; + var src1 = "class C { int this[int a, string b] { get => 1; set { } } }"; + var src2 = "class C { int this[int a] { get => 1; set { } } }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.get_Item").FirstOrDefault(m => m.GetParameterCount() == 2)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.set_Item").FirstOrDefault(m => m.GetParameterCount() == 3)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C.this[]").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Item")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.set_Item")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } + [Fact] + public void Indexer_Parameter_Reorder_Stackalloc() + { + var src1 = @" +using System; + +class C +{ + int this[int a, byte b] { get { return stackalloc int[1].Length; } } +} +"; + var src2 = @" +using System; + +class C +{ + int this[byte b, int a] { get { return stackalloc int[1].Length; } } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("indexer getter"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Indexer_Parameter_Reorder_Stackalloc_WithGetter_WithExpressionBody() + { + var src1 = @" +using System; + +class C +{ + int this[int a, byte b] { get => stackalloc int[1].Length; } +} +"; + var src2 = @" +using System; + +class C +{ + int this[byte b, int a] { get => stackalloc int[1].Length; } +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("indexer getter"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Indexer_Parameter_Reorder_Stackalloc_WithExpressionBody() + { + var src1 = @" +using System; + +class C +{ + int this[int a, byte b] => stackalloc int[1].Length; +} +"; + var src2 = @" +using System; + +class C +{ + int this[byte b, int a] => stackalloc int[1].Length; +} +"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("indexer getter"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + [Fact] public void Indexer_AddSetAccessor() { @@ -19018,6 +19794,7 @@ class C edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.set_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, @@ -19109,16 +19886,22 @@ public void Indexer_ReadOnlyRef_Parameter_InsertWhole() [Fact] public void Indexer_ReadOnlyRef_Parameter_Update() { - var src1 = "class Test { int this[int i] => throw null; }"; - var src2 = "class Test { int this[in int i] => throw null; }"; + var src1 = "class C { int this[int i] => throw null; }"; + var src2 = "class C { int this[in int i] => throw null; }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int i]@22 -> [in int i]@22"); + "Update [int i]@19 -> [in int i]@19"); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "in int i", FeaturesResources.parameter)); + edits.VerifySemantics( + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Item")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.this[]")) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] @@ -19142,24 +19925,26 @@ public void Indexer_ReadOnlyRef_ReturnType_Insert() [Fact] public void Indexer_ReadOnlyRef_ReturnType_Update() { - var src1 = "class Test { int this[int i] => throw null; }"; - var src2 = "class Test { ref readonly int this[int i] => throw null; }"; + var src1 = "class C { int this[int i] => throw null; }"; + var src2 = "class C { ref readonly int this[int i] => throw null; }"; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Update [int this[int i] => throw null;]@13 -> [ref readonly int this[int i] => throw null;]@13"); + "Update [int this[int i] => throw null;]@10 -> [ref readonly int this[int i] => throw null;]@10"); edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("Test.get_Item"), deletedSymbolContainerProvider: c => c.GetMember("Test")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("Test.this[]")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.this[]"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.get_Item"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.get_Item")), }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "ref readonly int this[int i]", FeaturesResources.indexer_) }, + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "ref readonly int this[int i]", FeaturesResources.indexer_) }, capabilities: EditAndContinueCapabilities.Baseline); } @@ -19179,8 +19964,9 @@ public void Indexer_Partial_InsertDelete() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").SetMethod) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]").SetMethod) }), DocumentResults(), @@ -19203,8 +19989,9 @@ public void IndexerInit_Partial_InsertDelete() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").SetMethod) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]").SetMethod) }), DocumentResults(), @@ -19229,8 +20016,9 @@ public void AutoIndexer_Partial_InsertDelete() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").GetMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("this[]").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.this[]").SetMethod), }), DocumentResults(), }); @@ -19250,8 +20038,14 @@ struct S readonly int this[int x] { get; } }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly int this[int x]", CSharpFeaturesResources.indexer_getter)); + + // Compiler generated attribute changed, we do not require runtime capability for custom attribute changes. + edits.VerifySemantics( + [ + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.this[]")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.this[]").GetMethod) + ], + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] @@ -19274,19 +20068,20 @@ struct S readonly int this[sbyte x] { get => 1; set {}} }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly int this[int x]", CSharpFeaturesResources.indexer_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly int this[sbyte x]", CSharpFeaturesResources.indexer_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly int this[sbyte x]", CSharpFeaturesResources.indexer_setter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly get", CSharpFeaturesResources.indexer_getter), - Diagnostic(RudeEditKind.ModifiersUpdate, "readonly set", CSharpFeaturesResources.indexer_setter)); + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Int32 }])), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_SByte }])), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Int32 }]).GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_SByte }]).GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_SByte }]).SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_UInt32 }]).GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Byte }]).SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_UInt32 }]).SetMethod)); } [Fact] public void Indexer_InReadOnlyStruct_ReadOnly_Add() { - // indent to align accessor bodies and avoid updates caused by sequence point location changes - var src1 = @" readonly struct S { @@ -19305,11 +20100,14 @@ readonly struct S }"; var edits = GetTopEdits(src1, src2); - // updates only for accessors whose modifiers were explicitly updated + // Updates only for accessors whose modifiers were explicitly updated. + // Indexers themselves are only updated when their modifiers change. The update is not necessary and could be eliminated. edits.VerifySemantics(new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMembers("this[]").Cast().Single(m => m.Parameters.Single().Type.Name == "UInt32").GetMethod, preserveLocalVariables: false), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMembers("this[]").Cast().Single(m => m.Parameters.Single().Type.Name == "Byte").SetMethod, preserveLocalVariables: false) + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_UInt32 }]).GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Byte }]).SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_Int32 }])), + SemanticEdit(SemanticEditKind.Update, c => c.GetMembers("S.this[]").Single(m => m.Parameters is [{ Type.SpecialType: SpecialType.System_SByte }])), }); } @@ -19323,7 +20121,7 @@ readonly struct S [InlineData("abstract")] [InlineData("override")] [InlineData("sealed override", "override")] - public void Event_Modifiers_Update(string oldModifiers, string newModifiers = "") + public void Event_Update_Modifiers(string oldModifiers, string newModifiers = "") { if (oldModifiers != "") { @@ -19406,7 +20204,7 @@ public void Event_Insert() } [Fact] - public void Event_Delete1() + public void Event_Delete_CustomAccessors() { var src1 = "class C { event int E { remove { } add { } } }"; var src2 = "class C { }"; @@ -19416,6 +20214,7 @@ public void Event_Delete1() edits.VerifySemantics( new[] { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.E"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.add_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.remove_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), }, @@ -19423,24 +20222,20 @@ public void Event_Delete1() } [Fact] - public void Event_Delete2() + public void Event_Delete_SynthesizedAccessors() { - var src1 = "class C { event int E; }"; + var src1 = "class C { event System.Action E; }"; var src2 = "class C { }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.add_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.remove_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), - }, - capabilities: EditAndContinueCapabilities.Baseline); + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.Delete, "class C", "event field 'E'")], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] - public void Event_Insert_IntoLayoutClass_Sequential() + public void Event_Insert_TypeLayout_CustomAccessors() { var src1 = @" using System; @@ -19467,6 +20262,36 @@ private event Action c { add { } remove { } } edits.VerifySemanticDiagnostics(capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } + [Fact] + public void Event_Insert_TypeLayout_SynthesizedAccessors() + { + var src1 = @" +using System; +using System.Runtime.InteropServices; + +[StructLayoutAttribute(LayoutKind.Sequential)] +class C +{ +} +"; + var src2 = @" +using System; +using System.Runtime.InteropServices; + +[StructLayoutAttribute(LayoutKind.Sequential)] +class C +{ + private event Action c; +} +"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "c", GetResource("event field"), GetResource("class"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17681")] public void Event_ExpressionBodyToBlockBody() { @@ -19551,16 +20376,24 @@ public void Event_InMutableStruct_ReadOnly_Add() var src1 = @" struct S { - public event Action E { add {} remove {} } + public event Action E + { + add {} remove {} + } }"; var src2 = @" struct S { - public readonly event Action E { add {} remove {} } + public readonly event Action E + { + add {} remove {} + } }"; var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "public readonly event Action E", FeaturesResources.event_)); + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.E")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.add_E")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S.remove_E"))); } [Fact] @@ -19569,23 +20402,26 @@ public void Event_InReadOnlyStruct_ReadOnly_Add1() var src1 = @" readonly struct S { - public event Action E { add {} remove {} } + public event Action E + { + add {} remove {} + } }"; var src2 = @" readonly struct S { - public readonly event Action E { add {} remove {} } + public readonly event Action E + { + add {} remove {} + } }"; var edits = GetTopEdits(src1, src2); - // Currently, an edit is produced eventhough bodies nor IsReadOnly attribute have changed. Consider improving. - edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("E").AddMethod), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("E").RemoveMethod)); + edits.VerifySemantics(); } [Fact] - public void Field_Event_Attribute_Add() + public void Event_Attribute_Add_SynthesizedAccessors() { var src1 = @" class C @@ -19616,7 +20452,7 @@ class C } [Fact] - public void Event_Attribute_Add() + public void Event_Attribute_Add_CustomAccessors() { var src1 = @" class C @@ -19768,6 +20604,169 @@ event Action F { add {} remove {} } capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); } + [Fact] + public void Event_Rename_Stackalloc() + { + // only name is changed, no changes to the accessors (not even whitespace) + var src1 = "class C { void G(Span s) {} event System.Action E { add => G(stackalloc int[1]); remove => G(stackalloc int[1]); } }"; + var src2 = "class C { void G(Span s) {} event System.Action F { add => G(stackalloc int[1]); remove => G(stackalloc int[1]); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [ + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("event accessor")), + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("event accessor")), + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + + [Fact] + public void Event_Update_Type_Stackalloc() + { + // only type is changed, no changes to the accessors (not even whitespace) + var src1 = "class C { void G(Span s) {} event System.Action E { add => G(stackalloc int[1]); remove => G(stackalloc int[1]); } }"; + var src2 = "class C { void G(Span s) {} event System.Action E { add => G(stackalloc int[1]); remove => G(stackalloc int[1]); } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [ + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("event accessor")), + Diagnostic(RudeEditKind.StackAllocUpdate, "stackalloc int[1]", GetResource("event accessor")) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + + [Fact] + public void Event_Rename_CustomAcccessors() + { + var src1 = "class C { event System.Action E { remove { } add { } } }"; + var src2 = "class C { event System.Action F { remove { } add { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.E"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.add_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.remove_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.add_F")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.remove_F")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + edits.VerifySemanticDiagnostics( + new[] { Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "event System.Action F", FeaturesResources.event_) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Event_Rename_SynthesizedAccessors() + { + var src1 = "class C { event System.Action E; }"; + var src2 = "class C { event System.Action F; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.Renamed, "F", GetResource("event field", "E"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Fact] + public void Event_Update_Type_CustomAcccessors() + { + var src1 = "class C { event System.Action E { remove { } add { } } }"; + var src2 = "class C { event System.Action E { remove { } add { } } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.E")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.add_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.remove_E"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.add_E")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.remove_E")), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "event System.Action E", GetResource("event"))], + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Fact] + public void Event_Update_Type_SynthesizedAcccessors_RuntimeTypeChanged() + { + var src1 = "class C { event System.Action E; }"; + var src2 = "class C { event System.Action E; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.TypeUpdate, "event System.Action E", GetResource("event field"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); + } + + [Theory] + [InlineData("string", "string?")] + [InlineData("object", "dynamic")] + [InlineData("(int a, int b)", "(int a, int c)")] + public void Event_Update_Type_SynthesizedAcccessors_RuntimeTypeUnchanged(string oldType, string newType) + { + var src1 = "class C { event System.Action<" + oldType + "> F, G; }"; + var src2 = "class C { event System.Action<" + newType + "> F, G; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.G"))); + } + + [Fact] + public void Event_Reorder_SynthesizedAccessors() + { + var src1 = "class C { int a = 0; int b = 1; event System.Action c = 2; }"; + var src2 = "class C { event System.Action c = 2; int a = 0; int b = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Reorder [event System.Action c = 2;]@32 -> @10"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Move, "event System.Action c = 2", CSharpFeaturesResources.event_field)); + } + + [Fact] + public void Event_Partial_InsertDelete_SynthesizedAccessors() + { + var srcA1 = "partial class C { static void F() {} }"; + var srcB1 = "partial class C { event System.Action E = F; }"; + + var srcA2 = "partial class C { static void F() {} event System.Action E = F; }"; + var srcB2 = "partial class C { }"; + + 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(), + }); + } + #endregion #region Parameter @@ -19810,7 +20809,7 @@ public void Parameter_Rename_Constructor() edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }, capabilities: EditAndContinueCapabilities.UpdateParameters); @@ -19844,17 +20843,6 @@ public void Parameter_Rename_Operator2() "Update [C b]@44 -> [C x]@44"); } - [Fact] - public void Parameter_Rename_Indexer() - { - var src1 = @"class C { public int this[int a, int b] { get { return 0; } } }"; - var src2 = @"class C { public int this[int a, int x] { get { return 0; } } }"; - - var edits = GetTopEdits(src1, src2); - edits.VerifyEdits( - "Update [int b]@33 -> [int x]@33"); - } - [Fact] public void Parameter_Insert() { @@ -19906,8 +20894,8 @@ public void Parameter_Insert_WithUpdatedReturnType() edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterCount() == 0)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } @@ -19974,7 +20962,7 @@ public void Parameter_Reorder() capabilities: EditAndContinueCapabilities.Baseline); } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/67268")] + [Fact] [WorkItem(67268, "https://github.com/dotnet/roslyn/issues/67268")] public void Parameter_Reorder_Constructor() { @@ -19988,7 +20976,7 @@ public void Parameter_Reorder_Constructor() edits.VerifySemantics( new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single()) + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), preserveLocalVariables: true) }, capabilities: EditAndContinueCapabilities.UpdateParameters); @@ -20035,14 +21023,15 @@ public void Parameter_Reorder_DifferentTypes() "Reorder [int b]@34 -> @24"); edits.VerifySemantics( - new[] { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_String)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("C.M").FirstOrDefault(m => m.GetParameterTypes()[0].SpecialType == SpecialType.System_Int32)?.ISymbol) + new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.RenamingNotSupportedByRuntime, "int b", FeaturesResources.parameter) }, + new[] { Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "public void M(int b, string a)", GetResource("method")) }, capabilities: EditAndContinueCapabilities.Baseline); } @@ -20109,71 +21098,57 @@ public void Parameter_Reorder_Rename_Generic() capabilities: EditAndContinueCapabilities.GenericUpdateMethod); } - [Fact] - public void Parameter_Type_Update() - { - var src1 = "class C { static void M(int a) {} }"; - var src2 = "class C { static void M(byte a) {} }"; - - var edits = GetTopEdits(src1, src2); - - edits.VerifySemantics( - new[] - { - SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")) - }, - capabilities: EditAndContinueCapabilities.AddMethodToExistingType); - - edits.VerifySemanticDiagnostics( - new[] - { - Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "byte a", FeaturesResources.parameter) - }, - capabilities: EditAndContinueCapabilities.Baseline); - } - - [Fact] - public void Parameter_Type_Update_Constructor() + [Theory] + [InlineData("int")] + [InlineData("in byte")] + [InlineData("ref byte")] + [InlineData("out byte")] + [InlineData("ref readonly byte")] + public void Parameter_Update_TypeOrRefKind_RuntimeTypeChanged(string oldType) { - var src1 = "class C { C(int a) {} }"; - var src2 = "class C { C(byte a) {} }"; + var src1 = "class C { int F(" + oldType + " a) => throw null!; }"; + var src2 = "class C { int F(byte a) => throw null!; }"; var edits = GetTopEdits(src1, src2); 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()) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.F"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F")) }, capabilities: EditAndContinueCapabilities.AddMethodToExistingType); edits.VerifySemanticDiagnostics( new[] { - Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "byte a", FeaturesResources.parameter) + Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "byte a", GetResource("method")) }, capabilities: EditAndContinueCapabilities.Baseline); } [Theory] + [InlineData("int", "this int")] + [InlineData("string[]", "params string[]")] [InlineData("string", "string?")] [InlineData("object", "dynamic")] [InlineData("(int a, int b)", "(int a, int c)")] - public void Parameter_Type_Update_RuntimeTypeUnchanged(string oldType, string newType) + public void Parameter_Update_Type_RuntimeTypeUnchanged(string oldType, string newType) { - var src1 = "class C { static void M(" + oldType + " a) {} }"; - var src2 = "class C { static void M(" + newType + " a) {} }"; + var src1 = "static class C { static void M(" + oldType + " a) {} }"; + var src2 = "static class C { static void M(" + newType + " a) {} }"; var edits = GetTopEdits(src1, src2); + // We don't require a runtime capability to update attributes. + // All runtimes support changing the attributes in metadata, some just don't reflect the changes in the Reflection model. + // Having compiler-generated attributes visible via Reflaction API is not that important. edits.VerifySemantics( SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.M"))); } [Fact] - public void Parameter_Type_Update_Nullable() + public void Parameter_Update_Type_Nullable() { var src1 = @" #nullable enable @@ -20191,8 +21166,6 @@ class C { static void M(string a) { } } [Theory] [InlineData("this")] - [InlineData("ref")] - [InlineData("out")] [InlineData("params")] public void Parameter_Modifier_Remove(string modifier) { @@ -20201,8 +21174,11 @@ public void Parameter_Modifier_Remove(string modifier) var edits = GetTopEdits(src1, src2); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "int[] a", FeaturesResources.parameter)); + // We don't require a runtime capability to update attributes. + // All runtimes support changing the attributes in metadata, some just don't reflect the changes in the Reflection model. + // Having compiler-generated attributes visible via Reflaction API is not that important. + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.F"))); } [Theory] @@ -20436,6 +21412,17 @@ public void MethodTypeParameterInsert1() edits.VerifyEdits( "Insert []@23", "Insert [A]@24"); + + edits.VerifySemantics( + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")), + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.GenericAddMethodToExistingType | EditAndContinueCapabilities.GenericUpdateMethod); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "A", GetResource("method"))], + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] @@ -20448,6 +21435,17 @@ public void MethodTypeParameterInsert2() edits.VerifyEdits( "Update []@23 -> []@23", "Insert [B]@26"); + + edits.VerifySemantics( + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")), + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.GenericAddMethodToExistingType | EditAndContinueCapabilities.GenericUpdateMethod); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "B", GetResource("method"))], + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] @@ -20460,6 +21458,17 @@ public void MethodTypeParameterDelete1() edits.VerifyEdits( "Delete []@23", "Delete [A]@24"); + + edits.VerifySemantics( + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")), + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.GenericAddMethodToExistingType | EditAndContinueCapabilities.GenericUpdateMethod); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "public void M()", GetResource("method"))], + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] @@ -20472,6 +21481,17 @@ public void MethodTypeParameterDelete2() edits.VerifyEdits( "Update []@23 -> []@23", "Delete [A]@24"); + + edits.VerifySemantics( + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.M"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.M")), + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.GenericAddMethodToExistingType | EditAndContinueCapabilities.GenericUpdateMethod); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "public void M()", GetResource("method"))], + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] @@ -20483,6 +21503,17 @@ public void MethodTypeParameterUpdate() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( "Update [A]@24 -> [B]@24"); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.Renamed, "B", GetResource("type parameter", "A"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.GenericAddMethodToExistingType | EditAndContinueCapabilities.GenericUpdateMethod); + + edits.VerifySemanticDiagnostics( + [ + Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "B", GetResource("method")), + Diagnostic(RudeEditKind.Renamed, "B", GetResource("type parameter", "A")) + ], + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] @@ -20494,6 +21525,10 @@ public void MethodTypeParameterReorder() var edits = GetTopEdits(src1, src2); edits.VerifyEdits( "Reorder [B]@26 -> @24"); + + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.Move, "B", GetResource("type parameter"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.GenericAddMethodToExistingType | EditAndContinueCapabilities.GenericUpdateMethod); } [Fact] @@ -20506,6 +21541,13 @@ public void MethodTypeParameterReorderAndUpdate() edits.VerifyEdits( "Reorder [B]@26 -> @24", "Update [A]@24 -> [C]@26"); + + edits.VerifySemanticDiagnostics( + [ + Diagnostic(RudeEditKind.Move, "B", GetResource("type parameter")), + Diagnostic(RudeEditKind.Renamed, "C", GetResource("type parameter", "A")) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.GenericAddMethodToExistingType | EditAndContinueCapabilities.GenericUpdateMethod); } [Fact] @@ -20521,8 +21563,9 @@ public void MethodTypeParameter_Attribute_Insert1() edits.VerifyEdits( "Update [T]@75 -> [[A]T]@72"); + // Updating attributes of type parameters not supported: edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.GenericMethodUpdate, "T") }, + [Diagnostic(RudeEditKind.GenericMethodUpdate, "T")], capabilities: EditAndContinueCapabilities.ChangeCustomAttributes | EditAndContinueCapabilities.GenericUpdateMethod); } @@ -20540,8 +21583,9 @@ public void MethodTypeParameter_Attribute_Insert2() edits.VerifyEdits( "Update [[A ]T]@120 -> [[A, B]T]@120"); + // Updating attributes of type parameters not supported: edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.GenericMethodUpdate, "T") }, + [Diagnostic(RudeEditKind.GenericMethodUpdate, "T")], capabilities: EditAndContinueCapabilities.ChangeCustomAttributes | EditAndContinueCapabilities.GenericUpdateMethod); } @@ -20558,33 +21602,9 @@ public void MethodTypeParameter_Attribute_Delete() edits.VerifyEdits( "Update [[A]T]@72 -> [T]@75"); + // Updating attributes of type parameters not supported: edits.VerifySemanticDiagnostics( - new[] - { - Diagnostic(RudeEditKind.GenericMethodUpdate, "T") - }, - capabilities: EditAndContinueCapabilities.ChangeCustomAttributes | EditAndContinueCapabilities.GenericUpdateMethod); - } - - [Fact] - public void MethodTypeParameter_Attribute_Update_NotSupportedByRuntime() - { - var attribute = "public class AAttribute : System.Attribute { }\n\n" + - "public class BAttribute : System.Attribute { }\n\n"; - - var src1 = attribute + @"class C { public void M<[System.Obsolete(""1""), B]T>() {} }"; - var src2 = attribute + @"class C { public void M<[System.Obsolete(""2""), A]T>() {} } "; - - var edits = GetTopEdits(src1, src2); - - edits.VerifyEdits( - "Update [[System.Obsolete(\"1\"), B]T]@120 -> [[System.Obsolete(\"2\"), A]T]@120"); - - edits.VerifySemanticDiagnostics( - new[] - { - Diagnostic(RudeEditKind.GenericMethodUpdate, "T") - }, + [Diagnostic(RudeEditKind.GenericMethodUpdate, "T")], capabilities: EditAndContinueCapabilities.ChangeCustomAttributes | EditAndContinueCapabilities.GenericUpdateMethod); } @@ -20601,9 +21621,10 @@ public void MethodTypeParameter_Attribute_Update() edits.VerifyEdits( "Update [[A(0)]T]@67 -> [[A(1)]T]@67"); + // Updating attributes of type parameters not supported: edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.GenericMethodUpdate, "T") }, - capabilities: EditAndContinueCapabilities.ChangeCustomAttributes); + [Diagnostic(RudeEditKind.GenericMethodUpdate, "T")], + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes | EditAndContinueCapabilities.GenericUpdateMethod); } [Fact] @@ -20620,12 +21641,16 @@ public void MethodTypeParameter_Attribute_Update_WithBodyUpdate() "Update [void F<[A(0)]T>(T a) { F(0); }]@60 -> [void F<[A(1)]T>(T a) { F(1); }]@60", "Update [[A(0)]T]@67 -> [[A(1)]T]@67"); + // Updating attributes of type parameters not supported: edits.VerifySemanticDiagnostics( - new[] - { + [Diagnostic(RudeEditKind.GenericMethodUpdate, "T")], + capabilities: EditAndContinueCapabilities.ChangeCustomAttributes | EditAndContinueCapabilities.GenericUpdateMethod); + + edits.VerifySemanticDiagnostics( + [ Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "void F<[A(1)]T>(T a)", GetResource("method")), Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "T", GetResource("type parameter")) - }, + ], capabilities: EditAndContinueCapabilities.Baseline); } @@ -20668,13 +21693,16 @@ public void TypeTypeParameterInsert2() [Fact] public void TypeTypeParameterDelete1() { - var src1 = @"class C { }"; - var src2 = @"class C { } "; + var src1 = @"using System; class C { }"; + var src2 = @"using System; class C { } "; var edits = GetTopEdits(src1, src2); edits.VerifyEdits( - "Delete []@7", - "Delete [A]@8"); + "Delete []@21", + "Delete [A]@22"); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", GetResource("type parameter", "A"))); } [Fact] @@ -20690,7 +21718,7 @@ public void TypeTypeParameterDelete2() "Delete [A]@8"); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.type_parameter, "A"))); + Diagnostic(RudeEditKind.Delete, "class C", GetResource("type parameter", "A"))); } [Fact] @@ -20994,7 +22022,7 @@ public void TypeConstraint_Delete_WithParameter() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.type_parameter, "T"))); + Diagnostic(RudeEditKind.Delete, "class C", GetResource("type parameter", "T"))); } [Fact] @@ -21370,7 +22398,6 @@ public void TopLevelStatements_Await_Delete_Last() edits.VerifySemanticDiagnostics( new[] { - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "return 1;", GetResource("top-level code")), Diagnostic(RudeEditKind.ChangeImplicitMainReturnType, "return 1;") }, capabilities: EditAndContinueCapabilities.Baseline); @@ -21611,7 +22638,6 @@ public void TopLevelStatements_TaskToVoid() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "Console.Write(1);", GetResource("top-level code")), Diagnostic(RudeEditKind.ChangeImplicitMainReturnType, "Console.Write(1);")); } @@ -21665,7 +22691,6 @@ Task GetInt() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ChangingFromAsynchronousToSynchronous, "Console.Write(1);", GetResource("top-level code")), Diagnostic(RudeEditKind.ChangeImplicitMainReturnType, "Console.Write(1);")); } diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index 5121c0002e647..a084c2e34402c 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -280,10 +280,20 @@ private static void VerifySemanticEdits( SyntaxNode newRoot, string? message = null) { + // sort expected and actual edits to ignore differences in order, which are insignificant: + expectedSemanticEdits = expectedSemanticEdits.Sort((x, y) => CompareEdits(CreateSymbolKey(x), x.Kind, CreateSymbolKey(y), y.Kind)); + actualSemanticEdits = actualSemanticEdits.Sort((x, y) => CompareEdits(x.Symbol, x.Kind, y.Symbol, y.Kind)); + + static int CompareEdits(SymbolKey leftKey, SemanticEditKind leftKind, SymbolKey rightKey, SemanticEditKind rightKind) + => leftKey.ToString().CompareTo(rightKey.ToString()) is not 0 and var result ? result : leftKind.CompareTo(rightKind); + + SymbolKey CreateSymbolKey(SemanticEditDescription edit) + => SymbolKey.Create(edit.SymbolProvider((edit.Kind == SemanticEditKind.Delete) ? oldCompilation : newCompilation)); + // string comparison to simplify understanding why a test failed: AssertEx.Equal( expectedSemanticEdits.Select(e => $"{e.Kind}: {e.SymbolProvider((e.Kind == SemanticEditKind.Delete ? oldCompilation : newCompilation))}"), - actualSemanticEdits.NullToEmpty().Select(e => $"{e.Kind}: {e.Symbol.Resolve(e.Kind == SemanticEditKind.Delete ? oldCompilation : newCompilation).Symbol}"), + actualSemanticEdits.Select(e => $"{e.Kind}: {e.Symbol.Resolve(e.Kind == SemanticEditKind.Delete ? oldCompilation : newCompilation).Symbol}"), message: message); for (var i = 0; i < actualSemanticEdits.Length; i++) diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index 5de45a128c1bd..95b726d40e429 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -342,12 +342,11 @@ End Class End Class " - ' TODO (bug 755959): better deleted active statement span Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("method", "Goo(a As Integer)"))) + Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("method", "C.Goo(Integer)"))) End Sub @@ -780,8 +779,8 @@ End Module Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.Delete, Nothing, GetResource("Module", "Module1")), - Diagnostic(RudeEditKind.DeleteActiveStatement, Nothing, GetResource("method", "Module1.Main()"))) + Diagnostic(RudeEditKind.Delete, "", GetResource("Module", "Module1")), + Diagnostic(RudeEditKind.DeleteActiveStatement, "", GetResource("method", "Module1.Main()"))) End Sub #End Region @@ -2252,7 +2251,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("field", "a")), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("field", "C.a")), Diagnostic(RudeEditKind.Delete, "Class C", GetResource("field", "a"))) End Sub @@ -2306,7 +2305,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("field", "b")), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("field", "C.b")), Diagnostic(RudeEditKind.Delete, "a, c As New D()", GetResource("field", "b"))) End Sub @@ -2334,7 +2333,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("field", "c")), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("field", "C.c")), Diagnostic(RudeEditKind.Delete, "a, b As New D()", GetResource("field", "c"))) End Sub @@ -2389,7 +2388,7 @@ End Class Dim active = GetActiveStatements(src1, src2) edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("field", "c")), + Diagnostic(RudeEditKind.DeleteActiveStatement, "Class C", GetResource("field", "C.c")), Diagnostic(RudeEditKind.Delete, "a,b As Integer", GetResource("field", "c"))) End Sub @@ -2404,9 +2403,9 @@ Class C End Class " - Dim src2 = " + Dim src2 = " Class C - Dim a,b(1),c As Integer + Dim a,b(1),c As Integer Sub New End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb index 9ccf4dc772147..32c515d18bda0 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/LineEditTests.vb @@ -369,7 +369,7 @@ End Class edits.VerifyLineEdits( Array.Empty(Of SequencePointUpdates), - diagnostics:={Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, vbCrLf & " ", FeaturesResources.method)}, + diagnostics:={Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, " ", FeaturesResources.method)}, capabilities:=EditAndContinueCapabilities.Baseline) edits.VerifyLineEdits( @@ -400,7 +400,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifyLineEdits( Array.Empty(Of SequencePointUpdates), - diagnostics:={Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, vbCrLf & " ", FeaturesResources.method)}, + diagnostics:={Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, " ", FeaturesResources.method)}, capabilities:=EditAndContinueCapabilities.Baseline) edits.VerifyLineEdits( @@ -1028,7 +1028,7 @@ End Class Array.Empty(Of SequencePointUpdates), diagnostics:= { - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "Class C(Of T)", GetResource("constructor", "New()")) + Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, " ", GetResource("field")) }, capabilities:=EditAndContinueCapabilities.Baseline) diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index eb248cebb4472..9c65386fee53d 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -12,6 +12,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue.UnitTests Public Class TopLevelEditingTests Inherits EditingTestBase + + Private Shared ReadOnly s_attribute As String = " +Class A + Inherits System.Attribute + + Sub New(Optional x As Integer = 0) + End Sub +End Class +" + #Region "Imports" @@ -239,7 +249,7 @@ Option Strict On "Delete [Option Strict On]@2") edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, Nothing, VBFeaturesResources.option_)) + Diagnostic(RudeEditKind.Delete, "", VBFeaturesResources.option_)) End Sub @@ -841,27 +851,37 @@ End Class" End Sub - Public Sub ModuleInsert() + Public Sub Module_Insert() Dim src1 = "" Dim src2 = "Module C : End Module" Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics( + semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C"))}, + capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "Module C", VBFeaturesResources.module_)) + diagnostics:={Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "Module C", GetResource("module"))}, + capabilities:=EditAndContinueCapabilities.Baseline) End Sub - Public Sub PartialModuleInsert() + Public Sub Module_Insert_Partial() Dim src1 = "" Dim src2 = "Partial Module C : End Module" Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics( + semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C"))}, + capabilities:=EditAndContinueCapabilities.NewTypeDefinition) + edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "Partial Module C", VBFeaturesResources.module_)) + diagnostics:={Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "Partial Module C", GetResource("module"))}, + capabilities:=EditAndContinueCapabilities.Baseline) End Sub - Public Sub PartialModuleDelete() + Public Sub Module_Delete_Partial() Dim src1 = "Partial Module C : End Module" Dim src2 = "" Dim edits = GetTopEdits(src1, src2) @@ -1366,8 +1386,7 @@ End Class" Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "F5(1, 2)", FeaturesResources.field), Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "F6?", FeaturesResources.field), Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "WE", VBFeaturesResources.WithEvents_field), - Diagnostic(RudeEditKind.InsertVirtual, "WE", VBFeaturesResources.WithEvents_field), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "Class C(Of T)", GetResource("constructor", "New()")) + Diagnostic(RudeEditKind.InsertVirtual, "WE", VBFeaturesResources.WithEvents_field) }, capabilities:=nonGenericCapabilities) edits.VerifySemanticDiagnostics( @@ -1381,8 +1400,7 @@ End Class" Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "F5(1, 2)", FeaturesResources.field), Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "F6?", FeaturesResources.field), Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "WE", VBFeaturesResources.WithEvents_field), - Diagnostic(RudeEditKind.InsertVirtual, "WE", VBFeaturesResources.WithEvents_field), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "Class C(Of T)", GetResource("constructor", "New()")) + Diagnostic(RudeEditKind.InsertVirtual, "WE", VBFeaturesResources.WithEvents_field) }, capabilities:=nonGenericCapabilities Or EditAndContinueCapabilities.GenericAddMethodToExistingType) edits.VerifySemanticDiagnostics( @@ -1392,14 +1410,12 @@ End Class" Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "Event E1(sender As Object, e As EventArgs)", FeaturesResources.event_), Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "Event E2", FeaturesResources.event_), Diagnostic(RudeEditKind.InsertNotSupportedByRuntime, "WE", VBFeaturesResources.WithEvents_field), - Diagnostic(RudeEditKind.InsertVirtual, "WE", VBFeaturesResources.WithEvents_field), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "Class C(Of T)", GetResource("constructor", "New()")) + Diagnostic(RudeEditKind.InsertVirtual, "WE", VBFeaturesResources.WithEvents_field) }, capabilities:=nonGenericCapabilities Or EditAndContinueCapabilities.GenericAddFieldToExistingType) edits.VerifySemanticDiagnostics( { - Diagnostic(RudeEditKind.InsertVirtual, "WE", VBFeaturesResources.WithEvents_field), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "Class C(Of T)", GetResource("constructor", "New()")) + Diagnostic(RudeEditKind.InsertVirtual, "WE", VBFeaturesResources.WithEvents_field) }, capabilities:=nonGenericCapabilities Or EditAndContinueCapabilities.GenericAddMethodToExistingType Or EditAndContinueCapabilities.GenericAddFieldToExistingType) End Sub @@ -1780,6 +1796,9 @@ End Interface DocumentResults( semanticEdits:= { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("S")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("I")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.F")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("S.F")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("I.F")) @@ -2441,7 +2460,7 @@ End Class "Insert [a]@27") edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "a As Integer", FeaturesResources.parameter)) + Diagnostic(RudeEditKind.TypeUpdate, "a As Integer", GetResource("delegate"))) End Sub @@ -2466,7 +2485,7 @@ End Class "Delete [a]@27") edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Public Delegate Function D()", GetResource("parameter", "a As Integer"))) + Diagnostic(RudeEditKind.TypeUpdate, "Public Delegate Function D()", GetResource("delegate"))) End Sub @@ -2502,7 +2521,7 @@ End Class "Update [a As Integer]@27 -> [a As Byte]@27") edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.TypeUpdate, "a As Byte", GetResource("parameter"))) + Diagnostic(RudeEditKind.TypeUpdate, "a As Byte", GetResource("delegate"))) End Sub @@ -3212,7 +3231,7 @@ End Structure DocumentResults( diagnostics:= { - Diagnostic(RudeEditKind.ChangingParameterTypes, "Delegate Sub D(a As Integer)", FeaturesResources.delegate_) + Diagnostic(RudeEditKind.TypeUpdate, "Delegate Sub D(a As Integer)", FeaturesResources.delegate_) }), DocumentResults() }) @@ -3868,16 +3887,99 @@ Class B : Inherits Attribute : End Class #Region "Members" - Public Sub PartialMember_DeleteInsert() + Public Sub PartialMember_DeleteInsert_Field() + Dim srcA1 = " +Partial Class C + Dim F1 = 1, F2 As New Object, F3 As Integer, F4 As New Object, F5(1, 2), F6? As Integer +End Class +" + Dim srcB1 = " +Partial Class C +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + 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) + }) + }) + End Sub + + + Public Sub PartialMember_DeleteInsert_Constructor() Dim srcA1 = " Imports System Partial Class C - Dim F1 = 1, F2 As New Object, F3 As Integer, F4 As New Object, F5(1, 2), F6? As Integer + Sub New() + End Sub +End Class +" + Dim srcB1 = " +Imports System + +Partial Class C +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + 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) + }) + }) + End Sub + + Public Sub PartialMember_DeleteInsert_Method() + Dim srcA1 = " +Imports System + +Partial Class C Sub M() End Sub +End Class +" + Dim srcB1 = " +Imports System +Partial Class C +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.M")) + }) + }) + End Sub + + + Public Sub PartialMember_DeleteInsert_Property() + Dim srcA1 = " +Imports System + +Partial Class C Property P1(i As Integer) As Integer Get Return 1 @@ -3888,12 +3990,70 @@ Partial Class C Property P2 As Integer Property P3 As New Object +End Class +" + Dim srcB1 = " +Imports System - Event E1(sender As Object, e As EventArgs) +Partial Class C +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 - Event E2 As Action + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + 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), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of PropertySymbol)("C.P1").GetMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of PropertySymbol)("C.P1").SetMethod) + }) + }) + End Sub + + + Public Sub PartialMember_DeleteInsert_Event_WithHandlerDeclaration() + Dim srcA1 = " +Imports System + +Partial Class C + Event E(sender As Object, e As EventArgs) +End Class +" + Dim srcB1 = " +Imports System - Custom Event E3 As EventHandler +Partial Class C +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) + End Sub + + + Public Sub PartialMember_DeleteInsert_Event() + Dim srcA1 = " +Imports System + +Partial Class C + Event E1 As Action + + Custom Event E2 As EventHandler AddHandler(value As EventHandler) End AddHandler RemoveHandler(value As EventHandler) @@ -3901,8 +4061,6 @@ Partial Class C RaiseEvent(sender As Object, e As EventArgs) End RaiseEvent End Event - - Dim WithEvents WE As Object End Class " Dim srcB1 = " @@ -3917,25 +4075,43 @@ 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").GetMember(Of MethodSymbol)("M")), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of PropertySymbol)("P1").GetMethod), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of PropertySymbol)("P1").SetMethod), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of EventSymbol)("E3").AddMethod), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of EventSymbol)("E3").RemoveMethod), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of EventSymbol)("E3").RaiseMethod), - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), partialType:="C", preserveLocalVariables:=True) + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of EventSymbol)("C.E2").AddMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of EventSymbol)("C.E2").RemoveMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of EventSymbol)("C.E2").RaiseMethod) }) }) End Sub + + Public Sub PartialMember_DeleteInsert_WithEvents() + Dim srcA1 = " +Imports System + +Partial Class C + Dim WithEvents WE As Object +End Class +" + Dim srcB1 = " +Imports System + +Partial Class C +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults(), + DocumentResults() + }) + End Sub + Public Sub PartialMember_InsertDelete_MultipleDocuments() Dim srcA1 = "Partial Class C : End Class" @@ -4105,6 +4281,135 @@ End Class capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub + + public Sub PartialMember_RenameInsertDelete_SameFile() + Dim src1 = " +Partial Class C + Sub F1(a As Integer) : End Sub + Sub F4(d As Integer) : End Sub +End Class + +Partial Class C + Sub F3(c As Integer) : End Sub + Sub F2(b As Integer) : End Sub +End Class + +Partial Class C +End Class +" + + Dim src2 = " +Partial Class C + Sub F2(b As Integer) : End Sub + Sub F4(d As Integer) : End Sub +End Class + +Partial Class C + Sub F1(a As Integer) : End Sub +End Class + +Partial Class C + Sub F3(c As Integer) : End Sub +End Class +" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifyEdits( + "Insert [Sub F3(c As Integer) : End Sub]@194", + "Update [Sub F1(a As Integer)]@24 -> [Sub F2(b As Integer)]@24", + "Update [Sub F3(c As Integer)]@127 -> [Sub F1(a As Integer)]@127", + "Insert [Sub F3(c As Integer)]@194", + "Insert [(c As Integer)]@200", + "Insert [c As Integer]@201", + "Update [a]@31 -> [b]@31", + "Update [c]@134 -> [a]@134", + "Insert [c]@201", + "Delete [Sub F2(b As Integer) : End Sub]@163", + "Delete [Sub F2(b As Integer)]@163", + "Delete [(b As Integer)]@169", + "Delete [b As Integer]@170", + "Delete [b]@170") + + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.F2")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.F3")) + }) + End Sub + + + Public Sub PartialMember_SignatureChangeInsertDelete() + Dim srcA1 = " +Partial Class C + Sub F(x As Byte) + End Sub +End Class +" + Dim srcB1 = " +Partial Class C + Sub F(x As Char) + End Sub +End Class +" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + { + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMembers("C.F").Single(Function(m) m.GetParameters()(0).Type.SpecialType = SpecialType.System_Char))}), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMembers("C.F").Single(Function(m) m.GetParameters()(0).Type.SpecialType = SpecialType.System_Byte))}) + }, + capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) + End Sub + + + Public Sub PartialMember_SignatureChangeInsertDelete_PropertyWithParameters() + Dim srcA1 = " +Partial Class C + Property P(x As Byte) As Integer + Get + Return 1 + End Get + Set + End Set + End Property +End Class" + Dim srcB1 = " +Partial Class C + Property P(x As Char) As Integer + Get + Return 1 + End Get + Set + End Set + End Property +End Class" + Dim srcA2 = srcB1 + Dim srcB2 = srcA1 + + EditAndContinueValidation.VerifySemantics( + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, + { + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) DirectCast(c.GetMembers("C.P").Single(Function(m) m.GetParameters()(0).Type.SpecialType = SpecialType.System_Char), PropertySymbol).GetMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) DirectCast(c.GetMembers("C.P").Single(Function(m) m.GetParameters()(0).Type.SpecialType = SpecialType.System_Char), PropertySymbol).SetMethod) + }), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) DirectCast(c.GetMembers("C.P").Single(Function(m) m.GetParameters()(0).Type.SpecialType = SpecialType.System_Byte), PropertySymbol).GetMethod), + SemanticEdit(SemanticEditKind.Update, Function(c) DirectCast(c.GetMembers("C.P").Single(Function(m) m.GetParameters()(0).Type.SpecialType = SpecialType.System_Byte), PropertySymbol).SetMethod) + }) + }) + End Sub + Public Sub PartialMember_DeleteInsert_UpdateMethodBodyError() Dim srcA1 = " @@ -4547,7 +4852,7 @@ End Structure edits.VerifySemanticDiagnostics( diagnostics:= { - Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "Function Goo()", FeaturesResources.method) + Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "Function Goo()", FeaturesResources.method) }, capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -4574,7 +4879,7 @@ End Structure Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "a As Boolean", FeaturesResources.parameter)) + Diagnostic(RudeEditKind.TypeUpdate, "a As Boolean", GetResource("method"))) End Sub @@ -5563,7 +5868,7 @@ End Interface DocumentResults(), DocumentResults(), DocumentResults( - semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F").PartialImplementationPart)}) + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of MethodSymbol)("C.F").PartialImplementationPart, partialType:="C")}) }) End Sub @@ -5583,7 +5888,7 @@ End Interface DocumentResults(), DocumentResults(), DocumentResults( - semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F").PartialImplementationPart)}) + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of MethodSymbol)("C.F").PartialImplementationPart, partialType:="C")}) }) End Sub @@ -5595,13 +5900,19 @@ End Interface Dim srcA2 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" Dim srcB2 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" - ' TODO: should be an update edit since the location of the implementation changed - ' semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("F"))} EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { - DocumentResults(), - DocumentResults() + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of MethodSymbol)("C.F").PartialImplementationPart, partialType:="C") + }), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of MethodSymbol)("C.F").PartialImplementationPart, partialType:="C") + }) }) End Sub @@ -5620,7 +5931,7 @@ End Interface DocumentResults( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember(Of MethodSymbol)("C.F"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C"), partialType:="C") }) }) End Sub @@ -5636,18 +5947,22 @@ End Interface EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { - DocumentResults(), DocumentResults( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F")?.PartialImplementationPart, deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember(Of MethodSymbol)("C.F").PartialImplementationPart, deletedSymbolContainerProvider:=Function(c) c.GetMember("C"), partialType:="C") + }), + DocumentResults( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember(Of MethodSymbol)("C.F").PartialImplementationPart, deletedSymbolContainerProvider:=Function(c) c.GetMember("C"), partialType:="C") } ) }) End Sub - Public Sub Method_Partial__DeleteInsertBoth() + Public Sub Method_Partial_DeleteInsertBoth() Dim srcA1 = "Partial Class C" + vbCrLf + "Partial Private Sub F() : End Sub : End Class" Dim srcB1 = "Partial Class C" + vbCrLf + "Private Sub F() : End Sub : End Class" Dim srcC1 = "Partial Class C : End Class" @@ -5661,11 +5976,12 @@ End Interface EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2), GetTopEdits(srcD1, srcD2)}, { - DocumentResults(), DocumentResults(), DocumentResults(), DocumentResults( - semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F").PartialImplementationPart)}) + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of MethodSymbol)("C.F").PartialImplementationPart, partialType:="C")}), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of MethodSymbol)("C.F").PartialImplementationPart, partialType:="C")}) }) End Sub @@ -5698,7 +6014,8 @@ End Interface EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { - DocumentResults(), + DocumentResults( + semanticEdits:={SemanticEdit(SemanticEditKind.Replace, Function(c) c.GetMember("C"), partialType:="C")}), DocumentResults( semanticEdits:={SemanticEdit(SemanticEditKind.Replace, Function(c) c.GetMember("C"), partialType:="C")}) }, @@ -6139,7 +6456,7 @@ End Class {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { DocumentResults( - semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(Function(m) m.Parameters.IsEmpty))}), + semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(Function(m) m.Parameters.IsEmpty), partialType:="C")}), DocumentResults() }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) @@ -6759,7 +7076,7 @@ End Class SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(Function(m) m.Parameters.Single().Type.Name = "Int32"), partialType:="C", syntaxMap:=syntaxMapB), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(Function(m) m.Parameters.Single().Type.Name = "Boolean"), partialType:="C", syntaxMap:=syntaxMapB), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(Function(m) m.Parameters.Single().Type.Name = "UInt32"), partialType:="C", syntaxMap:=syntaxMapB), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(Function(m) m.Parameters.Single().Type.Name = "Byte"), syntaxMap:=Nothing) + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("C").Constructors.Single(Function(m) m.Parameters.Single().Type.Name = "Byte"), partialType:="C", syntaxMap:=Nothing) }) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) @@ -7471,13 +7788,9 @@ End Class "Insert [a]@14", "Delete [Event a As Action]@10") - edits.VerifySemantics( - semanticEdits:= - { - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.a")), - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.add_a"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.remove_a"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) - }, + ' Deleting field aEvent + edits.VerifySemanticDiagnostics( + {Diagnostic(RudeEditKind.Delete, "Class C", GetResource("event", "a"))}, capabilities:=EditAndContinueCapabilities.AddInstanceFieldToExistingType) End Sub @@ -8083,9 +8396,12 @@ End Class edits.VerifySemantics( semanticEdits:= { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.set_P").FirstOrDefault(Function(m) m.GetParameters().Any(Function(p) p.Type.SpecialType = SpecialType.System_Int32)), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.P")) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.set_P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.P")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.get_P")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.set_P")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType Or EditAndContinueCapabilities.AddInstanceFieldToExistingType) End Sub @@ -8263,8 +8579,10 @@ End Class" edits.VerifySemantics( semanticEdits:= { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.Q")) + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.Q")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.get_Q")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -8281,8 +8599,10 @@ End Class" edits.VerifySemantics( semanticEdits:= { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.Q")) + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.Q")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.get_Q")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType Or EditAndContinueCapabilities.AddInstanceFieldToExistingType) End Sub @@ -9153,10 +9473,7 @@ End Class Dim edits = GetTopEdits(src1, src2) edits.VerifySemanticDiagnostics( - { - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "a As Integer = 2", GetResource("field")), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "Class C(Of T)", GetResource("constructor", "New()")) - }, + {Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "a As Integer = 2", GetResource("field"))}, capabilities:=EditAndContinueCapabilities.Baseline) edits.VerifySemantics( @@ -9173,8 +9490,7 @@ End Class edits.VerifySemanticDiagnostics( { - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "Property a", GetResource("auto-property")), - Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "Class C(Of T)", GetResource("constructor", "New()")) + Diagnostic(RudeEditKind.UpdatingGenericNotSupportedByRuntime, "Property a", GetResource("auto-property")) }, capabilities:=EditAndContinueCapabilities.Baseline) @@ -9429,7 +9745,7 @@ End Class DocumentResults(), DocumentResults(diagnostics:= { - Diagnostic(RudeEditKind.TypeUpdate, "a, b As Byte", FeaturesResources.field) + Diagnostic(RudeEditKind.TypeUpdate, "a", FeaturesResources.field) }) }) End Sub @@ -9535,6 +9851,7 @@ End Class { 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.a"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) }) End Sub @@ -10542,6 +10859,10 @@ End Class SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.add_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.remove_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.raise_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.add_F")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.remove_F")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.raise_F")), SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.F")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) @@ -10589,10 +10910,11 @@ End Class edits.VerifySemantics( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.add_E").FirstOrDefault(Function(m) m.GetParameters()(0).Type.GetTypeParameters().Length = 0), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.remove_E").FirstOrDefault(Function(m) m.GetParameters()(0).Type.GetTypeParameters().Length = 0), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.raise_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.E")) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.add_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.remove_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.add_E")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.remove_E")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.E")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -10663,7 +10985,8 @@ End Class { SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.add_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.remove_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.raise_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.raise_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) }) End Sub @@ -10750,13 +11073,10 @@ End Class Class C End Class " + ' Deleting EEvent field Dim edits = GetTopEdits(src1, src2) - edits.VerifySemantics( - semanticEdits:= - { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.add_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.remove_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) - }, + edits.VerifySemanticDiagnostics( + {Diagnostic(RudeEditKind.Delete, "Class C", GetResource("event", "E(a As Integer)"))}, capabilities:=EditAndContinueCapabilities.AddInstanceFieldToExistingType Or EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -10771,14 +11091,57 @@ End Class Class C End Class " + ' Deleting EEvent field + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemanticDiagnostics( + {Diagnostic(RudeEditKind.Delete, "Class C", GetResource("event", "E()"))}, + capabilities:=EditAndContinueCapabilities.AddInstanceFieldToExistingType Or EditAndContinueCapabilities.AddMethodToExistingType) + End Sub + + + Public Sub Event_WithHandlerDeclaration_Parameter_Update_Attribute() + Dim src1 = s_attribute & " +Class C + Event E(a As Integer) +End Class +" + Dim src2 = s_attribute & " +Class C + Event E(a As Integer) +End Class +" + ' parameter attributes are applied to BeginInvoke and Invoke methods of the generated event handler delegate type: Dim edits = GetTopEdits(src1, src2) edits.VerifySemantics( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.add_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.remove_E"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.EEventHandler.BeginInvoke")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.EEventHandler.Invoke")) }, - capabilities:=EditAndContinueCapabilities.AddInstanceFieldToExistingType Or EditAndContinueCapabilities.AddMethodToExistingType) + capabilities:=EditAndContinueCapabilities.ChangeCustomAttributes) + End Sub + + + Public Sub Event_WithHandlerDeclaration_Parameter_Update_Rename() + Dim src1 = s_attribute & " +Class C + Event E(a As Integer) +End Class +" + Dim src2 = s_attribute & " +Class C + Event E(b As Integer) +End Class +" + ' parameter names are used for parameters of BeginInvoke and Invoke methods of the generated event handler delegate type: + Dim edits = GetTopEdits(src1, src2) + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.EEventHandler.BeginInvoke")), + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.EEventHandler.Invoke")) + }, + capabilities:=EditAndContinueCapabilities.UpdateParameters) End Sub #End Region @@ -10824,7 +11187,7 @@ End Class ActiveStatementsDescription.Empty, semanticEdits:= { - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single()) + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").InstanceConstructors.Single(), preserveLocalVariables:=True) }, capabilities:=EditAndContinueCapabilities.UpdateParameters) End Sub @@ -10890,7 +11253,7 @@ End Class ActiveStatementsDescription.Empty, semanticEdits:= { - SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.P")) + SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("C.get_P")) }, capabilities:=EditAndContinueCapabilities.UpdateParameters) End Sub @@ -10930,7 +11293,9 @@ End Class ActiveStatementsDescription.Empty, semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.get_P").FirstOrDefault(Function(m) m.GetParameters().Any(Function(p) p.Type.SpecialType = SpecialType.System_Int32)), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.get_P")), SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.P")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) @@ -10949,7 +11314,9 @@ End Class ActiveStatementsDescription.Empty, semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.get_P").FirstOrDefault(Function(m) m.GetParameters().Any(Function(p) p.Type.SpecialType = SpecialType.System_Int32)), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.get_P")), SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.P")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) @@ -10968,7 +11335,9 @@ End Class ActiveStatementsDescription.Empty, semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.get_P").FirstOrDefault(Function(m) m.GetParameters().Any(Function(p) p.Type.SpecialType = SpecialType.System_String)), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.get_P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.P"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.get_P")), SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.P")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) @@ -11013,8 +11382,8 @@ End Class edits.VerifySemantics( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 0), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 1)) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -11033,8 +11402,8 @@ End Class edits.VerifySemantics( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 1), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 2)) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -11065,8 +11434,8 @@ End Class edits.VerifySemantics( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 0), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 1)) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -11084,13 +11453,13 @@ End Class edits.VerifySemantics( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 1), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 0)) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "Public Sub M()", GetResource("parameter", "a", "method", "M(a As Integer)"))}, + {Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "Public Sub M()", GetResource("method"))}, capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -11108,13 +11477,13 @@ End Class edits.VerifySemantics( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 2), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 1)) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.DeleteNotSupportedByRuntime, "Public Sub M(b As Integer)", GetResource("parameter", "a", "method", "M(a As Integer, b As Integer)"))}, + {Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "Public Sub M(b As Integer)", GetResource("method"))}, capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -11144,8 +11513,8 @@ End Class edits.VerifySemantics( semanticEdits:= { - SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 1), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMembers("C.M").FirstOrDefault(Function(m) m.GetParameters().Length = 0)) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) }, capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -11187,17 +11556,31 @@ End Class capabilities:=EditAndContinueCapabilities.Baseline) End Sub - - - - Public Sub Parameter_Modifier_Remove(modifier As String) - Dim src1 = "Module M" & vbLf & "Sub F(" & modifier & " a As Integer()) : End Sub : End Module" + + Public Sub Parameter_Modifier_Remove_ByRef() + Dim src1 = "Module M" & vbLf & "Sub F(ByRef a As Integer()) : End Sub : End Module" Dim src2 = "Module M" & vbLf & "Sub F(a As Integer()) : End Sub : End Module" Dim edits = GetTopEdits(src1, src2) - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.ModifiersUpdate, "a As Integer()", FeaturesResources.parameter)) + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("M.F"), deletedSymbolContainerProvider:=Function(c) c.GetMember("M")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("M.F")) + }, + capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) + End Sub + + + Public Sub Parameter_Modifier_Remove_ParamArray() + Dim src1 = "Module M" & vbLf & "Sub F(ParamArray a As Integer()) : End Sub : End Module" + Dim src2 = "Module M" & vbLf & "Sub F(a As Integer()) : End Sub : End Module" + + Dim edits = GetTopEdits(src1, src2) + + edits.VerifySemantics( + semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember("M.F"))}) End Sub @@ -11373,8 +11756,7 @@ End Class "Update [Public Function M() As Integer]@11 -> [Public Function M()]@11") edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Public Function M()", FeaturesResources.method), - Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "Public Function M()", FeaturesResources.method)}, + {Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "Public Function M()", FeaturesResources.method)}, capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -11388,8 +11770,7 @@ End Class "Update [Public Function M()]@11 -> [Public Function M() As Integer]@11") edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.ChangingAttributesNotSupportedByRuntime, "Public Function M()", FeaturesResources.method), - Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "Public Function M()", FeaturesResources.method)}, + {Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "Public Function M()", FeaturesResources.method)}, capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -11411,7 +11792,7 @@ End Class capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.ChangingTypeNotSupportedByRuntime, "Public Function M()", FeaturesResources.method)}, + {Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "Public Function M()", FeaturesResources.method)}, capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -11429,8 +11810,17 @@ End Class "Insert [(Of A)]@23", "Insert [A]@27") + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) + }, + capabilities:=EditAndContinueCapabilities.AddMethodToExistingType Or EditAndContinueCapabilities.GenericAddMethodToExistingType) + edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "A", FeaturesResources.type_parameter)) + {Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "A", GetResource("method"))}, + capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -11443,8 +11833,17 @@ End Class "Update [(Of A )]@23 -> [(Of A, B)]@23", "Insert [B]@30") + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) + }, + capabilities:=EditAndContinueCapabilities.AddMethodToExistingType Or EditAndContinueCapabilities.GenericAddMethodToExistingType Or EditAndContinueCapabilities.GenericUpdateMethod) + edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Insert, "B", FeaturesResources.type_parameter)) + {Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "B", GetResource("method"))}, + capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -11457,9 +11856,17 @@ End Class "Delete [(Of A)]@23", "Delete [A]@27") + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) + }, + capabilities:=EditAndContinueCapabilities.GenericUpdateMethod Or EditAndContinueCapabilities.AddMethodToExistingType) + edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.Delete, "Public Sub M ()", GetResource("type parameter", "A"))}, - capabilities:=EditAndContinueCapabilities.GenericUpdateMethod) + {Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "Public Sub M ()", GetResource("method"))}, + capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -11472,9 +11879,17 @@ End Class "Update [(Of A, B)]@23 -> [(Of B )]@23", "Delete [A]@27") + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.M"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.M")) + }, + capabilities:=EditAndContinueCapabilities.GenericUpdateMethod Or EditAndContinueCapabilities.AddMethodToExistingType Or EditAndContinueCapabilities.GenericAddMethodToExistingType) + edits.VerifySemanticDiagnostics( - {Diagnostic(RudeEditKind.Delete, "Public Sub M(Of B )()", GetResource("type parameter", "A"))}, - capabilities:=EditAndContinueCapabilities.GenericUpdateMethod) + {Diagnostic(RudeEditKind.ChangingSignatureNotSupportedByRuntime, "Public Sub M(Of B )()", GetResource("method"))}, + capabilities:=EditAndContinueCapabilities.Baseline) End Sub @@ -11551,13 +11966,19 @@ End Class Public Sub TypeTypeParameterDelete1() - Dim src1 = "Class C(Of A) : End Class" - Dim src2 = "Class C : End Class" + Dim src1 = " +Imports System +Class C(Of A) : End Class +" + Dim src2 = " +Imports System +Class C : End Class +" Dim edits = GetTopEdits(src1, src2) edits.VerifyEdits( - "Delete [(Of A)]@7", - "Delete [A]@11") + "Delete [(Of A)]@25", + "Delete [A]@29") edits.VerifySemanticDiagnostics( Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.type_parameter, "A"))) diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index f195c9aef33d2..9f2118f186372 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1021,36 +1021,146 @@ WellKnownMemberNames.ObjectToString or return model.GetDeclaredSymbol(declaration, cancellationToken); } - protected override OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol, EditKind editKind)> GetSymbolEdits( + protected override OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol)> GetEditedSymbols( EditKind editKind, SyntaxNode? oldNode, SyntaxNode? newNode, SemanticModel? oldModel, SemanticModel newModel, - IReadOnlyDictionary editMap, CancellationToken cancellationToken) { + // Chnage in type of a field affects all its variable declarations. + if (oldNode is VariableDeclarationSyntax oldVariableDeclaration && newNode is VariableDeclarationSyntax newVariableDeclaration) + { + return AddFieldSymbolUpdates(oldVariableDeclaration.Variables, newVariableDeclaration.Variables); + } + + // Change in attributes or modifiers of a field affects all its variable declarations. + if (oldNode is BaseFieldDeclarationSyntax oldField && newNode is BaseFieldDeclarationSyntax newField) + { + return AddFieldSymbolUpdates(oldField.Declaration.Variables, newField.Declaration.Variables); + } + + OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol)> AddFieldSymbolUpdates(SeparatedSyntaxList oldVariables, SeparatedSyntaxList newVariables) + { + Debug.Assert(oldModel != null); + + if (oldVariables.Count == 1 && newVariables.Count == 1) + { + return OneOrMany.Create((GetDeclaredSymbol(oldModel, oldVariables[0], cancellationToken), GetDeclaredSymbol(newModel, newVariables[0], cancellationToken))); + } + + return OneOrMany.Create( + (from oldVariable in oldVariables + join newVariable in newVariables on oldVariable.Identifier.Text equals newVariable.Identifier.Text + select (GetDeclaredSymbol(oldModel, oldVariable, cancellationToken), GetDeclaredSymbol(newModel, newVariable, cancellationToken))).ToImmutableArray()); + } + var oldSymbol = (oldNode != null) ? GetSymbolForEdit(oldNode, oldModel!, cancellationToken) : null; var newSymbol = (newNode != null) ? GetSymbolForEdit(newNode, newModel, cancellationToken) : null; + return (oldSymbol == null && newSymbol == null) + ? OneOrMany<(ISymbol?, ISymbol?)>.Empty + : OneOrMany.Create((oldSymbol, newSymbol)); + } + + protected override void AddSymbolEdits( + ref TemporaryArray<(ISymbol?, ISymbol?, EditKind)> result, + EditKind editKind, + SyntaxNode? oldNode, + ISymbol? oldSymbol, + SyntaxNode? newNode, + ISymbol? newSymbol, + SemanticModel? oldModel, + SemanticModel newModel, + Match topMatch, + IReadOnlyDictionary editMap, + SymbolInfoCache symbolCache, + CancellationToken cancellationToken) + { + if (oldNode is ParameterSyntax or TypeParameterSyntax or TypeParameterConstraintClauseSyntax || + newNode is ParameterSyntax or TypeParameterSyntax or TypeParameterConstraintClauseSyntax) + { + var oldContainingMemberOrType = GetParameterContainingMemberOrType(oldNode, newNode, oldModel, topMatch.ReverseMatches, cancellationToken); + var newContainingMemberOrType = GetParameterContainingMemberOrType(newNode, oldNode, newModel, topMatch.Matches, cancellationToken); + + var matchingNewContainingMemberOrType = GetSemanticallyMatchingNewSymbol(oldContainingMemberOrType, newContainingMemberOrType, newModel, symbolCache, cancellationToken); + + // Create candidate symbol edits to analyze: + // 1) An update of the containing member or type + // Produces an update edit of the containing member or type, or delete & insert edits if the signature changed. + // Reports rude edits for unsupported updates to the body (e.g. to active statements, lambdas, etc.). + // The containing member edit will cover any changes of the name, modifiers and attributes of the parameter. + // 2) Edit of the parameter itself + // Produces semantic edits for any synthesized members generated based on the parameter. + // Reports rude edits for unsupported renames, reoders, attribute and modifer changes. + // Does not result in a semantic edit for the parameter itself. + // 3) Edits of symbols synthesized based on the parameters that have declaring syntax. + // These members need to be analyzed for active statement mapping, type layout, etc. + // E.g. property accessors synthesized for record primary constructor parameters have bodies that may contain active statements. + + // If the signature of a property/indexer changed or a parameter of an indexer has been renamed we need to update all its accessors + if (oldContainingMemberOrType is IPropertySymbol oldPropertySymbol && + newContainingMemberOrType is IPropertySymbol newPropertySymbol && + (IsMemberOrDelegateReplaced(oldPropertySymbol, newPropertySymbol) || + oldSymbol != null && newSymbol != null && oldSymbol.Name != newSymbol.Name)) + { + AddMemberUpdate(ref result, oldPropertySymbol.GetMethod, newPropertySymbol.GetMethod, matchingNewContainingMemberOrType); + AddMemberUpdate(ref result, oldPropertySymbol.SetMethod, newPropertySymbol.SetMethod, matchingNewContainingMemberOrType); + } + + // record primary constructor parameter + if (oldNode is ParameterSyntax { Parent.Parent: RecordDeclarationSyntax } || + newNode is ParameterSyntax { Parent.Parent: RecordDeclarationSyntax }) + { + Debug.Assert(matchingNewContainingMemberOrType == null); + + var oldSynthesizedAutoProperty = (IPropertySymbol?)oldSymbol?.ContainingType.GetMembers(oldSymbol.Name).FirstOrDefault(m => m.IsSynthesizedAutoProperty()); + var newSynthesizedAutoProperty = (IPropertySymbol?)newSymbol?.ContainingType.GetMembers(newSymbol.Name).FirstOrDefault(m => m.IsSynthesizedAutoProperty()); + + if (oldSynthesizedAutoProperty != null || newSynthesizedAutoProperty != null) + { + result.Add((oldSynthesizedAutoProperty, newSynthesizedAutoProperty, editKind)); + result.Add((oldSynthesizedAutoProperty?.GetMethod, newSynthesizedAutoProperty?.GetMethod, editKind)); + result.Add((oldSynthesizedAutoProperty?.SetMethod, newSynthesizedAutoProperty?.SetMethod, editKind)); + } + } + + AddMemberUpdate(ref result, oldContainingMemberOrType, newContainingMemberOrType, matchingNewContainingMemberOrType); + + // Any change to a constraint should be analyzed as an update of the type parameter: + var isTypeConstraint = oldNode is TypeParameterConstraintClauseSyntax || newNode is TypeParameterConstraintClauseSyntax; + + if (matchingNewContainingMemberOrType != null) + { + // Map parameter to the corresponding semantically matching member. + // Since the signature of the member matches we can direcly map by parameter ordinal. + if (oldSymbol is IParameterSymbol oldParameter) + { + newSymbol = matchingNewContainingMemberOrType.GetParameters()[oldParameter.Ordinal]; + } + else if (oldSymbol is ITypeParameterSymbol oldTypeParameter) + { + newSymbol = matchingNewContainingMemberOrType.GetTypeParameters()[oldTypeParameter.Ordinal]; + } + } + + result.Add((oldSymbol, newSymbol, isTypeConstraint ? EditKind.Update : editKind)); + + return; + } + switch (editKind) { case EditKind.Reorder: Contract.ThrowIfNull(oldNode); - if (oldNode is ParameterSyntax) - { - Debug.Assert(oldSymbol is IParameterSymbol); - Debug.Assert(newSymbol is IParameterSymbol); - - // When parameters are reordered, we issue an update edit for the containing method - return new OneOrMany<(ISymbol?, ISymbol?, EditKind)>((oldSymbol.ContainingSymbol, newSymbol.ContainingSymbol, EditKind.Update)); - } - else if (IsGlobalStatement(oldNode)) + if (IsGlobalStatement(oldNode)) { // When global statements are reordered, we issue an update edit for the synthesized main method, which is what // oldSymbol and newSymbol will point to - return new OneOrMany<(ISymbol?, ISymbol?, EditKind)>((oldSymbol, newSymbol, EditKind.Update)); + result.Add((oldSymbol, newSymbol, EditKind.Update)); + return; } // Otherwise, we don't do any semantic checks for reordering @@ -1061,89 +1171,87 @@ WellKnownMemberNames.ObjectToString or // This ordering should however not matter unless the type has explicit layout so we might want to allow it. // We do not check changes to the order if they occur across multiple documents (the containing type is partial). Debug.Assert(!IsDeclarationWithInitializer(oldNode!) && !IsDeclarationWithInitializer(newNode!)); - return OneOrMany<(ISymbol?, ISymbol?, EditKind)>.Empty; + return; case EditKind.Update: Contract.ThrowIfNull(oldNode); Contract.ThrowIfNull(newNode); Contract.ThrowIfNull(oldModel); - // Certain updates of a property/indexer node affects its accessors. - // Return all affected symbols for these updates. + // Updates of a property/indexer/event node might affect its accessors. + // Return all affected symbols for these updates so that the changes in the accessor bodies get analyzed. - // 1) Old or new property/indexer has an expression body: - // int this[...] => expr; - // int this[...] { get => expr; } - // int P => expr; - // int P { get => expr; } = init - if (oldNode is PropertyDeclarationSyntax { ExpressionBody: not null } or IndexerDeclarationSyntax { ExpressionBody: not null } || - newNode is PropertyDeclarationSyntax { ExpressionBody: not null } or IndexerDeclarationSyntax { ExpressionBody: not null }) + if (oldSymbol is IPropertySymbol oldPropertySymbol && newSymbol is IPropertySymbol newPropertySymbol) { - Debug.Assert(oldSymbol is IPropertySymbol); - Debug.Assert(newSymbol is IPropertySymbol); + // 1) Old or new property/indexer has an expression body: + // int this[...] => expr; + // int this[...] { get => expr; } + // int P => expr; + // int P { get => expr; } = init + // 2) Property/indexer declarations differ in readonly keyword. + // 3) Property signature changes + // 4) Property name changes - var oldGetterSymbol = ((IPropertySymbol)oldSymbol).GetMethod; - var newGetterSymbol = ((IPropertySymbol)newSymbol).GetMethod; + var oldHasExpressionBody = oldNode is PropertyDeclarationSyntax { ExpressionBody: not null } or IndexerDeclarationSyntax { ExpressionBody: not null }; + var newHasExpressionBody = newNode is PropertyDeclarationSyntax { ExpressionBody: not null } or IndexerDeclarationSyntax { ExpressionBody: not null }; - return OneOrMany.Create((oldSymbol, newSymbol, editKind), (oldGetterSymbol, newGetterSymbol, editKind)); - } - - // 2) Property/indexer declarations differ in readonly keyword. - if (oldNode is PropertyDeclarationSyntax oldProperty && newNode is PropertyDeclarationSyntax newProperty && DiffersInReadOnlyModifier(oldProperty.Modifiers, newProperty.Modifiers) || - oldNode is IndexerDeclarationSyntax oldIndexer && newNode is IndexerDeclarationSyntax newIndexer && DiffersInReadOnlyModifier(oldIndexer.Modifiers, newIndexer.Modifiers)) - { - Debug.Assert(oldSymbol is IPropertySymbol); - Debug.Assert(newSymbol is IPropertySymbol); - - var oldPropertySymbol = (IPropertySymbol)oldSymbol; - var newPropertySymbol = (IPropertySymbol)newSymbol; + result.Add((oldPropertySymbol, newPropertySymbol, editKind)); - using var _ = ArrayBuilder<(ISymbol?, ISymbol?, EditKind)>.GetInstance(out var builder); - - builder.Add((oldPropertySymbol, newPropertySymbol, editKind)); - - if (oldPropertySymbol.GetMethod != null && newPropertySymbol.GetMethod != null && oldPropertySymbol.GetMethod.IsReadOnly != newPropertySymbol.GetMethod.IsReadOnly) + if (oldPropertySymbol.GetMethod != null || newPropertySymbol.GetMethod != null) { - builder.Add((oldPropertySymbol.GetMethod, newPropertySymbol.GetMethod, editKind)); + if (oldHasExpressionBody || + newHasExpressionBody || + DiffersInReadOnlyModifier(oldPropertySymbol.GetMethod, newPropertySymbol.GetMethod) || + IsMemberOrDelegateReplaced(oldPropertySymbol, newPropertySymbol)) + { + result.Add((oldPropertySymbol.GetMethod, newPropertySymbol.GetMethod, editKind)); + } } - if (oldPropertySymbol.SetMethod != null && newPropertySymbol.SetMethod != null && oldPropertySymbol.SetMethod.IsReadOnly != newPropertySymbol.SetMethod.IsReadOnly) + if (oldPropertySymbol.SetMethod != null || newPropertySymbol.SetMethod != null) { - builder.Add((oldPropertySymbol.SetMethod, newPropertySymbol.SetMethod, editKind)); + if (DiffersInReadOnlyModifier(oldPropertySymbol.SetMethod, newPropertySymbol.SetMethod) || + IsMemberOrDelegateReplaced(oldPropertySymbol, newPropertySymbol)) + { + result.Add((oldPropertySymbol.SetMethod, newPropertySymbol.SetMethod, editKind)); + } } - return OneOrMany.Create(builder.ToImmutable()); + return; } - static bool DiffersInReadOnlyModifier(SyntaxTokenList oldModifiers, SyntaxTokenList newModifiers) - => (oldModifiers.IndexOf(SyntaxKind.ReadOnlyKeyword) >= 0) != (newModifiers.IndexOf(SyntaxKind.ReadOnlyKeyword) >= 0); - - // Change in attributes or modifiers of a field affects all its variable declarations. - if (oldNode is BaseFieldDeclarationSyntax oldField && newNode is BaseFieldDeclarationSyntax newField) + if (oldSymbol is IEventSymbol oldEventSymbol && newSymbol is IEventSymbol newEventSymbol) { - return GetFieldSymbolUpdates(oldField.Declaration.Variables, newField.Declaration.Variables); - } + // 1) Event declarations differ in readonly keyword. + // 2) Event signature changes + // 3) Event name changes - // Chnage in type of a field affects all its variable declarations. - if (oldNode is VariableDeclarationSyntax oldVariableDeclaration && newNode is VariableDeclarationSyntax newVariableDeclaration) - { - return GetFieldSymbolUpdates(oldVariableDeclaration.Variables, newVariableDeclaration.Variables); - } + result.Add((oldEventSymbol, newEventSymbol, editKind)); - OneOrMany<(ISymbol?, ISymbol?, EditKind)> GetFieldSymbolUpdates(SeparatedSyntaxList oldVariables, SeparatedSyntaxList newVariables) - { - if (oldVariables.Count == 1 && newVariables.Count == 1) + if (oldEventSymbol.AddMethod != null || newEventSymbol.AddMethod != null) { - return OneOrMany.Create((GetDeclaredSymbol(oldModel, oldVariables[0], cancellationToken), GetDeclaredSymbol(newModel, newVariables[0], cancellationToken), EditKind.Update)); + if (DiffersInReadOnlyModifier(oldEventSymbol.AddMethod, newEventSymbol.AddMethod) || + IsMemberOrDelegateReplaced(oldEventSymbol, newEventSymbol)) + { + result.Add((oldEventSymbol.AddMethod, newEventSymbol.AddMethod, editKind)); + } } - var result = from oldVariable in oldVariables - join newVariable in newVariables on oldVariable.Identifier.Text equals newVariable.Identifier.Text - select (GetDeclaredSymbol(oldModel, oldVariable, cancellationToken), GetDeclaredSymbol(newModel, newVariable, cancellationToken), EditKind.Update); + if (oldEventSymbol.RemoveMethod != null || newEventSymbol.RemoveMethod != null) + { + if (DiffersInReadOnlyModifier(oldEventSymbol.RemoveMethod, newEventSymbol.RemoveMethod) || + IsMemberOrDelegateReplaced(oldEventSymbol, newEventSymbol)) + { + result.Add((oldEventSymbol.RemoveMethod, newEventSymbol.RemoveMethod, editKind)); + } + } - return OneOrMany.Create(result.ToImmutableArray()); + return; } + static bool DiffersInReadOnlyModifier(IMethodSymbol? oldMethod, IMethodSymbol? newMethod) + => oldMethod != null && newMethod != null && oldMethod.IsReadOnly != newMethod.IsReadOnly; + // Update to a type declaration with primary constructor may also need to update // the primary constructor, copy-constructor and/or synthesized record auto-properties. // Active statements in bodies of these symbols lay within the type declaration node. @@ -1156,8 +1264,6 @@ newNode is TypeDeclarationSyntax newTypeDeclaration && throw ExceptionUtilities.Unreachable(); } - var result = new TemporaryArray<(ISymbol?, ISymbol?, EditKind)>(); - // the type kind, attributes, constracints may have changed: result.Add((oldSymbol, newSymbol, EditKind.Update)); @@ -1170,7 +1276,10 @@ newNode is TypeDeclarationSyntax newTypeDeclaration && oldTypeDeclaration.ParameterList?.Span != newTypeDeclaration.ParameterList?.Span) { var oldPrimaryConstructor = GetPrimaryConstructor(oldType, cancellationToken); - var newPrimaryConstructor = GetPrimaryConstructor(newType, cancellationToken); + + // The matching constructor might not be specified using primary constructor syntax. + // Let the symbol resolution fill in the other constructor instead of specifying both symbols here. + var newPrimaryConstructor = (oldPrimaryConstructor == null) ? GetPrimaryConstructor(newType, cancellationToken) : null; result.Add((oldPrimaryConstructor, newPrimaryConstructor, EditKind.Update)); } @@ -1185,7 +1294,7 @@ newNode is TypeDeclarationSyntax newTypeDeclaration && result.Add((oldCopyConstructor, newCopyConstructor, EditKind.Update)); } - return (result.Count == 1) ? OneOrMany.Create(result[0]) : OneOrMany.Create(result.ToImmutableAndClear()); + return; } break; @@ -1202,7 +1311,7 @@ newNode is TypeDeclarationSyntax newTypeDeclaration && if (HasEdit(editMap, node.Parent, editKind) && !HasEdit(editMap, node.Parent.Parent, editKind)) { - return OneOrMany<(ISymbol?, ISymbol?, EditKind)>.Empty; + return; } } @@ -1214,26 +1323,31 @@ newNode is TypeDeclarationSyntax newTypeDeclaration && { var oldGetterSymbol = ((IPropertySymbol?)oldSymbol)?.GetMethod; var newGetterSymbol = ((IPropertySymbol?)newSymbol)?.GetMethod; - return OneOrMany.Create((oldSymbol, newSymbol, editKind), (oldGetterSymbol, newGetterSymbol, editKind)); + result.Add((oldSymbol, newSymbol, editKind)); + result.Add((oldGetterSymbol, newGetterSymbol, editKind)); + return; } // Inserting/deleting a type parameter constraint should result in an update of the corresponding type parameter symbol: if (node.IsKind(SyntaxKind.TypeParameterConstraintClause)) { - return OneOrMany.Create((oldSymbol, newSymbol, EditKind.Update)); + result.Add((oldSymbol, newSymbol, EditKind.Update)); + return; } // Inserting/deleting a global statement should result in an update of the implicit main method: if (node.IsKind(SyntaxKind.GlobalStatement)) { - return OneOrMany.Create((oldSymbol, newSymbol, EditKind.Update)); + result.Add((oldSymbol, newSymbol, EditKind.Update)); + return; } // 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((oldSymbol, newSymbol, EditKind.Update)); + result.Add((oldSymbol, newSymbol, EditKind.Update)); + return; } break; @@ -1247,30 +1361,21 @@ newNode is TypeDeclarationSyntax newTypeDeclaration && Debug.Assert(SupportsMove(oldNode)); Debug.Assert(SupportsMove(newNode)); - return oldNode.IsKind(SyntaxKind.LocalFunctionStatement) - ? OneOrMany<(ISymbol?, ISymbol?, EditKind)>.Empty - : OneOrMany.Create((oldSymbol, newSymbol, editKind)); + if (oldNode.IsKind(SyntaxKind.LocalFunctionStatement)) + { + return; + } + + result.Add((oldSymbol, newSymbol, editKind)); + return; } - // record primary constructor parameter - if (oldNode is ParameterSyntax { Parent.Parent: RecordDeclarationSyntax } || - newNode is ParameterSyntax { Parent.Parent: RecordDeclarationSyntax }) + if ((editKind == EditKind.Delete ? oldSymbol : newSymbol) is null) { - var oldSynthesizedAutoProperty = (IPropertySymbol?)oldSymbol?.ContainingType.GetMembers(oldSymbol.Name).FirstOrDefault(m => m.IsSynthesizedAutoProperty()); - var newSynthesizedAutoProperty = (IPropertySymbol?)newSymbol?.ContainingType.GetMembers(newSymbol.Name).FirstOrDefault(m => m.IsSynthesizedAutoProperty()); - - if (oldSynthesizedAutoProperty != null || newSynthesizedAutoProperty != null) - { - return OneOrMany.Create(ImmutableArray.Create( - (oldSymbol, newSymbol, editKind), - (oldSynthesizedAutoProperty, newSynthesizedAutoProperty, editKind), - (oldSynthesizedAutoProperty?.GetMethod, newSynthesizedAutoProperty?.GetMethod, editKind), - (oldSynthesizedAutoProperty?.SetMethod, newSynthesizedAutoProperty?.SetMethod, editKind))); - } + return; } - return (editKind == EditKind.Delete ? oldSymbol : newSymbol) is null ? - OneOrMany<(ISymbol?, ISymbol?, EditKind)>.Empty : new OneOrMany<(ISymbol?, ISymbol?, EditKind)>((oldSymbol, newSymbol, editKind)); + result.Add((oldSymbol, newSymbol, editKind)); } private ISymbol? GetSymbolForEdit( @@ -1307,18 +1412,38 @@ newNode is TypeDeclarationSyntax newTypeDeclaration && node = node.Parent; } - var symbol = GetDeclaredSymbol(model, node, cancellationToken); + return GetDeclaredSymbol(model, node, cancellationToken); + } - // TODO: this is incorrect (https://github.com/dotnet/roslyn/issues/54800) - // Ignore partial method definition parts. - // Partial method that does not have implementation part is not emitted to metadata. - // Partial method without a definition part is a compilation error. - if (symbol is IMethodSymbol { IsPartialDefinition: true }) + private ISymbol? GetParameterContainingMemberOrType(SyntaxNode? node, SyntaxNode? otherNode, SemanticModel? model, IReadOnlyDictionary fromOtherMap, CancellationToken cancellationToken) + { + Debug.Assert(node is null or ParameterSyntax or TypeParameterSyntax or TypeParameterConstraintClauseSyntax); + + // parameter list, member, or type declaration: + SyntaxNode? declaration; + + if (node == null) { - return null; + Debug.Assert(otherNode != null); + + // A parameter-less method/indexer has a parameter list, but non-generic method does not have a type parameter list. + fromOtherMap.TryGetValue(GetContainingDeclaration(otherNode), out declaration); + } + else + { + declaration = GetContainingDeclaration(node); } - return symbol; + // Parent is a type for primary constructor parameters + if (declaration is BaseParameterListSyntax and not { Parent: TypeDeclarationSyntax }) + { + declaration = declaration.Parent; + } + + return (declaration != null) ? GetDeclaredSymbol(model!, declaration, cancellationToken) : null; + + static SyntaxNode GetContainingDeclaration(SyntaxNode node) + => node is TypeParameterSyntax ? node.Parent!.Parent! : node!.Parent!; } private static bool SupportsMove(SyntaxNode node) @@ -1376,15 +1501,15 @@ internal override bool QueryClauseLambdasTypeEquivalent(SemanticModel oldModel, var oldQueryClauseInfo = oldModel.GetQueryClauseInfo((QueryClauseSyntax)oldNode, cancellationToken); var newQueryClauseInfo = newModel.GetQueryClauseInfo((QueryClauseSyntax)newNode, cancellationToken); - return MemberSignaturesEquivalent(oldQueryClauseInfo.CastInfo.Symbol, newQueryClauseInfo.CastInfo.Symbol) && - MemberSignaturesEquivalent(oldQueryClauseInfo.OperationInfo.Symbol, newQueryClauseInfo.OperationInfo.Symbol); + return MemberOrDelegateSignaturesEquivalent(oldQueryClauseInfo.CastInfo.Symbol, newQueryClauseInfo.CastInfo.Symbol) && + MemberOrDelegateSignaturesEquivalent(oldQueryClauseInfo.OperationInfo.Symbol, newQueryClauseInfo.OperationInfo.Symbol); case SyntaxKind.AscendingOrdering: case SyntaxKind.DescendingOrdering: var oldOrderingInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken); var newOrderingInfo = newModel.GetSymbolInfo(newNode, cancellationToken); - return MemberSignaturesEquivalent(oldOrderingInfo.Symbol, newOrderingInfo.Symbol); + return MemberOrDelegateSignaturesEquivalent(oldOrderingInfo.Symbol, newOrderingInfo.Symbol); case SyntaxKind.SelectClause: var oldSelectInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken); @@ -1395,19 +1520,19 @@ internal override bool QueryClauseLambdasTypeEquivalent(SemanticModel oldModel, return oldSelectInfo.Symbol == null || newSelectInfo.Symbol == null || - MemberSignaturesEquivalent(oldSelectInfo.Symbol, newSelectInfo.Symbol); + MemberOrDelegateSignaturesEquivalent(oldSelectInfo.Symbol, newSelectInfo.Symbol); case SyntaxKind.GroupClause: - var oldGroupByInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken); - var newGroupByInfo = newModel.GetSymbolInfo(newNode, cancellationToken); - return MemberSignaturesEquivalent(oldGroupByInfo.Symbol, newGroupByInfo.Symbol, GroupBySignatureComparer); + var oldGroupInfo = oldModel.GetSymbolInfo(oldNode, cancellationToken); + var newGroupInfo = newModel.GetSymbolInfo(newNode, cancellationToken); + return GroupBySignatureEquivalent(oldGroupInfo.Symbol as IMethodSymbol, newGroupInfo.Symbol as IMethodSymbol); default: return true; } } - private static bool GroupBySignatureComparer(ImmutableArray oldParameters, ITypeSymbol oldReturnType, ImmutableArray newParameters, ITypeSymbol newReturnType) + private static bool GroupBySignatureEquivalent(IMethodSymbol? oldMethod, IMethodSymbol? newMethod) { // C# spec paragraph 7.16.2.6 "Groupby clauses": // @@ -1422,11 +1547,24 @@ private static bool GroupBySignatureComparer(ImmutableArray ol // C> GroupBy(Func keySelector); // C> GroupBy(Func keySelector, Func elementSelector); - if (!TypesEquivalent(oldReturnType, newReturnType, exact: false)) + if (oldMethod == newMethod) + { + return true; + } + + if (oldMethod == null || newMethod == null) { return false; } + if (!TypesEquivalent(oldMethod.ReturnType, newMethod.ReturnType, exact: false)) + { + return false; + } + + var oldParameters = oldMethod.Parameters; + var newParameters = newMethod.Parameters; + Debug.Assert(oldParameters.Length is 1 or 2); Debug.Assert(newParameters.Length is 1 or 2); @@ -1446,7 +1584,7 @@ private static bool GroupBySignatureComparer(ImmutableArray ol return true; } - #endregion +#endregion #region Diagnostic Info @@ -2373,6 +2511,8 @@ internal override bool HasUnsupportedOperation(IEnumerable nodes, [N internal override void ReportInsertedMemberSymbolRudeEdits(ArrayBuilder diagnostics, ISymbol newSymbol, SyntaxNode newNode, bool insertingIntoExistingContainingType) { + Debug.Assert(IsMember(newSymbol)); + var rudeEditKind = newSymbol switch { // Inserting extern member into a new or existing type is not allowed. diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 285e23351586a..57620ea6ce567 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -21,6 +21,7 @@ using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Shared.Collections; namespace Microsoft.CodeAnalysis.EditAndContinue { @@ -181,18 +182,81 @@ private bool AreEquivalent(SyntaxToken oldToken, SyntaxToken newToken) protected abstract IEnumerable GetTopLevelTypeDeclarations(SyntaxNode compilationUnit); /// - /// Returns all symbols associated with an edit and an actual edit kind, which may be different then the specified one. + /// Returns all symbols with declaring syntax ( must return a syntax node) + /// associated with an edit and an actual edit kind, which may be different then the specified one. /// Returns an empty set if the edit is not associated with any symbols. /// - protected abstract OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol, EditKind editKind)> GetSymbolEdits( + protected abstract void AddSymbolEdits( + ref TemporaryArray<(ISymbol?, ISymbol?, EditKind)> result, EditKind editKind, SyntaxNode? oldNode, + ISymbol? oldSymbol, SyntaxNode? newNode, + ISymbol? newSymbol, SemanticModel? oldModel, SemanticModel newModel, + Match topMatch, IReadOnlyDictionary editMap, + SymbolInfoCache symbolCache, CancellationToken cancellationToken); + /// + /// Returns pairs of old and new symbols associated with a given syntactic edit. + /// + protected abstract OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol)> GetEditedSymbols( + EditKind editKind, + SyntaxNode? oldNode, + SyntaxNode? newNode, + SemanticModel? oldModel, + SemanticModel newModel, + CancellationToken cancellationToken); + + private OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol, EditKind editKind)> GetSymbolEdits( + EditKind editKind, + SyntaxNode? oldNode, + SyntaxNode? newNode, + SemanticModel? oldModel, + SemanticModel newModel, + Match topMatch, + IReadOnlyDictionary editMap, + SymbolInfoCache symbolCache, + CancellationToken cancellationToken) + { + var result = new TemporaryArray<(ISymbol?, ISymbol?, EditKind)>(); + + var symbols = GetEditedSymbols(editKind, oldNode, newNode, oldModel, newModel, cancellationToken); + foreach (var (oldSymbol, newSymbol) in symbols) + { + Debug.Assert(oldSymbol != null || newSymbol != null); + + // Top-level members may be matched by syntax even when their name and signature are different. + // An update edit is created for these matches that usually produces an insert + delete semantic edits. + // + // If however the members were just moved to another partial type declaration and now are "accidentally" syntax-matched, + // we shouldn't treat such update as insert and delete. + // + // Instead, a simple semantic update (or no edit at all if the body has not changed, other then trivia that can be expressed as a line delta) should be created. + // When we detect this case we break the original update edit into two edits: delete and insert. + // The logic in the analyzer then consolidates these edits into updates across partial type declarations if applicable. + if (editKind == EditKind.Update && GetSemanticallyMatchingNewSymbol(oldSymbol, newSymbol, newModel, symbolCache, cancellationToken) != null) + { + AddSymbolEdits(ref result, EditKind.Delete, oldNode, oldSymbol, newNode: null, newSymbol: null, oldModel, newModel, topMatch, editMap, symbolCache, cancellationToken); + AddSymbolEdits(ref result, EditKind.Insert, oldNode: null, oldSymbol: null, newNode, newSymbol, oldModel, newModel, topMatch, editMap, symbolCache, cancellationToken); + } + else + { + AddSymbolEdits(ref result, editKind, oldNode, oldSymbol, newNode, newSymbol, oldModel, newModel, topMatch, editMap, symbolCache, cancellationToken); + } + } + + return result.Count switch + { + 0 => OneOrMany<(ISymbol?, ISymbol?, EditKind)>.Empty, + 1 => OneOrMany.Create(result[0]), + _ => OneOrMany.Create(result.ToImmutableAndClear()) + }; + } + /// /// Enumerates all use sites of a specified variable within the specified syntax subtrees. /// @@ -865,6 +929,7 @@ private void AnalyzeChangedMemberBody( ISymbol newMember, Compilation oldCompilation, SourceText newText, + bool isMemberReplaced, Match topMatch, ImmutableArray oldActiveStatements, ImmutableArray newActiveStatementSpans, @@ -881,10 +946,15 @@ private void AnalyzeChangedMemberBody( Debug.Assert(newActiveStatements.Count == newExceptionRegions.Count); Debug.Assert(oldMemberBody != null || newMemberBody != null); - var diagnosticContext = CreateDiagnosticContext(diagnostics, oldMember, newMember, newDeclaration, newModel); + var diagnosticContext = CreateDiagnosticContext(diagnostics, oldMember, newMember, newDeclaration, newModel, topMatch); syntaxMap = null; - var activeStatementIndices = oldMemberBody?.GetOverlappingActiveStatements(oldActiveStatements)?.ToArray() ?? Array.Empty(); + var activeStatementIndices = oldMemberBody?.GetOverlappingActiveStatements(oldActiveStatements)?.ToArray() ?? []; + + if (isMemberReplaced && !activeStatementIndices.IsEmpty()) + { + diagnosticContext.Report(RudeEditKind.ChangingNameOrSignatureOfActiveMember, cancellationToken); + } try { @@ -903,6 +973,9 @@ private void AnalyzeChangedMemberBody( if (newMemberBody == null) { + // The name or signature has been changed and the member needs to be deleted and a new one emitted. + // The debugger does not support active statement remapping between two different methods, so report rude edits. + // // The body has been deleted. Two cases: // 1) The declaration is available // Example: Deleting field initializer. @@ -913,8 +986,6 @@ private void AnalyzeChangedMemberBody( // 2) The declaration is also deleted, but a synthesized one is generated in its place and thus an update edit is issued. // Will remap active statements to the first instruction of the synthesized body (same as above). - TextSpan? lazyNewSpan = null; - foreach (var activeStatementIndex in activeStatementIndices) { // the declaration must exist, otherwise we wouldn't find any active statements overlapping the old body @@ -928,8 +999,8 @@ private void AnalyzeChangedMemberBody( continue; } - lazyNewSpan ??= GetDeletedDeclarationActiveSpan(topMatch.Matches, oldDeclaration); - newActiveStatements[activeStatementIndex] = GetActiveStatementWithSpan(oldActiveStatements[activeStatementIndex], topMatch.NewRoot.SyntaxTree, lazyNewSpan.Value, diagnostics, cancellationToken); + var newSpan = GetDeletedDeclarationActiveSpan(topMatch.Matches, oldDeclaration); + newActiveStatements[activeStatementIndex] = GetActiveStatementWithSpan(oldActiveStatements[activeStatementIndex], topMatch.NewRoot.SyntaxTree, newSpan, diagnostics, cancellationToken); newExceptionRegions[activeStatementIndex] = ImmutableArray.Empty; } } @@ -2082,9 +2153,25 @@ void AddCurrentSegment() // report the rude edit for the span of tokens that forced recompilation: if (rudeEditSpan.IsEmpty) { - rudeEditSpan = TextSpan.FromBounds( - lastNewToken.HasTrailingTrivia ? lastNewToken.Span.End : newTokensEnum.Current.FullSpan.Start, - newTokensEnum.Current.SpanStart); + if (newTokensEnum.Current.HasLeadingTrivia) + { + // [token1](trailing-trivia1)(leading-trivia2)[token2] + // ~~~~~~~~~~~~~~~~~ + rudeEditSpan = TextSpan.FromBounds(newTokensEnum.Current.FullSpan.Start, newTokensEnum.Current.SpanStart); + } + else if (lastNewToken.HasTrailingTrivia) + { + // [token1](trailing-trivia1)[token2] + // ~~~~~~~~~~~~~~~~~~ + rudeEditSpan = TextSpan.FromBounds(lastNewToken.Span.End, newTokensEnum.Current.SpanStart); + } + else + { + // The current token is the first token of the body and has no leading trivia. + // [token1] + // ~~~~~~~~ + rudeEditSpan = newTokensEnum.Current.Span; + } } triviaEdits.Add((oldNode, newNode, rudeEditSpan)); @@ -2198,22 +2285,18 @@ public int GetHashCode(IAssemblySymbol? obj) => obj?.Identity.GetHashCode() ?? 0; } - // Ignore tuple element changes, nullability and dynamic. These type changes do not affect runtime type. - // They only affect custom attributes emitted on the members - all runtimes are expected to accept - // custom attribute updates in metadata deltas, even if they do not have any observable effect. + // Ignore tuple element changes, nullability, dynamic and parameter refkinds. These type changes do not affect runtime type. + // They only affect custom attributes or metadata flags emitted on the members - all runtimes are expected to accept + // these updates in metadata deltas, even if they do not have any observable effect. private static readonly SymbolEquivalenceComparer s_runtimeSymbolEqualityComparer = new( - AssemblyEqualityComparer.Instance, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: true); + AssemblyEqualityComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: true, objectAndDynamicCompareEqually: true); private static readonly SymbolEquivalenceComparer s_exactSymbolEqualityComparer = new( - AssemblyEqualityComparer.Instance, distinguishRefFromOut: true, tupleNamesMustMatch: true, ignoreNullableAnnotations: false); + AssemblyEqualityComparer.Instance, distinguishRefFromOut: true, tupleNamesMustMatch: true, ignoreNullableAnnotations: false, objectAndDynamicCompareEqually: false); protected static bool SymbolsEquivalent(ISymbol oldSymbol, ISymbol newSymbol) => s_exactSymbolEqualityComparer.Equals(oldSymbol, newSymbol); - protected static bool SignaturesEquivalent(ImmutableArray oldParameters, ITypeSymbol oldReturnType, ImmutableArray newParameters, ITypeSymbol newReturnType) - => ParameterTypesEquivalent(oldParameters, newParameters, exact: false) && - s_runtimeSymbolEqualityComparer.Equals(oldReturnType, newReturnType); // TODO: should check ref, ref readonly, custom mods - protected static bool ParameterTypesEquivalent(ImmutableArray oldParameters, ImmutableArray newParameters, bool exact) => oldParameters.SequenceEqual(newParameters, exact, ParameterTypesEquivalent); @@ -2241,6 +2324,10 @@ protected static bool ReturnTypesEquivalent(IPropertySymbol oldProperty, IProper protected static bool ReturnTypesEquivalent(IEventSymbol oldEvent, IEventSymbol newEvent, bool exact) => TypesEquivalent(oldEvent.Type, newEvent.Type, exact); + protected static bool ReturnTypesEquivalent(IFieldSymbol oldField, IFieldSymbol newField, bool exact) + => CustomModifiersEquivalent(oldField.RefCustomModifiers, newField.RefCustomModifiers, exact) && + TypesEquivalent(oldField.Type, newField.Type, exact); + // Note: SignatureTypeEquivalenceComparer compares dynamic and object the same. protected static bool TypesEquivalent(ITypeSymbol? oldType, ITypeSymbol? newType, bool exact) => (exact ? s_exactSymbolEqualityComparer : (IEqualityComparer)s_runtimeSymbolEqualityComparer.SignatureTypeEquivalenceComparer).Equals(oldType, newType); @@ -2261,16 +2348,13 @@ protected static bool TypeParameterConstraintsEquivalent(ITypeParameterSymbol ol oldParameter.Variance == newParameter.Variance; protected static bool TypeParametersEquivalent(ImmutableArray oldParameters, ImmutableArray newParameters, bool exact) - => oldParameters.SequenceEqual(newParameters, exact, (oldParameter, newParameter, exact) => oldParameter.Name == newParameter.Name && TypeParameterConstraintsEquivalent(oldParameter, newParameter, exact)); + => oldParameters.SequenceEqual(newParameters, exact, TypeParameterConstraintsEquivalent); protected static bool BaseTypesEquivalent(INamedTypeSymbol oldType, INamedTypeSymbol newType, bool exact) => TypesEquivalent(oldType.BaseType, newType.BaseType, exact) && TypesEquivalent(oldType.AllInterfaces, newType.AllInterfaces, exact); - protected static bool MemberSignaturesEquivalent( - ISymbol? oldMember, - ISymbol? newMember, - Func, ITypeSymbol, ImmutableArray, ITypeSymbol, bool>? signatureComparer = null) + protected static bool MemberOrDelegateSignaturesEquivalent(ISymbol? oldMember, ISymbol? newMember, bool exact = false) { if (oldMember == newMember) { @@ -2282,24 +2366,32 @@ protected static bool MemberSignaturesEquivalent( return false; } - signatureComparer ??= SignaturesEquivalent; - switch (oldMember.Kind) { case SymbolKind.Field: - var oldField = (IFieldSymbol)oldMember; - var newField = (IFieldSymbol)newMember; - return signatureComparer(ImmutableArray.Empty, oldField.Type, ImmutableArray.Empty, newField.Type); + return ReturnTypesEquivalent((IFieldSymbol)oldMember, (IFieldSymbol)newMember, exact); + + case SymbolKind.Event: + return ReturnTypesEquivalent((IEventSymbol)oldMember, (IEventSymbol)newMember, exact); case SymbolKind.Property: var oldProperty = (IPropertySymbol)oldMember; var newProperty = (IPropertySymbol)newMember; - return signatureComparer(oldProperty.Parameters, oldProperty.Type, newProperty.Parameters, newProperty.Type); + return ParameterTypesEquivalent(oldProperty.Parameters, newProperty.Parameters, exact) && + ReturnTypesEquivalent(oldProperty, newProperty, exact); case SymbolKind.Method: var oldMethod = (IMethodSymbol)oldMember; var newMethod = (IMethodSymbol)newMember; - return signatureComparer(oldMethod.Parameters, oldMethod.ReturnType, newMethod.Parameters, newMethod.ReturnType); + return ParameterTypesEquivalent(oldMethod.Parameters, newMethod.Parameters, exact) && + oldMethod.TypeParameters.Length == newMethod.TypeParameters.Length && + ReturnTypesEquivalent(oldMethod, newMethod, exact); + + case SymbolKind.NamedType when oldMember is INamedTypeSymbol { DelegateInvokeMethod: { } oldInvokeMethod }: + var newInvokeMethod = ((INamedTypeSymbol)newMember).DelegateInvokeMethod; + return newInvokeMethod != null && + ParameterTypesEquivalent(oldInvokeMethod.Parameters, newInvokeMethod.Parameters, exact) && + ReturnTypesEquivalent(oldInvokeMethod, newInvokeMethod, exact); default: throw ExceptionUtilities.UnexpectedValue(oldMember.Kind); @@ -2326,6 +2418,13 @@ private sealed class MemberInitializationUpdates(INamedTypeSymbol oldType) public bool HasDeletedMemberInitializer; } + protected sealed class SymbolInfoCache( + PooledDictionary symbolKeyCache) + { + public SymbolKey GetKey(ISymbol symbol, CancellationToken cancellationToken) + => symbolKeyCache.GetOrAdd(symbol, static (symbol, cancellationToken) => SymbolKey.Create(symbol, cancellationToken), cancellationToken); + } + private async Task> AnalyzeSemanticsAsync( EditScript editScript, IReadOnlyDictionary editMap, @@ -2353,9 +2452,12 @@ private async Task> AnalyzeSemanticsAsync( // { new type -> constructor update } PooledDictionary? instanceConstructorEdits = null; PooledDictionary? staticConstructorEdits = null; - + using var _1 = PooledHashSet.GetInstance(out var processedSymbols); using var _2 = ArrayBuilder.GetInstance(out var semanticEdits); + using var _3 = PooledDictionary.GetInstance(out var symbolKeyCache); + + var symbolCache = new SymbolInfoCache(symbolKeyCache); try { @@ -2363,6 +2465,8 @@ private async Task> AnalyzeSemanticsAsync( var newModel = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var oldCompilation = oldModel?.Compilation ?? await oldProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var newCompilation = newModel.Compilation; + var oldTree = editScript.Match.OldRoot.SyntaxTree; + var newTree = editScript.Match.NewRoot.SyntaxTree; foreach (var edit in editScript.Edits) { @@ -2374,7 +2478,7 @@ private async Task> AnalyzeSemanticsAsync( // all newly added types in these namespaces will have their own syntax edit. var symbolEdits = oldModel != null && IsNamespaceDeclaration(edit.OldNode ?? edit.NewNode!) ? OneOrMany.Create(GetNamespaceSymbolEdits(oldModel, newModel, cancellationToken)) - : GetSymbolEdits(edit.Kind, edit.OldNode, edit.NewNode, oldModel, newModel, editMap, cancellationToken); + : GetSymbolEdits(edit.Kind, edit.OldNode, edit.NewNode, oldModel, newModel, editScript.Match, editMap, symbolCache, cancellationToken); foreach (var symbolEdit in symbolEdits) { @@ -2390,8 +2494,8 @@ private async Task> AnalyzeSemanticsAsync( continue; } - var oldSymbolInNewCompilation = SymbolKey.Create(oldSymbol, cancellationToken).Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; - var newSymbolInOldCompilation = SymbolKey.Create(newSymbol, cancellationToken).Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; + var oldSymbolInNewCompilation = symbolCache.GetKey(oldSymbol, cancellationToken).Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; + var newSymbolInOldCompilation = symbolCache.GetKey(newSymbol, cancellationToken).Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; if (oldSymbolInNewCompilation == null || newSymbolInOldCompilation == null) { @@ -2410,7 +2514,7 @@ private async Task> AnalyzeSemanticsAsync( } else { - CreateDiagnosticContext(diagnostics, oldSymbol, newSymbol, edit.NewNode, newModel). + CreateDiagnosticContext(diagnostics, oldSymbol, newSymbol, edit.NewNode, newModel, editScript.Match). Report(RudeEditKind.Move, cancellationToken); } } @@ -2418,26 +2522,20 @@ private async Task> AnalyzeSemanticsAsync( continue; } - var symbolKey = SymbolKey.Create(newSymbol ?? oldSymbol, cancellationToken); - - // Ignore ambiguous resolution result - it may happen if there are semantic errors in the compilation. - oldSymbol ??= symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; - newSymbol ??= symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; - - var symbol = newSymbol ?? oldSymbol; - Contract.ThrowIfNull(symbol); - - if (!processedSymbols.Add(symbol)) + if (!PreprocessSymbolEdit(ref oldSymbol, ref newSymbol)) { continue; } + var symbol = newSymbol ?? oldSymbol; + Contract.ThrowIfNull(symbol); + Func? syntaxMap; SemanticEditKind editKind; var (oldDeclaration, newDeclaration) = GetSymbolDeclarationNodes(oldSymbol, newSymbol, edit.OldNode, edit.NewNode); - var diagnosticContext = CreateDiagnosticContext(diagnostics, oldSymbol, newSymbol, edit.NewNode, newModel); + var diagnosticContext = CreateDiagnosticContext(diagnostics, oldSymbol, newSymbol, edit.NewNode, newModel, editScript.Match); // The syntax change implies an update of the associated symbol but the old/new symbol does not actually exist. // 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. @@ -2465,7 +2563,7 @@ private async Task> AnalyzeSemanticsAsync( if (containingType != null && (syntacticEditKind != EditKind.Delete || newSymbol == null)) { - var containingTypeSymbolKey = SymbolKey.Create(containingType, cancellationToken); + var containingTypeSymbolKey = symbolCache.GetKey(containingType, cancellationToken); oldContainingType ??= (INamedTypeSymbol?)containingTypeSymbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; newContainingType ??= (INamedTypeSymbol?)containingTypeSymbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; @@ -2475,12 +2573,12 @@ private async Task> AnalyzeSemanticsAsync( { if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) { - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, containingTypeSymbolKey, syntaxMap: null, syntaxMapTree: null, - IsPartialEdit(oldContainingType, newContainingType, editScript.Match) ? containingTypeSymbolKey : null)); + semanticEdits.Add(SemanticEditInfo.CreateReplace(containingTypeSymbolKey, + IsPartialTypeEdit(oldContainingType, newContainingType, oldTree, newTree) ? containingTypeSymbolKey : null)); } else { - CreateDiagnosticContext(diagnostics, oldContainingType, newContainingType, newDeclaration, newModel). + CreateDiagnosticContext(diagnostics, oldContainingType, newContainingType, newDeclaration, newModel, editScript.Match). Report(RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, cancellationToken); } } @@ -2513,8 +2611,9 @@ private async Task> AnalyzeSemanticsAsync( } else { - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, symbolKey, syntaxMap: null, syntaxMapTree: null, - IsPartialEdit(oldType, newType, editScript.Match) ? symbolKey : null)); + var typeKey = symbolCache.GetKey(newType, cancellationToken); + semanticEdits.Add(SemanticEditInfo.CreateReplace(typeKey, + IsPartialTypeEdit(oldType, newType, oldTree, newTree) ? typeKey : null)); } } @@ -2535,7 +2634,7 @@ private async Task> AnalyzeSemanticsAsync( syntaxMap = null; // Check if the declaration has been moved from one document to another. - if (newSymbol is { } and not IMethodSymbol { IsPartialDefinition: true }) + if (newSymbol != null) { // 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.) @@ -2563,6 +2662,13 @@ newSymbol is IPropertySymbol newProperty && break; } + // If a partial method definition is deleted (and not moved to another partial type declaration, which is handled above) + // so must be the implementation. An edit will be issued for the implementation change. + if (newSymbol is IMethodSymbol { IsPartialDefinition: true }) + { + continue; + } + var diagnosticSpan = GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldDeclaration); // If we got here for a global statement then the actual edit is a delete of the synthesized Main method @@ -2572,61 +2678,10 @@ newSymbol is IPropertySymbol newProperty && continue; } - editKind = SemanticEditKind.Delete; ReportDeletedMemberActiveStatementsRudeEdits(); var rudeEditKind = RudeEditKind.Delete; - // If the associated member declaration (parameter/type parameter -> method) has also been deleted skip - // the delete of the symbol as it will be deleted by the delete of the associated member. We pass the edit kind - // in here to avoid property/event accessors from being caught up in this, because those deletes we want to process - // separately, below. - // - // Associated member declarations must be in the same document as the symbol, so we don't need to resolve their symbol. - // In some cases the symbol even can't be resolved unambiguously. Consider e.g. resolving a method with its parameter deleted - - // we wouldn't know which overload to resolve to. - if (TryGetAssociatedMemberDeclaration(oldSymbol, EditKind.Delete, cancellationToken, out var oldAssociatedMemberDeclaration)) - { - if (HasEdit(editMap, oldAssociatedMemberDeclaration, EditKind.Delete)) - { - continue; - } - - // We allow deleting parameters, by issuing delete and insert edits for the old and new method - if (oldSymbol is IParameterSymbol oldParameter) - { - if (TryAddParameterInsertOrDeleteEdits( - semanticEdits, - oldParameter, - oldModel, - newModel, - capabilities, - syntaxMap: null, - editScript, - processedSymbols, - cancellationToken, - out var notSupportedByRuntime)) - { - continue; - } - - if (notSupportedByRuntime) - { - rudeEditKind = RudeEditKind.DeleteNotSupportedByRuntime; - } - } - - // deleting is not allowed - - diagnostics.Add(new RudeEditDiagnostic( - rudeEditKind, - diagnosticSpan, - oldDeclaration, - new[] { GetDisplayKindAndName(oldSymbol, GetDisplayName(oldDeclaration, EditKind.Delete), fullyQualify: diagnosticSpan.IsEmpty) })); - - continue; - } - if (oldSymbol.ContainingType == null) { // deleting type is not allowed @@ -2643,10 +2698,17 @@ newSymbol is IPropertySymbol newProperty && // 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 containingTypeKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken); + var oldContainingType = oldSymbol.ContainingType; + var containingTypeKey = symbolCache.GetKey(oldContainingType, cancellationToken); var newContainingType = (INamedTypeSymbol?)containingTypeKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol; if (newContainingType == null) { + // If a type parameter is deleted from the parameter list of a type declaration, the symbol key won't be resolved (because the arities do not match). + if (oldSymbol is ITypeParameterSymbol) + { + diagnosticContext.Report(RudeEditKind.Delete, cancellationToken); + } + continue; } @@ -2667,7 +2729,7 @@ newSymbol is IPropertySymbol newProperty && if (IsDeclarationWithInitializer(oldDeclaration)) { - DeferConstructorEdit(oldSymbol.ContainingType, newContainingType, oldDeclaration, syntaxMap, oldSymbol.IsStatic, isMemberWithDeletedInitializer: true); + DeferConstructorEdit(oldContainingType, newContainingType, oldDeclaration, syntaxMap, oldSymbol.IsStatic, isMemberWithDeletedInitializer: true); } // If a property or field is deleted from a record the synthesized members may change @@ -2680,25 +2742,37 @@ newSymbol is IPropertySymbol newProperty && var newMatchingSymbol = newContainingType.GetMembers(oldSymbol.Name).FirstOrDefault(m => m is IPropertySymbol or IFieldSymbol); if (newMatchingSymbol is null) { - AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newModel.Compilation, newContainingType, partialType: null, cancellationToken); + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newModel.Compilation, newContainingType, cancellationToken); } } - 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)) + if (oldContainingType.IsRecord) { - var oldPrimaryConstructor = (IMethodSymbol)oldSymbol; + if (IsPrimaryConstructor(oldSymbol, cancellationToken)) + { + var oldPrimaryConstructor = (IMethodSymbol)oldSymbol; - // Deconstructor delete: - AddDeconstructorEdits(semanticEdits, oldPrimaryConstructor, otherConstructor: null, containingTypeKey, oldCompilation, newCompilation, syntaxMap: null, processedSymbols, isParameterDelete: true, cancellationToken); + // Deconstructor delete: + AddDeconstructorEdits(semanticEdits, oldPrimaryConstructor, otherConstructor: null, containingTypeKey, oldCompilation, newCompilation, isParameterDelete: true, cancellationToken); + + // Synthesized method updates: + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newModel.Compilation, newContainingType, cancellationToken); + } + else if (oldSymbol is IParameterSymbol oldParameter && IsPrimaryConstructor(oldParameter.ContainingSymbol, cancellationToken)) + { + AddSynthesizedMemberEditsForRecordParameterChange(semanticEdits, oldParameter, newContainingType, containingTypeKey, isParameterDelete: true, cancellationToken); + } + } - // Synthesized method updates: - AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newModel.Compilation, newContainingType, partialType: null, cancellationToken); + // do not add delete edits for parameters: + if (oldSymbol is IParameterSymbol or ITypeParameterSymbol) + { + continue; } + AddDeleteEditsForMemberAndAccessors(semanticEdits, oldSymbol.PartialAsImplementation(), containingTypeKey, cancellationToken); continue; } @@ -2730,109 +2804,85 @@ newSymbol is IPropertySymbol newProperty && break; } - // 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)) - { - diagnosticContext.Report(RudeEditKind.ChangingParameterTypes, cancellationToken); - } - } - // Handles cases when a data member explicit declaration is moved, which may change the type layout. // As of C# 12, replacing implicitly declared member with explicitly declared does not introduce a field, // but future language features might. This type layout update covers that case as well. - ReportTypeLayoutUpdateRudeEdits(diagnosticContext, cancellationToken); + ReportTypeLayoutUpdateRudeEdits(diagnosticContext, newSymbol, cancellationToken); break; } - if (TryGetAssociatedMemberDeclaration(newSymbol, EditKind.Insert, cancellationToken, out var newAssociatedMemberDeclaration) && - HasEdit(editMap, newAssociatedMemberDeclaration, EditKind.Insert)) + // If a partial method definition is inserted (and not moved to another partial type declaration, which is handled above) + // so must be the implementation. An edit will be issued for the implementation change. + if (newSymbol is IMethodSymbol { IsPartialDefinition: true }) { - // 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; } - if (newSymbol is ITypeParameterSymbol) + if (newContainingType != null && !IsGlobalMain(newSymbol)) { - diagnostics.Add(new RudeEditDiagnostic( - RudeEditKind.Insert, - GetDiagnosticSpan(newDeclaration, EditKind.Insert), - newDeclaration, - arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) })); + // The edit actually adds a new symbol into an existing or a new type. - continue; - } + var hasAssociatedSymbolInsert = + GetAssociatedMember(newSymbol) is { } newAssociatedMember && + HasEdit(editMap, GetSymbolDeclarationSyntax(newAssociatedMember, cancellationToken), EditKind.Insert); - if (newSymbol is IParameterSymbol newParameter) - { - if (!TryAddParameterInsertOrDeleteEdits( - semanticEdits, - newParameter, - newModel, - oldModel, - capabilities, - syntaxMap, - editScript, - processedSymbols, - cancellationToken, - out var notSupportedByRuntime)) + var containingTypeKey = symbolCache.GetKey(newContainingType, cancellationToken); + oldContainingType = containingTypeKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol as INamedTypeSymbol; + + // Check rude edits for each member even if it is inserted into a new type. + if (!hasAssociatedSymbolInsert && IsMember(newSymbol)) { - diagnostics.Add(new RudeEditDiagnostic( - notSupportedByRuntime ? RudeEditKind.InsertNotSupportedByRuntime : RudeEditKind.Insert, - GetDiagnosticSpan(newDeclaration, EditKind.Insert), - newDeclaration, - arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) })); + ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: oldContainingType != null); } - continue; - } - - if (newContainingType != null && !IsGlobalMain(newSymbol)) - { - // The edit actually adds a new symbol into an existing or a new type. + if (oldContainingType == null) + { + // If a type parameter is inserted into the parameter list of a type declaration, the symbol key won't be resolved (because the arities do not match). + if (!hasAssociatedSymbolInsert && newSymbol is ITypeParameterSymbol) + { + diagnosticContext.Report(RudeEditKind.Insert, cancellationToken); + } - var containingSymbolKey = SymbolKey.Create(newContainingType, cancellationToken); - oldContainingType = containingSymbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol as INamedTypeSymbol; + // Insertion of a new symbol into a new type. + // We'll produce a single insert edit for the entire type. + continue; + } - if (oldContainingType != null && !CanAddNewMemberToExistingType(newSymbol, capabilities)) + if (!hasAssociatedSymbolInsert && !CanAddNewMemberToExistingType(newSymbol, capabilities)) { diagnostics.Add(new RudeEditDiagnostic( RudeEditKind.InsertNotSupportedByRuntime, GetDiagnosticSpan(newDeclaration, EditKind.Insert), newDeclaration, arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) })); - } - // Check rude edits for each member even if it is inserted into a new type. - ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: oldContainingType != null); - - if (oldContainingType == null) - { - // Insertion of a new symbol into a new type. - // We'll produce a single insert edit for the entire type. continue; } // Report rude edits for changes to data member changes of a type with an explicit layout. // We disallow moving a data member of a partial type with explicit layout even when it actually does not change the layout. // We could compare the exact order of the members but the scenario is unlikely to occur. - ReportTypeLayoutUpdateRudeEdits(diagnosticContext, cancellationToken); + ReportTypeLayoutUpdateRudeEdits(diagnosticContext, newSymbol, cancellationToken); // 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)) { - AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newContainingType, partialType: null, cancellationToken); + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newContainingType, cancellationToken); + } + + if (newSymbol is IParameterSymbol newParameter && + newContainingType.IsRecord && + IsPrimaryConstructor(newParameter.ContainingSymbol, cancellationToken)) + { + AddSynthesizedMemberEditsForRecordParameterChange(semanticEdits, newParameter, oldContainingType, containingTypeKey, isParameterDelete: false, cancellationToken); + } + + // do not create semantic edit for parameter insert or symbols whose associated symbol is also being inserted: + if (hasAssociatedSymbolInsert || newSymbol is IParameterSymbol or ITypeParameterSymbol) + { + continue; } } else @@ -2851,7 +2901,11 @@ newSymbol is IPropertySymbol newProperty && } oldContainingType = null; - ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: false); + + if (IsMember(newSymbol)) + { + ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: false); + } } Contract.ThrowIfFalse(editKind == SemanticEditKind.Insert); @@ -2885,6 +2939,19 @@ newSymbol is IPropertySymbol newProperty && syntaxMap = null; break; + case EditKind.Reorder: + Contract.ThrowIfNull(oldSymbol); + Contract.ThrowIfNull(newSymbol); + + if (oldSymbol is IParameterSymbol && + !IsMemberOrDelegateReplaced(oldSymbol.ContainingSymbol, newSymbol.ContainingSymbol) && + !capabilities.Grant(EditAndContinueCapabilities.UpdateParameters)) + { + diagnosticContext.Report(RudeEditKind.RenamingNotSupportedByRuntime, cancellationToken); + } + + continue; + default: throw ExceptionUtilities.UnexpectedValue(edit.Kind); } @@ -2917,17 +2984,17 @@ void AnalyzeRecordPropertyReplacement(IPropertySymbol oldProperty, IPropertySymb // Custom read-only property replaced with synthesized auto-property if (isDeleteEdit) { - AddInsertEditsForMemberAndAccessors(semanticEdits, synthesizedProperty.SetMethod, syntaxMap, partialType: null, processedSymbols, cancellationToken); + AddInsertEditsForMemberAndAccessors(semanticEdits, synthesizedProperty.SetMethod, cancellationToken); } else { - AddDeleteEditsForMemberAndAccessors(semanticEdits, synthesizedProperty.SetMethod, SymbolKey.Create(oldProperty.ContainingType, cancellationToken), syntaxMap, partialType: null, cancellationToken); + AddDeleteEditsForMemberAndAccessors(semanticEdits, synthesizedProperty.SetMethod, symbolCache.GetKey(oldProperty.ContainingType, cancellationToken), 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, partialType: null, cancellationToken); + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newProperty.ContainingType, cancellationToken); // When a custom property w/o a backing field is replaced with synthesized in a type with explicit layout, // the synthesized one adds a backing field, which changes the layout of the type. @@ -2935,7 +3002,7 @@ void AnalyzeRecordPropertyReplacement(IPropertySymbol oldProperty, IPropertySymb // The removed field remains in the type (so its layout is unchanged). if (isDeleteEdit && !customProperty.IsAutoProperty()) { - ReportTypeLayoutUpdateRudeEdits(diagnosticContext, cancellationToken); + ReportTypeLayoutUpdateRudeEdits(diagnosticContext, newProperty, cancellationToken); } } @@ -2962,7 +3029,7 @@ void ReportDeletedMemberActiveStatementsRudeEdits() if (newActiveStatements[index] == null) { newActiveStatementSpan ??= GetDeletedDeclarationActiveSpan(editScript.Match.Matches, oldDeclaration); - newActiveStatements[index] = GetActiveStatementWithSpan(oldActiveStatements[index], editScript.Match.NewRoot.SyntaxTree, newActiveStatementSpan.Value, diagnostics, cancellationToken); + newActiveStatements[index] = GetActiveStatementWithSpan(oldActiveStatements[index], newTree, newActiveStatementSpan.Value, diagnostics, cancellationToken); newExceptionRegions[index] = ImmutableArray.Empty; } else @@ -2984,6 +3051,18 @@ void ReportDeletedMemberActiveStatementsRudeEdits() { Contract.ThrowIfNull(oldSymbol); + var replaceMember = IsMemberOrDelegate(oldSymbol) && IsMemberOrDelegateReplaced(oldSymbol, newSymbol); + + if (replaceMember && oldSymbol.Name == newSymbol.Name) + { + var signatureRudeEdit = GetSignatureChangeRudeEdit(oldSymbol, newSymbol, capabilities); + if (signatureRudeEdit != RudeEditKind.None) + { + diagnosticContext.Report(signatureRudeEdit, cancellationToken); + continue; + } + } + var oldBody = (oldDeclaration != null) ? TryGetDeclarationBody(oldDeclaration, oldSymbol) : null; if (!skipBodyAnalysis) { @@ -3006,6 +3085,7 @@ void ReportDeletedMemberActiveStatementsRudeEdits() newSymbol, oldCompilation, newText, + replaceMember, editScript.Match, oldActiveStatements, newActiveStatementSpans, @@ -3018,110 +3098,82 @@ void ReportDeletedMemberActiveStatementsRudeEdits() } } + AnalyzeSymbolUpdate(diagnosticContext, capabilities, semanticEdits, out var hasAttributeChange, cancellationToken); + + if (newSymbol is IParameterSymbol or ITypeParameterSymbol) + { + // All (type) parameter changes are applied by an update created for the containing symbol. + continue; + } + // 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(newSymbol, cancellationToken); var isOldDeclarationWithInitializer = oldDeclaration != null && IsDeclarationWithInitializer(oldDeclaration); var isNewDeclarationWithInitializer = newDeclaration != null && IsDeclarationWithInitializer(newDeclaration); - if (!isConstructorWithMemberInitializers) - { - AnalyzeSymbolUpdate(diagnosticContext, editScript.Match, capabilities, semanticEdits, syntaxMap, processedSymbols, cancellationToken); - } - if (isConstructorWithMemberInitializers || isOldDeclarationWithInitializer || isNewDeclarationWithInitializer) { DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, isMemberWithDeletedInitializer: isOldDeclarationWithInitializer && !isNewDeclarationWithInitializer); - - // 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; } - if (newSymbol is INamedTypeSymbol or IFieldSymbol or IParameterSymbol or ITypeParameterSymbol) + if (isConstructorWithMemberInitializers) { + // all updates to constructors with initializers will be created later continue; } - // For renames where the symbol allows deletion, we don't create an update edit, we create a delete - // and an add. During emit an empty body will be created for the old name. - var createDeleteAndInsertEdits = oldSymbol.Name != newSymbol.Name; - - // When a methods parameters are reordered or there is an insert or an add, we need to handle things differently - if (oldSymbol is IMethodSymbol oldMethod && - newSymbol is IMethodSymbol newMethod) + if (replaceMember) { - // For inserts and deletes, the edits for the parameter itself will do the work - if (oldMethod.Parameters.Length != newMethod.Parameters.Length) + // skip for delegates, rude edits have already been reported + if (oldSymbol is not INamedTypeSymbol { TypeKind: TypeKind.Delegate }) { - continue; - } - - // For reordering of parameters we need to report insert and delete edits, but we also need to account for - // renames if the runtime doesn't support it. We track this with a syntax node that we can use to report - // the rude edit. - var renamedParameterOrdinal = -1; - for (var i = 0; i < oldMethod.Parameters.Length; i++) - { - var rudeEditKind = RudeEditKind.None; - var hasParameterTypeChange = false; - var unused = false; - AnalyzeParameterType(oldMethod.Parameters[i], newMethod.Parameters[i], capabilities, ref rudeEditKind, ref unused, ref hasParameterTypeChange); - - createDeleteAndInsertEdits |= hasParameterTypeChange; + // symbol insertion might change type layout: + ReportTypeLayoutUpdateRudeEdits(diagnosticContext, newSymbol, cancellationToken); - if (renamedParameterOrdinal == -1 && oldMethod.Parameters[i].Name != newMethod.Parameters[i].Name) - { - renamedParameterOrdinal = i; - } - } - - if (!createDeleteAndInsertEdits && renamedParameterOrdinal >= 0 && !capabilities.Grant(EditAndContinueCapabilities.UpdateParameters)) - { - var newRenamedParameter = newMethod.Parameters[renamedParameterOrdinal]; - var oldRenamedParameter = oldMethod.Parameters[renamedParameterOrdinal]; - processedSymbols.Add(newRenamedParameter); - - CreateDiagnosticContext(diagnostics, oldRenamedParameter, newRenamedParameter, newNode: null, newModel). - Report(RudeEditKind.RenamingNotSupportedByRuntime, cancellationToken); - continue; - } - } - - // Sometimes when members are moved between documents in partial classes, they can appear as renames, - // so we also check that the old symbol can't be resolved in the new compilation - if (createDeleteAndInsertEdits && - AllowsDeletion(oldSymbol) && - CanAddNewMemberToExistingType(oldSymbol, capabilities) && - SymbolKey.Create(oldSymbol, cancellationToken).Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol is null) - { - if (oldBody?.GetOverlappingActiveStatements(oldActiveStatements).Any() == true) - { - Contract.ThrowIfNull(newDeclaration); - AddRudeUpdateAroundActiveStatement(diagnostics, newDeclaration); - } - else - { - var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken); - - AddDeleteEditsForMemberAndAccessors(semanticEdits, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, cancellationToken); - AddInsertEditsForMemberAndAccessors(semanticEdits, newSymbol, syntaxMap, - partialType: IsPartialEdit(oldSymbol, newSymbol, editScript.Match) ? symbolKey : null, processedSymbols, - cancellationToken); + var containingSymbolKey = symbolCache.GetKey(oldSymbol.ContainingType, cancellationToken); + AddMemberSignatureOrNameChangeEdits(semanticEdits, oldSymbol.PartialAsImplementation(), newSymbol.PartialAsImplementation(), containingSymbolKey, cancellationToken); } + // do not emit update continue; } - if (newSymbol is IPropertySymbol or IEventSymbol) + // Avoid creating unnecessary updates that are easy to determine. + if (!hasAttributeChange && newSymbol is + INamedTypeSymbol { IsGenericType: false } or // changes in type parameter attributes and constraints need type update + IPropertySymbol { IsIndexer: false } or // changes in parameter attributes need indexer update + IFieldSymbol or + IEventSymbol) { continue; } + + // While the above analysis operates on a partial definition or implementation, + // semantic edits must only be issued for the implementation. + symbol = symbol.PartialAsImplementation(); } - semanticEdits.Add(new SemanticEditInfo(editKind, symbolKey, syntaxMap, syntaxMapTree: null, - IsPartialEdit(oldSymbol, newSymbol, editScript.Match) ? symbolKey : null)); + var symbolKey = symbolCache.GetKey(symbol, cancellationToken); + + // Specify partial type so that all edits of the same symbol located in multiple documents can be merged later on. + // The partial type needs to be specified in the following cases: + // 1) partial method is updated (in case both implementation and definition are updated) + // 2) partial type is updated + var partialType = editKind == SemanticEditKind.Update && symbol is IMethodSymbol { PartialDefinitionPart: not null } + ? symbolCache.GetKey(symbol.ContainingType, cancellationToken) + : IsPartialTypeEdit(oldSymbol, newSymbol, oldTree, newTree) + ? symbolKey + : (SymbolKey?)null; + + semanticEdits.Add(editKind switch + { + SemanticEditKind.Update => SemanticEditInfo.CreateUpdate(symbolKey, syntaxMap, syntaxMapTree: (syntaxMap != null) ? newModel.SyntaxTree : null, partialType), + SemanticEditKind.Insert => SemanticEditInfo.CreateInsert(symbolKey, partialType), + SemanticEditKind.Replace => SemanticEditInfo.CreateReplace(symbolKey, partialType), + _ => throw ExceptionUtilities.UnexpectedValue(editKind) + }); } } @@ -3131,49 +3183,39 @@ void ReportDeletedMemberActiveStatementsRudeEdits() Contract.ThrowIfNull(oldModel); Contract.ThrowIfNull(newModel); - foreach (var (oldSymbol, newSymbol, editKind) in GetSymbolEdits(EditKind.Update, oldEditNode, newEditNode, oldModel, newModel, editMap, cancellationToken)) + var triviaSymbolEdits = GetSymbolEdits(EditKind.Update, oldEditNode, newEditNode, oldModel, newModel, editScript.Match, editMap, symbolCache, cancellationToken); + foreach (var edit in triviaSymbolEdits) { - // Trivia edits are only calculated for member bodies and each member has a symbol. - Contract.ThrowIfNull(newSymbol); - Contract.ThrowIfNull(oldSymbol); + var (oldSymbol, newSymbol, _) = edit; - if (!processedSymbols.Add(newSymbol)) + if (!PreprocessSymbolEdit(ref oldSymbol, ref newSymbol)) { // symbol already processed continue; } + Contract.ThrowIfNull(oldSymbol); + Contract.ThrowIfNull(newSymbol); + var (oldDeclaration, newDeclaration) = GetSymbolDeclarationNodes(oldSymbol, newSymbol, oldEditNode, newEditNode); Contract.ThrowIfNull(oldDeclaration); Contract.ThrowIfNull(newDeclaration); - // if the member doesn't have a body triva changes have no effect: - var oldBody = TryGetDeclarationBody(oldDeclaration, oldSymbol); - if (oldBody == null) - { - continue; - } - var oldContainingType = oldSymbol.ContainingType; var newContainingType = newSymbol.ContainingType; - - // types do not have bodies: - Contract.ThrowIfNull(oldContainingType); - Contract.ThrowIfNull(newContainingType); - - if (IsReloadable(oldContainingType)) + if (oldContainingType != null && newContainingType != null && IsReloadable(oldContainingType)) { if (processedSymbols.Add(newContainingType)) { if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition)) { - var containingTypeSymbolKey = SymbolKey.Create(oldContainingType, cancellationToken); - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, containingTypeSymbolKey, syntaxMap: null, syntaxMapTree: null, - IsPartialEdit(oldContainingType, newContainingType, editScript.Match) ? containingTypeSymbolKey : null)); + var oldContainingTypeKey = SymbolKey.Create(oldContainingType, cancellationToken); + semanticEdits.Add(SemanticEditInfo.CreateReplace(oldContainingTypeKey, + IsPartialTypeEdit(oldContainingType, newContainingType, oldTree, newTree) ? oldContainingTypeKey : null)); } else { - CreateDiagnosticContext(diagnostics, oldContainingType, newContainingType, newDeclaration, newModel) + CreateDiagnosticContext(diagnostics, oldContainingType, newContainingType, newDeclaration, newModel, editScript.Match) .Report(RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, cancellationToken); } } @@ -3181,15 +3223,32 @@ void ReportDeletedMemberActiveStatementsRudeEdits() continue; } + var diagnosticContext = CreateDiagnosticContext(diagnostics, oldSymbol, newSymbol, newDeclaration, newModel, editScript.Match, diagnosticSpan); + + AnalyzeSymbolUpdate(diagnosticContext, capabilities, semanticEdits, out var _, cancellationToken); + + // if the member doesn't have a body triva changes have no effect: + var oldBody = TryGetDeclarationBody(oldDeclaration, oldSymbol); + if (oldBody == null) + { + continue; + } + var newBody = TryGetDeclarationBody(newDeclaration, newSymbol); + Contract.ThrowIfNull(newBody); + if (ReportUnsupportedOperations(diagnosticContext, newBody, cancellationToken)) + { + continue; + } + + Func? syntaxMap = null; + // only trivia changed: Contract.ThrowIfNull(newBody); Debug.Assert(IsConstructorWithMemberInitializers(oldSymbol, cancellationToken) == IsConstructorWithMemberInitializers(newSymbol, cancellationToken)); Debug.Assert(IsDeclarationWithInitializer(oldDeclaration) == IsDeclarationWithInitializer(newDeclaration)); - Func? syntaxMap = null; - // We need to provide syntax map to the compiler if the member is active (see member update above): var isActiveMember = oldBody.GetOverlappingActiveStatements(oldActiveStatements).Any() || @@ -3198,14 +3257,14 @@ void ReportDeletedMemberActiveStatementsRudeEdits() syntaxMap = isActiveMember ? CreateSyntaxMapForEquivalentNodes(oldBody, newBody) : null; - var diagnosticContext = CreateDiagnosticContext(diagnostics, oldSymbol, newSymbol, newDeclaration, newModel); - ReportUnsupportedOperations(diagnosticContext, newBody, cancellationToken); - var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newSymbol, cancellationToken); var isDeclarationWithInitializer = IsDeclarationWithInitializer(newDeclaration); if (isConstructorWithMemberInitializers || isDeclarationWithInitializer) { + Contract.ThrowIfNull(oldContainingType); + Contract.ThrowIfNull(newContainingType); + // TODO: only create syntax map if any field initializers are active/contain lambdas or this is a partial type syntaxMap ??= CreateSyntaxMapForEquivalentNodes(oldBody, newBody); @@ -3216,16 +3275,20 @@ void ReportDeletedMemberActiveStatementsRudeEdits() continue; } - // updating generic methods and types - if (InGenericContext(oldSymbol) && !capabilities.Grant(EditAndContinueCapabilities.GenericUpdateMethod)) + // If the member changed signature or name no additional updates are needed. + // E.g. property accessor might have trivia changes while the property type/name is being changed. + if (IsMember(oldSymbol) && IsMemberOrDelegateReplaced(oldSymbol, newSymbol)) { - diagnosticContext.Report(RudeEditKind.UpdatingGenericNotSupportedByRuntime, cancellationToken, diagnosticSpan); continue; } - var symbolKey = SymbolKey.Create(newSymbol, cancellationToken); - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null, - IsPartialEdit(oldSymbol, newSymbol, editScript.Match) ? symbolKey : null)); + var symbolKey = symbolCache.GetKey(newSymbol, cancellationToken); + + semanticEdits.Add(SemanticEditInfo.CreateUpdate( + symbolKey, + syntaxMap, + syntaxMapTree: (syntaxMap != null) ? newTree : null, + partialType: IsPartialTypeEdit(oldSymbol, newSymbol, oldTree, newTree) ? symbolKey : null)); } } @@ -3237,8 +3300,6 @@ void ReportDeletedMemberActiveStatementsRudeEdits() oldModel, oldCompilation, newModel, - processedSymbols, - capabilities, isStatic: false, semanticEdits, diagnostics, @@ -3253,14 +3314,34 @@ void ReportDeletedMemberActiveStatementsRudeEdits() oldModel, oldCompilation, newModel, - processedSymbols, - capabilities, isStatic: true, semanticEdits, diagnostics, cancellationToken); } + bool PreprocessSymbolEdit(ref ISymbol? oldSymbol, ref ISymbol? newSymbol) + { + Contract.ThrowIfFalse(oldSymbol != null || newSymbol != null); + + oldSymbol ??= Resolve(newSymbol!, symbolCache.GetKey(newSymbol!, cancellationToken), oldCompilation, cancellationToken); + newSymbol ??= Resolve(oldSymbol!, symbolCache.GetKey(oldSymbol!, cancellationToken), newCompilation, cancellationToken); + + static ISymbol? Resolve(ISymbol symbol, SymbolKey symbolKey, Compilation compilation, CancellationToken cancellationToken) + { + // Ignore ambiguous resolution result - it may happen if there are semantic errors in the compilation. + var result = symbolKey.Resolve(compilation, ignoreAssemblyKey: true, cancellationToken).Symbol; + + // If we were looking for a definition and an implementation is returned the definition does not exist. + return symbol is IMethodSymbol { PartialDefinitionPart: not null } && result is IMethodSymbol { IsPartialDefinition: true } ? null : result; + } + + var symbol = newSymbol ?? oldSymbol; + Contract.ThrowIfNull(symbol); + + return processedSymbols.Add(symbol); + } + // 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. @@ -3316,119 +3397,49 @@ void DeferConstructorEdit( newDeclaration: (newSymbol != null && GetSingleSymbolDeclarationSyntax(newSymbol, cancellationToken) is { } newDeclaration) ? newDeclaration : newNode); } - /// - /// 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 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, - IParameterSymbol parameterSymbol, - SemanticModel model, - SemanticModel? otherModel, - EditAndContinueCapabilitiesGrantor capabilities, - Func? syntaxMap, - EditScript editScript, - HashSet processedSymbols, - CancellationToken cancellationToken, - out bool notSupportedByRuntime) - { - var member = parameterSymbol.ContainingSymbol; - Debug.Assert(member is IPropertySymbol or IMethodSymbol); - - notSupportedByRuntime = false; + protected static bool IsMemberOrDelegateReplaced(ISymbol oldMember, ISymbol newMember) + => oldMember.Name != newMember.Name || + !MemberOrDelegateSignaturesEquivalent(oldMember, newMember, exact: false); - // Since we're inserting (or deleting) a parameter node, oldSymbol (or newSymbol) would have been null, - // 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 || - member.DeclaringSyntaxReferences.Length != 1) - { - return false; - } + protected static bool IsMember(ISymbol symbol) + => symbol.Kind is SymbolKind.Method or SymbolKind.Property or SymbolKind.Field or SymbolKind.Event; - // 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 (member is IMethodSymbol { IsPartialDefinition: true }) - { - return true; - } + protected static bool IsMemberOrDelegate(ISymbol symbol) + => IsMember(symbol) || symbol is INamedTypeSymbol { TypeKind: TypeKind.Delegate }; - // We don't support delegate parameters - if (member.ContainingType.IsDelegateType()) - { - return false; - } + protected static ISymbol? GetSemanticallyMatchingNewSymbol(ISymbol? oldSymbol, ISymbol? newSymbol, SemanticModel newModel, SymbolInfoCache symbolCache, CancellationToken cancellationToken) + => oldSymbol != null && IsMember(oldSymbol) && + newSymbol != null && IsMember(newSymbol) && + symbolCache.GetKey(oldSymbol, cancellationToken).Resolve(newModel.Compilation, ignoreAssemblyKey: true, cancellationToken).Symbol is { } matchingNewSymbol && + !matchingNewSymbol.IsSynthesized() && + matchingNewSymbol != newSymbol + ? matchingNewSymbol + : null; - // Find the node that matches this declaration - SyntaxNode otherContainingDeclaration; - bool isParameterDelete; - var containingDeclaration = GetSymbolDeclarationSyntax(member, cancellationToken); - if (editScript.Match.TryGetOldNode(containingDeclaration, out var oldNode)) - { - otherContainingDeclaration = oldNode; - isParameterDelete = false; - } - else if (editScript.Match.TryGetNewNode(containingDeclaration, out var newNode)) - { - otherContainingDeclaration = newNode; - isParameterDelete = true; - } - else - { - return false; - } - - // 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)) + protected static void AddMemberUpdate(ref TemporaryArray<(ISymbol?, ISymbol?, EditKind)> result, ISymbol? oldSymbol, ISymbol? newSymbol, ISymbol? newSemanticallyMatchingSymbol) + { + if (newSemanticallyMatchingSymbol != null) { - 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 (oldContainingSymbol, newContainingSymbol) = isParameterDelete ? (member, otherMember) : (otherMember, member); + Debug.Assert(oldSymbol != null); + Debug.Assert(newSymbol != null); - if (!CanRenameOrChangeSignature(oldContainingSymbol, newContainingSymbol, capabilities)) - { - notSupportedByRuntime = true; - return false; + result.Add((oldSymbol, null, EditKind.Delete)); + result.Add((null, newSymbol, EditKind.Insert)); } - - var containingTypeKey = SymbolKey.Create(oldContainingSymbol.ContainingType, cancellationToken); - - if (member.ContainingType.IsRecord && - otherMember.ContainingType.IsRecord && - IsPrimaryConstructor(member, cancellationToken) && - IsPrimaryConstructor(otherMember, cancellationToken)) + else if (oldSymbol != null || newSymbol != null) { - AddSynthesizedMemberEditsForRecordParameterChange(semanticEdits, parameterSymbol, otherMember, containingTypeKey, model, otherModel, syntaxMap, processedSymbols, isParameterDelete, cancellationToken); + result.Add((oldSymbol, newSymbol, EditKind.Update)); } - - AddDeleteAndInsertEditsForMemberAndAccessors(semanticEdits, oldContainingSymbol, newContainingSymbol, containingTypeKey, syntaxMap, processedSymbols, cancellationToken); - return true; } /// /// 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 AddSynthesizedMemberEditsForRecordParameterChange( + private static void AddSynthesizedMemberEditsForRecordParameterChange( ArrayBuilder semanticEdits, IParameterSymbol parameterSymbol, - ISymbol otherMember, + INamedTypeSymbol otherContainingType, SymbolKey containingTypeKey, - SemanticModel model, - SemanticModel otherModel, - Func? syntaxMap, - HashSet processedSymbols, bool isParameterDelete, CancellationToken cancellationToken) { @@ -3443,7 +3454,6 @@ private void AddSynthesizedMemberEditsForRecordParameterChange( // 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. @@ -3454,29 +3464,23 @@ private void AddSynthesizedMemberEditsForRecordParameterChange( var synthesizedProperty = GetPropertySynthesizedForRecordPrimaryConstructorParameter(parameterSymbol); if (synthesizedProperty != null) { - var otherMembersOfParameterName = otherMember.ContainingType.GetMembers(parameterSymbol.Name); + var otherMembersOfParameterName = otherContainingType.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); + AddUpdateEditsForMemberAndAccessors(semanticEdits, synthesizedProperty, cancellationToken); } else if (isParameterDelete) { // Delete synthesized property: - AddDeleteEditsForMemberAndAccessors(semanticEdits, synthesizedProperty, deletedSymbolContainer: containingTypeKey, syntaxMap, partialType: null, cancellationToken); + AddDeleteEditsForMemberAndAccessors(semanticEdits, synthesizedProperty, deletedSymbolContainer: containingTypeKey, cancellationToken); } else { // Insert synthesized property: - AddInsertEditsForMemberAndAccessors(semanticEdits, synthesizedProperty, syntaxMap, partialType: null, processedSymbols, cancellationToken); + AddInsertEditsForMemberAndAccessors(semanticEdits, synthesizedProperty, 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, partialType: null, cancellationToken); } /// @@ -3490,8 +3494,6 @@ private void AddDeconstructorEdits( SymbolKey containingTypeKey, Compilation compilation, Compilation otherCompilation, - Func? syntaxMap, - HashSet processedSymbols, bool isParameterDelete, CancellationToken cancellationToken) { @@ -3507,17 +3509,17 @@ void AddEdits(IMethodSymbol? constructor, Compilation otherCompilation, bool isD 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); + AddUpdateEditsForMemberAndAccessors(semanticEdits, deconstructor, cancellationToken); } else if (isDelete) { // Delete synthesized deconstructor: - AddDeleteEditsForMemberAndAccessors(semanticEdits, deconstructor, deletedSymbolContainer: containingTypeKey, syntaxMap, partialType: null, cancellationToken); + AddDeleteEditsForMemberAndAccessors(semanticEdits, deconstructor, deletedSymbolContainer: containingTypeKey, cancellationToken); } else { // Insert synthesized deconstructor: - AddInsertEditsForMemberAndAccessors(semanticEdits, deconstructor, syntaxMap, partialType: null, processedSymbols, cancellationToken); + AddInsertEditsForMemberAndAccessors(semanticEdits, deconstructor, cancellationToken); } } } @@ -3539,8 +3541,8 @@ private static bool AllowsDeletion(ISymbol symbol) if (symbol.IsExtern) return false; - // We don't allow deleting members from interfaces etc. only normal classes and structs - if (symbol.ContainingType is not { TypeKind: TypeKind.Class or TypeKind.Struct }) + // We don't allow deleting members from interfaces + if (symbol.ContainingType is { TypeKind: TypeKind.Interface }) return false; // We store the containing symbol in NewSymbol of the edit for later use. @@ -3561,18 +3563,22 @@ MethodKind.PropertyGet or return true; } - return symbol is IPropertySymbol or IEventSymbol; + // Can only delete event with explicitly declared accessors (otherwise a private field is generated that can't be deleted) + return symbol is + IParameterSymbol or + ITypeParameterSymbol or + IPropertySymbol or + IEventSymbol { AddMethod.IsImplicitlyDeclared: false, RemoveMethod.IsImplicitlyDeclared: false }; } /// /// Add edit for the specified symbol and its accessors. /// - private static void AddUpdateEditsForMemberAndAccessors( - ArrayBuilder semanticEdits, ISymbol symbol, Func? syntaxMap, SymbolKey? partialType, CancellationToken cancellationToken) + private static void AddUpdateEditsForMemberAndAccessors(ArrayBuilder semanticEdits, ISymbol symbol, CancellationToken cancellationToken) { switch (symbol) { - case IMethodSymbol: + case IMethodSymbol or IFieldSymbol: AddUpdate(symbol); break; @@ -3598,31 +3604,33 @@ void AddUpdate(ISymbol? symbol) if (symbol is null) return; - var symbolKey = SymbolKey.Create(symbol, cancellationToken); - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null, partialType, deletedSymbolContainer: null)); + Debug.Assert(symbol is not IMethodSymbol { IsPartialDefinition: true }); + + semanticEdits.Add(SemanticEditInfo.CreateUpdate(SymbolKey.Create(symbol, cancellationToken), syntaxMap: null, syntaxMapTree: null, partialType: null)); } } /// /// 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) + private static void AddDeleteEditsForMemberAndAccessors(ArrayBuilder semanticEdits, ISymbol oldSymbol, SymbolKey deletedSymbolContainer, CancellationToken cancellationToken) { switch (oldSymbol) { - case IMethodSymbol: + case IMethodSymbol or IFieldSymbol: AddDelete(oldSymbol); break; case IPropertySymbol propertySymbol: // Delete accessors individually, because we actually just update them to be throwing. + AddDelete(propertySymbol); AddDelete(propertySymbol.GetMethod); AddDelete(propertySymbol.SetMethod); break; case IEventSymbol eventSymbol: // Delete accessors individually, because we actually just update them to be throwing. + AddDelete(eventSymbol); AddDelete(eventSymbol.AddMethod); AddDelete(eventSymbol.RemoveMethod); AddDelete(eventSymbol.RaiseMethod); @@ -3637,83 +3645,92 @@ void AddDelete(ISymbol? symbol) if (symbol is null) return; - var symbolKey = SymbolKey.Create(symbol, cancellationToken); - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Delete, symbolKey, syntaxMap, syntaxMapTree: null, partialType, deletedSymbolContainer: deletedSymbolContainer)); + Debug.Assert(symbol is not IMethodSymbol { IsPartialDefinition: true }); + + var partialType = symbol is IMethodSymbol { PartialDefinitionPart: not null } ? SymbolKey.Create(symbol.ContainingType, cancellationToken) : (SymbolKey?)null; + semanticEdits.Add(SemanticEditInfo.CreateDelete(SymbolKey.Create(symbol, cancellationToken), deletedSymbolContainer, partialType)); } } /// /// 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) + private static void AddInsertEditsForMemberAndAccessors(ArrayBuilder semanticEdits, ISymbol newSymbol, CancellationToken cancellationToken) + { + // 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. + // + // 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. + + var partialType = newSymbol is IMethodSymbol { PartialDefinitionPart: not null } ? SymbolKey.Create(newSymbol.ContainingType, cancellationToken) : (SymbolKey?)null; + semanticEdits.Add(SemanticEditInfo.CreateInsert(SymbolKey.Create(newSymbol, cancellationToken), partialType)); + } + + private static void AddMemberSignatureOrNameChangeEdits( + ArrayBuilder semanticEdits, + ISymbol oldSymbol, + ISymbol newSymbol, + SymbolKey containingSymbolKey, + CancellationToken cancellationToken) { - switch (newSymbol) + if (oldSymbol.Name != newSymbol.Name || oldSymbol is IMethodSymbol or IFieldSymbol) + { + AddDeleteEditsForMemberAndAccessors(semanticEdits, oldSymbol, containingSymbolKey, cancellationToken); + AddInsertEditsForMemberAndAccessors(semanticEdits, newSymbol, cancellationToken); + return; + } + + switch (oldSymbol) { - case IMethodSymbol: + case IPropertySymbol oldPropertySymbol: + // Properties may be overloaded on signature. + + // delete the property and its accessors + AddDelete(oldPropertySymbol); + AddDelete(oldPropertySymbol.GetMethod); + AddDelete(oldPropertySymbol.SetMethod); + + // insert new property: 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); + case IEventSymbol oldEventSymbol: + // Events can't be overloaded on their type. - // Mark processed to suppress adding update edits of the accessors based on syntactic edits: - MarkProcessed(propertySymbol.GetMethod); - MarkProcessed(propertySymbol.SetMethod); - break; + // Update the event to associate it with the new accessors + semanticEdits.Add(SemanticEditInfo.CreateUpdate(SymbolKey.Create(oldSymbol, cancellationToken), syntaxMap: null, syntaxMapTree: null, partialType: null)); - 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); + // Do not change raise since its signature is not impacted by the event type change. + + // Update old bodies of add and remove to throw. + AddDelete(oldEventSymbol.AddMethod); + AddDelete(oldEventSymbol.RemoveMethod); + + // Insert new add and remove: + var newEventSymbol = (IEventSymbol)newSymbol; + AddInsert(newEventSymbol.AddMethod); + AddInsert(newEventSymbol.RemoveMethod); break; default: - throw ExceptionUtilities.UnexpectedValue(newSymbol.Kind); + throw ExceptionUtilities.UnexpectedValue(oldSymbol.Kind); } - void AddInsert(ISymbol symbol) + void AddInsert(ISymbol? symbol) { - var symbolKey = SymbolKey.Create(symbol, cancellationToken); - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Insert, symbolKey, syntaxMap, syntaxMapTree: null, partialType)); - } + if (symbol is null) + return; - void MarkProcessed(ISymbol? symbol) - { - if (symbol != null) - { - processedSymbols.Add(symbol); - } + semanticEdits.Add(SemanticEditInfo.CreateInsert(SymbolKey.Create(symbol, cancellationToken), partialType: null)); } - } - - 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) + void AddDelete(ISymbol? symbol) { - AddDeleteEditsForMemberAndAccessors(semanticEdits, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, cancellationToken); - } + if (symbol is null) + return; - if (newSymbol != null) - { - AddInsertEditsForMemberAndAccessors(semanticEdits, newSymbol, syntaxMap, partialType: null, processedSymbols, cancellationToken); + semanticEdits.Add(SemanticEditInfo.CreateDelete(SymbolKey.Create(symbol, cancellationToken), containingSymbolKey, partialType: null)); } } @@ -3908,9 +3925,6 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( EditAndContinueCapabilitiesGrantor capabilities, out bool hasGeneratedAttributeChange, out bool hasGeneratedReturnTypeAttributeChange, - out bool hasParameterRename, - out bool hasParameterTypeChange, - out bool hasReturnTypeChange, CancellationToken cancellationToken) { var rudeEdit = RudeEditKind.None; @@ -3919,9 +3933,6 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( hasGeneratedAttributeChange = false; hasGeneratedReturnTypeAttributeChange = false; - hasParameterRename = false; - hasParameterTypeChange = false; - hasReturnTypeChange = false; if (oldSymbol.Kind != newSymbol.Kind) { @@ -4044,20 +4055,14 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( rudeEdit = RudeEditKind.FixedSizeFieldUpdate; } - AnalyzeType(oldField.Type, newField.Type, ref rudeEdit, ref hasGeneratedAttributeChange); + if (!IsMemberOrDelegateReplaced(oldField, newField)) + { + Debug.Assert(ReturnTypesEquivalent(oldField, newField, exact: false)); + hasGeneratedAttributeChange |= !ReturnTypesEquivalent(oldField, newField, exact: true); + } } else if (oldSymbol is IMethodSymbol oldMethod && newSymbol is IMethodSymbol newMethod) { - if (oldMethod.IsReadOnly != newMethod.IsReadOnly) - { - rudeEdit = RudeEditKind.ModifiersUpdate; - } - - if (oldMethod.IsInitOnly != newMethod.IsInitOnly) - { - rudeEdit = RudeEditKind.AccessorKindUpdate; - } - // Changing property accessor to auto-property accessor adds a field: if (oldMethod is { MethodKind: MethodKind.PropertyGet, AssociatedSymbol: IPropertySymbol oldProperty } && !oldProperty.IsAutoProperty() && newMethod is { MethodKind: MethodKind.PropertyGet, AssociatedSymbol: IPropertySymbol newProperty } && newProperty.IsAutoProperty() && @@ -4112,10 +4117,25 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( rudeEdit = RudeEditKind.HandlesClauseUpdate; } + if (oldMethod.IsReadOnly != newMethod.IsReadOnly) + { + hasGeneratedAttributeChange = true; + } + + if (oldMethod.IsInitOnly != newMethod.IsInitOnly) + { + // modreq(IsExternalInit) on the return type + rudeEdit = RudeEditKind.AccessorKindUpdate; + } + // Check return type - do not report for accessors, their containing symbol will report the rude edits and attribute updates. - if (rudeEdit == RudeEditKind.None && oldMethod.AssociatedSymbol == null && newMethod.AssociatedSymbol == null) + if (rudeEdit == RudeEditKind.None && + oldMethod.AssociatedSymbol == null && + newMethod.AssociatedSymbol == null && + !IsMemberOrDelegateReplaced(oldMethod, newMethod)) { - AnalyzeReturnType(oldMethod, newMethod, capabilities, ref rudeEdit, ref hasGeneratedReturnTypeAttributeChange, ref hasReturnTypeChange); + Debug.Assert(ReturnTypesEquivalent(oldMethod, newMethod, exact: false)); + hasGeneratedReturnTypeAttributeChange |= !ReturnTypesEquivalent(oldMethod, newMethod, exact: true); } } else if (oldSymbol is INamedTypeSymbol oldType && newSymbol is INamedTypeSymbol newType) @@ -4138,13 +4158,19 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( if (oldType.DelegateInvokeMethod != null) { Contract.ThrowIfNull(newType.DelegateInvokeMethod); - AnalyzeReturnType(oldType.DelegateInvokeMethod, newType.DelegateInvokeMethod, capabilities, ref rudeEdit, ref hasGeneratedReturnTypeAttributeChange, ref hasReturnTypeChange); + Debug.Assert(ReturnTypesEquivalent(oldType.DelegateInvokeMethod, newType.DelegateInvokeMethod, exact: false)); + + hasGeneratedReturnTypeAttributeChange |= !ReturnTypesEquivalent(oldType.DelegateInvokeMethod, newType.DelegateInvokeMethod, exact: true); } } } else if (oldSymbol is IPropertySymbol oldProperty && newSymbol is IPropertySymbol newProperty) { - AnalyzeReturnType(oldProperty, newProperty, capabilities, ref rudeEdit, ref hasGeneratedReturnTypeAttributeChange, ref hasReturnTypeChange); + if (!IsMemberOrDelegateReplaced(oldProperty, newProperty)) + { + Debug.Assert(ReturnTypesEquivalent(oldProperty, newProperty, exact: false)); + hasGeneratedReturnTypeAttributeChange |= !ReturnTypesEquivalent(oldProperty, newProperty, exact: true); + } } else if (oldSymbol is IEventSymbol oldEvent && newSymbol is IEventSymbol newEvent) { @@ -4152,40 +4178,35 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( if (oldEvent.AddMethod != null && newEvent.AddMethod != null && oldEvent.AddMethod.IsReadOnly != newEvent.AddMethod.IsReadOnly || oldEvent.RemoveMethod != null && newEvent.RemoveMethod != null && oldEvent.RemoveMethod.IsReadOnly != newEvent.RemoveMethod.IsReadOnly) { - rudeEdit = RudeEditKind.ModifiersUpdate; + hasGeneratedAttributeChange = true; } - else + else if (!IsMemberOrDelegateReplaced(oldEvent, newEvent)) { - AnalyzeReturnType(oldEvent, newEvent, capabilities, ref rudeEdit, ref hasGeneratedReturnTypeAttributeChange, ref hasReturnTypeChange); + Debug.Assert(ReturnTypesEquivalent(oldEvent, newEvent, exact: false)); + hasGeneratedReturnTypeAttributeChange |= !ReturnTypesEquivalent(oldEvent, newEvent, exact: true); } } else if (oldSymbol is IParameterSymbol oldParameter && newSymbol is IParameterSymbol newParameter) { - if (oldParameter.RefKind != newParameter.RefKind || - oldParameter.IsParams != newParameter.IsParams || - IsExtensionMethodThisParameter(oldParameter) != IsExtensionMethodThisParameter(newParameter)) - { - rudeEdit = RudeEditKind.ModifiersUpdate; - } - else if (oldParameter.HasExplicitDefaultValue != newParameter.HasExplicitDefaultValue || - oldParameter.HasExplicitDefaultValue && !Equals(oldParameter.ExplicitDefaultValue, newParameter.ExplicitDefaultValue)) - { - rudeEdit = RudeEditKind.InitializerUpdate; - } - else + // If the containing member is being replaced then parameters are not being updated. + if (!IsMemberOrDelegateReplaced(oldParameter.ContainingSymbol, newParameter.ContainingSymbol)) { - AnalyzeParameterType(oldParameter, newParameter, capabilities, ref rudeEdit, ref hasGeneratedAttributeChange, ref hasParameterTypeChange); + if (IsExtensionMethodThisParameter(oldParameter) != IsExtensionMethodThisParameter(newParameter) || + GeneratesParameterAttribute(oldParameter.RefKind) != GeneratesParameterAttribute(newParameter.RefKind) || + oldParameter.IsParams != newParameter.IsParams || + !ParameterTypesEquivalent(oldParameter, newParameter, exact: true)) + { + hasGeneratedAttributeChange = true; + } - if (!hasParameterTypeChange && oldParameter.Name != newParameter.Name) + if (oldParameter.HasExplicitDefaultValue != newParameter.HasExplicitDefaultValue || + oldParameter.HasExplicitDefaultValue && !Equals(oldParameter.ExplicitDefaultValue, newParameter.ExplicitDefaultValue)) { - if (capabilities.Grant(EditAndContinueCapabilities.UpdateParameters)) - { - hasParameterRename = true; - } - else - { - rudeEdit = RudeEditKind.RenamingNotSupportedByRuntime; - } + rudeEdit = RudeEditKind.InitializerUpdate; + } + else if (oldParameter.Name != newParameter.Name && !capabilities.Grant(EditAndContinueCapabilities.UpdateParameters)) + { + rudeEdit = RudeEditKind.RenamingNotSupportedByRuntime; } } } @@ -4207,7 +4228,7 @@ private void ReportUpdatedSymbolDeclarationRudeEdits( // updating within generic context if (rudeEdit == RudeEditKind.None && oldSymbol is not INamedTypeSymbol and not ITypeParameterSymbol and not IParameterSymbol && - (InGenericContext(oldSymbol) || InGenericContext(newSymbol)) && + InGenericContext(oldSymbol) && !capabilities.Grant(EditAndContinueCapabilities.GenericUpdateMethod)) { rudeEdit = RudeEditKind.UpdatingGenericNotSupportedByRuntime; @@ -4219,20 +4240,8 @@ oldSymbol is not INamedTypeSymbol and not ITypeParameterSymbol and not IParamete } } - private static void AnalyzeType(ITypeSymbol oldType, ITypeSymbol newType, ref RudeEditKind rudeEdit, ref bool hasGeneratedAttributeChange, RudeEditKind rudeEditKind = RudeEditKind.TypeUpdate) - { - if (!TypesEquivalent(oldType, newType, exact: true)) - { - if (TypesEquivalent(oldType, newType, exact: false)) - { - hasGeneratedAttributeChange = true; - } - else - { - rudeEdit = rudeEditKind; - } - } - } + private static bool GeneratesParameterAttribute(RefKind kind) + => kind is RefKind.In or RefKind.RefReadOnlyParameter; private static void AnalyzeBaseTypes(INamedTypeSymbol oldType, INamedTypeSymbol newType, ref RudeEditKind rudeEdit, ref bool hasGeneratedAttributeChange) { @@ -4263,148 +4272,48 @@ private static void AnalyzeBaseTypes(INamedTypeSymbol oldType, INamedTypeSymbol } } - private static void AnalyzeParameterType( - IParameterSymbol oldParameter, - IParameterSymbol newParameter, - EditAndContinueCapabilitiesGrantor capabilities, - ref RudeEditKind rudeEdit, - ref bool hasGeneratedAttributeChange, - ref bool hasParameterTypeChange) + private static RudeEditKind GetSignatureChangeRudeEdit(ISymbol oldMember, ISymbol newMember, EditAndContinueCapabilitiesGrantor capabilities) { - if (!ParameterTypesEquivalent(oldParameter, newParameter, exact: true)) + if (oldMember.Kind != newMember.Kind) { - if (ParameterTypesEquivalent(oldParameter, newParameter, exact: false)) - { - hasGeneratedAttributeChange = true; - } - else if (newParameter.ContainingType.IsDelegateType()) - { - // We don't allow changing parameter types in delegates - rudeEdit = RudeEditKind.TypeUpdate; - } - else if (AllowsDeletion(newParameter.ContainingSymbol)) - { - if (CanRenameOrChangeSignature(oldParameter.ContainingSymbol, newParameter.ContainingSymbol, capabilities)) - { - hasParameterTypeChange = true; - } - else - { - rudeEdit = RudeEditKind.ChangingTypeNotSupportedByRuntime; - } - } - else - { - rudeEdit = RudeEditKind.TypeUpdate; - } + // rude edit will be reported later + return RudeEditKind.None; } - } - private static void AnalyzeTypeParameter(ITypeParameterSymbol oldParameter, ITypeParameterSymbol newParameter, ref RudeEditKind rudeEdit, ref bool hasGeneratedAttributeChange) - { - if (!TypeParameterConstraintsEquivalent(oldParameter, newParameter, exact: true)) + if (IsGlobalMain(oldMember)) { - if (TypeParameterConstraintsEquivalent(oldParameter, newParameter, exact: false)) - { - hasGeneratedAttributeChange = true; - } - else - { - rudeEdit = (oldParameter.Variance != newParameter.Variance) ? RudeEditKind.VarianceUpdate : RudeEditKind.ChangingConstraints; - } + // Only return type can be changed: + Debug.Assert(ParameterTypesEquivalent(oldMember.GetParameters(), newMember.GetParameters(), exact: true)); + + return RudeEditKind.ChangeImplicitMainReturnType; } - } - private static void AnalyzeReturnType(IMethodSymbol oldMethod, IMethodSymbol newMethod, EditAndContinueCapabilitiesGrantor capabilities, ref RudeEditKind rudeEdit, ref bool hasGeneratedReturnTypeAttributeChange, ref bool hasReturnTypeChange) - { - if (!ReturnTypesEquivalent(oldMethod, newMethod, exact: true)) + if (!AllowsDeletion(newMember)) { - if (ReturnTypesEquivalent(oldMethod, newMethod, exact: false)) - { - hasGeneratedReturnTypeAttributeChange = true; - } - else if (IsGlobalMain(oldMethod) || IsGlobalMain(newMethod)) - { - rudeEdit = RudeEditKind.ChangeImplicitMainReturnType; - } - else if (oldMethod.ContainingType.IsDelegateType()) - { - rudeEdit = RudeEditKind.TypeUpdate; - } - else if (AllowsDeletion(newMethod)) - { - if (CanRenameOrChangeSignature(oldMethod, newMethod, capabilities)) - { - hasReturnTypeChange = true; - } - else - { - rudeEdit = RudeEditKind.ChangingTypeNotSupportedByRuntime; - } - } - else - { - rudeEdit = RudeEditKind.TypeUpdate; - } + return RudeEditKind.TypeUpdate; } - } - private static void AnalyzeReturnType(IEventSymbol oldEvent, IEventSymbol newEvent, EditAndContinueCapabilitiesGrantor capabilities, ref RudeEditKind rudeEdit, ref bool hasGeneratedReturnTypeAttributeChange, ref bool hasReturnTypeChange) - { - if (!ReturnTypesEquivalent(oldEvent, newEvent, exact: true)) + // Note: do not report a rude edit for property/event accessors as it will already be reported for the property/event itself. + if (!CanRenameOrChangeSignature(oldMember, newMember, capabilities) && + oldMember is not IMethodSymbol { AssociatedSymbol.Kind: SymbolKind.Property or SymbolKind.Event }) { - if (ReturnTypesEquivalent(oldEvent, newEvent, exact: false)) - { - hasGeneratedReturnTypeAttributeChange = true; - } - else if (oldEvent.ContainingType.IsDelegateType()) - { - rudeEdit = RudeEditKind.TypeUpdate; - } - else if (AllowsDeletion(newEvent)) - { - if (CanRenameOrChangeSignature(oldEvent, newEvent, capabilities)) - { - hasReturnTypeChange = true; - } - else - { - rudeEdit = RudeEditKind.ChangingTypeNotSupportedByRuntime; - } - } - else - { - rudeEdit = RudeEditKind.TypeUpdate; - } + return RudeEditKind.ChangingSignatureNotSupportedByRuntime; } + + return RudeEditKind.None; } - private static void AnalyzeReturnType(IPropertySymbol oldProperty, IPropertySymbol newProperty, EditAndContinueCapabilitiesGrantor capabilities, ref RudeEditKind rudeEdit, ref bool hasGeneratedReturnTypeAttributeChange, ref bool hasReturnTypeChange) + private static void AnalyzeTypeParameter(ITypeParameterSymbol oldParameter, ITypeParameterSymbol newParameter, ref RudeEditKind rudeEdit, ref bool hasGeneratedAttributeChange) { - if (!ReturnTypesEquivalent(oldProperty, newProperty, exact: true)) + if (!TypeParameterConstraintsEquivalent(oldParameter, newParameter, exact: true)) { - if (ReturnTypesEquivalent(oldProperty, newProperty, exact: false)) - { - hasGeneratedReturnTypeAttributeChange = true; - } - else if (oldProperty.ContainingType.IsDelegateType()) - { - rudeEdit = RudeEditKind.TypeUpdate; - } - else if (AllowsDeletion(newProperty)) + if (TypeParameterConstraintsEquivalent(oldParameter, newParameter, exact: false)) { - if (CanRenameOrChangeSignature(oldProperty, newProperty, capabilities)) - { - hasReturnTypeChange = true; - } - else - { - rudeEdit = RudeEditKind.ChangingTypeNotSupportedByRuntime; - } + hasGeneratedAttributeChange = true; } else { - rudeEdit = RudeEditKind.TypeUpdate; + rudeEdit = (oldParameter.Variance != newParameter.Variance) ? RudeEditKind.VarianceUpdate : RudeEditKind.ChangingConstraints; } } } @@ -4414,162 +4323,99 @@ private static bool IsExtensionMethodThisParameter(IParameterSymbol parameter) private void AnalyzeSymbolUpdate( in DiagnosticContext diagnosticContext, - Match topMatch, EditAndContinueCapabilitiesGrantor capabilities, ArrayBuilder semanticEdits, - Func? syntaxMap, - HashSet processedSymbols, + out bool hasAttributeChange, CancellationToken cancellationToken) { // TODO: fails in VB on delegate parameter https://github.com/dotnet/roslyn/issues/53337 // Contract.ThrowIfFalse(newSymbol.IsImplicitlyDeclared == newDeclaration is null); - ReportCustomAttributeRudeEdits(diagnosticContext, capabilities, out var hasAttributeChange, out var hasReturnTypeAttributeChange, cancellationToken); + ReportUpdatedSymbolDeclarationRudeEdits( + diagnosticContext, capabilities, out var hasGeneratedAttributeChange, out var hasGeneratedReturnTypeAttributeChange, cancellationToken); - ReportUpdatedSymbolDeclarationRudeEdits(diagnosticContext, capabilities, out var hasGeneratedAttributeChange, out var hasGeneratedReturnTypeAttributeChange, out var hasParameterRename, out var hasParameterTypeChange, out var hasReturnTypeChange, cancellationToken); - hasAttributeChange |= hasGeneratedAttributeChange; + // We don't check capabilities of the runtime to update compiler generated attributes. + // All runtimes support changing the attributes in metadata, some just don't reflect the changes in the Reflection model. + // Having compiler-generated attributes visible via Reflaction API is not that important. + ReportCustomAttributeRudeEdits(diagnosticContext, capabilities, out var hasSymbolAttributeChange, out var hasReturnTypeAttributeChange, cancellationToken); + hasSymbolAttributeChange |= hasGeneratedAttributeChange; hasReturnTypeAttributeChange |= hasGeneratedReturnTypeAttributeChange; var oldSymbol = diagnosticContext.RequiredOldSymbol; var newSymbol = diagnosticContext.RequiredNewSymbol; - if (hasParameterRename || hasParameterTypeChange) + if (oldSymbol is IParameterSymbol oldParameter && newSymbol is IParameterSymbol newParameter) { - Debug.Assert(newSymbol is IParameterSymbol); + AddSemanticEditsOriginatingFromParameterUpdate(semanticEdits, oldParameter, newParameter, diagnosticContext.NewModel.Compilation, cancellationToken); - // 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)) + // Attributes applied on parameters of a delegate are applied to both Invoke and BeginInvoke methods. So are the parameter names. + if ((hasSymbolAttributeChange || oldParameter.Name != newParameter.Name) && + newParameter.ContainingType is INamedTypeSymbol { TypeKind: TypeKind.Delegate } newContainingDelegateType) { - return; + AddDelegateMethodEdit(semanticEdits, newContainingDelegateType, "Invoke", cancellationToken); + AddDelegateMethodEdit(semanticEdits, newContainingDelegateType, "BeginInvoke", cancellationToken); } - - AddParameterUpdateSemanticEdit(semanticEdits, (IParameterSymbol)oldSymbol, (IParameterSymbol)newSymbol, diagnosticContext.NewModel.Compilation, syntaxMap, reportDeleteAndInsertEdits: hasParameterTypeChange, processedSymbols, cancellationToken); - } - else if (hasReturnTypeChange) - { - var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingSymbol, cancellationToken); - AddDeleteAndInsertEditsForMemberAndAccessors(semanticEdits, oldSymbol, newSymbol, containingSymbolKey, syntaxMap, processedSymbols, cancellationToken); - } - else if (hasAttributeChange || hasReturnTypeAttributeChange) - { - AddCustomAttributeSemanticEdits(semanticEdits, oldSymbol, newSymbol, diagnosticContext.NewModel.Compilation, topMatch, syntaxMap, processedSymbols, hasAttributeChange, hasReturnTypeAttributeChange, cancellationToken); } - } - private void AddCustomAttributeSemanticEdits( - ArrayBuilder semanticEdits, - ISymbol oldSymbol, - ISymbol newSymbol, - Compilation newCompilation, - Match topMatch, - Func? syntaxMap, - HashSet processedSymbols, - bool hasAttributeChange, - bool hasReturnTypeAttributeChange, - CancellationToken cancellationToken) - { // Most symbol types will automatically have an edit added, so we just need to handle a few - if (newSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var newDelegateInvokeMethod } newDelegateType) - { - if (hasAttributeChange) - { - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(newDelegateType, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null)); - } - - if (hasReturnTypeAttributeChange) - { - // attributes applied on return type of a delegate are applied to both Invoke and BeginInvoke methods - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(newDelegateInvokeMethod, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null)); - AddDelegateBeginInvokeEdit(semanticEdits, newDelegateType, syntaxMap, cancellationToken); - } - } - else if (newSymbol is INamedTypeSymbol) + if (hasReturnTypeAttributeChange && newSymbol is INamedTypeSymbol { TypeKind: TypeKind.Delegate } newDelegateType) { - var symbolKey = SymbolKey.Create(newSymbol, cancellationToken); - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null, - IsPartialEdit(oldSymbol, newSymbol, topMatch) ? symbolKey : null)); - } - else if (newSymbol is ITypeParameterSymbol) - { - var containingTypeSymbolKey = SymbolKey.Create(newSymbol.ContainingSymbol, cancellationToken); - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, containingTypeSymbolKey, syntaxMap, syntaxMapTree: null, - IsPartialEdit(oldSymbol.ContainingSymbol, newSymbol.ContainingSymbol, topMatch) ? containingTypeSymbolKey : null)); - } - else if (newSymbol is IFieldSymbol or IPropertySymbol or IEventSymbol) - { - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(newSymbol, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null)); - } - else if (newSymbol is IParameterSymbol newParameterSymbol) - { - AddParameterUpdateSemanticEdit(semanticEdits, (IParameterSymbol)oldSymbol, newParameterSymbol, newCompilation, syntaxMap, reportDeleteAndInsertEdits: false, processedSymbols, cancellationToken); + // attributes applied on return type of a delegate are applied to both Invoke and EndInvoke methods + AddDelegateMethodEdit(semanticEdits, newDelegateType, "Invoke", cancellationToken); + AddDelegateMethodEdit(semanticEdits, newDelegateType, "EndInvoke", cancellationToken); } + + hasAttributeChange = hasSymbolAttributeChange || hasReturnTypeAttributeChange; } - private void AddParameterUpdateSemanticEdit( + /// + /// Semantic edits of members synthesized based on parameters that have no declaring syntax ( returns null) + /// and therefore not produced by + /// + private void AddSemanticEditsOriginatingFromParameterUpdate( ArrayBuilder semanticEdits, IParameterSymbol oldParameterSymbol, IParameterSymbol newParameterSymbol, Compilation newCompilation, - Func? syntaxMap, - bool reportDeleteAndInsertEdits, - HashSet processedSymbols, CancellationToken cancellationToken) { + var oldContainingMember = oldParameterSymbol.ContainingSymbol; var newContainingMember = newParameterSymbol.ContainingSymbol; - if (reportDeleteAndInsertEdits) + if (oldContainingMember.ContainingType.IsRecord && + newContainingMember.ContainingType.IsRecord && + IsPrimaryConstructor(oldContainingMember, cancellationToken) is var oldIsPrimary && + IsPrimaryConstructor(newContainingMember, cancellationToken) is var newIsPrimary) { - 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)) + // both parameters are primary and differ in name or type + if (oldIsPrimary && newIsPrimary && (oldParameterSymbol.Name != newParameterSymbol.Name || !ParameterTypesEquivalent(oldParameterSymbol, newParameterSymbol, exact: false))) { var oldPrimaryConstructor = (IMethodSymbol)oldContainingMember; var newPrimaryConstructor = (IMethodSymbol)newContainingMember; + var containingSymbolKey = SymbolKey.Create(oldContainingMember.ContainingSymbol, cancellationToken); - // add delete and insert edits of synthesized properties: - var oldSynthesizedProperty = GetPropertySynthesizedForRecordPrimaryConstructorParameter(oldParameterSymbol); - var newSynthesizedProperty = GetPropertySynthesizedForRecordPrimaryConstructorParameter(newParameterSymbol); - AddDeleteAndInsertEditsForMemberAndAccessors(semanticEdits, oldSynthesizedProperty, newSynthesizedProperty, containingSymbolKey, syntaxMap, processedSymbols, cancellationToken); + // Note: Edits for synthesized properties were already created by GetSymbolEdits. // add delete and insert edits of synthesized deconstructor: var oldSynthesizedDeconstructor = oldPrimaryConstructor.GetMatchingDeconstructor(); var newSynthesizedDeconstructor = newPrimaryConstructor.GetMatchingDeconstructor(); - AddDeleteAndInsertEditsForMemberAndAccessors(semanticEdits, oldSynthesizedDeconstructor, newSynthesizedDeconstructor, containingSymbolKey, syntaxMap, processedSymbols, cancellationToken); + Contract.ThrowIfNull(oldSynthesizedDeconstructor); + Contract.ThrowIfNull(newSynthesizedDeconstructor); + + AddMemberSignatureOrNameChangeEdits(semanticEdits, oldSynthesizedDeconstructor, newSynthesizedDeconstructor, containingSymbolKey, cancellationToken); // add updates of synthesized methods: - AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newContainingMember.ContainingType, partialType: null, cancellationToken); + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newCompilation, newContainingMember.ContainingType, cancellationToken); } } - else - { - 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 (newContainingMember.ContainingType is INamedTypeSymbol { TypeKind: TypeKind.Delegate } newContainingDelegateType) - { - Debug.Assert(reportDeleteAndInsertEdits == false); - AddDelegateBeginInvokeEdit(semanticEdits, newContainingDelegateType, syntaxMap, cancellationToken); - } } - private static void AddDelegateBeginInvokeEdit(ArrayBuilder semanticEdits, INamedTypeSymbol delegateType, Func? syntaxMap, CancellationToken cancellationToken) + private static void AddDelegateMethodEdit(ArrayBuilder semanticEdits, INamedTypeSymbol delegateType, string methodName, CancellationToken cancellationToken) { - Debug.Assert(semanticEdits != null); - - var beginInvokeMethod = delegateType.GetMembers("BeginInvoke").FirstOrDefault(); + var beginInvokeMethod = delegateType.GetMembers(methodName).FirstOrDefault(); if (beginInvokeMethod != null) { - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(beginInvokeMethod, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null)); + semanticEdits.Add(SemanticEditInfo.CreateUpdate(SymbolKey.Create(beginInvokeMethod, cancellationToken), syntaxMap: null, syntaxMapTree: null, partialType: null)); } } @@ -4789,7 +4635,7 @@ static bool IsSecurityAttribute(INamedTypeSymbol namedTypeSymbol) /// private static bool CanRenameOrChangeSignature(ISymbol oldSymbol, ISymbol newSymbol, EditAndContinueCapabilitiesGrantor capabilities) => CanAddNewMemberToExistingType(newSymbol, capabilities) && - CanUpdateMemberBody(oldSymbol, newSymbol, capabilities); + CanUpdateMemberBody(oldSymbol, capabilities); private static bool CanAddNewMemberToExistingType(ISymbol newSymbol, EditAndContinueCapabilitiesGrantor capabilities) { @@ -4816,9 +4662,10 @@ private static EditAndContinueCapabilities GetRequiredAddFieldCapabilities(ISymb => (symbol.IsStatic ? EditAndContinueCapabilities.AddStaticFieldToExistingType : EditAndContinueCapabilities.AddInstanceFieldToExistingType) | (InGenericContext(symbol) ? EditAndContinueCapabilities.GenericAddFieldToExistingType : 0); - private static bool CanUpdateMemberBody(ISymbol oldSymbol, ISymbol newSymbol, EditAndContinueCapabilitiesGrantor capabilities) + private static bool CanUpdateMemberBody(ISymbol oldSymbol, EditAndContinueCapabilitiesGrantor capabilities) { - if (InGenericContext(oldSymbol) || InGenericContext(newSymbol)) + // If the new member is generic and the old one isn't then the old one will be updated (via delete edit) and the new one inserted. + if (InGenericContext(oldSymbol)) { return capabilities.Grant(EditAndContinueCapabilities.GenericUpdateMethod); } @@ -4833,7 +4680,6 @@ private static void AddSynthesizedRecordMethodUpdatesForPropertyChange( ArrayBuilder semanticEdits, Compilation compilation, INamedTypeSymbol recordType, - SymbolKey? partialType, CancellationToken cancellationToken) { Debug.Assert(recordType.IsRecord); @@ -4844,7 +4690,7 @@ private static void AddSynthesizedRecordMethodUpdatesForPropertyChange( // We could avoid these updates if we check the details (e.g. name & type matching, etc.) var symbolKey = SymbolKey.Create(member, cancellationToken); - semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap: null, syntaxMapTree: null, partialType)); + semanticEdits.Add(SemanticEditInfo.CreateUpdate(symbolKey, syntaxMap: null, syntaxMapTree: null, partialType: null)); } } @@ -4898,10 +4744,15 @@ internal readonly struct DiagnosticContext( ISymbol? oldSymbol, ISymbol? newSymbol, SyntaxNode? newNode, - SemanticModel newModel) + SemanticModel newModel, + Match? topMatch, + TextSpan diagnosticSpan) { public SemanticModel NewModel => newModel; + public ISymbol? OldSymbol + => oldSymbol; + public ISymbol RequiredOldSymbol { get @@ -4933,7 +4784,20 @@ private SyntaxNode GetDiagnosticNode(out int distance, CancellationToken cancell if (newDiagnosticSymbol == null) { Debug.Assert(oldSymbol != null); + + // try to resolve containing symbol: newDiagnosticSymbol = TryGetNewContainer(oldSymbol, ref distance, cancellationToken); + + // try to map container syntax: + if (newDiagnosticSymbol == null && topMatch != null) + { + var oldContainerDeclaration = analyzer.GetSymbolDeclarationSyntax(oldSymbol.ContainingSymbol, topMatch.OldRoot.SyntaxTree, cancellationToken); + if (oldContainerDeclaration != null && + topMatch.TryGetNewNode(oldContainerDeclaration, out var newContainerDeclaration)) + { + return newContainerDeclaration; + } + } } while (newDiagnosticSymbol != null) @@ -4941,8 +4805,7 @@ private SyntaxNode GetDiagnosticNode(out int distance, CancellationToken cancell // TODO: condition !newDiagnosticSymbol.IsImplicitlyDeclared should not be needed https://github.com/dotnet/roslyn/issues/68510 if (newDiagnosticSymbol.DeclaringSyntaxReferences.Length > 0 && !newDiagnosticSymbol.IsImplicitlyDeclared) { - var newTree = newModel.SyntaxTree; - var node = analyzer.GetSymbolDeclarationSyntax(newDiagnosticSymbol, syntaxRefs => syntaxRefs.FirstOrDefault(r => r.SyntaxTree == newTree), cancellationToken); + var node = analyzer.GetSymbolDeclarationSyntax(newDiagnosticSymbol, newModel.SyntaxTree, cancellationToken); if (node != null) { return node; @@ -4969,13 +4832,12 @@ private SyntaxNode GetDiagnosticNode(out int distance, CancellationToken cancell distance++; } - while (oldContainer != null) + while (oldContainer is not null and not INamespaceSymbol { IsGlobalNamespace: true }) { - var containerKey = SymbolKey.Create(oldContainer, cancellationToken); - var newContainer = containerKey.Resolve(newModel.Compilation, ignoreAssemblyKey: true, cancellationToken).Symbol; - if (newContainer != null) + var symbolKey = SymbolKey.Create(oldSymbol, cancellationToken); + if (symbolKey.Resolve(newModel.Compilation, ignoreAssemblyKey: true, cancellationToken).Symbol is { } newSymbol) { - return newContainer; + return newSymbol; } oldContainer = oldContainer.ContainingSymbol; @@ -5016,9 +4878,13 @@ public void Report(RudeEditKind kind, CancellationToken cancellationToken, TextS { var node = GetDiagnosticNode(out var distance, cancellationToken); + span ??= diagnosticSpan.IsEmpty ? + analyzer.GetDiagnosticSpan(node, (distance > 0 || kind == RudeEditKind.ChangeImplicitMainReturnType) ? EditKind.Delete : EditKind.Update) : + diagnosticSpan; + diagnostics.Add(new RudeEditDiagnostic( kind, - span ?? analyzer.GetDiagnosticSpan(node, (distance > 0 || kind == RudeEditKind.ChangeImplicitMainReturnType) ? EditKind.Delete : EditKind.Update), + span.Value, node, arguments ?? kind switch { @@ -5065,18 +4931,16 @@ public void ReportTypeLayoutUpdateRudeEdits(CancellationToken cancellationToken) } public DiagnosticContext WithSymbols(ISymbol oldSymbol, ISymbol newSymbol) - => new(analyzer, diagnostics, oldSymbol, newSymbol, newNode, newModel); + => new(analyzer, diagnostics, oldSymbol, newSymbol, newNode, newModel, topMatch, diagnosticSpan); } - private DiagnosticContext CreateDiagnosticContext(ArrayBuilder diagnostics, ISymbol? oldSymbol, ISymbol? newSymbol, SyntaxNode? newNode, SemanticModel newModel) - => new(this, diagnostics, oldSymbol, newSymbol, newNode, newModel); + private DiagnosticContext CreateDiagnosticContext(ArrayBuilder diagnostics, ISymbol? oldSymbol, ISymbol? newSymbol, SyntaxNode? newNode, SemanticModel newModel, Match? topMatch, TextSpan diagnosticSpan = default) + => new(this, diagnostics, oldSymbol, newSymbol, newNode, newModel, topMatch, diagnosticSpan); #region Type Layout Update Validation - internal static void ReportTypeLayoutUpdateRudeEdits(in DiagnosticContext diagnosticContext, CancellationToken cancellationToken) + internal static void ReportTypeLayoutUpdateRudeEdits(in DiagnosticContext diagnosticContext, ISymbol newSymbol, CancellationToken cancellationToken) { - var newSymbol = diagnosticContext.RequiredNewSymbol; - switch (newSymbol.Kind) { case SymbolKind.Field: @@ -5220,8 +5084,6 @@ private void AddConstructorEdits( SemanticModel? oldModel, Compilation oldCompilation, SemanticModel newModel, - HashSet processedSymbols, - EditAndContinueCapabilitiesGrantor capabilities, bool isStatic, [Out] ArrayBuilder semanticEdits, [Out] ArrayBuilder diagnostics, @@ -5235,9 +5097,10 @@ private void AddConstructorEdits( var oldType = updatesInCurrentDocument.OldType; var anyInitializerUpdatesInCurrentDocument = updatesInCurrentDocument.ChangedDeclarations.Keys.Any(IsDeclarationWithInitializer) || updatesInCurrentDocument.HasDeletedMemberInitializer; - var isPartialEdit = IsPartialEdit(oldType, newType, oldSyntaxTree, newSyntaxTree); + var isPartialEdit = IsPartialTypeEdit(oldType, newType, oldSyntaxTree, newSyntaxTree); var typeKey = SymbolKey.Create(newType, cancellationToken); var partialType = isPartialEdit ? typeKey : (SymbolKey?)null; + var syntaxMapTree = isPartialEdit ? newSyntaxTree : 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. @@ -5278,6 +5141,8 @@ private void AddConstructorEdits( SyntaxNode? oldDeclaration = null; SyntaxNode? newDeclaration = null; IMethodSymbol? oldCtor; + bool hasSignatureChanges; + if (!newCtor.IsImplicitlyDeclared) { // Constructors have to have a single declaration syntax, they can't be partial @@ -5302,10 +5167,13 @@ private void AddConstructorEdits( oldCtor = (IMethodSymbol)GetRequiredDeclaredSymbol(oldModel, oldDeclaration, cancellationToken); Contract.ThrowIfFalse(oldCtor is { MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor }); + + hasSignatureChanges = !MemberOrDelegateSignaturesEquivalent(oldCtor, newCtor, exact: false); } else if (newCtor.Parameters.Length == 0) { oldCtor = TryGetParameterlessConstructor(oldType, isStatic); + hasSignatureChanges = false; } else { @@ -5315,6 +5183,10 @@ private void AddConstructorEdits( // Pick the first candidate. oldCtor = (IMethodSymbol?)resolution.Symbol; + + // SymbolKey-resolved constructors have the same signatures. + Debug.Assert(oldCtor == null || MemberOrDelegateSignaturesEquivalent(oldCtor, newCtor, exact: false)); + hasSignatureChanges = false; } } else @@ -5331,9 +5203,11 @@ private void AddConstructorEdits( { continue; } + + hasSignatureChanges = false; } - var diagnosticContext = CreateDiagnosticContext(diagnostics, oldCtor, newCtor, newDeclaration, newModel); + var diagnosticContext = CreateDiagnosticContext(diagnostics, oldCtor, newCtor, newDeclaration, newModel, topMatch); // Report an error if the updated constructor's declaration is in the current document // and its body edit is disallowed (e.g. the body itself or any member initializer contains stackalloc). @@ -5347,8 +5221,6 @@ private void AddConstructorEdits( if (oldCtor != null) { - AnalyzeSymbolUpdate(diagnosticContext, topMatch, capabilities, semanticEdits, syntaxMapToUse, processedSymbols, cancellationToken); - // We don't need to check initializers of the new type since any change that would // add stackalloc or other disallowed syntax would already be reported as rude edit. unsupportedOperationReported |= AnyMemberInitializerBody( @@ -5357,12 +5229,16 @@ private void AddConstructorEdits( isStatic, cancellationToken); - semanticEdits.Add(new SemanticEditInfo( - SemanticEditKind.Update, - newCtorKey, - syntaxMapToUse, - syntaxMapTree: isPartialEdit ? newSyntaxTree : null, - partialType: partialType)); + if (hasSignatureChanges) + { + // Even though we can't remap active statements between the deleted and inserted methods, + // we still need syntax map to map lambdas. + AddMemberSignatureOrNameChangeEdits(semanticEdits, oldCtor, newCtor, typeKey, cancellationToken); + } + else + { + semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, newCtorKey, syntaxMapToUse, syntaxMapTree, partialType, deletedSymbolContainer: null)); + } } else { @@ -5376,12 +5252,7 @@ private void AddConstructorEdits( continue; } - semanticEdits.Add(new SemanticEditInfo( - SemanticEditKind.Insert, - newCtorKey, - syntaxMap: null, - syntaxMapTree: null, - partialType: null)); + semanticEdits.Add(SemanticEditInfo.CreateInsert(newCtorKey, partialType)); } // primary record constructor updated to non-primary and vice versa: @@ -5390,13 +5261,14 @@ private void AddConstructorEdits( var oldCtorIsPrimary = oldCtor != null && IsPrimaryConstructor(oldCtor, cancellationToken); var newCtorIsPrimary = IsPrimaryConstructor(newCtor, cancellationToken); - if (oldCtorIsPrimary != newCtorIsPrimary) + if (hasSignatureChanges && oldCtorIsPrimary && newCtorIsPrimary || + oldCtorIsPrimary != newCtorIsPrimary) { // Deconstructor: - AddDeconstructorEdits(semanticEdits, oldCtor, newCtor, typeKey, oldCompilation, newModel.Compilation, syntaxMap: null, processedSymbols, isParameterDelete: newCtorIsPrimary, cancellationToken); + AddDeconstructorEdits(semanticEdits, oldCtor, newCtor, typeKey, oldCompilation, newModel.Compilation, isParameterDelete: newCtorIsPrimary, cancellationToken); // Synthesized method updates: - AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newModel.Compilation, newCtor.ContainingType, partialType: null, cancellationToken); + AddSynthesizedRecordMethodUpdatesForPropertyChange(semanticEdits, newModel.Compilation, newCtor.ContainingType, cancellationToken); } } } @@ -5406,13 +5278,10 @@ private void AddConstructorEdits( // Adding the first instance constructor with parameters suppresses synthesized default constructor. if (oldType.HasSynthesizedDefaultConstructor() && !newType.HasSynthesizedDefaultConstructor()) { - semanticEdits.Add(new SemanticEditInfo( - SemanticEditKind.Delete, + semanticEdits.Add(SemanticEditInfo.CreateDelete( SymbolKey.Create(oldType.InstanceConstructors.Single(c => c.Parameters is []), cancellationToken), - syntaxMap: null, - syntaxMapTree: null, - partialType: partialType, - deletedSymbolContainer: typeKey)); + deletedSymbolContainer: typeKey, + partialType)); } // Removing the last instance constructor with parameters inserts synthesized default constructor. @@ -5459,18 +5328,19 @@ private bool AnyMemberInitializerBody(INamedTypeSymbol type, Func topMatch) - => IsPartialEdit(oldSymbol, newSymbol, topMatch.OldRoot.SyntaxTree, topMatch.NewRoot.SyntaxTree); - - private static bool IsPartialEdit(ISymbol? oldSymbol, ISymbol? newSymbol, SyntaxTree oldSyntaxTree, SyntaxTree newSyntaxTree) + private static bool IsPartialTypeEdit(ISymbol? oldSymbol, ISymbol? newSymbol, SyntaxTree oldSyntaxTree, SyntaxTree newSyntaxTree) { // If any of the partial declarations of the new or the old type are in another document // the edit will need to be merged with other partial edits with matching partial type static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) => reference.SyntaxTree != syntaxTree; - return oldSymbol?.Kind == SymbolKind.NamedType && oldSymbol.DeclaringSyntaxReferences.Length > 1 && oldSymbol.DeclaringSyntaxReferences.Any(IsNotInDocument, oldSyntaxTree) || - newSymbol?.Kind == SymbolKind.NamedType && newSymbol.DeclaringSyntaxReferences.Length > 1 && newSymbol.DeclaringSyntaxReferences.Any(IsNotInDocument, newSyntaxTree); + static bool IsPartialTypeEdit(ISymbol? symbol, SyntaxTree tree) + => symbol is INamedTypeSymbol && + symbol.DeclaringSyntaxReferences.Length > 1 && symbol.DeclaringSyntaxReferences.Any(IsNotInDocument, tree); + + return IsPartialTypeEdit(oldSymbol, oldSyntaxTree) || + IsPartialTypeEdit(newSymbol, newSyntaxTree); } #endregion @@ -5522,7 +5392,7 @@ private void ReportLambdaAndClosureRudeEdits( var oldLambdaSymbol = isNestedFunction ? GetLambdaExpressionSymbol(oldModel, oldLambda, cancellationToken) : null; var newLambdaSymbol = isNestedFunction ? GetLambdaExpressionSymbol(newModel, newLambda, cancellationToken) : null; - var diagnosticContext = CreateDiagnosticContext(diagnostics, oldLambdaSymbol, newLambdaSymbol, newLambda, newModel); + var diagnosticContext = CreateDiagnosticContext(diagnostics, oldLambdaSymbol, newLambdaSymbol, newLambda, newModel, topMatch: null); var oldStateMachineInfo = oldLambdaBody.GetStateMachineInfo(); var newStateMachineInfo = newLambdaBody.GetStateMachineInfo(); @@ -6183,31 +6053,14 @@ private static void BuildIndex(Dictionary index, ImmutableArray } } - /// - /// Returns the declaration of - /// - a property, indexer or event declaration whose accessor is the specified , - /// - 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 bool TryGetAssociatedMemberDeclaration(ISymbol symbol, EditKind editKind, CancellationToken cancellationToken, [NotNullWhen(true)] out SyntaxNode? declaration) - { - var associatedSymbol = symbol switch + internal static ISymbol? GetAssociatedMember(ISymbol symbol) + => symbol switch { - IParameterSymbol or ITypeParameterSymbol => symbol.ContainingSymbol, - IMethodSymbol method when editKind != EditKind.Delete => method.AssociatedSymbol, + IMethodSymbol method => method.AssociatedSymbol, + ITypeParameterSymbol or IParameterSymbol => symbol.ContainingSymbol, _ => null }; - if (associatedSymbol == null) - { - declaration = null; - return false; - } - - declaration = GetSymbolDeclarationSyntax(associatedSymbol, cancellationToken); - return true; - } - /// /// Returns node that represents a declaration of the symbol. /// @@ -6219,6 +6072,9 @@ protected SyntaxNode GetSymbolDeclarationSyntax(ISymbol symbol, CancellationToke protected SyntaxNode? GetSingleSymbolDeclarationSyntax(ISymbol symbol, CancellationToken cancellationToken) => GetSymbolDeclarationSyntax(symbol, selector: refs => refs is [var single] ? single : null, cancellationToken)!; + protected SyntaxNode? GetSymbolDeclarationSyntax(ISymbol symbol, SyntaxTree tree, CancellationToken cancellationToken) + => GetSymbolDeclarationSyntax(symbol, syntaxRefs => syntaxRefs.FirstOrDefault(r => r.SyntaxTree == tree), cancellationToken); + protected abstract ISymbol? GetDeclaredSymbol(SemanticModel model, SyntaxNode declaration, CancellationToken cancellationToken); protected ISymbol GetRequiredDeclaredSymbol(SemanticModel model, SyntaxNode declaration, CancellationToken cancellationToken) @@ -6651,7 +6507,8 @@ private void ReportLambdaSignatureRudeEdits( diagnosticContext.Report(RudeEditKind.ChangingLambdaReturnType, cancellationToken); hasSignatureErrors = true; } - else if (!TypeParametersEquivalent(oldLambdaSymbol.TypeParameters, newLambdaSymbol.TypeParameters, exact: false)) + else if (!TypeParametersEquivalent(oldLambdaSymbol.TypeParameters, newLambdaSymbol.TypeParameters, exact: false) || + !oldLambdaSymbol.TypeParameters.SequenceEqual(newLambdaSymbol.TypeParameters, static (p, q) => p.Name == q.Name)) { diagnosticContext.Report(RudeEditKind.ChangingTypeParameters, cancellationToken); hasSignatureErrors = true; @@ -6748,7 +6605,7 @@ private static void ReportMissingStateMachineAttribute( #endregion - #endregion +#endregion #region Helpers @@ -7057,9 +6914,10 @@ private bool DeleteEditImpliesInsertEdit(ISymbol oldSymbol, ISymbol newSymbol, C // // old: // - // record R() { int P { get; init; } } // insert exists if oldParameter == null + // record R() { int P { get; init; } } // insert exists: oldParameter == null + // record R() { R(int P) {} int P { get; init; } } // insert exists: old constructor is not primary // record R(int P) { int P { get; init; } } // no insert - // record R(int P); // insert exists if oldProperty is synthesized auto-prop + // record R(int P); // insert exists: oldProperty is synthesized auto-prop // // new: // diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index 8212585a4edcf..8e21cfd2517d3 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -110,7 +110,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.ChangingCapturedVariableScope, nameof(FeaturesResources.Changing_the_declaration_scope_of_a_captured_variable_0_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.ChangingLambdaParameters, nameof(FeaturesResources.Changing_the_parameters_of_0_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.ChangingLambdaReturnType, nameof(FeaturesResources.Changing_the_return_type_of_0_requires_restarting_the_application)); - AddRudeEdit(RudeEditKind.ChangingQueryLambdaType, nameof(FeaturesResources.Changing_the_type_of_0_requires_restarting_the_application)); + AddRudeEdit(RudeEditKind.ChangingQueryLambdaType, nameof(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.AccessingCapturedVariableInLambda, nameof(FeaturesResources.Accessing_captured_variable_0_that_hasn_t_been_accessed_before_in_1_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.NotAccessingCapturedVariableInLambda, nameof(FeaturesResources.Ceasing_to_access_captured_variable_0_in_1_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.InsertLambdaWithMultiScopeCapture, nameof(FeaturesResources.Adding_0_that_accesses_captured_variables_1_and_2_declared_in_different_scopes_requires_restarting_the_application)); @@ -146,13 +146,14 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.RenamingNotSupportedByRuntime, nameof(FeaturesResources.Renaming_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.ChangingNonCustomAttribute, nameof(FeaturesResources.Changing_pseudo_custom_attribute_0_of_1_requires_restarting_th_application)); AddRudeEdit(RudeEditKind.ChangingNamespace, nameof(FeaturesResources.Changing_the_containing_namespace_of_0_from_1_to_2_requires_restarting_th_application)); - AddRudeEdit(RudeEditKind.ChangingTypeNotSupportedByRuntime, nameof(FeaturesResources.Changing_the_type_of_0_requires_restarting_the_application)); + AddRudeEdit(RudeEditKind.ChangingSignatureNotSupportedByRuntime, nameof(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.DeleteNotSupportedByRuntime, nameof(FeaturesResources.Deleting_0_requires_restarting_the_application_because_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.UpdatingStateMachineMethodNotSupportedByRuntime, nameof(FeaturesResources.Updating_async_or_iterator_requires_restarting_the_application_because_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.UpdatingGenericNotSupportedByRuntime, nameof(FeaturesResources.Updating_0_within_generic_type_or_method_requires_restarting_the_application_because_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.CapturingPrimaryConstructorParameter, nameof(FeaturesResources.Capturing_primary_constructor_parameter_0_that_hasn_t_been_captured_before_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.NotCapturingPrimaryConstructorParameter, nameof(FeaturesResources.Ceasing_to_capture_primary_constructor_parameter_0_of_1_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.ChangingAttribute, nameof(FeaturesResources.Changing_attribute_0_requires_restarting_the_application)); + AddRudeEdit(RudeEditKind.ChangingNameOrSignatureOfActiveMember, nameof(FeaturesResources.Changing_name_or_signature_of_0_that_contains_an_active_statement_requires_restarting_the_application)); // VB specific AddRudeEdit(RudeEditKind.HandlesClauseUpdate, nameof(FeaturesResources.Updating_the_Handles_clause_of_0_requires_restarting_the_application)); diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index cab54fde03ffa..b67ecd8b20499 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -739,24 +739,21 @@ internal static void MergePartialEdits( // Calculate merged syntax map for each partial type symbol: var symbolKeyComparer = SymbolKey.GetComparer(ignoreCase: false, ignoreAssemblyKeys: true); - var mergedSyntaxMaps = new Dictionary?>(symbolKeyComparer); + var mergedUpdateEditSyntaxMaps = new Dictionary?>(symbolKeyComparer); - var editsByPartialType = edits - .Where(edit => edit.PartialType != null) + var updatesByPartialType = edits + .Where(edit => edit is { PartialType: not null, Kind: SemanticEditKind.Update }) .GroupBy(edit => edit.PartialType!.Value, symbolKeyComparer); - foreach (var partialTypeEdits in editsByPartialType) + foreach (var partialTypeEdits in updatesByPartialType) { - // Either all edits have syntax map or none has. - Debug.Assert( - partialTypeEdits.All(edit => edit.SyntaxMapTree != null && edit.SyntaxMap != null) || - partialTypeEdits.All(edit => edit.SyntaxMapTree == null && edit.SyntaxMap == null)); + Debug.Assert(partialTypeEdits.All(edit => edit.SyntaxMapTree is null == edit.SyntaxMap is null)); Func? mergedSyntaxMap; - if (partialTypeEdits.First().SyntaxMap != null) + if (partialTypeEdits.Any(static e => e.SyntaxMap != null)) { - var newTrees = partialTypeEdits.SelectAsArray(edit => edit.SyntaxMapTree!); - var syntaxMaps = partialTypeEdits.SelectAsArray(edit => edit.SyntaxMap!); + var newTrees = partialTypeEdits.Where(edit => edit.SyntaxMapTree != null).SelectAsArray(edit => edit.SyntaxMapTree!); + var syntaxMaps = partialTypeEdits.Where(edit => edit.SyntaxMap != null).SelectAsArray(edit => edit.SyntaxMap!); mergedSyntaxMap = node => syntaxMaps[newTrees.IndexOf(node.SyntaxTree)](node); } else @@ -764,7 +761,7 @@ internal static void MergePartialEdits( mergedSyntaxMap = null; } - mergedSyntaxMaps.Add(partialTypeEdits.Key, mergedSyntaxMap); + mergedUpdateEditSyntaxMaps.Add(partialTypeEdits.Key, mergedSyntaxMap); } // Deduplicate edits based on their target symbol and use merged syntax map calculated above for a given partial type. @@ -780,7 +777,7 @@ internal static void MergePartialEdits( var (oldSymbol, newSymbol) = resolvedSymbols[i]; if (visitedSymbols.Add(newSymbol ?? oldSymbol!)) { - var syntaxMap = mergedSyntaxMaps[edit.PartialType.Value]; + var syntaxMap = (edit.Kind == SemanticEditKind.Update) ? mergedUpdateEditSyntaxMaps[edit.PartialType.Value] : null; mergedEditsBuilder.Add(new SemanticEdit(edit.Kind, oldSymbol, newSymbol, syntaxMap, preserveLocalVariables: syntaxMap != null)); } } diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index 078ee248465bc..45b018a20960c 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -136,12 +136,13 @@ internal enum RudeEditKind : ushort RenamingNotSupportedByRuntime = 107, ChangingNonCustomAttribute = 108, ChangingNamespace = 109, - ChangingTypeNotSupportedByRuntime = 110, + ChangingSignatureNotSupportedByRuntime = 110, DeleteNotSupportedByRuntime = 111, UpdatingStateMachineMethodNotSupportedByRuntime = 112, UpdatingGenericNotSupportedByRuntime = 113, CapturingPrimaryConstructorParameter = 114, NotCapturingPrimaryConstructorParameter = 115, ChangingAttribute = 116, + ChangingNameOrSignatureOfActiveMember = 117, } } diff --git a/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs b/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs index 61e5966bfb7c9..c760f36e5a59c 100644 --- a/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs +++ b/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs @@ -3,22 +3,48 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using Microsoft.CodeAnalysis.Emit; namespace Microsoft.CodeAnalysis.EditAndContinue { - internal readonly struct SemanticEditInfo( - SemanticEditKind kind, - SymbolKey symbol, - Func? syntaxMap, - SyntaxTree? syntaxMapTree, - SymbolKey? partialType, - SymbolKey? deletedSymbolContainer = null) + internal readonly struct SemanticEditInfo { + public SemanticEditInfo( + SemanticEditKind kind, + SymbolKey symbol, + Func? syntaxMap, + SyntaxTree? syntaxMapTree, + SymbolKey? partialType, + SymbolKey? deletedSymbolContainer) + { + Debug.Assert(kind == SemanticEditKind.Delete || deletedSymbolContainer == null); + Debug.Assert(partialType == null || syntaxMap is null == syntaxMapTree is null); + + Kind = kind; + Symbol = symbol; + SyntaxMap = syntaxMap; + SyntaxMapTree = syntaxMapTree; + PartialType = partialType; + DeletedSymbolContainer = deletedSymbolContainer; + } + + public static SemanticEditInfo CreateInsert(SymbolKey symbol, SymbolKey? partialType) + => new(SemanticEditKind.Insert, symbol, syntaxMap: null, syntaxMapTree: null, partialType, deletedSymbolContainer: null); + + public static SemanticEditInfo CreateUpdate(SymbolKey symbol, Func? syntaxMap, SyntaxTree? syntaxMapTree, SymbolKey? partialType) + => new(SemanticEditKind.Update, symbol, syntaxMap, syntaxMapTree, partialType, deletedSymbolContainer: null); + + public static SemanticEditInfo CreateReplace(SymbolKey symbol, SymbolKey? partialType) + => new(SemanticEditKind.Replace, symbol, syntaxMap: null, syntaxMapTree: null, partialType, deletedSymbolContainer: null); + + public static SemanticEditInfo CreateDelete(SymbolKey symbol, SymbolKey deletedSymbolContainer, SymbolKey? partialType) + => new(SemanticEditKind.Delete, symbol, syntaxMap: null, syntaxMapTree: null, partialType, deletedSymbolContainer); + /// /// or or . /// - public SemanticEditKind Kind { get; } = kind; + public SemanticEditKind Kind { get; } /// /// If is represents the inserted symbol in the new compilation. @@ -29,7 +55,7 @@ internal readonly struct SemanticEditInfo( /// since different semantic edits might have been calculated against different solution snapshot and thus symbols are not directly comparable. /// When the edits are processed we map the to the current compilation. /// - public SymbolKey Symbol { get; } = symbol; + public SymbolKey Symbol { get; } /// /// If is represents the containing symbol in the new compilation. @@ -38,25 +64,24 @@ internal readonly struct SemanticEditInfo( /// since different semantic edits might have been calculated against different solution snapshot and thus symbols are not directly comparable. /// When the edits are processed we map the to the current compilation. /// - public SymbolKey? DeletedSymbolContainer { get; } = deletedSymbolContainer; + public SymbolKey? DeletedSymbolContainer { get; } /// /// The syntax map for nodes in the tree for this edit, which will be merged with other maps from other trees for this type. - /// Only available when is not null. /// - public Func? SyntaxMap { get; } = syntaxMap; + public Func? SyntaxMap { get; } /// /// The tree operates on (the new tree, since the map is mapping from new nodes to old nodes). /// Only available when is not null. /// - public SyntaxTree? SyntaxMapTree { get; } = syntaxMapTree; + public SyntaxTree? SyntaxMapTree { get; } /// /// Specified if the edit needs to be merged with other edits of the same . /// /// If specified, the is either null or incomplete: it only provides mapping of the changed members of a single partial type declaration. /// - public SymbolKey? PartialType { get; } = partialType; + public SymbolKey? PartialType { get; } } } diff --git a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs index 60b37e93f22d9..d5fea92baced4 100644 --- a/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs +++ b/src/Features/Core/Portable/EditAndContinue/Utilities/Extensions.cs @@ -6,8 +6,10 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -151,7 +153,7 @@ public static ManagedHotReloadDiagnostic ToHotReloadDiagnostic(this DiagnosticDa } public static bool IsSynthesized(this ISymbol symbol) - => symbol.IsImplicitlyDeclared || symbol.IsSynthesizedAutoProperty(); + => symbol.IsImplicitlyDeclared || symbol.IsSynthesizedAutoProperty() || symbol.IsSynthesizedParameter(); public static bool IsSynthesizedAutoProperty(this IPropertySymbol property) => property is { GetMethod.IsImplicitlyDeclared: true, SetMethod.IsImplicitlyDeclared: true }; @@ -159,6 +161,25 @@ public static bool IsSynthesizedAutoProperty(this IPropertySymbol property) public static bool IsSynthesizedAutoProperty(this ISymbol symbol) => symbol is IPropertySymbol property && property.IsSynthesizedAutoProperty(); + public static bool IsSynthesizedParameter(this ISymbol symbol) + => symbol is IParameterSymbol parameter && parameter.IsSynthesizedParameter(); + + /// + /// True if the parameter is synthesized based on some other symbol (origin). + /// In some cases of parameters of synthezied methods might be false. + /// The parameter syntax in these cases is associated with multiple symbols. + /// We pick one that is considered the origin and the others are considered synthesized based on it. + /// + /// 1) Parameter of a record deconstructor + /// Considered synthesized since the primary parameter syntax represents the parameter of the primary constructor. + /// The deconstructor is synthesized based on the primary constructor. + /// 2) Parameter of an Invoke method of a delegate type + /// The Invoke method is synthesized but its parameters represent the parameters of the delegate. + /// The parameters of BeginInvoke and EndInvoke are synthesized based on the Invoke method parameters. + /// + public static bool IsSynthesizedParameter(this IParameterSymbol parameter) + => parameter.IsImplicitlyDeclared || parameter.ContainingSymbol.IsSynthesized() && parameter.ContainingSymbol != parameter.ContainingType.DelegateInvokeMethod; + public static bool IsAutoProperty(this ISymbol symbol) => symbol is IPropertySymbol property && IsAutoProperty(property); @@ -201,6 +222,9 @@ private static bool ParsePrimaryParameterBackingFieldName(string fieldName, [Not /// 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); + static (symbol, constructor) => symbol is IMethodSymbol method && HasDeconstructorSignature(method, constructor), constructor)?.PartialAsImplementation(); + + public static ISymbol PartialAsImplementation(this ISymbol symbol) + => symbol is IMethodSymbol { PartialImplementationPart: { } impl } ? impl : symbol; } } diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 6fb849621b0e1..93b6ebba9205c 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -384,6 +384,9 @@ Changing attribute '{0}' requires restarting the application. + + Changing attribute '{0}' requires restarting the application. + Capturing primary constructor parameter '{0}' that hasn't been capture before requires restarting the application. @@ -399,8 +402,8 @@ Changing the return type of {0} requires restarting the application. - - Changing the type of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application. Changing the declaration scope of a captured variable '{0}' requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index b606850d61d42..53db3e7e4eb60 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -445,6 +445,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Změna omezení pro {0} vyžaduje restartování aplikace. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. Změna typů parametrů u {0} vyžaduje restartování aplikace. @@ -475,9 +480,9 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Změna návratového typu {0} vyžaduje restartování aplikace. - - Changing the type of {0} requires restarting the application. - Změna typu {0} vyžaduje restartování aplikace. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 747bbfc64f423..faf0fd9754279 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -445,6 +445,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Das Ändern der Einschränkungen von {0} erfordert einen Neustart der Anwendung. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. Das Ändern von Parametertypen von {0} erfordert einen Neustart der Anwendung. @@ -475,9 +480,9 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Das Ändern des Rückgabetyps von {0} erfordert einen Neustart der Anwendung. - - Changing the type of {0} requires restarting the application. - Das Ändern des Typs von {0} erfordert einen Neustart der Anwendung. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 00774b290e076..3bcd4b4056275 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -445,6 +445,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Para cambiar las restricciones de {0} es necesario reiniciar la aplicación. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. Para cambiar los tipos de parámetros de {0}se requiere reiniciar la aplicación. @@ -475,9 +480,9 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Para cambiar el tipo de valor devuelto de {0} es necesario reiniciar la aplicación. - - Changing the type of {0} requires restarting the application. - Al cambiar el tipo de {0} es necesario reiniciar la aplicación. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index f7b6d32d7d2b5..2874def64a5a0 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -445,6 +445,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai La modification des contraintes de {0} requiert le redémarrage de l’application. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. La modification des types de paramètres de {0} requiert le redémarrage de l’application. @@ -475,9 +480,9 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai La modification du type de retour de {0} requiert le redémarrage de l’application. - - Changing the type of {0} requires restarting the application. - La modification du type de {0} requiert le redémarrage de l’application. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 8334f048b9f17..6bbe84b08ebd4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -445,6 +445,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Se si modificano i vincoli di {0}, è necessario riavviare l'applicazione. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. Se si modificano i tipi di parametro di {0}, è necessario riavviare l'applicazione. @@ -475,9 +480,9 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Se si modifica il tipo restituito di {0}, è necessario riavviare l'applicazione. - - Changing the type of {0} requires restarting the application. - Se si modifica il tipo di {0}, è necessario riavviare l'applicazione. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index ae9245e6dd018..9f6127b408432 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -445,6 +445,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0} の制約を変更するには、アプリケーションを再起動する必要があります。 + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. {0} のパラメーターの種類を変更するには、アプリケーションを再起動する必要があります。 @@ -475,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0} の戻り値の型を変更するには、アプリケーションを再起動する必要があります。 - - Changing the type of {0} requires restarting the application. - {0} の型を変更するには、アプリケーションを再起動する必要があります。 + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 41fc5de14bb99..0ce1e140bf43c 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -445,6 +445,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0} 제약 조건을 변경하려면 애플리케이션을 다시 시작해야 합니다. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. {0}의 매개 변수 유형을 변경하려면 애플리케이션을 다시 시작해야 합니다. @@ -475,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0}의 반환 형식을 변경하려면 애플리케이션을 다시 시작해야 합니다. - - Changing the type of {0} requires restarting the application. - {0} 유형을 변경하려면 응용 프로그램을 다시 시작해야 합니다. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index cc362b11798e0..d6362cdc97c7b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -445,6 +445,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Zmiana ograniczeń elementów {0} wymaga ponownego uruchomienia aplikacji. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. Zmiana typów parametrów elementów {0} wymaga ponownego uruchomienia aplikacji. @@ -475,9 +480,9 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Zmiana zwracanego typu elementu {0} wymaga ponownego uruchomienia aplikacji. - - Changing the type of {0} requires restarting the application. - Zmienianie typu elementu {0} wymaga ponownego uruchomienia aplikacji. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 75a6bbe659c5d..fa49917e6fd79 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -445,6 +445,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Alterar as restrições de {0} requer o reinício do aplicativo. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. A alteração dos tipos de parâmetro de {0} requer o reinício do aplicativo. @@ -475,9 +480,9 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Alterar o tipo de retorno de {0} requer o reinício do aplicativo. - - Changing the type of {0} requires restarting the application. - Alterar o tipo de {0} requer a reinicialização do aplicativo. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 3ae7cf4374fd1..20455fdf042cc 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -445,6 +445,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Для изменения ограничений {0} требуется перезапустить приложение. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. Для изменения типов параметров {0} требуется перезапустить приложение. @@ -475,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Для изменения типа возвращаемого значения {0} требуется перезапустить приложение. - - Changing the type of {0} requires restarting the application. - Для изменения типа {0} требуется перезапустить приложение. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 84972c35f2789..35f6bad0ea934 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -445,6 +445,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be {0} öğesinin kısıtlamalarının değiştirilmesi, uygulamanın yeniden başlatılmasını gerektirir. + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. {0} öğesinin parametre türlerinin değiştirilmesi, uygulamanın yeniden başlatılmasını gerektirir. @@ -475,9 +480,9 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be {0} öğesinin dönüş türünün değiştirilmesi, uygulamanın yeniden başlatılmasını gerektirir. - - Changing the type of {0} requires restarting the application. - {0} türünün değiştirilmesi, uygulamanın yeniden başlatılmasını gerektirir. + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 66487eef7d474..63bf59cbb6145 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -445,6 +445,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 更改 {0} 的约束需要重启应用程序。 + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. 更改 {0} 的参数类型需要重启应用程序。 @@ -475,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 更改 {0} 的返回类型需要重启应用程序。 - - Changing the type of {0} requires restarting the application. - 更改 {0} 的类型需要重新启动应用程序。 + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 58f9536675948..aa0a6de8cfaac 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -445,6 +445,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 變更 {0} 的限制式需要重新啟動應用程式。 + + Changing attribute '{0}' requires restarting the application. + Changing attribute '{0}' requires restarting the application. + + Changing parameter types of {0} requires restarting the application. 變更 {0} 的參數類型需要重新啟動應用程式。 @@ -475,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 變更 {0} 的傳回型別需要重新啟動應用程式。 - - Changing the type of {0} requires restarting the application. - 變更 {0} 的類型需要重新啟動應用程式。 + + Changing the signature of {0} requires restarting the application. + Changing the signature of {0} requires restarting the application. diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index c775b2662335d..61de28a1ae1fc 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -11,6 +11,7 @@ Imports Microsoft.CodeAnalysis.EditAndContinue Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.PooledObjects +Imports Microsoft.CodeAnalysis.Shared.Collections Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -698,75 +699,174 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue DirectCast(syntaxRefs.Single().GetSyntax(), TypeStatementSyntax).Modifiers.Any(SyntaxKind.PartialKeyword) End Function - Protected Overrides Function GetSymbolEdits( + Protected Overrides Function GetEditedSymbols( editKind As EditKind, oldNode As SyntaxNode, newNode As SyntaxNode, oldModel As SemanticModel, newModel As SemanticModel, + cancellationToken As CancellationToken) As OneOrMany(Of (oldSymbol As ISymbol, newSymbol As ISymbol)) + + Dim oldSymbols = OneOrMany(Of ISymbol).Empty + Dim newSymbols = OneOrMany(Of ISymbol).Empty + + If oldNode IsNot Nothing AndAlso Not TryGetSyntaxNodesForEdit(editKind, oldNode, oldModel, oldSymbols, cancellationToken) OrElse + newNode IsNot Nothing AndAlso Not TryGetSyntaxNodesForEdit(editKind, newNode, newModel, newSymbols, cancellationToken) Then + Return OneOrMany(Of (ISymbol, ISymbol)).Empty + End If + + Debug.Assert(Not oldSymbols.IsEmpty OrElse Not newSymbols.IsEmpty) + + If oldSymbols.Count <= 1 AndAlso newSymbols.Count <= 1 Then + Return OneOrMany.Create((oldSymbols.FirstOrDefault(), newSymbols.FirstOrDefault())) + End If + + ' This only occurs when field identifiers are deleted/inserted/reordered from/to/within their variable declarator list, + ' or their shared initializer is updated. The particular inserted and deleted fields will be represented by separate edits, + ' but the AsNew clause of the declarator may have been updated as well, which needs to update the remaining (matching) fields. + Return OneOrMany.Create(PairSymbols(oldSymbols, newSymbols).ToImmutableArray()) + End Function + + Private Shared Iterator Function PairSymbols( + oldSymbols As OneOrMany(Of ISymbol), + newSymbols As OneOrMany(Of ISymbol)) As IEnumerable(Of (ISymbol, ISymbol)) + + For Each oldSymbol In oldSymbols + Dim newSymbol = newSymbols.FirstOrDefault(Function(s, o) CaseInsensitiveComparison.Equals(s.Name, o.Name), oldSymbol) + If newSymbol IsNot Nothing Then + Yield (oldSymbol, newSymbol) + End If + Next + End Function + + Protected Overrides Sub AddSymbolEdits( + ByRef result As TemporaryArray(Of (ISymbol, ISymbol, EditKind)), + editKind As EditKind, + oldNode As SyntaxNode, + oldSymbol As ISymbol, + newNode As SyntaxNode, + newSymbol As ISymbol, + oldModel As SemanticModel, + newModel As SemanticModel, + topMatch As Match(Of SyntaxNode), editMap As IReadOnlyDictionary(Of SyntaxNode, EditKind), - cancellationToken As CancellationToken) As OneOrMany(Of (oldSymbol As ISymbol, newSymbol As ISymbol, editKind As EditKind)) + symbolCache As SymbolInfoCache, + cancellationToken As CancellationToken) - Dim oldSymbols As OneOrMany(Of ISymbol) = Nothing - Dim newSymbols As OneOrMany(Of ISymbol) = Nothing + Debug.Assert(oldSymbol IsNot Nothing OrElse newSymbol IsNot Nothing) - Select Case editKind - Case EditKind.Reorder - If TryCast(oldNode, ParameterSyntax) Is Nothing OrElse TryCast(newNode, ParameterSyntax) Is Nothing Then - ' Other than parameters, we don't do any semantic checks for reordering - ' And we don't need to report them to the compiler either. - ' Consider: Currently Symbol ordering changes are Not reflected in metadata (Reflection will report original order). - - ' Consider Reordering of fields Is Not allowed since it changes the layout of the type. - ' This ordering should however Not matter unless the type has explicit layout so we might want to allow it. - ' We do Not check changes to the order if they occur across multiple documents (the containing type Is partial). - Debug.Assert(Not IsDeclarationWithInitializer(oldNode) AndAlso Not IsDeclarationWithInitializer(newNode)) - Return OneOrMany(Of (ISymbol, ISymbol, EditKind)).Empty - End If + If oldNode.IsKind(SyntaxKind.Parameter, SyntaxKind.TypeParameter) OrElse + oldNode.IsKind(SyntaxKind.ModifiedIdentifier) AndAlso oldNode.IsParentKind(SyntaxKind.Parameter) OrElse + newNode.IsKind(SyntaxKind.Parameter, SyntaxKind.TypeParameter) OrElse + newNode.IsKind(SyntaxKind.ModifiedIdentifier) AndAlso newNode.IsParentKind(SyntaxKind.Parameter) Then - If Not TryGetSyntaxNodesForEdit(editKind, oldNode, oldModel, oldSymbols, cancellationToken) OrElse - Not TryGetSyntaxNodesForEdit(editKind, newNode, newModel, newSymbols, cancellationToken) Then - Return OneOrMany(Of (ISymbol, ISymbol, EditKind)).Empty - End If + ' parameter list, member, Or type declaration + Dim oldContainingMemberOrType = GetParameterContainingMemberOrType(oldNode, newNode, oldModel, topMatch.ReverseMatches, cancellationToken) + Dim newContainingMemberOrType = GetParameterContainingMemberOrType(newNode, oldNode, newModel, topMatch.Matches, cancellationToken) - Return OneOrMany.Create((oldSymbols(0).ContainingSymbol, newSymbols(0).ContainingSymbol, EditKind.Update)) + Dim matchingNewContainingMemberOrType = GetSemanticallyMatchingNewSymbol(oldContainingMemberOrType, newContainingMemberOrType, newModel, symbolCache, cancellationToken) - Case EditKind.Delete - If Not TryGetSyntaxNodesForEdit(editKind, oldNode, oldModel, oldSymbols, cancellationToken) Then - Return OneOrMany(Of (ISymbol, ISymbol, EditKind)).Empty + ' Any change to a constraint should be analyzed as an update of the type parameter + Dim isTypeConstraint = TypeOf oldNode Is TypeParameterConstraintClauseSyntax OrElse + TypeOf newNode Is TypeParameterConstraintClauseSyntax + + ' If the signature of a property changed or its parameter has been renamed we need to update all its accessors + Dim oldPropertySymbol = TryCast(oldContainingMemberOrType, IPropertySymbol) + Dim newPropertySymbol = TryCast(newContainingMemberOrType, IPropertySymbol) + + If oldPropertySymbol IsNot Nothing AndAlso + newPropertySymbol IsNot Nothing AndAlso + (IsMemberOrDelegateReplaced(oldPropertySymbol, newPropertySymbol) OrElse + oldSymbol IsNot Nothing AndAlso newSymbol IsNot Nothing AndAlso oldSymbol.Name <> newSymbol.Name) Then + + AddMemberUpdate(result, oldPropertySymbol.GetMethod, newPropertySymbol.GetMethod, matchingNewContainingMemberOrType) + AddMemberUpdate(result, oldPropertySymbol.SetMethod, newPropertySymbol.SetMethod, matchingNewContainingMemberOrType) + End If + + AddMemberUpdate(result, oldContainingMemberOrType, newContainingMemberOrType, matchingNewContainingMemberOrType) + + If matchingNewContainingMemberOrType IsNot Nothing Then + ' Map parameter to the corresponding semantically matching member. + ' Since the signature of the member matches we can direcly map by parameter ordinal. + If oldSymbol.Kind = SymbolKind.Parameter Then + newSymbol = matchingNewContainingMemberOrType.GetParameters()(DirectCast(oldSymbol, IParameterSymbol).Ordinal) + ElseIf oldSymbol.Kind = SymbolKind.TypeParameter Then + newSymbol = matchingNewContainingMemberOrType.GetTypeParameters()(DirectCast(oldSymbol, ITypeParameterSymbol).Ordinal) End If + End If - Return oldSymbols.Select(Function(s) New ValueTuple(Of ISymbol, ISymbol, EditKind)(s, Nothing, editKind)) + result.Add((oldSymbol, newSymbol, If(isTypeConstraint, EditKind.Update, editKind))) - Case EditKind.Insert - If Not TryGetSyntaxNodesForEdit(editKind, newNode, newModel, newSymbols, cancellationToken) Then - Return OneOrMany(Of (ISymbol, ISymbol, EditKind)).Empty + Return + End If + + Select Case editKind + Case EditKind.Reorder + If oldSymbol Is Nothing OrElse newSymbol Is Nothing Then + Return End If - Return newSymbols.Select(Function(s) New ValueTuple(Of ISymbol, ISymbol, EditKind)(Nothing, s, editKind)) + result.Add((oldSymbol.ContainingSymbol, newSymbol.ContainingSymbol, EditKind.Update)) + + Case EditKind.Delete + result.Add((oldSymbol, Nothing, editKind)) + + Case EditKind.Insert + result.Add((Nothing, newSymbol, editKind)) Case EditKind.Update - If Not TryGetSyntaxNodesForEdit(editKind, oldNode, oldModel, oldSymbols, cancellationToken) OrElse - Not TryGetSyntaxNodesForEdit(editKind, newNode, newModel, newSymbols, cancellationToken) Then - Return OneOrMany(Of (ISymbol, ISymbol, EditKind)).Empty - End If + ' Updates of a property/indexer/event node might affect its accessors. + ' Return all affected symbols for these updates so that the changes in the accessor bodies get analyzed. + + Dim oldPropertySymbol = TryCast(oldSymbol, IPropertySymbol) + Dim newPropertySymbol = TryCast(newSymbol, IPropertySymbol) + If oldPropertySymbol IsNot Nothing AndAlso newPropertySymbol IsNot Nothing Then + ' Note: a signature change does not affect the property itself. + result.Add((oldPropertySymbol, newPropertySymbol, EditKind.Update)) + + If oldPropertySymbol.GetMethod IsNot Nothing OrElse newPropertySymbol.GetMethod IsNot Nothing Then + If IsMemberOrDelegateReplaced(oldPropertySymbol, newPropertySymbol) Then + result.Add((oldPropertySymbol.GetMethod, newPropertySymbol.GetMethod, editKind)) + End If + End If - If oldSymbols.Count = 1 AndAlso newSymbols.Count = 1 Then - Return OneOrMany.Create((oldSymbols(0), newSymbols(0), editKind)) + If oldPropertySymbol.SetMethod IsNot Nothing OrElse newPropertySymbol.SetMethod IsNot Nothing Then + If IsMemberOrDelegateReplaced(oldPropertySymbol, newPropertySymbol) Then + result.Add((oldPropertySymbol.SetMethod, newPropertySymbol.SetMethod, editKind)) + End If + End If + + Return End If - ' This only occurs when field identifiers are deleted/inserted/reordered from/to/within their variable declarator list, - ' or their shared initializer is updated. The particular inserted and deleted fields will be represented by separate edits, - ' but the AsNew clause of the declarator may have been updated as well, which needs to update the remaining (matching) fields. - Dim builder = ArrayBuilder(Of (ISymbol, ISymbol, EditKind)).GetInstance() - For Each oldSymbol In oldSymbols - Dim newSymbol = newSymbols.FirstOrDefault(Function(s, o) CaseInsensitiveComparison.Equals(s.Name, o.Name), oldSymbol) - If newSymbol IsNot Nothing Then - builder.Add((oldSymbol, newSymbol, editKind)) + Dim oldEventSymbol = TryCast(oldSymbol, IEventSymbol) + Dim newEventSymbol = TryCast(newSymbol, IEventSymbol) + If oldEventSymbol IsNot Nothing AndAlso newEventSymbol IsNot Nothing Then + result.Add((oldEventSymbol, newEventSymbol, EditKind.Update)) + + If oldEventSymbol.AddMethod IsNot Nothing OrElse newEventSymbol.AddMethod IsNot Nothing Then + If IsMemberOrDelegateReplaced(oldEventSymbol, newEventSymbol) Then + result.Add((oldEventSymbol.AddMethod, newEventSymbol.AddMethod, editKind)) + End If + End If + + If oldEventSymbol.RemoveMethod IsNot Nothing OrElse newEventSymbol.RemoveMethod IsNot Nothing Then + If IsMemberOrDelegateReplaced(oldEventSymbol, newEventSymbol) Then + result.Add((oldEventSymbol.RemoveMethod, newEventSymbol.RemoveMethod, editKind)) + End If + End If + + If oldEventSymbol.RaiseMethod IsNot Nothing OrElse newEventSymbol.RaiseMethod IsNot Nothing Then + ' change in event type does not affect Raise method, but rename does + If oldEventSymbol.Name <> newEventSymbol.Name Then + result.Add((oldEventSymbol.RaiseMethod, newEventSymbol.RaiseMethod, editKind)) + End If End If - Next - Return OneOrMany.Create(builder.ToImmutableAndFree()) + Return + End If + + result.Add((oldSymbol, newSymbol, editKind)) Case EditKind.Move Contract.ThrowIfNull(oldNode) @@ -775,16 +875,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Debug.Assert(oldNode.RawKind = newNode.RawKind) If Not IsTypeDeclaration(oldNode) Then - Return OneOrMany(Of (ISymbol, ISymbol, EditKind)).Empty + Return End If - Dim oldSymbol = GetDeclaredSymbol(oldModel, oldNode, cancellationToken) - Dim newSymbol = GetDeclaredSymbol(newModel, newNode, cancellationToken) - Return OneOrMany.Create((oldSymbol, newSymbol, editKind)) - End Select + result.Add((oldSymbol, newSymbol, editKind)) - Throw ExceptionUtilities.UnexpectedValue(editKind) - End Function + Case Else + Throw ExceptionUtilities.UnexpectedValue(editKind) + End Select + End Sub Private Function TryGetSyntaxNodesForEdit( editKind As EditKind, @@ -832,17 +931,31 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return False End If - ' Ignore partial method definition parts. - ' Partial method that does not have implementation part is not emitted to metadata. - ' Partial method without a definition part is a compilation error. - If symbol.Kind = SymbolKind.Method AndAlso CType(symbol, IMethodSymbol).IsPartialDefinition Then - Return False - End If - symbols = OneOrMany.Create(symbol) Return True End Function + Private Function GetParameterContainingMemberOrType(node As SyntaxNode, otherNode As SyntaxNode, model As SemanticModel, fromOtherMap As IReadOnlyDictionary(Of SyntaxNode, SyntaxNode), cancellationToken As CancellationToken) As ISymbol + Debug.Assert(node Is Nothing OrElse + node.IsKind(SyntaxKind.Parameter, SyntaxKind.TypeParameter) OrElse + node.IsKind(SyntaxKind.ModifiedIdentifier) AndAlso node.IsParentKind(SyntaxKind.Parameter) OrElse + TypeOf node Is TypeParameterConstraintClauseSyntax) + + ' parameter list, member, or type declaration + Dim declaration As SyntaxNode = Nothing + If node Is Nothing Then + fromOtherMap.TryGetValue(GetContainingDeclaration(otherNode), declaration) + Else + declaration = GetContainingDeclaration(node) + End If + + Return If(declaration IsNot Nothing, GetDeclaredSymbol(model, declaration, cancellationToken), Nothing) + End Function + + Private Shared Function GetContainingDeclaration(node As SyntaxNode) As SyntaxNode + Return If(node.IsKind(SyntaxKind.ModifiedIdentifier), node.Parent.Parent.Parent, node.Parent.Parent) + End Function + Friend Overrides ReadOnly Property IsLambda As Func(Of SyntaxNode, Boolean) Get Return AddressOf LambdaUtilities.IsLambda @@ -895,31 +1008,31 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Case SyntaxKind.AggregateClause Dim oldInfo = oldModel.GetAggregateClauseSymbolInfo(DirectCast(oldNode, AggregateClauseSyntax), cancellationToken) Dim newInfo = newModel.GetAggregateClauseSymbolInfo(DirectCast(newNode, AggregateClauseSyntax), cancellationToken) - Return MemberSignaturesEquivalent(oldInfo.Select1.Symbol, newInfo.Select1.Symbol) AndAlso - MemberSignaturesEquivalent(oldInfo.Select2.Symbol, newInfo.Select2.Symbol) + Return MemberOrDelegateSignaturesEquivalent(oldInfo.Select1.Symbol, newInfo.Select1.Symbol) AndAlso + MemberOrDelegateSignaturesEquivalent(oldInfo.Select2.Symbol, newInfo.Select2.Symbol) Case SyntaxKind.CollectionRangeVariable Dim oldInfo = oldModel.GetCollectionRangeVariableSymbolInfo(DirectCast(oldNode, CollectionRangeVariableSyntax), cancellationToken) Dim newInfo = newModel.GetCollectionRangeVariableSymbolInfo(DirectCast(newNode, CollectionRangeVariableSyntax), cancellationToken) - Return MemberSignaturesEquivalent(oldInfo.AsClauseConversion.Symbol, newInfo.AsClauseConversion.Symbol) AndAlso - MemberSignaturesEquivalent(oldInfo.SelectMany.Symbol, newInfo.SelectMany.Symbol) AndAlso - MemberSignaturesEquivalent(oldInfo.ToQueryableCollectionConversion.Symbol, newInfo.ToQueryableCollectionConversion.Symbol) + Return MemberOrDelegateSignaturesEquivalent(oldInfo.AsClauseConversion.Symbol, newInfo.AsClauseConversion.Symbol) AndAlso + MemberOrDelegateSignaturesEquivalent(oldInfo.SelectMany.Symbol, newInfo.SelectMany.Symbol) AndAlso + MemberOrDelegateSignaturesEquivalent(oldInfo.ToQueryableCollectionConversion.Symbol, newInfo.ToQueryableCollectionConversion.Symbol) Case SyntaxKind.FunctionAggregation Dim oldInfo = oldModel.GetSymbolInfo(DirectCast(oldNode, FunctionAggregationSyntax), cancellationToken) Dim newInfo = newModel.GetSymbolInfo(DirectCast(newNode, FunctionAggregationSyntax), cancellationToken) - Return MemberSignaturesEquivalent(oldInfo.Symbol, newInfo.Symbol) + Return MemberOrDelegateSignaturesEquivalent(oldInfo.Symbol, newInfo.Symbol) Case SyntaxKind.ExpressionRangeVariable Dim oldInfo = oldModel.GetSymbolInfo(DirectCast(oldNode, ExpressionRangeVariableSyntax), cancellationToken) Dim newInfo = newModel.GetSymbolInfo(DirectCast(newNode, ExpressionRangeVariableSyntax), cancellationToken) - Return MemberSignaturesEquivalent(oldInfo.Symbol, newInfo.Symbol) + Return MemberOrDelegateSignaturesEquivalent(oldInfo.Symbol, newInfo.Symbol) Case SyntaxKind.AscendingOrdering, SyntaxKind.DescendingOrdering Dim oldInfo = oldModel.GetSymbolInfo(DirectCast(oldNode, OrderingSyntax), cancellationToken) Dim newInfo = newModel.GetSymbolInfo(DirectCast(newNode, OrderingSyntax), cancellationToken) - Return MemberSignaturesEquivalent(oldInfo.Symbol, newInfo.Symbol) + Return MemberOrDelegateSignaturesEquivalent(oldInfo.Symbol, newInfo.Symbol) Case SyntaxKind.FromClause, SyntaxKind.WhereClause, @@ -933,7 +1046,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue SyntaxKind.SelectClause Dim oldInfo = oldModel.GetSymbolInfo(DirectCast(oldNode, QueryClauseSyntax), cancellationToken) Dim newInfo = newModel.GetSymbolInfo(DirectCast(newNode, QueryClauseSyntax), cancellationToken) - Return MemberSignaturesEquivalent(oldInfo.Symbol, newInfo.Symbol) + Return MemberOrDelegateSignaturesEquivalent(oldInfo.Symbol, newInfo.Symbol) Case Else Return True @@ -968,7 +1081,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Friend Shared Function TryGetDiagnosticSpanImpl(kind As SyntaxKind, node As SyntaxNode, editKind As EditKind) As TextSpan? Select Case kind Case SyntaxKind.CompilationUnit - Return New TextSpan() + Dim unit = DirectCast(node, CompilationUnitSyntax) + + Dim globalNode = unit.ChildNodes().FirstOrDefault() + If globalNode Is Nothing Then + Return Nothing + End If + + Return GetDiagnosticSpan(globalNode, editKind) Case SyntaxKind.OptionStatement, SyntaxKind.ImportsStatement diff --git a/src/Workspaces/CoreTest/SymbolKeyTests.cs b/src/Workspaces/CoreTest/SymbolKeyTests.cs index 4562466fa5ce5..29fc832ce6c92 100644 --- a/src/Workspaces/CoreTest/SymbolKeyTests.cs +++ b/src/Workspaces/CoreTest/SymbolKeyTests.cs @@ -375,6 +375,54 @@ public void M(ref int p) { } TestRoundTrip(GetDeclaredSymbols(compilation).OfType().SelectMany(ms => ms.Parameters), compilation); } + [Fact] + public void TestParameterRename() + { + var source1 = @" +public class C +{ + public void M(int a, int b, int c) { } +} +"; + var source2 = @" +public class C +{ + public void M(int a, int x, int c) { } +} +"; + var compilation1 = GetCompilation(source1, LanguageNames.CSharp); + var compilation2 = GetCompilation(source2, LanguageNames.CSharp); + + var b = ((IMethodSymbol)compilation1.GlobalNamespace.GetTypeMembers("C").Single().GetMembers("M").Single()).Parameters[1]; + var key = SymbolKey.CreateString(b); + var resolved = SymbolKey.ResolveString(key, compilation2).Symbol; + Assert.Equal("x", resolved?.Name); + } + + [Fact] + public void TestParameterReorder() + { + var source1 = @" +public class C +{ + public void M(int a, int b, int c) { } +} +"; + var source2 = @" +public class C +{ + public void M(int b, int a, int c) { } +} +"; + var compilation1 = GetCompilation(source1, LanguageNames.CSharp); + var compilation2 = GetCompilation(source2, LanguageNames.CSharp); + + var b = ((IMethodSymbol)compilation1.GlobalNamespace.GetTypeMembers("C").Single().GetMembers("M").Single()).Parameters[1]; + var key = SymbolKey.CreateString(b); + var resolved = SymbolKey.ResolveString(key, compilation2).Symbol; + Assert.Equal("b", resolved?.Name); + } + [Fact] public void TestTypeParameters() { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.ParameterSymbolKey.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.ParameterSymbolKey.cs index 44908cf83aae4..8b27f97a9174d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.ParameterSymbolKey.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.ParameterSymbolKey.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Diagnostics; namespace Microsoft.CodeAnalysis { @@ -15,6 +16,7 @@ private sealed class ParameterSymbolKey : AbstractSymbolKey public sealed override void Create(IParameterSymbol symbol, SymbolKeyWriter visitor) { visitor.WriteString(symbol.MetadataName); + visitor.WriteInteger(symbol.Ordinal); visitor.WriteSymbolKey(symbol.ContainingSymbol); } @@ -22,6 +24,7 @@ protected sealed override SymbolKeyResolution Resolve( SymbolKeyReader reader, IParameterSymbol? contextualSymbol, out string? failureReason) { var metadataName = reader.ReadRequiredString(); + var ordinal = reader.ReadInteger(); // Parameters are owned by members, and members are never resolved in a way where we have contextual // types to guide how the outer parts of the member may resolve. We can use contextual typing for the @@ -41,10 +44,10 @@ protected sealed override SymbolKeyResolution Resolve( switch (container) { case IMethodSymbol method: - Resolve(result, reader, metadataName, method.Parameters); + Resolve(result, reader, metadataName, ordinal, method.Parameters); break; case IPropertySymbol property: - Resolve(result, reader, metadataName, property.Parameters); + Resolve(result, reader, metadataName, ordinal, property.Parameters); break; case IEventSymbol eventSymbol: // Parameters can be owned by events in VB. i.e. it's legal in VB to have: @@ -63,7 +66,7 @@ protected sealed override SymbolKeyResolution Resolve( if (delegateInvoke != null) { - Resolve(result, reader, metadataName, delegateInvoke.Parameters); + Resolve(result, reader, metadataName, ordinal, delegateInvoke.Parameters); } break; @@ -75,15 +78,25 @@ protected sealed override SymbolKeyResolution Resolve( private static void Resolve( PooledArrayBuilder result, SymbolKeyReader reader, - string metadataName, ImmutableArray parameters) + string metadataName, int ordinal, ImmutableArray parameters) { + // Try to resolve to a parameter with matching name first: + var hasMatchingName = false; foreach (var parameter in parameters) { if (SymbolKey.Equals(reader.Compilation, parameter.MetadataName, metadataName)) { result.AddIfNotNull(parameter); + hasMatchingName = true; } } + + // The signatures of the containing member matches, so we can fall back to using parameter ordinal. + // The fallback handles the case when the parameter has been renamed. + if (!hasMatchingName) + { + result.AddIfNotNull(parameters[ordinal]); + } } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.cs index 8c9156344405c..5976bc5a8f75c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.cs @@ -115,7 +115,7 @@ internal partial struct SymbolKey(string data) : IEquatable /// out a SymbolKey from a previous version of Roslyn and then attempt to use it in a /// newer version where the encoding has changed. /// - internal const int FormatVersion = 5; + internal const int FormatVersion = 6; [DataMember(Order = 0)] private readonly string _symbolKeyData = data ?? throw new ArgumentNullException(nameof(data)); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IDictionaryExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IDictionaryExtensions.cs index a7b96572e4896..66b57c0faa855 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IDictionaryExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/IDictionaryExtensions.cs @@ -25,6 +25,18 @@ public static V GetOrAdd(this IDictionary dictionary, K key, Func(this IDictionary dictionary, K key, Func function, TArg arg) + where K : notnull + { + if (!dictionary.TryGetValue(key, out var value)) + { + value = function(key, arg); + dictionary.Add(key, value); + } + + return value; + } + public static TValue? GetValueOrDefault(this IDictionary dictionary, TKey key) where TKey : notnull { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.SignatureTypeSymbolEquivalenceComparer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.SignatureTypeSymbolEquivalenceComparer.cs index cfab690699e04..fc37939285cda 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.SignatureTypeSymbolEquivalenceComparer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.SignatureTypeSymbolEquivalenceComparer.cs @@ -14,10 +14,10 @@ public bool Equals(ITypeSymbol? x, ITypeSymbol? y) => this.Equals(x, y, null); public bool Equals(ITypeSymbol? x, ITypeSymbol? y, Dictionary? equivalentTypesWithDifferingAssemblies) - => symbolEquivalenceComparer.GetEquivalenceVisitor(compareMethodTypeParametersByIndex: true, objectAndDynamicCompareEqually: true).AreEquivalent(x, y, equivalentTypesWithDifferingAssemblies); + => symbolEquivalenceComparer.GetEquivalenceVisitor(compareMethodTypeParametersByIndex: true, symbolEquivalenceComparer._objectAndDynamicCompareEqually).AreEquivalent(x, y, equivalentTypesWithDifferingAssemblies); public int GetHashCode(ITypeSymbol? x) - => symbolEquivalenceComparer.GetGetHashCodeVisitor(compareMethodTypeParametersByIndex: true, objectAndDynamicCompareEqually: true).GetHashCode(x, currentHash: 0); + => symbolEquivalenceComparer.GetGetHashCodeVisitor(compareMethodTypeParametersByIndex: true, symbolEquivalenceComparer._objectAndDynamicCompareEqually).GetHashCode(x, currentHash: 0); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.cs index bb44f55ca85ff..558b8557bfc7b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolEquivalenceComparer.cs @@ -39,13 +39,14 @@ internal partial class SymbolEquivalenceComparer : private readonly ImmutableArray _equivalenceVisitors; private readonly ImmutableArray _getHashCodeVisitors; - public static readonly SymbolEquivalenceComparer Instance = new(SimpleNameAssemblyComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: true); - public static readonly SymbolEquivalenceComparer TupleNamesMustMatchInstance = new(SimpleNameAssemblyComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: true, ignoreNullableAnnotations: true); - public static readonly SymbolEquivalenceComparer IgnoreAssembliesInstance = new(assemblyComparerOpt: null, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: true); + public static readonly SymbolEquivalenceComparer Instance = new(SimpleNameAssemblyComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: true, objectAndDynamicCompareEqually: true); + public static readonly SymbolEquivalenceComparer TupleNamesMustMatchInstance = new(SimpleNameAssemblyComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: true, ignoreNullableAnnotations: true, objectAndDynamicCompareEqually: true); + public static readonly SymbolEquivalenceComparer IgnoreAssembliesInstance = new(assemblyComparerOpt: null, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: true, objectAndDynamicCompareEqually: true); private readonly IEqualityComparer? _assemblyComparerOpt; private readonly bool _tupleNamesMustMatch; private readonly bool _ignoreNullableAnnotations; + private readonly bool _objectAndDynamicCompareEqually; public ParameterSymbolEqualityComparer ParameterEquivalenceComparer { get; } public SignatureTypeSymbolEquivalenceComparer SignatureTypeEquivalenceComparer { get; } @@ -54,11 +55,13 @@ internal SymbolEquivalenceComparer( IEqualityComparer? assemblyComparerOpt, bool distinguishRefFromOut, bool tupleNamesMustMatch, - bool ignoreNullableAnnotations) + bool ignoreNullableAnnotations, + bool objectAndDynamicCompareEqually) { _assemblyComparerOpt = assemblyComparerOpt; _tupleNamesMustMatch = tupleNamesMustMatch; _ignoreNullableAnnotations = ignoreNullableAnnotations; + _objectAndDynamicCompareEqually = objectAndDynamicCompareEqually; this.ParameterEquivalenceComparer = new ParameterSymbolEqualityComparer(this, distinguishRefFromOut); this.SignatureTypeEquivalenceComparer = new SignatureTypeSymbolEquivalenceComparer(this); From c9fa5a8a064eca25a4ed3c00f6ede0c9c65a5a16 Mon Sep 17 00:00:00 2001 From: tmat Date: Sun, 10 Sep 2023 19:14:30 -0700 Subject: [PATCH 02/14] Fix --- .../Utilities/SymbolEquivalenceComparerTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Test/Utilities/SymbolEquivalenceComparerTests.cs b/src/EditorFeatures/Test/Utilities/SymbolEquivalenceComparerTests.cs index 0f256f5ba12ff..64ecf71f2f183 100644 --- a/src/EditorFeatures/Test/Utilities/SymbolEquivalenceComparerTests.cs +++ b/src/EditorFeatures/Test/Utilities/SymbolEquivalenceComparerTests.cs @@ -1116,8 +1116,8 @@ void M(ref int i) { } var method_v1 = type1_v1.GetMembers("M").Single(); var method_v2 = type1_v2.GetMembers("M").Single(); - var trueComp = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: false); - var falseComp = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: false); + var trueComp = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: false, objectAndDynamicCompareEqually: true); + var falseComp = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: false, objectAndDynamicCompareEqually: true); Assert.False(trueComp.Equals(method_v1, method_v2)); Assert.False(trueComp.Equals(method_v2, method_v1)); @@ -1355,8 +1355,8 @@ class T Assert.Equal(NullableAnnotation.Annotated, a1.NullableAnnotation); Assert.Equal(NullableAnnotation.NotAnnotated, a2.NullableAnnotation); - var ignoreComparer = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: true); - var notIgnoreComparer = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: false); + var ignoreComparer = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: true, objectAndDynamicCompareEqually: true); + var notIgnoreComparer = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: false, objectAndDynamicCompareEqually: true); Assert.True(ignoreComparer.Equals(a1, a2)); Assert.True(ignoreComparer.Equals(b1, b2)); @@ -1416,8 +1416,8 @@ class T Assert.Equal(NullableAnnotation.None, a1.NullableAnnotation); Assert.Equal(NullableAnnotation.NotAnnotated, a2.NullableAnnotation); - var ignoreComparer = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: true); - var notIgnoreComparer = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: false); + var ignoreComparer = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: true, objectAndDynamicCompareEqually: true); + var notIgnoreComparer = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: false, objectAndDynamicCompareEqually: true); Assert.True(ignoreComparer.Equals(a1, a2)); Assert.True(ignoreComparer.Equals(b1, b2)); @@ -1620,7 +1620,7 @@ public void AssemblyComparer1() var tb2 = (ITypeSymbol)b2.GlobalNamespace.GetMembers("T").Single(); var tb3 = (ITypeSymbol)b3.GlobalNamespace.GetMembers("T").Single(); - var identityComparer = new SymbolEquivalenceComparer(AssemblySymbolIdentityComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: false); + var identityComparer = new SymbolEquivalenceComparer(AssemblySymbolIdentityComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: false, objectAndDynamicCompareEqually: true); // same name: Assert.True(SymbolEquivalenceComparer.IgnoreAssembliesInstance.Equals(ta1, ta2)); @@ -1706,7 +1706,7 @@ .method public instance int32[] F( // 3 var type1 = (ITypeSymbol)c1.GlobalNamespace.GetMembers("C").Single(); var type2 = (ITypeSymbol)c2.GlobalNamespace.GetMembers("C").Single(); - var identityComparer = new SymbolEquivalenceComparer(AssemblySymbolIdentityComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: false); + var identityComparer = new SymbolEquivalenceComparer(AssemblySymbolIdentityComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: false, ignoreNullableAnnotations: false, objectAndDynamicCompareEqually: true); var f1 = type1.GetMembers("F"); var f2 = type2.GetMembers("F"); From f3053e21896f801010fc3ed487c3c3cb9169adc2 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 11 Sep 2023 13:55:02 -0700 Subject: [PATCH 03/14] Fixes --- .../EditAndContinueWorkspaceServiceTests.cs | 29 +++++++++---------- .../EditAndContinueDiagnosticDescriptors.cs | 4 +-- .../Core/Portable/FeaturesResources.resx | 4 +-- .../Portable/xlf/FeaturesResources.cs.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.de.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.es.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.fr.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.it.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.ja.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.ko.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.pl.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.pt-BR.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.ru.xlf | 6 ++-- .../Portable/xlf/FeaturesResources.tr.xlf | 6 ++-- .../xlf/FeaturesResources.zh-Hans.xlf | 6 ++-- .../xlf/FeaturesResources.zh-Hant.xlf | 6 ++-- 16 files changed, 56 insertions(+), 59 deletions(-) diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index ef72e30dfee06..5e204a1583d2e 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -1409,7 +1409,7 @@ public async Task RudeEdits(bool breakMode) var document2 = solution.GetDocument(document1.Id); var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); - AssertEx.Equal(new[] { "ENC0021: " + string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.type_parameter) }, + AssertEx.Equal(new[] { "ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method) }, diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}")); // validate solution update status and emit: @@ -1436,7 +1436,7 @@ public async Task RudeEdits(bool breakMode) { "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2", "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=21|RudeEditSyntaxKind=8910|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}" + "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8910|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}" }, _telemetryLog); } else @@ -1445,7 +1445,7 @@ public async Task RudeEdits(bool breakMode) { "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount=0", "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges=", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=21|RudeEditSyntaxKind=8910|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}" + "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8910|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}" }, _telemetryLog); } } @@ -1547,7 +1547,7 @@ class C { int Y => 2; } var generatedDocument = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).Single(); var diagnostics1 = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noActiveSpans, CancellationToken.None); - AssertEx.Equal(new[] { "ENC0021: " + string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.type_parameter) }, + AssertEx.Equal(new[] { "ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method) }, diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}")); var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); @@ -1622,8 +1622,7 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal(new[] { - "ENC0113: " + string.Format(FeaturesResources.Updating_0_within_generic_type_or_method_requires_restarting_the_application_because_is_not_supported_by_the_runtime, FeaturesResources.method), - "ENC0021: " + string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.type_parameter) + "ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method) }, diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); @@ -1649,9 +1648,8 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) AssertEx.Equal(new[] { "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2", - "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=2|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=113|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=21|RudeEditSyntaxKind=8910|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}" + "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=", + "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}" }, _telemetryLog); } else @@ -1659,9 +1657,8 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) AssertEx.Equal(new[] { "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount=0", - "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=2|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges=", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=113|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=21|RudeEditSyntaxKind=8910|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}" + "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges=", + "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={00000000-AAAA-AAAA-AAAA-111111111111}" }, _telemetryLog); } } @@ -1742,7 +1739,7 @@ public async Task RudeEdits_DelayLoadedModule() // Rude Edits reported: var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( - new[] { "ENC0021: " + string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.type_parameter) }, + new[] { "ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method) }, diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); @@ -1756,7 +1753,7 @@ public async Task RudeEdits_DelayLoadedModule() // Rude Edits still reported: diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); AssertEx.Equal( - new[] { "ENC0021: " + string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.type_parameter) }, + new[] { "ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method) }, diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); @@ -4545,7 +4542,7 @@ public async Task WatchHotReloadServiceTest() result = await hotReload.EmitSolutionUpdateAsync(solution, CancellationToken.None); AssertEx.Equal( - new[] { "ENC0021: " + string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.type_parameter) }, + new[] { "ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method) }, result.diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); Assert.Empty(result.updates); @@ -4611,7 +4608,7 @@ public async Task UnitTestingHotReloadServiceTest() // Rude edit result = await hotReload.EmitSolutionUpdateAsync(solution, commitUpdates: true, CancellationToken.None); AssertEx.Equal( - new[] { "ENC0021: " + string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.type_parameter) }, + new[] { "ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method) }, result.diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}")); Assert.Empty(result.updates); diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index 8e21cfd2517d3..e69f46e4c2a99 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -110,7 +110,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.ChangingCapturedVariableScope, nameof(FeaturesResources.Changing_the_declaration_scope_of_a_captured_variable_0_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.ChangingLambdaParameters, nameof(FeaturesResources.Changing_the_parameters_of_0_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.ChangingLambdaReturnType, nameof(FeaturesResources.Changing_the_return_type_of_0_requires_restarting_the_application)); - AddRudeEdit(RudeEditKind.ChangingQueryLambdaType, nameof(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application)); + AddRudeEdit(RudeEditKind.ChangingQueryLambdaType, nameof(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.AccessingCapturedVariableInLambda, nameof(FeaturesResources.Accessing_captured_variable_0_that_hasn_t_been_accessed_before_in_1_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.NotAccessingCapturedVariableInLambda, nameof(FeaturesResources.Ceasing_to_access_captured_variable_0_in_1_requires_restarting_the_application)); AddRudeEdit(RudeEditKind.InsertLambdaWithMultiScopeCapture, nameof(FeaturesResources.Adding_0_that_accesses_captured_variables_1_and_2_declared_in_different_scopes_requires_restarting_the_application)); @@ -146,7 +146,7 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.RenamingNotSupportedByRuntime, nameof(FeaturesResources.Renaming_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.ChangingNonCustomAttribute, nameof(FeaturesResources.Changing_pseudo_custom_attribute_0_of_1_requires_restarting_th_application)); AddRudeEdit(RudeEditKind.ChangingNamespace, nameof(FeaturesResources.Changing_the_containing_namespace_of_0_from_1_to_2_requires_restarting_th_application)); - AddRudeEdit(RudeEditKind.ChangingSignatureNotSupportedByRuntime, nameof(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application)); + AddRudeEdit(RudeEditKind.ChangingSignatureNotSupportedByRuntime, nameof(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.DeleteNotSupportedByRuntime, nameof(FeaturesResources.Deleting_0_requires_restarting_the_application_because_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.UpdatingStateMachineMethodNotSupportedByRuntime, nameof(FeaturesResources.Updating_async_or_iterator_requires_restarting_the_application_because_is_not_supported_by_the_runtime)); AddRudeEdit(RudeEditKind.UpdatingGenericNotSupportedByRuntime, nameof(FeaturesResources.Updating_0_within_generic_type_or_method_requires_restarting_the_application_because_is_not_supported_by_the_runtime)); diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 93b6ebba9205c..a7e66f1b77bfd 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -402,8 +402,8 @@ Changing the return type of {0} requires restarting the application. - - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. Changing the declaration scope of a captured variable '{0}' requires restarting the application. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 53db3e7e4eb60..75e2dd4c17d66 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -480,9 +480,9 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Změna návratového typu {0} vyžaduje restartování aplikace. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index faf0fd9754279..d652f07a4d6d5 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -480,9 +480,9 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Das Ändern des Rückgabetyps von {0} erfordert einen Neustart der Anwendung. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 3bcd4b4056275..9d52083f26370 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -480,9 +480,9 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Para cambiar el tipo de valor devuelto de {0} es necesario reiniciar la aplicación. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 2874def64a5a0..db38c105a1100 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -480,9 +480,9 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai La modification du type de retour de {0} requiert le redémarrage de l’application. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 6bbe84b08ebd4..4d9001a7779a6 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -480,9 +480,9 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Se si modifica il tipo restituito di {0}, è necessario riavviare l'applicazione. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 9f6127b408432..1470971f326f4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -480,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0} の戻り値の型を変更するには、アプリケーションを再起動する必要があります。 - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 0ce1e140bf43c..355a3d6537c1f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -480,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma {0}의 반환 형식을 변경하려면 애플리케이션을 다시 시작해야 합니다. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index d6362cdc97c7b..34c7ba263e9b3 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -480,9 +480,9 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Zmiana zwracanego typu elementu {0} wymaga ponownego uruchomienia aplikacji. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index fa49917e6fd79..63a4a993bdba2 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -480,9 +480,9 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Alterar o tipo de retorno de {0} requer o reinício do aplicativo. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 20455fdf042cc..ef5ecf769c75a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -480,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Для изменения типа возвращаемого значения {0} требуется перезапустить приложение. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 35f6bad0ea934..c03d065e46688 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -480,9 +480,9 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be {0} öğesinin dönüş türünün değiştirilmesi, uygulamanın yeniden başlatılmasını gerektirir. - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 63bf59cbb6145..e4f0327463b85 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -480,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 更改 {0} 的返回类型需要重启应用程序。 - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index aa0a6de8cfaac..c1802d0e8ed99 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -480,9 +480,9 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 變更 {0} 的傳回型別需要重新啟動應用程式。 - - Changing the signature of {0} requires restarting the application. - Changing the signature of {0} requires restarting the application. + + Changing the signature of {0} requires restarting the application because is not supported by the runtime. + Changing the signature of {0} requires restarting the application because is not supported by the runtime. From d659a6d20955b6fc17e8eaf6ab0361fb24c6e30a Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 11 Sep 2023 13:55:47 -0700 Subject: [PATCH 04/14] Formatting --- .../Core/Portable/Emit/EditAndContinue/SymbolChanges.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs index 6ded492c794d3..282af0a86def5 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs @@ -490,7 +490,7 @@ private static void CalculateChanges(IEnumerable edits, out IReadO replaceSymbols = lazyReplaceSymbolsBuilder ?? SpecializedCollections.EmptySet(); deletedMembers = lazyDeletedMembersBuilder?.ToImmutableDictionary( - keySelector: static e => e.Key, + keySelector: static e => e.Key, elementSelector: static e => e.Value.ToImmutableAndFree()) ?? ImmutableDictionary>.Empty; } From 67e366476917dca0ea77c88e038406523153ffe7 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 11 Sep 2023 14:27:13 -0700 Subject: [PATCH 05/14] Formatting --- ...necessaryPragmaSuppressionsCodeFixProvider.cs | 1 - .../AbstractEditAndContinueAnalyzer.cs | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessaryPragmaSuppressionsCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessaryPragmaSuppressionsCodeFixProvider.cs index e80e803decba5..5b3d705ff6a14 100644 --- a/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessaryPragmaSuppressionsCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/RemoveUnnecessarySuppressions/RemoveUnnecessaryPragmaSuppressionsCodeFixProvider.cs @@ -37,7 +37,6 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var syntaxFacts = context.Document.GetRequiredLanguageService(); - foreach (var diagnostic in context.Diagnostics) { // Defensive check that we are operating on the diagnostic on a pragma. diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 57620ea6ce567..857153cf1ce65 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -12,16 +12,16 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; +using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using Microsoft.CodeAnalysis.Shared.Collections; namespace Microsoft.CodeAnalysis.EditAndContinue { @@ -2452,7 +2452,7 @@ private async Task> AnalyzeSemanticsAsync( // { new type -> constructor update } PooledDictionary? instanceConstructorEdits = null; PooledDictionary? staticConstructorEdits = null; - + using var _1 = PooledHashSet.GetInstance(out var processedSymbols); using var _2 = ArrayBuilder.GetInstance(out var semanticEdits); using var _3 = PooledDictionary.GetInstance(out var symbolKeyCache); @@ -2944,7 +2944,7 @@ newSymbol is IPropertySymbol newProperty && Contract.ThrowIfNull(newSymbol); if (oldSymbol is IParameterSymbol && - !IsMemberOrDelegateReplaced(oldSymbol.ContainingSymbol, newSymbol.ContainingSymbol) && + !IsMemberOrDelegateReplaced(oldSymbol.ContainingSymbol, newSymbol.ContainingSymbol) && !capabilities.Grant(EditAndContinueCapabilities.UpdateParameters)) { diagnosticContext.Report(RudeEditKind.RenamingNotSupportedByRuntime, cancellationToken); @@ -3243,7 +3243,7 @@ IFieldSymbol or } Func? syntaxMap = null; - + // only trivia changed: Contract.ThrowIfNull(newBody); Debug.Assert(IsConstructorWithMemberInitializers(oldSymbol, cancellationToken) == IsConstructorWithMemberInitializers(newSymbol, cancellationToken)); @@ -3685,7 +3685,7 @@ private static void AddMemberSignatureOrNameChangeEdits( { case IPropertySymbol oldPropertySymbol: // Properties may be overloaded on signature. - + // delete the property and its accessors AddDelete(oldPropertySymbol); AddDelete(oldPropertySymbol.GetMethod); @@ -4349,7 +4349,7 @@ private void AnalyzeSymbolUpdate( AddSemanticEditsOriginatingFromParameterUpdate(semanticEdits, oldParameter, newParameter, diagnosticContext.NewModel.Compilation, cancellationToken); // Attributes applied on parameters of a delegate are applied to both Invoke and BeginInvoke methods. So are the parameter names. - if ((hasSymbolAttributeChange || oldParameter.Name != newParameter.Name) && + if ((hasSymbolAttributeChange || oldParameter.Name != newParameter.Name) && newParameter.ContainingType is INamedTypeSymbol { TypeKind: TypeKind.Delegate } newContainingDelegateType) { AddDelegateMethodEdit(semanticEdits, newContainingDelegateType, "Invoke", cancellationToken); @@ -6605,7 +6605,7 @@ private static void ReportMissingStateMachineAttribute( #endregion -#endregion + #endregion #region Helpers From cad782a6d0e06e542aa3f6919434238b66be7d3f Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 11 Sep 2023 15:36:40 -0700 Subject: [PATCH 06/14] Fix RefKind checking in the compiler --- .../EditAndContinue/CSharpSymbolMatcher.cs | 4 +- .../EditAndContinue/EditAndContinueTests.cs | 50 +++++++++++++++++++ .../VisualBasicSymbolMatcher.vb | 2 + .../EditAndContinue/EditAndContinueTests.vb | 46 +++++++++++++++++ .../AbstractEditAndContinueAnalyzer.cs | 2 +- 5 files changed, 102 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs index ae3f2555ef453..694ecf8b2a038 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs @@ -871,7 +871,9 @@ private bool AreNamespacesEqual(NamespaceSymbol @namespace, NamespaceSymbol othe private bool AreParametersEqual(ParameterSymbol parameter, ParameterSymbol other) { Debug.Assert(parameter.Ordinal == other.Ordinal); - return (parameter.RefKind == other.RefKind) && + + // allow a different ref-kind as long as the runtime time is the same: + return parameter.RefKind is RefKind.None == other.RefKind is RefKind.None && _comparer.Equals(parameter.Type, other.Type); } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index d724f9ab7f6ca..f66decb101d43 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -590,6 +590,56 @@ public void ModifyMethod_RenameParameter() Handle(3, TableIndex.StandAloneSig)); } + [Theory] + [InlineData("in")] + [InlineData("out")] + [InlineData("ref readonly")] + public void ModifyMethod_ParameterModifiers_RefOut(string newModifier) + { + using var _ = new EditAndContinueTest() + .AddBaseline( + source: """ + class C + { + public int F(ref int x) => x = 1; + } + """, + validator: g => + { + }) + + .AddGeneration( + source: $$""" + class C + { + public int F({{newModifier}} int x) => x = 1; + } + """, + edits: new[] + { + Edit(SemanticEditKind.Update, c => c.GetMember("C.F")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("F"); + + g.VerifyEncLogDefinitions(new[] + { + Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(1, TableIndex.MethodDef), + Handle(1, TableIndex.Param), + Handle(2, TableIndex.StandAloneSig) + }); + }) + .Verify(); + } + [CompilerTrait(CompilerFeature.Tuples)] [Fact] public void ModifyMethod_WithTuples() diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb index 7a22573ba47b3..ab9e105cdb298 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb @@ -608,6 +608,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private Function AreParametersEqual(parameter As ParameterSymbol, other As ParameterSymbol) As Boolean Debug.Assert(parameter.Ordinal = other.Ordinal) + + ' allow a different ref-kind as long as the runtime time is the same: Return parameter.IsByRef = other.IsByRef AndAlso Me._comparer.Equals(parameter.Type, other.Type) End Function diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb index c5110614c320c..ba703d66e7fb5 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb @@ -240,6 +240,52 @@ End Class End Using End Sub + + Public Sub ModifyMethod_ParameterModifiers_RefOut() + Using New EditAndContinueTest(). + AddBaseline( + source:=" +Class C + Public Sub F(ByRef x As Integer) + x = 1 + End Sub +End Class + ", + validator:=Sub(g) + End Sub). + AddGeneration( + source:=" +Imports System.Runtime.InteropServices +Class C + Public Sub F( ByRef x As Integer) + x = 1 + End Sub +End Class +", + edits:= + { + Edit(SemanticEditKind.Update, Function(c) c.GetMember("C.F")) + }, + validator:=Sub(g) + g.VerifyTypeDefNames() + g.VerifyMethodDefNames("F") + + g.VerifyEncLogDefinitions( + { + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default) + }) + + g.VerifyEncMapDefinitions( + { + Handle(2, TableIndex.MethodDef), + Handle(1, TableIndex.Param) + }) + End Sub). + Verify() + End Using + End Sub + Public Sub PartialMethod() diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 857153cf1ce65..90c7e8572dad0 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2309,7 +2309,7 @@ protected static bool CustomModifiersEquivalent(ImmutableArray o protected static bool ReturnTypesEquivalent(IMethodSymbol oldMethod, IMethodSymbol newMethod, bool exact) => oldMethod.ReturnsByRef == newMethod.ReturnsByRef && - oldMethod.ReturnsByRefReadonly == newMethod.ReturnsByRefReadonly && + oldMethod.ReturnsByRefReadonly == newMethod.ReturnsByRefReadonly && // modreq emitted on the return type CustomModifiersEquivalent(oldMethod.ReturnTypeCustomModifiers, newMethod.ReturnTypeCustomModifiers, exact) && CustomModifiersEquivalent(oldMethod.RefCustomModifiers, newMethod.RefCustomModifiers, exact) && TypesEquivalent(oldMethod.ReturnType, newMethod.ReturnType, exact); From 31fb12c866d1951f58c33308726ddd138d54b2de Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 11 Sep 2023 17:27:20 -0700 Subject: [PATCH 07/14] Unskip tests, fix reporting rude edit for primary parameter reorder --- .../EditAndContinue/ActiveStatementTests.cs | 6 +- .../EditAndContinue/StatementEditingTests.cs | 2 +- .../EditAndContinue/TopLevelEditingTests.cs | 94 ++++++++++++++----- .../AbstractEditAndContinueAnalyzer.cs | 18 +++- 4 files changed, 89 insertions(+), 31 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 4d22390b5cd3b..5ca8b0f4f816e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -1640,11 +1640,9 @@ public void Constructor_Instance_ImplicitInitializer_ParameterChange() capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Theory(Skip = "https://github.com/dotnet/roslyn/issues/68708")] + [Theory] [InlineData("class")] [InlineData("struct")] - [InlineData("record")] - [InlineData("record struct")] [WorkItem("https://github.com/dotnet/roslyn/issues/68708")] public void Constructor_Instance_Primary_ImplicitInitializer_ParameterChange(string keyword) { @@ -1654,9 +1652,9 @@ public void Constructor_Instance_Primary_ImplicitInitializer_ParameterChange(str var edits = GetTopEdits(src1, src2); var active = GetActiveStatements(src1, src2); - // TODO: should be rude edit (https://github.com/dotnet/roslyn/issues/68708) edits.VerifySemanticDiagnostics( active, + diagnostics: [Diagnostic(RudeEditKind.ChangingNameOrSignatureOfActiveMember, "(byte P)", GetResource("constructor"))], capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index c87f1a2baddb0..b9f71702f01ec 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -6047,7 +6047,7 @@ static void F(int X) Diagnostic(RudeEditKind.RenamingCapturedVariable, "X", "x", "X")); } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/68708")] + [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/68708")] public void Lambdas_CapturedParameter_ChangeType() { diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index dae7a21cafbf4..c62589afe722e 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -11512,7 +11512,10 @@ public void Constructor_Parameter_Insert_Primary_Captured_Struct() var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - new[] { Diagnostic(RudeEditKind.CapturingPrimaryConstructorParameter, "b", GetResource("struct"), "b") }, + [ + Diagnostic(RudeEditKind.CapturingPrimaryConstructorParameter, "b", GetResource("struct"), "b"), + Diagnostic(RudeEditKind.InsertIntoStruct, "int b", GetResource("parameter"), GetResource("struct")) + ], capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } @@ -11814,7 +11817,10 @@ class C(int x, int y) var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.CapturingPrimaryConstructorParameter, "y", GetResource("class with explicit or sequential layout"), "y")); + [ + Diagnostic(RudeEditKind.CapturingPrimaryConstructorParameter, "y", GetResource("class with explicit or sequential layout"), "y"), + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "int y", GetResource("parameter"), GetResource("class")) + ]); } [Fact] @@ -11843,13 +11849,15 @@ class C(int x, int y) var edits = GetTopEdits(src1, src2); edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.CapturingPrimaryConstructorParameter, "y", GetResource("class with explicit or sequential layout"), "y")); + [ + Diagnostic(RudeEditKind.CapturingPrimaryConstructorParameter, "y", GetResource("class with explicit or sequential layout"), "y"), + Diagnostic(RudeEditKind.InsertIntoClassWithLayout, "int y", GetResource("parameter"), GetResource("class")) + ]); } - [Theory(Skip = "https://github.com/dotnet/roslyn/issues/68708")] + [Theory] [InlineData("struct")] [InlineData("class")] - [InlineData("record")] [WorkItem("https://github.com/dotnet/roslyn/issues/68708")] public void Constructor_Parameter_Reorder_Primary_NotLifted(string keyword) { @@ -11858,11 +11866,14 @@ public void Constructor_Parameter_Reorder_Primary_NotLifted(string keyword) var edits = GetTopEdits(src1, src2); edits.VerifySemantics( - SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C"))); + [ + SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")), + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/68708")] + [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/68708")] public void Constructor_Parameter_Reorder_Primary_NotLifted_Record_Struct() { @@ -11870,37 +11881,70 @@ public void Constructor_Parameter_Reorder_Primary_NotLifted_Record_Struct() var src2 = "record struct C(byte y, int x) { }"; var edits = GetTopEdits(src1, src2); - // TODO: type layout changes - edits.VerifySemanticDiagnostics(); + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.InsertIntoStruct, "byte y", GetResource("auto-property"), GetResource("record struct"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } - [Theory(Skip = "https://github.com/dotnet/roslyn/issues/68708")] - [InlineData("struct")] - [InlineData("record struct")] + [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/68708")] - public void Constructor_Parameter_Reorder_Primary_Lifted_Struct(string keyword) + public void Constructor_Parameter_Reorder_Primary_Lifted_Struct() { - var src1 = keyword + " C(int x, byte y) { int M() => x + y; }"; - var src2 = keyword + " C(byte y, int x) { int M() => x + y; }"; + var src1 = "struct C(int x, byte y) { int M() => x + y; }"; + var src2 = "struct C(byte y, int x) { int M() => x + y; }"; var edits = GetTopEdits(src1, src2); - // TODO: type layout changes - edits.VerifySemanticDiagnostics(); + edits.VerifySemanticDiagnostics( + [Diagnostic(RudeEditKind.InsertIntoStruct, "byte y", GetResource("parameter"), GetResource("struct"))], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } - [Theory(Skip = "https://github.com/dotnet/roslyn/issues/68708")] - [InlineData("class")] - [InlineData("record")] + [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/68708")] - public void Constructor_Parameter_Reorder_Primary_Lifted_Class(string keyword) + public void Constructor_Parameter_Reorder_Primary_Lifted_Class() { - var src1 = keyword + " C(int x, byte y) { int M() => x + y; }"; - var src2 = keyword + " C(byte y, int x) { int M() => x + y; }"; + var src1 = "class C(int x, byte y) { int M() => x + y; }"; + var src2 = "class C(byte y, int x) { int M() => x + y; }"; var edits = GetTopEdits(src1, src2); edits.VerifySemantics( + [ SemanticEdit(SemanticEditKind.Delete, c => c.GetPrimaryConstructor("C"), deletedSymbolContainerProvider: c => c.GetMember("C")), - SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C"))); + SemanticEdit(SemanticEditKind.Insert, c => c.GetPrimaryConstructor("C")) + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/68708")] + [WorkItem("https://github.com/dotnet/roslyn/issues/69894")] + public void Constructor_Parameter_Reorder_Primary_Lifted_Record() + { + var src1 = "record C(int x, byte y) { int M() => x + y; }"; + var src2 = "record C(byte y, int x) { int M() => x + y; }"; + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + [ + 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.Deconstruct"), deletedSymbolContainerProvider: c => c.GetMember("C")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Deconstruct")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetSpecializedEqualsOverload("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetCopyConstructor("C")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.x")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_x")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_x")), + // TODO: y should also be updated (to update sequence points to the new location) + // https://github.com/dotnet/roslyn/issues/69894 + // SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.y")), + // SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.get_y")), + // SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.set_y")), + + ], + capabilities: EditAndContinueCapabilities.AddMethodToExistingType | EditAndContinueCapabilities.AddInstanceFieldToExistingType); } [Fact] diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 90c7e8572dad0..765420b7b1b0c 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2943,6 +2943,8 @@ newSymbol is IPropertySymbol newProperty && Contract.ThrowIfNull(oldSymbol); Contract.ThrowIfNull(newSymbol); + ReportTypeLayoutUpdateRudeEdits(diagnosticContext, oldSymbol, cancellationToken); + if (oldSymbol is IParameterSymbol && !IsMemberOrDelegateReplaced(oldSymbol.ContainingSymbol, newSymbol.ContainingSymbol) && !capabilities.Grant(EditAndContinueCapabilities.UpdateParameters)) @@ -4939,7 +4941,7 @@ private DiagnosticContext CreateDiagnosticContext(ArrayBuilder IsPrimaryConstructor(parameter.ContainingSymbol, cancellationToken) && + parameter.ContainingType.GetMembers($"<{parameter.Name}>P").Any(m => m.Kind == SymbolKind.Field); + private static bool HasBackingField(IEventSymbol @event) { #nullable disable // https://github.com/dotnet/roslyn/issues/39288 From 580c4e49e1219a003817a26baa3e71f2c05e3a71 Mon Sep 17 00:00:00 2001 From: tmat Date: Fri, 15 Sep 2023 14:11:42 -0700 Subject: [PATCH 08/14] Fixes --- .../Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs | 4 ++-- .../EditAndContinue/AbstractEditAndContinueAnalyzer.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index f66decb101d43..85271779d176f 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -601,7 +601,7 @@ public void ModifyMethod_ParameterModifiers_RefOut(string newModifier) source: """ class C { - public int F(ref int x) => x = 1; + public int F(ref int x) => throw null; } """, validator: g => @@ -612,7 +612,7 @@ class C source: $$""" class C { - public int F({{newModifier}} int x) => x = 1; + public int F({{newModifier}} int x) => throw null; } """, edits: new[] diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 765420b7b1b0c..811c38eb44407 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -4880,9 +4880,9 @@ public void Report(RudeEditKind kind, CancellationToken cancellationToken, TextS { var node = GetDiagnosticNode(out var distance, cancellationToken); - span ??= diagnosticSpan.IsEmpty ? - analyzer.GetDiagnosticSpan(node, (distance > 0 || kind == RudeEditKind.ChangeImplicitMainReturnType) ? EditKind.Delete : EditKind.Update) : - diagnosticSpan; + span ??= diagnosticSpan.IsEmpty + ? analyzer.GetDiagnosticSpan(node, (distance > 0 || kind == RudeEditKind.ChangeImplicitMainReturnType) ? EditKind.Delete : EditKind.Update) + : diagnosticSpan; diagnostics.Add(new RudeEditDiagnostic( kind, From 1eb77de5b1eaddbc0570642899bf278b05f834e8 Mon Sep 17 00:00:00 2001 From: tmat Date: Sat, 16 Sep 2023 10:25:54 -0700 Subject: [PATCH 09/14] Fix --- .../Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 85271779d176f..bd343b1bf0832 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -621,7 +621,6 @@ class C }, validator: g => { - g.VerifyTypeDefNames(); g.VerifyMethodDefNames("F"); g.VerifyEncLogDefinitions(new[] From 9f5a45684b6fd0178edd0659e1274d93cee0228a Mon Sep 17 00:00:00 2001 From: tmat Date: Sat, 16 Sep 2023 15:29:35 -0700 Subject: [PATCH 10/14] Fix --- .../EditAndContinue/EditAndContinueTests.cs | 106 +++++++++++++++++- .../EditAndContinue/DeletedEventDefinition.cs | 86 -------------- .../DeletedPropertyDefinition.cs | 105 ----------------- 3 files changed, 101 insertions(+), 196 deletions(-) delete mode 100644 src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedEventDefinition.cs delete mode 100644 src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedPropertyDefinition.cs diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index bd343b1bf0832..6d380b276ed1f 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -592,9 +592,8 @@ public void ModifyMethod_RenameParameter() [Theory] [InlineData("in")] - [InlineData("out")] [InlineData("ref readonly")] - public void ModifyMethod_ParameterModifiers_RefOut(string newModifier) + public void ModifyMethod_ParameterModifiers_Ref_In_RefReadonly(string newModifier) { using var _ = new EditAndContinueTest() .AddBaseline( @@ -602,6 +601,7 @@ public void ModifyMethod_ParameterModifiers_RefOut(string newModifier) class C { public int F(ref int x) => throw null; + public int G(in int y, ref readonly int z) => throw null; } """, validator: g => @@ -612,7 +612,8 @@ class C source: $$""" class C { - public int F({{newModifier}} int x) => throw null; + public int F({{newModifier}} int x) => throw null; + public int G(in int y, ref readonly int z) => throw null; } """, edits: new[] @@ -621,19 +622,114 @@ class C }, validator: g => { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("F"); + + g.VerifyEncLogDefinitions(new[] + { + Row(4, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(12, TableIndex.CustomAttribute, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(4, TableIndex.MethodDef), + Handle(1, TableIndex.Param), + Handle(12, TableIndex.CustomAttribute) + }); + }) + .Verify(); + } + + [Fact] + public void ModifyMethod_ParameterModifiers_Ref_Out() + { + using var _ = new EditAndContinueTest() + .AddBaseline( + source: """ + class C + { + public int F(ref int x) => throw null; + } + """, + validator: g => + { + }) + + .AddGeneration( + source: $$""" + class C + { + public int F(out int x) => throw null; + } + """, + edits: new[] + { + Edit(SemanticEditKind.Update, c => c.GetMember("C.F")), + }, + validator: g => + { + g.VerifyTypeDefNames(); g.VerifyMethodDefNames("F"); g.VerifyEncLogDefinitions(new[] { - Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default), Row(1, TableIndex.Param, EditAndContinueOperation.Default) }); g.VerifyEncMapDefinitions(new[] { Handle(1, TableIndex.MethodDef), + Handle(1, TableIndex.Param) + }); + }) + .Verify(); + } + + [Fact] + public void ModifyMethod_ParameterModifiers_RefReadOnly() + { + using var _ = new EditAndContinueTest() + .AddBaseline( + source: """ + class C + { + public int F(ref int x) => throw null; + public int G(ref readonly int x) => throw null; + } + """, + validator: g => + { + }) + + .AddGeneration( + source: $$""" + class C + { + public int F(ref readonly int x) => throw null; + public int G(ref readonly int x) => throw null; + } + """, + edits: new[] + { + Edit(SemanticEditKind.Update, c => c.GetMember("C.F")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("F"); + + g.VerifyEncLogDefinitions(new[] + { + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(9, TableIndex.CustomAttribute, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(3, TableIndex.MethodDef), Handle(1, TableIndex.Param), - Handle(2, TableIndex.StandAloneSig) + Handle(9, TableIndex.CustomAttribute) }); }) .Verify(); diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedEventDefinition.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedEventDefinition.cs deleted file mode 100644 index 4dcfe89818fdf..0000000000000 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedEventDefinition.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using Microsoft.Cci; -using Microsoft.CodeAnalysis.Symbols; - -namespace Microsoft.CodeAnalysis.Emit.EditAndContinue -{ - internal sealed class DeletedEventDefinition : DeletedDefinition, IEventDefinition - { - private readonly ITypeDefinition _containingTypeDef; - private readonly DeletedMethodDefinition _adder; - private readonly DeletedMethodDefinition _remover; - private readonly DeletedMethodDefinition? _caller; - - public DeletedEventDefinition(IEventDefinition oldEvent, DeletedMethodDefinition adder, DeletedMethodDefinition remover, DeletedMethodDefinition? caller, ITypeDefinition containingTypeDef, Dictionary typesUsedByDeletedMembers) - : base(oldEvent, typesUsedByDeletedMembers) - { - - _containingTypeDef = containingTypeDef; - _adder = adder; - _remover = remover; - _caller = caller; - } - - public IMethodReference Adder => _adder; - - public IMethodReference? Caller => _caller; - - public bool IsRuntimeSpecial => OldDefinition.IsRuntimeSpecial; - - public bool IsSpecialName => OldDefinition.IsSpecialName; - - public IMethodReference Remover => _remover; - - public ITypeDefinition ContainingTypeDefinition => _containingTypeDef; - - public TypeMemberVisibility Visibility => OldDefinition.Visibility; - - public string? Name => OldDefinition.Name; - - public IDefinition? AsDefinition(EmitContext context) - { - return OldDefinition.AsDefinition(context); - } - - public void Dispatch(MetadataVisitor visitor) - { - visitor.Visit(this); - } - - public IEnumerable GetAccessors(EmitContext context) - { - if (_adder is not null) - yield return _adder; - - if (_remover is not null) - yield return _remover; - - if (_caller is not null) - yield return _caller; - } - - public IEnumerable GetAttributes(EmitContext context) - { - return WrapAttributes(OldDefinition.GetAttributes(context)); - } - - public ITypeReference GetContainingType(EmitContext context) - { - return _containingTypeDef; - } - - public ISymbolInternal? GetInternalSymbol() - { - return OldDefinition.GetInternalSymbol(); - } - - public ITypeReference GetType(EmitContext context) - { - return WrapType(OldDefinition.GetType(context)); - } - } -} diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedPropertyDefinition.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedPropertyDefinition.cs deleted file mode 100644 index 68be5b42d1da2..0000000000000 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedPropertyDefinition.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using Microsoft.Cci; -using Microsoft.CodeAnalysis.CodeGen; -using Microsoft.CodeAnalysis.Symbols; - -namespace Microsoft.CodeAnalysis.Emit.EditAndContinue -{ - internal sealed class DeletedPropertyDefinition : DeletedDefinition, IPropertyDefinition - { - private readonly ITypeDefinition _containingTypeDef; - private readonly ImmutableArray _parameters; - - private readonly IMethodReference? _getter; - private readonly IMethodReference? _setter; - - public DeletedPropertyDefinition(IPropertyDefinition oldProperty, DeletedMethodDefinition? getter, DeletedMethodDefinition? setter, ITypeDefinition containingTypeDef, Dictionary typesUsedByDeletedMembers) - : base(oldProperty, typesUsedByDeletedMembers) - { - _containingTypeDef = containingTypeDef; - _getter = getter; - _setter = setter; - - _parameters = WrapParameters(oldProperty.Parameters); - } - - public MetadataConstant? DefaultValue => OldDefinition.DefaultValue; - - public IMethodReference? Getter => _getter; - - public bool HasDefaultValue => OldDefinition.HasDefaultValue; - - public bool IsRuntimeSpecial => OldDefinition.IsRuntimeSpecial; - - public bool IsSpecialName => OldDefinition.IsSpecialName; - - public ImmutableArray Parameters => StaticCast.From(_parameters); - - public IMethodReference? Setter => _setter; - - public CallingConvention CallingConvention => OldDefinition.CallingConvention; - - public ushort ParameterCount => (ushort)_parameters.Length; - - public ImmutableArray ReturnValueCustomModifiers => OldDefinition.ReturnValueCustomModifiers; - - public ImmutableArray RefCustomModifiers => OldDefinition.RefCustomModifiers; - - public bool ReturnValueIsByRef => OldDefinition.ReturnValueIsByRef; - - public ITypeDefinition ContainingTypeDefinition => _containingTypeDef; - - public TypeMemberVisibility Visibility => OldDefinition.Visibility; - - public string? Name => OldDefinition.Name; - - public IDefinition? AsDefinition(EmitContext context) - { - return OldDefinition.AsDefinition(context); - } - - public void Dispatch(MetadataVisitor visitor) - { - visitor.Visit(this); - } - - public IEnumerable GetAccessors(EmitContext context) - { - if (_getter is not null) - yield return _getter; - - if (_setter is not null) - yield return _setter; - } - - public IEnumerable GetAttributes(EmitContext context) - { - return WrapAttributes(OldDefinition.GetAttributes(context)); - } - - public ITypeReference GetContainingType(EmitContext context) - { - return _containingTypeDef; - } - - public ISymbolInternal? GetInternalSymbol() - { - return OldDefinition.GetInternalSymbol(); - } - - public ImmutableArray GetParameters(EmitContext context) - { - return StaticCast.From(_parameters); - } - - public ITypeReference GetType(EmitContext context) - { - return WrapType(OldDefinition.GetType(context)); - } - } -} From e3894897e54d455eec01148d51f021d871651e19 Mon Sep 17 00:00:00 2001 From: tmat Date: Sun, 17 Sep 2023 10:29:14 -0700 Subject: [PATCH 11/14] Formatting --- .../CSharpTest/EditAndContinue/ActiveStatementTests.cs | 1 + .../Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 5ca8b0f4f816e..5c0165c75717f 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. #nullable disable +#pragma warning disable IDE0055 // Collection expression formatting using System; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index 9f2118f186372..2cb5ac8302412 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1584,7 +1584,7 @@ private static bool GroupBySignatureEquivalent(IMethodSymbol? oldMethod, IMethod return true; } -#endregion + #endregion #region Diagnostic Info From a340249530ee436ca88384d64fc4da08d396b296 Mon Sep 17 00:00:00 2001 From: tmat Date: Sun, 17 Sep 2023 11:12:39 -0700 Subject: [PATCH 12/14] More deleted members cleanup --- .../EditAndContinue/DeltaMetadataWriter.cs | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs index 9268a693d4ca9..59b0db9bc2c6b 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs @@ -12,6 +12,7 @@ using System.Threading; using Microsoft.Cci; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Emit.EditAndContinue; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; @@ -38,7 +39,7 @@ internal sealed class DeltaMetadataWriter : MetadataWriter /// private readonly Dictionary _typesUsedByDeletedMembers; - private readonly Dictionary> _deletedTypeMembers; + private readonly Dictionary> _deletedTypeMembers; private readonly DefinitionIndex _typeDefs; private readonly DefinitionIndex _eventDefs; @@ -104,7 +105,7 @@ public DeltaMetadataWriter( _changedTypeDefs = new List(); _typesUsedByDeletedMembers = new Dictionary(ReferenceEqualityComparer.Instance); - _deletedTypeMembers = new Dictionary>(ReferenceEqualityComparer.Instance); + _deletedTypeMembers = new Dictionary>(ReferenceEqualityComparer.Instance); _typeDefs = new DefinitionIndex(this.TryGetExistingTypeDefIndex, sizes[(int)TableIndex.TypeDef]); _eventDefs = new DefinitionIndex(this.TryGetExistingEventDefIndex, sizes[(int)TableIndex.Event]); _fieldDefs = new DefinitionIndex(this.TryGetExistingFieldDefIndex, sizes[(int)TableIndex.Field]); @@ -540,24 +541,6 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) int typeRowId = _typeDefs.GetRowId(typeDef); - // First we find the deleted methods, and add them to our dictionary. This is used later when - // processing events, properties, methods, and references. - ImmutableDictionary? deletedMethodDefinitions = null; - - var deletedMethods = _changes.GetDeletedMethods(typeDef); - if (deletedMethods.Length > 0) - { - var deletedTypeMembers = ImmutableDictionary.CreateBuilder(ReferenceEqualityComparer.Instance); - foreach (var methodDef in deletedMethods) - { - var oldMethodDef = (IMethodDefinition)methodDef.GetCciAdapter(); - deletedTypeMembers.Add(oldMethodDef, new DeletedMethodDefinition(oldMethodDef, typeDef, _typesUsedByDeletedMembers)); - } - - deletedMethodDefinitions = deletedTypeMembers.ToImmutableDictionary(); - _deletedTypeMembers.Add(typeDef, deletedMethodDefinitions); - } - foreach (var eventDef in typeDef.GetEvents(this.Context)) { if (!_eventMap.Contains(typeRowId)) @@ -582,15 +565,22 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) CreateIndicesForMethod(methodDef, methodChange); } - // Because we already processed the deleted methods above, this is a bit easier than - // properties and events, and we just need to make sure we add the right indices - if (deletedMethodDefinitions is not null) + var deletedMethods = _changes.GetDeletedMethods(typeDef); + if (deletedMethods.Length > 0) { - foreach (var (_, newMethodDef) in deletedMethodDefinitions) + // create representations of the old deleted methods in this compilation: + var newMethodDefs = deletedMethods.SelectAsArray( + static (m, args) => new DeletedMethodDefinition((IMethodDefinition)m.GetCciAdapter(), args.typeDef, args._typesUsedByDeletedMembers), + (typeDef, _typesUsedByDeletedMembers)); + + // Assign the deleted method and its parameters row ids in the delta metadata: + foreach (var newMethodDef in newMethodDefs) { _methodDefs.AddUpdated(newMethodDef); CreateIndicesForMethod(newMethodDef, SymbolChange.Updated); } + + _deletedTypeMembers.Add(typeDef, newMethodDefs); } foreach (var propertyDef in typeDef.GetProperties(this.Context)) @@ -1714,7 +1704,7 @@ public void Add(MethodImplKey item) private sealed class DeltaReferenceIndexer : ReferenceIndexer { private readonly SymbolChanges _changes; - private readonly Dictionary> _deletedTypeMembers; + private readonly IReadOnlyDictionary> _deletedTypeMembers; public DeltaReferenceIndexer(DeltaMetadataWriter writer) : base(writer) @@ -1794,7 +1784,7 @@ public override void Visit(ITypeDefinition typeDefinition) // We need to visit deleted members to ensure attribute method references are recorded if (_deletedTypeMembers.TryGetValue(typeDefinition, out var deletedMembers)) { - this.Visit(deletedMembers.Values); + this.Visit(deletedMembers); } } } From a7c64280bc09655a8619c5f1cbc09a41b2010a78 Mon Sep 17 00:00:00 2001 From: tmat Date: Sun, 17 Sep 2023 11:18:11 -0700 Subject: [PATCH 13/14] Formatting --- .../EditAndContinue/ActiveStatementTests.Methods.cs | 1 + .../CSharpTest/EditAndContinue/LineEditTests.cs | 1 + .../EditAndContinue/TopLevelEditingTests.cs | 13 +++++++------ .../EditAndContinue/TopLevelEditingTests.vb | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs index fd053fd568528..546129a205ba2 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. #nullable disable +#pragma warning disable IDE0055 // Collection expression formatting using System.Linq; using Microsoft.CodeAnalysis.CSharp.UnitTests; diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs index 1afbe077d53eb..b88c4cbe60b13 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/LineEditTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. #nullable disable +#pragma warning disable IDE0055 // Collection expression formatting using System; using System.Collections.Immutable; diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index c62589afe722e..f9c7ad3f30ae4 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. #nullable disable +#pragma warning disable IDE0055 // Collection expression formatting using System; using System.Collections.Generic; @@ -1241,8 +1242,8 @@ class B : System.Attribute { } [Fact] public void Type_Update_Attribute_Insert_Reloadable() - { - var attributes = ReloadableAttributeSrc + + { + var attributes = ReloadableAttributeSrc + """ class A : System.Attribute { } class B : System.Attribute { } @@ -8525,7 +8526,7 @@ void M(int a, int b, int c) [Fact] public void Method_Update_Parameter_Partial() { - var src1 = @" + var src1 = @" class C { partial void M(int a); @@ -10354,7 +10355,7 @@ public void Method_Partial_Parameter_TypeChange() semanticEdits: [ SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.F").PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C"), partialType: "C"), - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C") + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.F").PartialImplementationPart, partialType: "C") ]), ], capabilities: EditAndContinueCapabilities.AddMethodToExistingType); @@ -15278,7 +15279,7 @@ public void MemberInitializer_Update_Property_GenericType() } [Fact] - public void MemberInitializer_Update_StackAllocInConstructor() + public void MemberInitializer_Update_StackAllocInConstructor() { var src1 = "unsafe class C { int a = 1; public C() { int* a = stackalloc int[10]; } }"; var src2 = "unsafe class C { int a = 2; public C() { int* a = stackalloc int[10]; } }"; @@ -15293,7 +15294,7 @@ public void MemberInitializer_Update_StackAllocInConstructor() } [Fact] - public void MemberInitializer_Update_StackAllocInConstructor_ThisInitializer() + public void MemberInitializer_Update_StackAllocInConstructor_ThisInitializer() { var src1 = "class C { int a = 1; C() : this(stackalloc int[1]) { } C(System.Span s) { } }"; var src2 = "class C { int a = 2; C() : this(stackalloc int[1]) { } C(System.Span s) { } }"; diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 9c65386fee53d..0b2f8430693a3 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -4282,7 +4282,7 @@ End Class End Sub - public Sub PartialMember_RenameInsertDelete_SameFile() + Public Sub PartialMember_RenameInsertDelete_SameFile() Dim src1 = " Partial Class C Sub F1(a As Integer) : End Sub @@ -4357,7 +4357,7 @@ End Class Dim srcB2 = srcA1 EditAndContinueValidation.VerifySemantics( - { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { DocumentResults( semanticEdits:={SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMembers("C.F").Single(Function(m) m.GetParameters()(0).Type.SpecialType = SpecialType.System_Char))}), From 5d5981f7755fd92f05cea4666131d20f0fbe8781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Tue, 26 Sep 2023 12:05:14 -0700 Subject: [PATCH 14/14] Apply suggestions from code review Co-authored-by: Jan Jones --- .../Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs | 2 +- src/Compilers/Core/Portable/Emit/SemanticEdit.cs | 2 +- .../Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs index 694ecf8b2a038..81afe5031b70d 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs @@ -872,7 +872,7 @@ private bool AreParametersEqual(ParameterSymbol parameter, ParameterSymbol other { Debug.Assert(parameter.Ordinal == other.Ordinal); - // allow a different ref-kind as long as the runtime time is the same: + // allow a different ref-kind as long as the runtime type is the same: return parameter.RefKind is RefKind.None == other.RefKind is RefKind.None && _comparer.Equals(parameter.Type, other.Type); } diff --git a/src/Compilers/Core/Portable/Emit/SemanticEdit.cs b/src/Compilers/Core/Portable/Emit/SemanticEdit.cs index cd1aaf85125d8..47fad96276d26 100644 --- a/src/Compilers/Core/Portable/Emit/SemanticEdit.cs +++ b/src/Compilers/Core/Portable/Emit/SemanticEdit.cs @@ -111,7 +111,7 @@ public SemanticEdit(SemanticEditKind kind, ISymbol? oldSymbol, ISymbol? newSymbo } // Syntax map is only meaningful for update edits that preserve local variables. - Debug.Assert(syntaxMap == null || kind == SemanticEditKind.Update && preserveLocalVariables); + Debug.Assert(syntaxMap == null || (kind == SemanticEditKind.Update && preserveLocalVariables)); // Partial methods should be implementations, not definitions. Debug.Assert(oldSymbol is not IMethodSymbol { PartialImplementationPart: not null }); diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb index ab9e105cdb298..3d214149dbee7 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb @@ -609,7 +609,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private Function AreParametersEqual(parameter As ParameterSymbol, other As ParameterSymbol) As Boolean Debug.Assert(parameter.Ordinal = other.Ordinal) - ' allow a different ref-kind as long as the runtime time is the same: + ' allow a different ref-kind as long as the runtime type is the same: Return parameter.IsByRef = other.IsByRef AndAlso Me._comparer.Equals(parameter.Type, other.Type) End Function