diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs index c9f41c978e759..07dc85c9f6e6e 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/CSharpSymbolMatcher.cs @@ -32,10 +32,11 @@ public CSharpSymbolMatcher( EmitContext sourceContext, SourceAssemblySymbol otherAssembly, EmitContext otherContext, - ImmutableDictionary> otherSynthesizedMembersOpt) + ImmutableDictionary>? otherSynthesizedMembers, + ImmutableDictionary>? otherDeletedMembers) { _defs = new MatchDefsToSource(sourceContext, otherContext); - _symbols = new MatchSymbols(anonymousTypeMap, anonymousDelegates, anonymousDelegatesWithFixedTypes, sourceAssembly, otherAssembly, otherSynthesizedMembersOpt, new DeepTranslator(otherAssembly.GetSpecialType(SpecialType.System_Object))); + _symbols = new MatchSymbols(anonymousTypeMap, anonymousDelegates, anonymousDelegatesWithFixedTypes, sourceAssembly, otherAssembly, otherSynthesizedMembers, otherDeletedMembers, new DeepTranslator(otherAssembly.GetSpecialType(SpecialType.System_Object))); } public CSharpSymbolMatcher( @@ -55,7 +56,8 @@ public CSharpSymbolMatcher( sourceAssembly, otherAssembly, otherSynthesizedMembers: null, - deepTranslator: null); + deepTranslator: null, + otherDeletedMembers: null); } public override Cci.IDefinition? MapDefinition(Cci.IDefinition definition) @@ -292,6 +294,8 @@ private sealed class MatchSymbols : CSharpSymbolVisitor /// private readonly ImmutableDictionary>? _otherSynthesizedMembers; + private readonly ImmutableDictionary>? _otherDeletedMembers; + private readonly SymbolComparer _comparer; private readonly ConcurrentDictionary _matches = new(ReferenceEqualityComparer.Instance); @@ -310,6 +314,7 @@ public MatchSymbols( SourceAssemblySymbol sourceAssembly, AssemblySymbol otherAssembly, ImmutableDictionary>? otherSynthesizedMembers, + ImmutableDictionary>? otherDeletedMembers, DeepTranslator? deepTranslator) { _anonymousTypeMap = anonymousTypeMap; @@ -318,6 +323,7 @@ public MatchSymbols( _sourceAssembly = sourceAssembly; _otherAssembly = otherAssembly; _otherSynthesizedMembers = otherSynthesizedMembers; + _otherDeletedMembers = otherDeletedMembers; _comparer = new SymbolComparer(this, deepTranslator); } @@ -987,6 +993,11 @@ private IReadOnlyDictionary> GetAllEmitt members.AddRange(synthesizedMembers); } + if (_otherDeletedMembers?.TryGetValue(symbol, out var deletedMembers) == true) + { + members.AddRange(deletedMembers); + } + var result = members.ToDictionary(s => s.MetadataName, StringOrdinalComparer.Instance); members.Free(); return result; @@ -1006,6 +1017,11 @@ public SymbolComparer(MatchSymbols matcher, DeepTranslator? deepTranslator) public bool Equals(TypeSymbol source, TypeSymbol other) { + if (ReferenceEquals(source, other)) + { + return true; + } + var visitedSource = (TypeSymbol?)_matcher.Visit(source); var visitedOther = (_deepTranslator != null) ? (TypeSymbol)_deepTranslator.Visit(other) : other; diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs index acf88a850bc11..7e214685964fa 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs @@ -174,8 +174,10 @@ private static EmitBaseline MapToCompilation( RoslynDebug.AssertNotNull(previousGeneration.Compilation); RoslynDebug.AssertNotNull(previousGeneration.PEModuleBuilder); + RoslynDebug.AssertNotNull(moduleBeingBuilt.EncSymbolChanges); var currentSynthesizedMembers = moduleBeingBuilt.GetAllSynthesizedMembers(); + var currentDeletedMembers = moduleBeingBuilt.EncSymbolChanges.GetAllDeletedMethods(); // Mapping from previous compilation to the current. var anonymousTypeMap = moduleBeingBuilt.GetAnonymousTypeMap(); @@ -193,10 +195,14 @@ private static EmitBaseline MapToCompilation( sourceContext, compilation.SourceAssembly, otherContext, - currentSynthesizedMembers); + currentSynthesizedMembers, + currentDeletedMembers); var mappedSynthesizedMembers = matcher.MapSynthesizedMembers(previousGeneration.SynthesizedMembers, currentSynthesizedMembers); + // Deleted members are mapped the same way as synthesized members, so we can just call the same method. + var mappedDeletedMembers = matcher.MapSynthesizedMembers(previousGeneration.DeletedMembers, currentDeletedMembers); + // TODO: can we reuse some data from the previous matcher? var matcherWithAllSynthesizedMembers = new CSharpSymbolMatcher( anonymousTypeMap, @@ -206,13 +212,15 @@ private static EmitBaseline MapToCompilation( sourceContext, compilation.SourceAssembly, otherContext, - mappedSynthesizedMembers); + mappedSynthesizedMembers, + mappedDeletedMembers); return matcherWithAllSynthesizedMembers.MapBaselineToCompilation( previousGeneration, compilation, moduleBeingBuilt, - mappedSynthesizedMembers); + mappedSynthesizedMembers, + mappedDeletedMembers); } } } diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs index efcb150be20da..9677abb570fec 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/PEDeltaAssemblyBuilder.cs @@ -72,7 +72,8 @@ public PEDeltaAssemblyBuilder( sourceContext: context, otherAssembly: previousAssembly, otherContext: previousContext, - otherSynthesizedMembersOpt: previousGeneration.SynthesizedMembers); + otherSynthesizedMembers: previousGeneration.SynthesizedMembers, + otherDeletedMembers: previousGeneration.DeletedMembers); } _previousDefinitions = new CSharpDefinitionMap(edits, metadataDecoder, matchToMetadata, matchToPrevious); diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.GenerationInfo.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.GenerationInfo.cs index 28bed0b6dcc21..32a7eaeb49179 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.GenerationInfo.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.GenerationInfo.cs @@ -5,6 +5,7 @@ using System; using System.Reflection.Metadata; using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Test.Utilities; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests { @@ -16,13 +17,15 @@ internal sealed class GenerationInfo public readonly MetadataReader MetadataReader; public readonly EmitBaseline Baseline; public readonly Action Verifier; + public readonly CompilationDifference? CompilationDifference; - public GenerationInfo(CSharpCompilation compilation, MetadataReader reader, EmitBaseline baseline, Action verifier) + public GenerationInfo(CSharpCompilation compilation, MetadataReader reader, CompilationDifference? diff, EmitBaseline baseline, Action verifier) { Compilation = compilation; MetadataReader = reader; Baseline = baseline; Verifier = verifier; + CompilationDifference = diff; } } } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs index 7a0ea905875d6..8572f285c8403 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.GenerationVerifier.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; +using Microsoft.CodeAnalysis.CSharp.UnitTests; using Roslyn.Test.Utilities; using static Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests.EditAndContinueTestBase; @@ -18,12 +19,14 @@ internal sealed class GenerationVerifier private readonly int _ordinal; private readonly MetadataReader _metadataReader; private readonly IEnumerable _readers; + private readonly GenerationInfo _generationInfo; - public GenerationVerifier(int ordinal, MetadataReader metadataReader, IEnumerable readers) + public GenerationVerifier(int ordinal, GenerationInfo generationInfo, IEnumerable readers) { _ordinal = ordinal; - _metadataReader = metadataReader; + _metadataReader = generationInfo.MetadataReader; _readers = readers; + _generationInfo = generationInfo; } private string GetAssertMessage(string message) @@ -91,6 +94,11 @@ internal void VerifyCustomAttributes(IEnumerable expected) { AssertEx.Equal(expected, _metadataReader.GetCustomAttributeRows(), itemInspector: AttributeRowToString); } + + internal void VerifyIL(string expectedIL) + { + _generationInfo.CompilationDifference!.VerifyIL(expectedIL); + } } } } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.cs index 7f29dea8f15fd..04447d026e1da 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTest.cs @@ -45,7 +45,7 @@ internal EditAndContinueTest AddGeneration(string source, Action GetSemanticEdits(SemanticEditDescription[] edits, Compilation oldCompilation, Compilation newCompilation) { - return ImmutableArray.CreateRange(edits.Select(e => new SemanticEdit(e.Kind, e.SymbolProvider(oldCompilation), e.SymbolProvider(newCompilation)))); + return ImmutableArray.CreateRange(edits.Select(e => new SemanticEdit(e.Kind, e.SymbolProvider(oldCompilation), e.NewSymbolProvider(newCompilation)))); } public void Dispose() diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs index 0ffe5bc342333..0990411a4ccea 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs @@ -129,8 +129,8 @@ internal static StatementSyntax GetNearestStatement(SyntaxNode node) return null; } - internal static SemanticEditDescription Edit(SemanticEditKind kind, Func symbolProvider) - => new(kind, symbolProvider); + internal static SemanticEditDescription Edit(SemanticEditKind kind, Func symbolProvider, Func newSymbolProvider = null) + => new(kind, symbolProvider, newSymbolProvider); internal static EditAndContinueLogEntry Row(int rowNumber, TableIndex table, EditAndContinueOperation operation) { diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index bb3c25cdde362..4a9568a43ef6e 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -13161,5 +13161,560 @@ .maxstack 1 IL_0002: ret }"); } + + [Theory] + [InlineData("void M1() { }", 0)] + [InlineData("void M1(string s) { }", 1)] + [InlineData("void M1(C c) { }", 1)] + [InlineData("C M1(C c) { return default; }", 1)] + [InlineData("void M1(N n) { }", 1)] + [InlineData("C M1() { return default; }", 0)] + [InlineData("N M1() { return default; }", 0)] + [InlineData("int M1(C c) { return 0; }", 1)] + [InlineData("void M1(T t) { }", 1)] + [InlineData("void M1(T t) where T : C { }", 1)] + [InlineData("T M1() { return default; }", 0)] + [InlineData("T M1() where T : C { return default; }", 0)] + public void Method_Delete(string methodDef, int parameterCount) + { + using var _ = new EditAndContinueTest(options: TestOptions.DebugDll, targetFramework: TargetFramework.NetStandard20) + .AddGeneration( + source: $$""" + class C + { + {{methodDef}} + } + + class N + { + } + """, + validator: g => + { + g.VerifyTypeDefNames("", "C", "N"); + g.VerifyMethodDefNames("M1", ".ctor", ".ctor"); + g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor"); + }) + + .AddGeneration( + source: """ + class C + { + } + + class N + { + } + """, + edits: new[] { + Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.M1"), newSymbolProvider: c => c.GetMember("C")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M1"); + g.VerifyMemberRefNames(/* MissingMethodException */ ".ctor"); + g.VerifyEncLogDefinitions(new[] + { + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default) + }.Concat(Enumerable.Range(1, parameterCount).Select(i => Row(i, TableIndex.Param, EditAndContinueOperation.Default)))); + g.VerifyEncMapDefinitions(new[] + { + Handle(1, TableIndex.MethodDef), + }.Concat(Enumerable.Range(1, parameterCount).Select(i => Handle(i, TableIndex.Param)))); + + var expectedIL = """ + { + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000005 + IL_0005: throw + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + }) + .Verify(); + } + + [Fact] + public void Method_AddThenDelete() + { + using var _ = new EditAndContinueTest(options: TestOptions.DebugDll, targetFramework: TargetFramework.NetStandard20) + .AddGeneration( + source: $$""" + class C + { + void M1() { } + } + """, + validator: g => + { + g.VerifyTypeDefNames("", "C"); + g.VerifyMethodDefNames("M1", ".ctor"); + g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor"); + }) + + .AddGeneration( + source: """ + class C + { + void M1() { } + void M2() { } + } + """, + edits: new[] { + Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.M2")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M2"); + g.VerifyEncLogDefinitions(new[] + { + Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(3, TableIndex.MethodDef), + }); + + var expectedIL = """ + { + // Code size 2 (0x2) + .maxstack 8 + IL_0000: nop + IL_0001: ret + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + }) + + .AddGeneration( + source: """ + class C + { + void M1() { } + } + """, + edits: new[] { + Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.M2"), newSymbolProvider: c=>c.GetMember("C")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M2"); + g.VerifyEncLogDefinitions(new[] + { + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(3, TableIndex.MethodDef), + }); + + var expectedIL = """ + { + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000005 + IL_0005: throw + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + }) + .Verify(); + } + + [Fact] + public void Method_DeleteThenAdd() + { + using var _ = new EditAndContinueTest(options: TestOptions.DebugDll, targetFramework: TargetFramework.NetStandard20) + .AddGeneration( + source: $$""" + class C + { + void M1() { } + } + """, + validator: g => + { + g.VerifyTypeDefNames("", "C"); + g.VerifyMethodDefNames("M1", ".ctor"); + g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor"); + }) + + .AddGeneration( + source: """ + class C + { + } + """, + edits: new[] { + Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.M1"), newSymbolProvider: c=>c.GetMember("C")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M1"); + g.VerifyEncLogDefinitions(new[] + { + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(1, TableIndex.MethodDef), + }); + + var expectedIL = """ + { + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000005 + IL_0005: throw + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + }) + + .AddGeneration( + source: """ + class C + { + void M1() { System.Console.Write(1); } + } + """, + edits: new[] { + Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.M1")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M1"); + g.VerifyEncLogDefinitions(new[] + { + Row(1, TableIndex.MethodDef, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(1, TableIndex.MethodDef), + }); + + var expectedIL = """ + { + // Code size 9 (0x9) + .maxstack 8 + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: call 0x0A000006 + IL_0007: nop + IL_0008: ret + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + }) + .Verify(); + } + + [Fact] + public void Method_DeleteThenAdd_WithAttributes() + { + using var _ = new EditAndContinueTest(options: TestOptions.DebugDll, targetFramework: TargetFramework.NetStandard20) + .AddGeneration( + source: $$""" + class A : System.Attribute { } + class B : System.Attribute { } + + class C + { + [A] + [return: A] + void M1([A]int x) { } + } + """, + validator: g => + { + g.VerifyTypeDefNames("", "A", "B", "C"); + g.VerifyMethodDefNames(".ctor", ".ctor", "M1", ".ctor"); + g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor", ".ctor"); + }) + + .AddGeneration( + source: """ + class A : System.Attribute { } + class B : System.Attribute { } + + class C + { + } + """, + edits: new[] { + Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.M1"), newSymbolProvider: c=>c.GetMember("C")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M1"); + g.VerifyEncLogDefinitions(new[] + { + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(2, TableIndex.Param, EditAndContinueOperation.Default), + Row(1, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(3, TableIndex.MethodDef), + Handle(1, TableIndex.Param), + Handle(2, TableIndex.Param), + Handle(1, TableIndex.CustomAttribute), + Handle(5, TableIndex.CustomAttribute), + Handle(6, TableIndex.CustomAttribute) + }); + g.VerifyCustomAttributes(new[] +{ + new CustomAttributeRow(Handle(1, TableIndex.Param), Handle(6, TableIndex.MemberRef)), + new CustomAttributeRow(Handle(2, TableIndex.Param), Handle(6, TableIndex.MemberRef)), + new CustomAttributeRow(Handle(3, TableIndex.MethodDef), Handle(6, TableIndex.MemberRef)) + }); + + var expectedIL = """ + { + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000007 + IL_0005: throw + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + }) + + .AddGeneration( + source: """ + class A : System.Attribute { } + class B : System.Attribute { } + + class C + { + [B] + [return: B] + void M1([B]int x) { } + + } + """, + edits: new[] { + Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.M1")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M1"); + g.VerifyEncLogDefinitions(new[] + { + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(2, TableIndex.Param, EditAndContinueOperation.Default), + Row(1, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(5, TableIndex.CustomAttribute, EditAndContinueOperation.Default), + Row(6, TableIndex.CustomAttribute, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(3, TableIndex.MethodDef), + Handle(1, TableIndex.Param), + Handle(2, TableIndex.Param), + Handle(1, TableIndex.CustomAttribute), + Handle(5, TableIndex.CustomAttribute), + Handle(6, TableIndex.CustomAttribute) + }); + g.VerifyCustomAttributes(new[] + { + new CustomAttributeRow(Handle(1, TableIndex.Param), Handle(2, TableIndex.MethodDef)), + new CustomAttributeRow(Handle(2, TableIndex.Param), Handle(2, TableIndex.MethodDef)), + new CustomAttributeRow(Handle(3, TableIndex.MethodDef), Handle(2, TableIndex.MethodDef)) + }); + + var expectedIL = """ + { + // Code size 2 (0x2) + .maxstack 8 + IL_0000: nop + IL_0001: ret + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + }) + .Verify(); + } + + [Fact] + public void Method_AddThenDeleteThenAdd() + { + using var _ = new EditAndContinueTest(options: TestOptions.DebugDll, targetFramework: TargetFramework.NetStandard20) + .AddGeneration( + source: $$""" + class C + { + void Goo() { } + } + """, + validator: g => + { + g.VerifyTypeDefNames("", "C"); + g.VerifyMethodDefNames("Goo", ".ctor"); + g.VerifyMemberRefNames(/*CompilationRelaxationsAttribute.*/".ctor", /*RuntimeCompatibilityAttribute.*/".ctor", /*Object.*/".ctor", /*DebuggableAttribute*/".ctor"); + }) + + .AddGeneration( + source: """ + class C + { + void Goo() { } + C M1(C c) { return default; } + } + """, + edits: new[] { + Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.M1")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M1"); + g.VerifyEncLogDefinitions(new[] + { + Row(1, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(2, TableIndex.TypeDef, EditAndContinueOperation.AddMethod), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.AddParameter), + Row(1, TableIndex.Param, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(3, TableIndex.MethodDef), + Handle(1, TableIndex.Param), + Handle(1, TableIndex.StandAloneSig) + }); + + var expectedIL = """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: nop + IL_0001: ldnull + 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); + }) + + .AddGeneration( + source: """ + class C + { + void Goo() { } + } + """, + edits: new[] { + Edit(SemanticEditKind.Delete, symbolProvider: c => c.GetMember("C.M1"), newSymbolProvider: c=>c.GetMember("C")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M1"); + g.VerifyEncLogDefinitions(new[] + { + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(3, TableIndex.MethodDef), + Handle(1, TableIndex.Param), + }); + + var expectedIL = """ + { + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj 0x0A000005 + IL_0005: throw + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + + }) + + .AddGeneration( + source: """ + class C + { + void Goo() { } + C M1(C b) { System.Console.Write(1); return default; } + } + """, + edits: new[] { + Edit(SemanticEditKind.Insert, symbolProvider: c => c.GetMember("C.M1")), + }, + validator: g => + { + g.VerifyTypeDefNames(); + g.VerifyMethodDefNames("M1"); + g.VerifyEncLogDefinitions(new[] + { + Row(2, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(3, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default) + }); + g.VerifyEncMapDefinitions(new[] + { + Handle(3, TableIndex.MethodDef), + Handle(1, TableIndex.Param), + Handle(2, TableIndex.StandAloneSig) + }); + + var expectedIL = """ + { + // Code size 14 (0xe) + .maxstack 1 + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: call 0x0A000006 + IL_0007: nop + IL_0008: ldnull + IL_0009: stloc.0 + IL_000a: br.s IL_000c + IL_000c: ldloc.0 + IL_000d: ret + } + """; + + // Can't verify the IL of individual methods because that requires IMethodSymbolInternal implementations + g.VerifyIL(expectedIL); + }) + .Verify(); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SemanticEditDescription.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SemanticEditDescription.cs index ad116d59ad308..d0921ded4ed65 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SemanticEditDescription.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SemanticEditDescription.cs @@ -11,13 +11,16 @@ internal sealed class SemanticEditDescription { public readonly SemanticEditKind Kind; public readonly Func SymbolProvider; + public readonly Func NewSymbolProvider; public SemanticEditDescription( SemanticEditKind kind, - Func symbolProvider) + Func symbolProvider, + Func? newSymbolProvider = null) { Kind = kind; SymbolProvider = symbolProvider; + NewSymbolProvider = newSymbolProvider ?? symbolProvider; } } } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs index ea1b221a7b186..adb9de01ffc9b 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs @@ -77,6 +77,7 @@ interface I { } default, compilation0.SourceAssembly, default, + null, null); var tasks = new Task[10]; @@ -136,6 +137,7 @@ struct S default, compilation0.SourceAssembly, default, + null, null); var members = compilation1.GetMember("A.B").GetMembers("M"); Assert.Equal(2, members.Length); @@ -170,6 +172,7 @@ static void M(I o) where T : I default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.M"); var other = matcher.MapDefinition(member.GetCciAdapter()); @@ -206,6 +209,7 @@ .method public abstract virtual instance object modopt(A) [] F(int32 modopt(obje default, compilation0.SourceAssembly, default, + null, null); var other = (MethodSymbol)matcher.MapDefinition(member1.GetCciAdapter()).GetInternalSymbol(); @@ -251,6 +255,7 @@ abstract class C default, compilation0.SourceAssembly, default, + null, null); var f0 = compilation0.GetMember("C.F"); @@ -341,6 +346,7 @@ public void F(D a) {} default, compilation0.SourceAssembly, default, + null, null); var f0 = compilation0.GetMember("C.F"); @@ -383,6 +389,7 @@ class D {} default, compilation0.SourceAssembly, default, + null, null); var elementType = compilation1.GetMember("C.D"); var member = compilation1.CreateArrayTypeSymbol(elementType); @@ -422,6 +429,7 @@ class D {} default, compilation0.SourceAssembly, default, + null, null); var elementType = compilation1.GetMember("C.D"); var member = compilation1.CreateArrayTypeSymbol(elementType); @@ -462,6 +470,7 @@ struct D {} default, compilation0.SourceAssembly, default, + null, null); var elementType = compilation1.GetMember("C.D"); var member = compilation1.CreatePointerTypeSymbol(elementType); @@ -505,6 +514,7 @@ class D {} default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.y"); var other = matcher.MapReference((Cci.ITypeReference)member.Type.GetCciAdapter()); @@ -672,6 +682,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.x"); @@ -704,6 +715,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.x"); @@ -736,6 +748,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.X"); @@ -768,6 +781,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.X"); @@ -800,6 +814,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.X"); @@ -832,6 +847,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.X"); @@ -864,6 +880,7 @@ public struct Vector default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("Vector.Coordinates"); @@ -896,6 +913,7 @@ public struct Vector default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("Vector.Coordinates"); @@ -928,6 +946,7 @@ public class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.F"); @@ -960,6 +979,7 @@ public class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.F"); @@ -1006,6 +1026,7 @@ struct C default, compilation0.SourceAssembly, default, + null, null); var s0 = compilation0.GetMember("C.S"); @@ -1062,6 +1083,7 @@ struct C default, compilation0.SourceAssembly, default, + null, null); var s0 = compilation0.GetMember("C.S"); @@ -1153,6 +1175,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.M"); @@ -1186,6 +1209,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.M"); @@ -1219,6 +1243,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.M"); @@ -1250,6 +1275,7 @@ class C default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("C.S"); @@ -1363,6 +1389,7 @@ event Action F { add { } remove { } } default, compilation0.SourceAssembly, default, + null, null); var x0 = compilation0.GetMember("I.X"); @@ -1420,6 +1447,7 @@ unsafe class C default, compilation0.SourceAssembly, default, + null, null); for (int i = 1; i <= 7; i++) @@ -1462,6 +1490,7 @@ unsafe class C default, compilation0.SourceAssembly, default, + null, null); var f_1 = compilation1.GetMember($"C.f1"); @@ -1521,6 +1550,7 @@ static void verify(string source1, string source2) default, compilation0.SourceAssembly, default, + null, null); var f_1 = compilation1.GetMember($"C.f1"); @@ -1552,6 +1582,7 @@ public record R default, compilation0.SourceAssembly, default, + null, null); var member = compilation1.GetMember("R.ToString"); @@ -1582,6 +1613,7 @@ public record R default, compilation0.SourceAssembly, default, + null, null); var member0 = compilation0.GetMember("R.PrintMembers"); @@ -1613,6 +1645,7 @@ public record R default, compilation0.SourceAssembly, default, + null, null); var member0 = compilation0.GetMember("R.PrintMembers"); @@ -1642,6 +1675,7 @@ public record R(int X) default, compilation0.SourceAssembly, default, + null, null); var member0 = compilation0.GetMember("R.X"); @@ -1676,6 +1710,7 @@ public R(int X) default, compilation0.SourceAssembly, default, + null, null); var members = compilation1.GetMembers("R..ctor"); @@ -1840,6 +1875,7 @@ class A default, compilation0.SourceAssembly, default, + null, null); var members1 = compilation1.GetMember("A").GetMembers().OfType().Where(m => m.MethodKind is (MethodKind.Conversion or MethodKind.UserDefinedOperator)).ToArray(); var members0 = compilation0.GetMember("A").GetMembers().OfType().Where(m => m.MethodKind is (MethodKind.Conversion or MethodKind.UserDefinedOperator)).ToArray(); @@ -1890,6 +1926,7 @@ class A default, compilation0.SourceAssembly, default, + null, null); var members1 = compilation1.GetMember("A").GetMembers().OfType().Where(m => m.MethodKind is (MethodKind.Conversion or MethodKind.UserDefinedOperator)).ToArray(); var members0 = compilation0.GetMember("A").GetMembers().OfType().Where(m => m.MethodKind is (MethodKind.Conversion or MethodKind.UserDefinedOperator)).ToArray(); @@ -1950,6 +1987,7 @@ class A default, compilation0.SourceAssembly, default, + null, null); var members1 = compilation1.GetMember("A").GetMembers().OfType().Where(m => m.MethodKind is (MethodKind.Conversion or MethodKind.UserDefinedOperator)).ToArray(); var members0 = compilation0.GetMember("A").GetMembers().OfType().Where(m => m.MethodKind is (MethodKind.Conversion or MethodKind.UserDefinedOperator)).ToArray(); @@ -2005,6 +2043,7 @@ class A default, compilation0.SourceAssembly, default, + null, null); var members1 = compilation1.GetMember("A").GetMembers().OfType().Where(m => m.MethodKind is (MethodKind.Conversion or MethodKind.UserDefinedOperator)).ToArray(); Assert.Equal(6, members1.Length); @@ -2058,6 +2097,7 @@ class A default, compilation0.SourceAssembly, default, + null, null); var members1 = compilation1.GetMember("A").GetMembers().OfType().Where(m => m.MethodKind is (MethodKind.Conversion or MethodKind.UserDefinedOperator)).ToArray(); Assert.Equal(6, members1.Length); diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedCustomAttribute.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedCustomAttribute.cs new file mode 100644 index 0000000000000..45c08466dbb2b --- /dev/null +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedCustomAttribute.cs @@ -0,0 +1,48 @@ +// 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; + +namespace Microsoft.CodeAnalysis.Emit.EditAndContinue +{ + internal sealed class DeletedCustomAttribute : ICustomAttribute + { + private readonly ICustomAttribute _oldAttribute; + private readonly Dictionary _typesUsedByDeletedMembers; + + public DeletedCustomAttribute(ICustomAttribute oldAttribute, Dictionary typesUsedByDeletedMembers) + { + _oldAttribute = oldAttribute; + _typesUsedByDeletedMembers = typesUsedByDeletedMembers; + } + + public int ArgumentCount => _oldAttribute.ArgumentCount; + + public ushort NamedArgumentCount => _oldAttribute.NamedArgumentCount; + + public bool AllowMultiple => _oldAttribute.AllowMultiple; + + public IMethodReference Constructor(EmitContext context, bool reportDiagnostics) + { + return _oldAttribute.Constructor(context, reportDiagnostics); + } + + public ImmutableArray GetArguments(EmitContext context) + { + return _oldAttribute.GetArguments(context); + } + + public ImmutableArray GetNamedArguments(EmitContext context) + { + return _oldAttribute.GetNamedArguments(context); + } + + public ITypeReference GetType(EmitContext context) + { + return DeletedTypeDefinition.TryCreate(_oldAttribute.GetType(context), _typesUsedByDeletedMembers); + } + } +} diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedGenericParameter.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedGenericParameter.cs new file mode 100644 index 0000000000000..4b6d5b1e8f8c4 --- /dev/null +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedGenericParameter.cs @@ -0,0 +1,110 @@ +// 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.Reflection.Metadata; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.Symbols; + +namespace Microsoft.CodeAnalysis.Emit.EditAndContinue +{ + internal sealed class DeletedGenericParameter : IGenericMethodParameter + { + private readonly IGenericMethodParameter _oldParameter; + private readonly DeletedMethodDefinition _method; + private readonly Dictionary _typesUsedByDeletedMembers; + + public DeletedGenericParameter(IGenericMethodParameter oldParameter, DeletedMethodDefinition method, Dictionary typesUsedByDeletedMembers) + { + _oldParameter = oldParameter; + _method = method; + _typesUsedByDeletedMembers = typesUsedByDeletedMembers; + } + + public IMethodDefinition DefiningMethod => _method; + + public bool MustBeReferenceType => _oldParameter.MustBeReferenceType; + + public bool MustBeValueType => _oldParameter.MustBeValueType; + + public bool MustHaveDefaultConstructor => _oldParameter.MustHaveDefaultConstructor; + + public TypeParameterVariance Variance => _oldParameter.Variance; + + public IGenericMethodParameter? AsGenericMethodParameter => _oldParameter.AsGenericMethodParameter; + + public IGenericTypeParameter? AsGenericTypeParameter => _oldParameter.AsGenericTypeParameter; + + public bool IsEnum => _oldParameter.IsEnum; + + public bool IsValueType => _oldParameter.IsValueType; + + public Cci.PrimitiveTypeCode TypeCode => _oldParameter.TypeCode; + + public TypeDefinitionHandle TypeDef => _oldParameter.TypeDef; + + public IGenericMethodParameterReference? AsGenericMethodParameterReference => _oldParameter.AsGenericMethodParameterReference; + + public IGenericTypeInstanceReference? AsGenericTypeInstanceReference => _oldParameter.AsGenericTypeInstanceReference; + + public IGenericTypeParameterReference? AsGenericTypeParameterReference => _oldParameter.AsGenericTypeParameterReference; + + public INamespaceTypeReference? AsNamespaceTypeReference => _oldParameter.AsNamespaceTypeReference; + + public INestedTypeReference? AsNestedTypeReference => _oldParameter.AsNestedTypeReference; + + public ISpecializedNestedTypeReference? AsSpecializedNestedTypeReference => _oldParameter.AsSpecializedNestedTypeReference; + + public string? Name => _oldParameter.Name; + + public ushort Index => _oldParameter.Index; + + IMethodReference IGenericMethodParameterReference.DefiningMethod => ((IGenericMethodParameterReference)_oldParameter).DefiningMethod; + + public IDefinition? AsDefinition(EmitContext context) + { + return _oldParameter.AsDefinition(context); + } + + public INamespaceTypeDefinition? AsNamespaceTypeDefinition(EmitContext context) + { + return _oldParameter.AsNamespaceTypeDefinition(context); + } + + public INestedTypeDefinition? AsNestedTypeDefinition(EmitContext context) + { + return _oldParameter.AsNestedTypeDefinition(context); + } + + public ITypeDefinition? AsTypeDefinition(EmitContext context) + { + return _oldParameter.AsTypeDefinition(context); + } + + public void Dispatch(MetadataVisitor visitor) + { + _oldParameter.Dispatch(visitor); + } + + public IEnumerable GetAttributes(EmitContext context) + { + return _oldParameter.GetAttributes(context); + } + + public IEnumerable GetConstraints(EmitContext context) + { + return _oldParameter.GetConstraints(context); + } + + public ISymbolInternal? GetInternalSymbol() + { + return _oldParameter.GetInternalSymbol(); + } + + public ITypeDefinition? GetResolvedType(EmitContext context) + { + return (ITypeDefinition?)DeletedTypeDefinition.TryCreate(_oldParameter.GetResolvedType(context), _typesUsedByDeletedMembers); + } + } +} diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedMethodBody.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedMethodBody.cs new file mode 100644 index 0000000000000..7edd6c109d5df --- /dev/null +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedMethodBody.cs @@ -0,0 +1,85 @@ +// 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. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Text; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.CodeGen; +using Microsoft.CodeAnalysis.Debugging; + +namespace Microsoft.CodeAnalysis.Emit.EditAndContinue +{ + internal sealed class DeletedMethodBody : IMethodBody + { + private readonly DeletedMethodDefinition _methodDef; + private readonly ImmutableArray _ilBytes; + + public DeletedMethodBody(DeletedMethodDefinition methodDef, EmitContext context) + { + _methodDef = methodDef; + _ilBytes = GetIL(context); + } + + public ImmutableArray ExceptionRegions => ImmutableArray.Empty; + + public bool AreLocalsZeroed => false; + + public bool HasStackalloc => false; + + public ImmutableArray LocalVariables => ImmutableArray.Empty; + + public IMethodDefinition MethodDefinition => _methodDef; + + public StateMachineMoveNextBodyDebugInfo MoveNextBodyInfo => null; + + public ushort MaxStack => 8; + + public ImmutableArray IL => _ilBytes; + + public ImmutableArray SequencePoints => ImmutableArray.Empty; + + public bool HasDynamicLocalVariables => false; + + public ImmutableArray LocalScopes => ImmutableArray.Empty; + + public Cci.IImportScope ImportScope => null; + + public DebugId MethodId => default; + + public ImmutableArray StateMachineHoistedLocalScopes => ImmutableArray.Empty; + + public string StateMachineTypeName => null; + + public ImmutableArray StateMachineHoistedLocalSlots => default; + + public ImmutableArray StateMachineAwaiterSlots => default; + + public ImmutableArray ClosureDebugInfo => ImmutableArray.Empty; + + public ImmutableArray LambdaDebugInfo => ImmutableArray.Empty; + + public DynamicAnalysisMethodBodyData DynamicAnalysisData => null; + + public StateMachineStatesDebugInfo StateMachineStatesDebugInfo => default; + + private static ImmutableArray GetIL(EmitContext context) + { + var missingMethodExceptionStringStringConstructor = context.Module.CommonCompilation.CommonGetWellKnownTypeMember(WellKnownMember.System_MissingMethodException__ctor); + Debug.Assert(missingMethodExceptionStringStringConstructor is not null); + + var builder = new ILBuilder((ITokenDeferral)context.Module, null, OptimizationLevel.Debug, false); + builder.EmitOpCode(System.Reflection.Metadata.ILOpCode.Newobj, 4); + builder.EmitToken(missingMethodExceptionStringStringConstructor.GetCciAdapter(), context.SyntaxNode!, context.Diagnostics); + builder.EmitThrow(isRethrow: false); + builder.Realize(); + + return builder.RealizedIL; + } + } +} diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedMethodDefinition.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedMethodDefinition.cs new file mode 100644 index 0000000000000..bfca472bd4d72 --- /dev/null +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedMethodDefinition.cs @@ -0,0 +1,179 @@ +// 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 System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.Symbols; + +namespace Microsoft.CodeAnalysis.Emit.EditAndContinue +{ + internal sealed class DeletedMethodDefinition : IMethodDefinition + { + private readonly IMethodDefinition _oldMethod; + private readonly ITypeDefinition _containingTypeDef; + private readonly Dictionary _typesUsedByDeletedMembers; + private readonly ImmutableArray _parameters; + private DeletedMethodBody? _body; + + public DeletedMethodDefinition(IMethodDefinition oldMethod, ITypeDefinition containingTypeDef, Dictionary typesUsedByDeletedMembers) + { + _oldMethod = oldMethod; + _containingTypeDef = containingTypeDef; + _typesUsedByDeletedMembers = typesUsedByDeletedMembers; + + _parameters = _oldMethod.Parameters.SelectAsArray(p => new DeletedParameterDefinition(p, typesUsedByDeletedMembers)); + } + + public IEnumerable GenericParameters + { + get + { + return _oldMethod.GenericParameters.Select(gp => new DeletedGenericParameter(gp, this, _typesUsedByDeletedMembers)); + } + } + + public bool HasDeclarativeSecurity => _oldMethod.HasDeclarativeSecurity; + + public bool IsAbstract => _oldMethod.IsAbstract; + + public bool IsAccessCheckedOnOverride => _oldMethod.IsAccessCheckedOnOverride; + + public bool IsConstructor => _oldMethod.IsConstructor; + + public bool IsExternal => _oldMethod.IsExternal; + + public bool IsHiddenBySignature => _oldMethod.IsHiddenBySignature; + + public bool IsNewSlot => _oldMethod.IsNewSlot; + + public bool IsPlatformInvoke => _oldMethod.IsPlatformInvoke; + + public bool IsRuntimeSpecial => _oldMethod.IsRuntimeSpecial; + + public bool IsSealed => _oldMethod.IsSealed; + + public bool IsSpecialName => _oldMethod.IsSpecialName; + + public bool IsStatic => _oldMethod.IsStatic; + + public bool IsVirtual => _oldMethod.IsVirtual; + + public ImmutableArray Parameters => StaticCast.From(_parameters); + + public IPlatformInvokeInformation PlatformInvokeData => _oldMethod.PlatformInvokeData; + + public bool RequiresSecurityObject => _oldMethod.RequiresSecurityObject; + + public bool ReturnValueIsMarshalledExplicitly => _oldMethod.ReturnValueIsMarshalledExplicitly; + + public IMarshallingInformation ReturnValueMarshallingInformation => _oldMethod.ReturnValueMarshallingInformation; + + public ImmutableArray ReturnValueMarshallingDescriptor => _oldMethod.ReturnValueMarshallingDescriptor; + + public IEnumerable SecurityAttributes => _oldMethod.SecurityAttributes; + + public INamespace ContainingNamespace => _oldMethod.ContainingNamespace; + + public ITypeDefinition ContainingTypeDefinition => _containingTypeDef; + + public TypeMemberVisibility Visibility => _oldMethod.Visibility; + + public bool AcceptsExtraArguments => _oldMethod.AcceptsExtraArguments; + + public ushort GenericParameterCount => _oldMethod.GenericParameterCount; + + public bool IsGeneric => _oldMethod.IsGeneric; + + public ImmutableArray ExtraParameters => _oldMethod.ExtraParameters; + + public IGenericMethodInstanceReference? AsGenericMethodInstanceReference => _oldMethod.AsGenericMethodInstanceReference; + + public ISpecializedMethodReference? AsSpecializedMethodReference => _oldMethod.AsSpecializedMethodReference; + + public CallingConvention CallingConvention => _oldMethod.CallingConvention; + + public ushort ParameterCount => (ushort)_parameters.Length; + + public ImmutableArray ReturnValueCustomModifiers => _oldMethod.ReturnValueCustomModifiers; + + public ImmutableArray RefCustomModifiers => _oldMethod.RefCustomModifiers; + + public bool ReturnValueIsByRef => _oldMethod.ReturnValueIsByRef; + + public string? Name => _oldMethod.Name; + + public IDefinition? AsDefinition(EmitContext context) + { + return _oldMethod.AsDefinition(context); + } + + public void Dispatch(MetadataVisitor visitor) + { + visitor.Visit(this); + } + + public IEnumerable GetAttributes(EmitContext context) + { + return _oldMethod.GetAttributes(context).Select(a => new DeletedCustomAttribute(a, _typesUsedByDeletedMembers)); + } + + public IMethodBody GetBody(EmitContext context) + { + _body ??= new DeletedMethodBody(this, context); + return _body; + } + + public ITypeReference GetContainingType(EmitContext context) + { + return _containingTypeDef; + } + + public MethodImplAttributes GetImplementationAttributes(EmitContext context) + { + return _oldMethod.GetImplementationAttributes(context); + } + + public ISymbolInternal? GetInternalSymbol() + { + return _oldMethod.GetInternalSymbol(); + } + + public ImmutableArray GetParameters(EmitContext context) + { + return StaticCast.From(_parameters); + } + + public IMethodDefinition GetResolvedMethod(EmitContext context) + { + return this; + } + + public IEnumerable GetReturnValueAttributes(EmitContext context) + { + return _oldMethod.GetReturnValueAttributes(context).Select(a => new DeletedCustomAttribute(a, _typesUsedByDeletedMembers)); + } + + public ITypeReference GetType(EmitContext context) + { + return DeletedTypeDefinition.TryCreate(_oldMethod.GetType(context), _typesUsedByDeletedMembers); + } + + public sealed override bool Equals(object? obj) + { + // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. + throw Roslyn.Utilities.ExceptionUtilities.Unreachable; + } + + public sealed override int GetHashCode() + { + // It is not supported to rely on default equality of these Cci objects, an explicit way to compare and hash them should be used. + throw Roslyn.Utilities.ExceptionUtilities.Unreachable; + } + } +} diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedParameterDefinition.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedParameterDefinition.cs new file mode 100644 index 0000000000000..3b9121e121cf0 --- /dev/null +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedParameterDefinition.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.CodeGen; +using Microsoft.CodeAnalysis.Symbols; + +namespace Microsoft.CodeAnalysis.Emit.EditAndContinue +{ + internal sealed class DeletedParameterDefinition : IParameterDefinition + { + private readonly IParameterDefinition _oldParameter; + private readonly Dictionary _typesUsedByDeletedMembers; + + public DeletedParameterDefinition(IParameterDefinition oldParameter, Dictionary typesUsedByDeletedMembers) + { + _oldParameter = oldParameter; + _typesUsedByDeletedMembers = typesUsedByDeletedMembers; + } + + public bool HasDefaultValue => _oldParameter.HasDefaultValue; + + public bool IsIn => _oldParameter.IsIn; + + public bool IsMarshalledExplicitly => _oldParameter.IsMarshalledExplicitly; + + public bool IsOptional => _oldParameter.IsOptional; + + public bool IsOut => _oldParameter.IsOut; + + public IMarshallingInformation? MarshallingInformation => _oldParameter.MarshallingInformation; + + public ImmutableArray MarshallingDescriptor => _oldParameter.MarshallingDescriptor; + + public string? Name => _oldParameter.Name; + + public ImmutableArray CustomModifiers => _oldParameter.CustomModifiers; + + public ImmutableArray RefCustomModifiers => _oldParameter.RefCustomModifiers; + + public bool IsByReference => _oldParameter.IsByReference; + + public ushort Index => _oldParameter.Index; + + public IDefinition? AsDefinition(EmitContext context) + { + return this; + } + + public void Dispatch(MetadataVisitor visitor) + { + _oldParameter.Dispatch(visitor); + } + + public IEnumerable GetAttributes(EmitContext context) + { + return _oldParameter.GetAttributes(context).Select(a => new DeletedCustomAttribute(a, _typesUsedByDeletedMembers)); + } + + public MetadataConstant? GetDefaultValue(EmitContext context) + { + return _oldParameter.GetDefaultValue(context); + } + + public ISymbolInternal? GetInternalSymbol() + { + return _oldParameter.GetInternalSymbol(); + } + + public ITypeReference GetType(EmitContext context) + { + return DeletedTypeDefinition.TryCreate(_oldParameter.GetType(context), _typesUsedByDeletedMembers); + } + } +} diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedTypeDefinition.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedTypeDefinition.cs new file mode 100644 index 0000000000000..5c637f6d9f3ef --- /dev/null +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeletedTypeDefinition.cs @@ -0,0 +1,183 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Reflection.Metadata; +using System.Runtime.InteropServices; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.Symbols; + +namespace Microsoft.CodeAnalysis.Emit.EditAndContinue +{ + /// + /// Represents a type referenced from a deleted member (as distinct from a type that has been deleted) + /// + internal sealed class DeletedTypeDefinition : ITypeDefinition + { + [return: NotNullIfNotNull("typeReference")] + public static ITypeReference? TryCreate(ITypeReference? typeReference, Dictionary cache) + { + if (typeReference is ITypeDefinition typeDef) + { + if (!cache.TryGetValue(typeDef, out var deletedType)) + { + deletedType = new DeletedTypeDefinition(typeDef); + cache.Add(typeDef, deletedType); + } + + return deletedType; + } + + return typeReference; + } + + private readonly ITypeDefinition _oldTypeReference; + + public ITypeDefinition Original => _oldTypeReference; + + private DeletedTypeDefinition(ITypeDefinition typeReference) + { + _oldTypeReference = typeReference; + } + + public ushort Alignment => _oldTypeReference.Alignment; + + public IEnumerable GenericParameters => _oldTypeReference.GenericParameters; + + public ushort GenericParameterCount => _oldTypeReference.GenericParameterCount; + + public bool HasDeclarativeSecurity => _oldTypeReference.HasDeclarativeSecurity; + + public bool IsAbstract => _oldTypeReference.IsAbstract; + + public bool IsBeforeFieldInit => _oldTypeReference.IsBeforeFieldInit; + + public bool IsComObject => _oldTypeReference.IsComObject; + + public bool IsGeneric => _oldTypeReference.IsGeneric; + + public bool IsInterface => _oldTypeReference.IsInterface; + + public bool IsDelegate => _oldTypeReference.IsDelegate; + + public bool IsRuntimeSpecial => _oldTypeReference.IsRuntimeSpecial; + + public bool IsSerializable => _oldTypeReference.IsSerializable; + + public bool IsSpecialName => _oldTypeReference.IsSpecialName; + + public bool IsWindowsRuntimeImport => _oldTypeReference.IsWindowsRuntimeImport; + + public bool IsSealed => _oldTypeReference.IsSealed; + + public LayoutKind Layout => _oldTypeReference.Layout; + + public IEnumerable SecurityAttributes => _oldTypeReference.SecurityAttributes; + + public uint SizeOf => _oldTypeReference.SizeOf; + + public CharSet StringFormat => _oldTypeReference.StringFormat; + + public bool IsEnum => _oldTypeReference.IsEnum; + + public bool IsValueType => _oldTypeReference.IsValueType; + + public Cci.PrimitiveTypeCode TypeCode => _oldTypeReference.TypeCode; + + public TypeDefinitionHandle TypeDef => _oldTypeReference.TypeDef; + + public IGenericMethodParameterReference? AsGenericMethodParameterReference => _oldTypeReference.AsGenericMethodParameterReference; + + public IGenericTypeInstanceReference? AsGenericTypeInstanceReference => _oldTypeReference.AsGenericTypeInstanceReference; + + public IGenericTypeParameterReference? AsGenericTypeParameterReference => _oldTypeReference.AsGenericTypeParameterReference; + + public INamespaceTypeReference? AsNamespaceTypeReference => _oldTypeReference.AsNamespaceTypeReference; + + public INestedTypeReference? AsNestedTypeReference => _oldTypeReference.AsNestedTypeReference; + + public ISpecializedNestedTypeReference? AsSpecializedNestedTypeReference => _oldTypeReference.AsSpecializedNestedTypeReference; + + public IDefinition? AsDefinition(EmitContext context) + { + return this; + } + + public INamespaceTypeDefinition? AsNamespaceTypeDefinition(EmitContext context) + { + return _oldTypeReference.AsNamespaceTypeDefinition(context); + } + + public INestedTypeDefinition? AsNestedTypeDefinition(EmitContext context) + { + return _oldTypeReference.AsNestedTypeDefinition(context); + } + + public ITypeDefinition? AsTypeDefinition(EmitContext context) + { + return this; + } + + public void Dispatch(MetadataVisitor visitor) + { + _oldTypeReference.Dispatch(visitor); + } + + public IEnumerable GetAttributes(EmitContext context) + { + return _oldTypeReference.GetAttributes(context); + } + + public ITypeReference? GetBaseClass(EmitContext context) + { + return _oldTypeReference.GetBaseClass(context); + } + + public IEnumerable GetEvents(EmitContext context) + { + return _oldTypeReference.GetEvents(context); + } + + public IEnumerable GetExplicitImplementationOverrides(EmitContext context) + { + return _oldTypeReference.GetExplicitImplementationOverrides(context); + } + + public IEnumerable GetFields(EmitContext context) + { + return _oldTypeReference.GetFields(context); + } + + public ISymbolInternal? GetInternalSymbol() + { + return _oldTypeReference.GetInternalSymbol(); + } + + public IEnumerable GetMethods(EmitContext context) + { + return _oldTypeReference.GetMethods(context); + } + + public IEnumerable GetNestedTypes(EmitContext context) + { + return _oldTypeReference.GetNestedTypes(context); + } + + public IEnumerable GetProperties(EmitContext context) + { + return _oldTypeReference.GetProperties(context); + } + + public ITypeDefinition? GetResolvedType(EmitContext context) + { + return _oldTypeReference.GetResolvedType(context); + } + + public IEnumerable Interfaces(EmitContext context) + { + return _oldTypeReference.Interfaces(context); + } + } +} diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs index 42c215222b11f..3fe391f9f2513 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DeltaMetadataWriter.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis; using Roslyn.Utilities; using Microsoft.CodeAnalysis.Symbols; +using Microsoft.CodeAnalysis.Emit.EditAndContinue; namespace Microsoft.CodeAnalysis.Emit { @@ -30,6 +31,14 @@ internal sealed class DeltaMetadataWriter : MetadataWriter /// private readonly List _changedTypeDefs; + /// + /// Cache of type definitions used in signatures of deleted members. Used so that if a method 'C M(C c)' is deleted + /// we use the same instance for the method return type, and the parameter type. + /// + private readonly Dictionary _typesUsedByDeletedMembers; + + private readonly Dictionary> _deletedTypeMembers; + private readonly DefinitionIndex _typeDefs; private readonly DefinitionIndex _eventDefs; private readonly DefinitionIndex _fieldDefs; @@ -93,6 +102,8 @@ public DeltaMetadataWriter( var sizes = previousGeneration.TableSizes; _changedTypeDefs = new List(); + _typesUsedByDeletedMembers = 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]); @@ -220,6 +231,7 @@ internal EmitBaseline GetDelta(Compilation compilation, Guid encId, MetadataSize anonymousDelegates: ((IPEDeltaAssemblyBuilder)module).GetAnonymousDelegates(), anonymousDelegatesWithFixedTypes: ((IPEDeltaAssemblyBuilder)module).GetAnonymousDelegatesWithFixedTypes(), synthesizedMembers: synthesizedMembers, + deletedMembers: _previousGeneration.DeletedMembers, addedOrChangedMethods: AddRange(_previousGeneration.AddedOrChangedMethods, addedOrChangedMethodsByIndex), debugInformationProvider: _previousGeneration.DebugInformationProvider, localSignatureProvider: _previousGeneration.LocalSignatureProvider); @@ -541,46 +553,43 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) foreach (var methodDef in typeDef.GetMethods(this.Context)) { - this.AddDefIfNecessary(_methodDefs, methodDef); var methodChange = _changes.GetChange(methodDef); - if (methodChange == SymbolChange.Added) + // If the method was added, and not replaced, but we can find an existing row id, then treat it + // as an update. This supercedes the other checks for edit types etc. because a method could be + // deleted in a generation, and then "added" in a subsequent one, but that is an update + // even if the collections at hand can't track it as such + if (methodChange == SymbolChange.Added && + !_changes.IsReplaced(methodDef.ContainingTypeDefinition) && + TryGetExistingMethodDefIndex(methodDef, out _)) { - _firstParamRowMap.Add(GetMethodDefinitionHandle(methodDef), _parameterDefs.NextRowId); - foreach (var paramDef in this.GetParametersToEmit(methodDef)) - { - _parameterDefs.Add(paramDef); - _parameterDefList.Add(paramDef, methodDef); - } + methodChange = SymbolChange.Updated; } - else if (methodChange == SymbolChange.Updated) - { - // If we're re-emitting parameters for an existing method we need to find their original row numbers - // and reuse them so the EnCLog, EnCMap and CustomAttributes tables refer to the right rows - // Unfortunately we have to check the original metadata and deltas separately as nothing tracks the aggregate data - // in a way that we can use - var handle = GetMethodDefinitionHandle(methodDef); - if (_previousGeneration.OriginalMetadata.MetadataReader.GetTableRowCount(TableIndex.MethodDef) >= MetadataTokens.GetRowNumber(handle)) - { - EmitParametersFromOriginalMetadata(methodDef, handle); - } - else - { - EmitParametersFromDelta(methodDef, handle); - } + this.AddDefIfNecessary(_methodDefs, methodDef, methodChange); + CreateIndicesForMethod(methodDef, methodChange); + } + + var deletedMethods = _changes.GetDeletedMethods(typeDef); + if (deletedMethods.Length > 0) + { + var deletedTypeMembers = ArrayBuilder.GetInstance(); + foreach (var methodDef in deletedMethods) + { + var oldMethodDef = (IMethodDefinition)methodDef.GetCciAdapter(); + deletedTypeMembers.Add(new DeletedMethodDefinition(oldMethodDef, typeDef, _typesUsedByDeletedMembers)); } - if (methodChange == SymbolChange.Added) + // Save for later, when processing references + _deletedTypeMembers.Add(typeDef, deletedTypeMembers.ToImmutable()); + + foreach (var newMethodDef in deletedTypeMembers) { - if (methodDef.GenericParameterCount > 0) - { - foreach (var typeParameter in methodDef.GenericParameters) - { - _genericParameters.Add(typeParameter); - } - } + _methodDefs.AddUpdated(newMethodDef); + CreateIndicesForMethod(newMethodDef, SymbolChange.Updated); } + + deletedTypeMembers.Free(); } foreach (var propertyDef in typeDef.GetProperties(this.Context)) @@ -633,6 +642,47 @@ protected override void CreateIndicesForNonTypeMembers(ITypeDefinition typeDef) implementingMethods.Free(); } + private void CreateIndicesForMethod(IMethodDefinition methodDef, SymbolChange methodChange) + { + if (methodChange == SymbolChange.Added) + { + _firstParamRowMap.Add(GetMethodDefinitionHandle(methodDef), _parameterDefs.NextRowId); + foreach (var paramDef in this.GetParametersToEmit(methodDef)) + { + _parameterDefs.Add(paramDef); + _parameterDefList.Add(paramDef, methodDef); + } + } + else if (methodChange == SymbolChange.Updated) + { + // If we're re-emitting parameters for an existing method we need to find their original row numbers + // and reuse them so the EnCLog, EnCMap and CustomAttributes tables refer to the right rows + + // Unfortunately we have to check the original metadata and deltas separately as nothing tracks the aggregate data + // in a way that we can use + var handle = GetMethodDefinitionHandle(methodDef); + if (_previousGeneration.OriginalMetadata.MetadataReader.GetTableRowCount(TableIndex.MethodDef) >= MetadataTokens.GetRowNumber(handle)) + { + EmitParametersFromOriginalMetadata(methodDef, handle); + } + else + { + EmitParametersFromDelta(methodDef, handle); + } + } + + if (methodChange == SymbolChange.Added) + { + if (methodDef.GenericParameterCount > 0) + { + foreach (var typeParameter in methodDef.GenericParameters) + { + _genericParameters.Add(typeParameter); + } + } + } + } + private void EmitParametersFromOriginalMetadata(IMethodDefinition methodDef, MethodDefinitionHandle handle) { var def = _previousGeneration.OriginalMetadata.MetadataReader.GetMethodDefinition(handle); @@ -665,8 +715,12 @@ private void EmitParametersFromDelta(IMethodDefinition methodDef, MethodDefiniti private bool AddDefIfNecessary(DefinitionIndex defIndex, T def) where T : class, IDefinition + => AddDefIfNecessary(defIndex, def, _changes.GetChange(def)); + + private bool AddDefIfNecessary(DefinitionIndex defIndex, T def, SymbolChange change) + where T : class, IDefinition { - switch (_changes.GetChange(def)) + switch (change) { case SymbolChange.Added: defIndex.Add(def); @@ -1364,8 +1418,14 @@ public override bool TryGetRowId(T item, out int index) if (_tryGetExistingIndex(item, out index)) { #if DEBUG - Debug.Assert(!_map.TryGetValue(index, out var other) || ((object)other == (object)item)); + // We expect that either we couldn't find the item in the map, because its new, or if we + // found it, we found the same one (ie, no item representing the same item is there twice), + // or it represents a deleted type. The deleted type, since we create it during emit, will + // never equal the original symbol that it wraps, even though it represents the same type, + // because the map uses reference equality. + Debug.Assert(!_map.TryGetValue(index, out var other) || ((object)other == (object)item) || other is DeletedTypeDefinition || item is DeletedTypeDefinition); #endif + _map[index] = item; return true; } @@ -1650,11 +1710,13 @@ public void Add(MethodImplKey item) private sealed class DeltaReferenceIndexer : ReferenceIndexer { private readonly SymbolChanges _changes; + private readonly Dictionary> _deletedTypeMembers; public DeltaReferenceIndexer(DeltaMetadataWriter writer) : base(writer) { _changes = writer._changes; + _deletedTypeMembers = writer._deletedTypeMembers; } public override void Visit(CommonPEModuleBuilder module) @@ -1724,6 +1786,12 @@ public override void Visit(ITypeDefinition typeDefinition) if (this.ShouldVisit(typeDefinition)) { base.Visit(typeDefinition); + + // We need to visit deleted members to ensure attribute method references are recorded + if (_deletedTypeMembers.TryGetValue(typeDefinition, out var deletedMembers)) + { + this.Visit(deletedMembers); + } } } @@ -1737,7 +1805,8 @@ public override void Visit(ITypeDefinitionMember typeMember) private bool ShouldVisit(IDefinition def) { - return _changes.GetChange(def) != SymbolChange.None; + return def is DeletedMethodDefinition || + _changes.GetChange(def) != SymbolChange.None; } } } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/EmitBaseline.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/EmitBaseline.cs index 3e87722e25d47..7f89f0d653518 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/EmitBaseline.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/EmitBaseline.cs @@ -249,6 +249,7 @@ public static EmitBaseline CreateInitialBaseline( anonymousDelegates: null, // Unset for initial metadata anonymousDelegatesWithFixedTypes: null, // Unset for initial metadata synthesizedMembers: ImmutableDictionary>.Empty, + deletedMembers: ImmutableDictionary>.Empty, methodsAddedOrChanged: new Dictionary(), debugInformationProvider: debugInformationProvider, localSignatureProvider: localSignatureProvider, @@ -338,6 +339,7 @@ public static EmitBaseline CreateInitialBaseline( private readonly IReadOnlyDictionary? _anonymousDelegates; private readonly IReadOnlyDictionary? _anonymousDelegatesWithFixedTypes; internal readonly ImmutableDictionary> SynthesizedMembers; + internal readonly ImmutableDictionary> DeletedMembers; private EmitBaseline( EmitBaseline? initialBaseline, @@ -368,6 +370,7 @@ private EmitBaseline( IReadOnlyDictionary? anonymousDelegates, IReadOnlyDictionary? anonymousDelegatesWithFixedTypes, ImmutableDictionary> synthesizedMembers, + ImmutableDictionary> deletedMembers, IReadOnlyDictionary methodsAddedOrChanged, Func debugInformationProvider, Func localSignatureProvider, @@ -389,6 +392,7 @@ private EmitBaseline( Debug.Assert(moduleVersionId != default); Debug.Assert(moduleVersionId == module.GetModuleVersionId()); Debug.Assert(synthesizedMembers != null); + Debug.Assert(deletedMembers != null); Debug.Assert(tableEntriesAdded.Length == MetadataTokens.TableCount); @@ -435,6 +439,7 @@ private EmitBaseline( _anonymousDelegates = anonymousDelegates; _anonymousDelegatesWithFixedTypes = anonymousDelegatesWithFixedTypes; SynthesizedMembers = synthesizedMembers; + DeletedMembers = deletedMembers; AddedOrChangedMethods = methodsAddedOrChanged; DebugInformationProvider = debugInformationProvider; @@ -470,6 +475,7 @@ internal EmitBaseline With( IReadOnlyDictionary anonymousDelegates, IReadOnlyDictionary anonymousDelegatesWithFixedTypes, ImmutableDictionary> synthesizedMembers, + ImmutableDictionary> deletedMembers, IReadOnlyDictionary addedOrChangedMethods, Func debugInformationProvider, Func localSignatureProvider) @@ -512,6 +518,7 @@ internal EmitBaseline With( anonymousDelegates: anonymousDelegates, anonymousDelegatesWithFixedTypes: anonymousDelegatesWithFixedTypes, synthesizedMembers: synthesizedMembers, + deletedMembers: deletedMembers, methodsAddedOrChanged: addedOrChangedMethods, debugInformationProvider: debugInformationProvider, localSignatureProvider: localSignatureProvider, diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs index b3ba75805e72e..94ef3985ed478 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolChanges.cs @@ -2,12 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Cci; -using Roslyn.Utilities; +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; -using System; +using System.Linq; +using Microsoft.Cci; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Emit { @@ -30,17 +33,71 @@ internal abstract class SymbolChanges /// private readonly ISet _replacedSymbols; + /// + /// A set of symbols, from the old compilation, that have been deleted from the new compilation + /// keyed by the containing type from the new compilation. + /// Populated based on semantic edits with . + /// + private readonly IReadOnlyDictionary> _deletedMembers; + private readonly Func _isAddedSymbol; protected SymbolChanges(DefinitionMap definitionMap, IEnumerable edits, Func isAddedSymbol) { _definitionMap = definitionMap; _isAddedSymbol = isAddedSymbol; - CalculateChanges(edits, out _changes, out _replacedSymbols); + CalculateChanges(edits, out _changes, out _replacedSymbols, out _deletedMembers); } public DefinitionMap DefinitionMap => _definitionMap; + public ImmutableDictionary> GetAllDeletedMethods() + { + var builder = ImmutableDictionary.CreateBuilder>(); + + foreach (var type in _deletedMembers) + { + if (GetISymbolInternalOrNull(type.Key) is { } typeSymbol) + { + builder.Add(typeSymbol, ToInternalSymbolArray(type.Value)); + } + } + + return builder.ToImmutable(); + } + + public ImmutableArray GetDeletedMethods(IDefinition containingType) + { + var containingSymbol = containingType.GetInternalSymbol()?.GetISymbol(); + if (containingSymbol is null) + { + return ImmutableArray.Empty; + } + + if (!_deletedMembers.TryGetValue(containingSymbol, out var deleted)) + { + return ImmutableArray.Empty; + } + + return ToInternalSymbolArray(deleted); + } + + private ImmutableArray ToInternalSymbolArray(ISet symbols) + { + var internalSymbols = ArrayBuilder.GetInstance(); + + foreach (var symbol in symbols) + { + var internalSymbol = GetISymbolInternalOrNull(symbol); + if (internalSymbol is not null) + { + internalSymbols.Add(internalSymbol); + } + } + + return internalSymbols.ToImmutableAndFree(); + } + public bool IsReplaced(IDefinition definition) { var symbol = definition.GetInternalSymbol(); @@ -94,6 +151,7 @@ private bool DefinitionExistsInPreviousGeneration(ISymbolInternal symbol) public SymbolChange GetChange(IDefinition def) { var symbol = def.GetInternalSymbol(); + if (symbol is ISynthesizedMethodBodyImplementationSymbol synthesizedSymbol) { RoslynDebug.Assert(synthesizedSymbol.Method != null); @@ -267,10 +325,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) + private static void CalculateChanges(IEnumerable edits, out IReadOnlyDictionary changes, out ISet replaceSymbols, out IReadOnlyDictionary> deletedMembers) { var changesBuilder = new Dictionary(); HashSet? lazyReplaceSymbolsBuilder = null; + Dictionary>? lazyDeletedMembersBuilder = null; foreach (var edit in edits) { @@ -293,7 +352,26 @@ private static void CalculateChanges(IEnumerable edits, out IReadO break; case SemanticEditKind.Delete: - // No work to do. + // We allow method deletions only at the moment. + // For deletions NewSymbol is actually containing symbol + if (edit.OldSymbol is IMethodSymbol && edit.NewSymbol is { } 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); + } + } continue; default: @@ -326,6 +404,7 @@ private static void CalculateChanges(IEnumerable edits, out IReadO changes = changesBuilder; replaceSymbols = lazyReplaceSymbolsBuilder ?? SpecializedCollections.EmptySet(); + deletedMembers = lazyDeletedMembersBuilder ?? SpecializedCollections.EmptyReadOnlyDictionary>(); } private static void AddContainingTypesAndNamespaces(Dictionary changes, ISymbol symbol) diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs index 9748a92841ec3..b497c0c700e83 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/SymbolMatcher.cs @@ -29,7 +29,8 @@ public EmitBaseline MapBaselineToCompilation( EmitBaseline baseline, Compilation targetCompilation, CommonPEModuleBuilder targetModuleBuilder, - ImmutableDictionary> mappedSynthesizedMembers) + ImmutableDictionary> mappedSynthesizedMembers, + ImmutableDictionary> mappedDeletedMembers) { // Map all definitions to this compilation. var typesAdded = MapDefinitions(baseline.TypesAdded); @@ -64,6 +65,7 @@ public EmitBaseline MapBaselineToCompilation( anonymousDelegates: MapAnonymousDelegates(baseline.AnonymousDelegates), anonymousDelegatesWithFixedTypes: MapAnonymousDelegatesWithFixedTypes(baseline.AnonymousDelegatesWithFixedTypes), synthesizedMembers: mappedSynthesizedMembers, + deletedMembers: mappedDeletedMembers, addedOrChangedMethods: MapAddedOrChangedMethods(baseline.AddedOrChangedMethods), debugInformationProvider: baseline.DebugInformationProvider, localSignatureProvider: baseline.LocalSignatureProvider); diff --git a/src/Compilers/Core/Portable/Emit/SemanticEdit.cs b/src/Compilers/Core/Portable/Emit/SemanticEdit.cs index d8dbae4aa1821..cf23930ffb2bb 100644 --- a/src/Compilers/Core/Portable/Emit/SemanticEdit.cs +++ b/src/Compilers/Core/Portable/Emit/SemanticEdit.cs @@ -26,8 +26,8 @@ namespace Microsoft.CodeAnalysis.Emit public ISymbol? OldSymbol { get; } /// - /// 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 the edit represents a deletion. /// public ISymbol? NewSymbol { get; } diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index 3c9be4ee289e7..e32700fdbd918 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -530,6 +530,8 @@ internal enum WellKnownMember System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor, + System_MissingMethodException__ctor, + Count // Remember to update the AllWellKnownTypeMembers tests when making changes here diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 9fcc740a0b55d..66117afc0bbae 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -3640,6 +3640,14 @@ static WellKnownMembers() 1, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + + // System_MissingMethodException__ctor + (byte)MemberFlags.Constructor, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_MissingMethodException - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, + }; string[] allNames = new string[(int)WellKnownMember.Count] @@ -4095,6 +4103,7 @@ static WellKnownMembers() "SequenceEqual", // System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T "AsSpan", // System_MemoryExtensions__AsSpan_String ".ctor", // System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute_ctor + ".ctor", // System_MissingMethodException__ctor }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index f08f8ebf76150..41bf3dc7e746d 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -324,6 +324,8 @@ internal enum WellKnownType System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute, + System_MissingMethodException, + NextAvailable, // Remember to update the AllWellKnownTypes tests when making changes here } @@ -638,6 +640,7 @@ internal static class WellKnownTypes "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute", "System.MemoryExtensions", "System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", + "System.MissingMethodException", }; private static readonly Dictionary s_nameToTypeIdMap = new Dictionary((int)Count); diff --git a/src/Compilers/Test/Utilities/VisualBasic/Extensions.vb b/src/Compilers/Test/Utilities/VisualBasic/Extensions.vb index 713040b947b6d..981b6bc7b6c57 100644 --- a/src/Compilers/Test/Utilities/VisualBasic/Extensions.vb +++ b/src/Compilers/Test/Utilities/VisualBasic/Extensions.vb @@ -65,7 +65,7 @@ Friend Module Extensions Dim lastContainer As NamespaceOrTypeSymbol = Nothing Dim members = GetMembers(container, qualifiedName, lastContainer) If members.Length = 0 Then - Assert.True(False, "Available members:" & vbCrLf + String.Join(vbCrLf, lastContainer.GetMembers())) + Return Nothing ElseIf members.Length > 1 Then Assert.True(False, "Found multiple members of specified name:" & vbCrLf + String.Join(vbCrLf, members)) End If diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb index a614dd868f956..314f80a0c169f 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb @@ -118,6 +118,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End If Dim currentSynthesizedMembers = moduleBeingBuilt.GetAllSynthesizedMembers() + Dim currentDeletedMembers = moduleBeingBuilt.EncSymbolChanges.GetAllDeletedMethods() ' Mapping from previous compilation to the current. Dim anonymousTypeMap = moduleBeingBuilt.GetAnonymousTypeMap() @@ -131,9 +132,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit sourceContext, compilation.SourceAssembly, otherContext, - currentSynthesizedMembers) + currentSynthesizedMembers, + currentDeletedMembers) Dim mappedSynthesizedMembers = matcher.MapSynthesizedMembers(previousGeneration.SynthesizedMembers, currentSynthesizedMembers) + Dim mappedDeletedMembers = matcher.MapSynthesizedMembers(previousGeneration.DeletedMembers, currentDeletedMembers) ' TODO can we reuse some data from the previous matcher? Dim matcherWithAllSynthesizedMembers = New VisualBasicSymbolMatcher( @@ -142,13 +145,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit sourceContext, compilation.SourceAssembly, otherContext, - mappedSynthesizedMembers) + mappedSynthesizedMembers, + mappedDeletedMembers) Return matcherWithAllSynthesizedMembers.MapBaselineToCompilation( previousGeneration, compilation, moduleBeingBuilt, - mappedSynthesizedMembers) + mappedSynthesizedMembers, + mappedDeletedMembers) End Function End Module End Namespace diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/PEDeltaAssemblyBuilder.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/PEDeltaAssemblyBuilder.vb index ca213c02940cc..6c8a0cd065d8f 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/PEDeltaAssemblyBuilder.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/PEDeltaAssemblyBuilder.vb @@ -58,7 +58,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit sourceContext:=context, otherAssembly:=previousAssembly, otherContext:=previousContext, - otherSynthesizedMembersOpt:=previousGeneration.SynthesizedMembers) + otherSynthesizedMembersOpt:=previousGeneration.SynthesizedMembers, + otherDeletedMembersOpt:=previousGeneration.DeletedMembers) End If _previousDefinitions = New VisualBasicDefinitionMap(edits, metadataDecoder, matchToMetadata, matchToPrevious) diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb index 652857777c74f..d2be34606e57d 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/VisualBasicSymbolMatcher.vb @@ -26,10 +26,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit sourceContext As EmitContext, otherAssembly As SourceAssemblySymbol, otherContext As EmitContext, - otherSynthesizedMembersOpt As ImmutableDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal))) + otherSynthesizedMembersOpt As ImmutableDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), + otherDeletedMembersOpt As ImmutableDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal))) _defs = New MatchDefsToSource(sourceContext, otherContext) - _symbols = New MatchSymbols(anonymousTypeMap, sourceAssembly, otherAssembly, otherSynthesizedMembersOpt, New DeepTranslator(otherAssembly.GetSpecialType(SpecialType.System_Object))) + _symbols = New MatchSymbols(anonymousTypeMap, sourceAssembly, otherAssembly, otherSynthesizedMembersOpt, otherDeletedMembersOpt, New DeepTranslator(otherAssembly.GetSpecialType(SpecialType.System_Object))) End Sub Public Sub New(anonymousTypeMap As IReadOnlyDictionary(Of AnonymousTypeKey, AnonymousTypeValue), @@ -38,7 +39,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit otherAssembly As PEAssemblySymbol) _defs = New MatchDefsToMetadata(sourceContext, otherAssembly) - _symbols = New MatchSymbols(anonymousTypeMap, sourceAssembly, otherAssembly, otherSynthesizedMembersOpt:=Nothing, deepTranslatorOpt:=Nothing) + _symbols = New MatchSymbols(anonymousTypeMap, sourceAssembly, otherAssembly, otherSynthesizedMembersOpt:=Nothing, otherDeletedMembers:=Nothing, deepTranslatorOpt:=Nothing) End Sub Public Overrides Function MapDefinition(definition As Cci.IDefinition) As Cci.IDefinition @@ -231,6 +232,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit Private ReadOnly _sourceAssembly As SourceAssemblySymbol Private ReadOnly _otherAssembly As AssemblySymbol Private ReadOnly _otherSynthesizedMembersOpt As ImmutableDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)) + Private ReadOnly _otherDeletedMembersOpt As ImmutableDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)) ' A cache of members per type, populated when the first member for a given ' type Is needed. Within each type, members are indexed by name. The reason @@ -242,12 +244,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit sourceAssembly As SourceAssemblySymbol, otherAssembly As AssemblySymbol, otherSynthesizedMembersOpt As ImmutableDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), + otherDeletedMembers As ImmutableDictionary(Of ISymbolInternal, ImmutableArray(Of ISymbolInternal)), deepTranslatorOpt As DeepTranslator) _anonymousTypeMap = anonymousTypeMap _sourceAssembly = sourceAssembly _otherAssembly = otherAssembly _otherSynthesizedMembersOpt = otherSynthesizedMembersOpt + _otherDeletedMembersOpt = otherDeletedMembers _comparer = New SymbolComparer(Me, deepTranslatorOpt) _matches = New ConcurrentDictionary(Of Symbol, Symbol)(ReferenceEqualityComparer.Instance) _otherMembers = New ConcurrentDictionary(Of ISymbolInternal, IReadOnlyDictionary(Of String, ImmutableArray(Of ISymbolInternal)))(ReferenceEqualityComparer.Instance) @@ -559,7 +563,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit method = SubstituteTypeParameters(method) other = SubstituteTypeParameters(other) - Return Me._comparer.Equals(method.ReturnType, other.ReturnType) AndAlso + Return _comparer.Equals(method.ReturnType, other.ReturnType) AndAlso method.Parameters.SequenceEqual(other.Parameters, AddressOf Me.AreParametersEqual) AndAlso method.TypeParameters.SequenceEqual(other.TypeParameters, AddressOf Me.AreTypesEqual) End Function @@ -654,6 +658,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit members.AddRange(synthesizedMembers) End If + Dim deletedMembers As ImmutableArray(Of ISymbolInternal) = Nothing + If _otherDeletedMembersOpt IsNot Nothing AndAlso _otherDeletedMembersOpt.TryGetValue(symbol, deletedMembers) Then + members.AddRange(deletedMembers) + End If + Dim result = members.ToDictionary(Function(s) s.Name, s_nameComparer) members.Free() Return result @@ -670,6 +679,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End Sub Public Overloads Function Equals(source As TypeSymbol, other As TypeSymbol) As Boolean + If ReferenceEquals(source, other) Then + Return True + End If + Dim visitedSource = DirectCast(_matcher.Visit(source), TypeSymbol) Dim visitedOther = If(_deepTranslatorOpt IsNot Nothing, DirectCast(_deepTranslatorOpt.Visit(other), TypeSymbol), other) diff --git a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb index 619f9b8c1f77f..449082250f976 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb @@ -738,7 +738,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit End Sub Public Function JITOptimizationIsDisabled(methodSymbol As MethodSymbol) As Boolean - Debug.Assert(methodSymbol.ContainingModule Is Me.SourceModule AndAlso methodSymbol Is methodSymbol.OriginalDefinition) + Debug.Assert(methodSymbol Is methodSymbol.OriginalDefinition) Return _disableJITOptimization.ContainsKey(methodSymbol) End Function diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.vb index a04548c21297d..4230b60ec8e5b 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.vb @@ -278,6 +278,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests Nothing, toCompilation.SourceAssembly, Nothing, + Nothing, Nothing) End Function End Class diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb index 9e0865245fdf8..bdec6d82eab9c 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.vb @@ -5508,5 +5508,62 @@ End Class }") End Sub + + Public Sub Method_Delete() + + Dim source0 = MarkedSource(" +Imports System.ComponentModel + +Class C + + Function M(c as C) As C + Return Nothing + End Function +End Class +") + Dim source1 = MarkedSource(" +Imports System.ComponentModel + +Class C +End Class +") + Dim compilation0 = CreateCompilation(source0.Tree, targetFramework:=TargetFramework.NetStandard20, options:=ComSafeDebugDll) + Dim compilation1 = compilation0.WithSource(source1.Tree) + + Dim m0 = compilation0.GetMember(Of MethodSymbol)("C.M") + Dim c0 = compilation1.GetMember(Of NamedTypeSymbol)("C") + + Dim v0 = CompileAndVerify(compilation0) + Dim md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData) + Dim generation0 = EmitBaseline.CreateInitialBaseline(md0, AddressOf v0.CreateSymReader().GetEncMethodDebugInfo) + + ' Pretend there was an update to C.E to ensure we haven't invalidated the test + + Dim diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(New SemanticEdit(SemanticEditKind.Delete, m0, c0))) + + Dim reader0 = md0.MetadataReader + + ' Verify delta metadata contains expected rows. + Using md1 = diff1.GetMetadata() + Dim reader1 = md1.Reader + Dim readers = {reader0, reader1} + EncValidation.VerifyModuleMvid(1, reader0, reader1) + CheckNames(readers, reader1.GetTypeDefNames()) + CheckNames(readers, reader1.GetMethodDefNames(), "M") + + CheckEncLogDefinitions(reader1, + Row(2, TableIndex.MethodDef, EditAndContinueOperation.Default), + Row(1, TableIndex.Param, EditAndContinueOperation.Default), + Row(4, TableIndex.CustomAttribute, EditAndContinueOperation.Default)) + + CheckEncMapDefinitions(reader1, + Handle(2, TableIndex.MethodDef), + Handle(1, TableIndex.Param), + Handle(4, TableIndex.CustomAttribute)) + End Using + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.vb index b464932a644cd..8db367e70e74f 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.vb @@ -540,6 +540,7 @@ End Class New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of MethodSymbol)("C.X") @@ -568,6 +569,7 @@ Class C New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of FieldSymbol)("C.x") @@ -598,6 +600,7 @@ Class C New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of FieldSymbol)("C.x") @@ -633,6 +636,7 @@ End Class New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of MethodSymbol)("C.X") @@ -666,6 +670,7 @@ End Class New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of MethodSymbol)("C.X") @@ -699,6 +704,7 @@ End Class New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of MethodSymbol)("C.X") @@ -732,6 +738,7 @@ End Class New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of MethodSymbol)("C.X") @@ -771,6 +778,7 @@ End Class New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of PropertySymbol)("C.X") @@ -808,6 +816,7 @@ End Class New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of PropertySymbol)("C.X") @@ -839,6 +848,7 @@ End Structure New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of FieldSymbol)("Vector.Coordinates") @@ -868,6 +878,7 @@ End Structure New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of FieldSymbol)("Vector.Coordinates") @@ -899,6 +910,7 @@ End Class New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of SourceNamedTypeSymbol)("C.F") @@ -929,6 +941,7 @@ End Class" New EmitContext(), compilation0.SourceAssembly, New EmitContext(), + Nothing, Nothing) Dim member = compilation1.GetMember(Of SourceNamedTypeSymbol)("C.F") diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs index 459583268a338..5ff1d656c7c7b 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.Methods.cs @@ -52,9 +52,8 @@ static void Main(string[] args) new[] { edits }, new[] { - DocumentResults( - active, - diagnostics: new[] { Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "Goo(int a)")) }) + DocumentResults(active, + new[] { SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.Goo"), deletedSymbolContainerProvider: c => c.GetMember("C")) }) }); } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs index b38f4698e1ef3..57fcddedcdaf2 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs @@ -59,10 +59,10 @@ internal static RudeEditDiagnosticDescription Diagnostic(RudeEditKind rudeEditKi => new(rudeEditKind, squiggle, arguments, firstLine: null); internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func symbolProvider, IEnumerable>? syntaxMap, string? partialType = null) - => new(kind, symbolProvider, (partialType != null) ? c => c.GetMember(partialType) : null, syntaxMap, hasSyntaxMap: syntaxMap != null); + => new(kind, symbolProvider, (partialType != null) ? c => c.GetMember(partialType) : null, syntaxMap, hasSyntaxMap: syntaxMap != null, deletedSymbolContainerProvider: null); - internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func symbolProvider, string? partialType = null, bool preserveLocalVariables = false) - => new(kind, symbolProvider, (partialType != null) ? c => c.GetMember(partialType) : null, syntaxMap: null, preserveLocalVariables); + internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func symbolProvider, string? partialType = null, bool preserveLocalVariables = false, Func? deletedSymbolContainerProvider = null) + => new(kind, symbolProvider, (partialType != null) ? c => c.GetMember(partialType) : null, syntaxMap: null, preserveLocalVariables, deletedSymbolContainerProvider); internal static string DeletedSymbolDisplay(string kind, string displayName) => string.Format(FeaturesResources.member_kind_and_name, kind, displayName); diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index cdd27bad1149d..06778b10ff5db 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -6,6 +6,7 @@ using System; using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.EditAndContinue; @@ -2064,7 +2065,10 @@ public void Type_Partial_DeleteDeclaration() new[] { DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.Delete, null, DeletedSymbolDisplay(FeaturesResources.method, "C.F()")) }), + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.F"), deletedSymbolContainerProvider: c => c.GetMember("C")) + }), DocumentResults( semanticEdits: new[] @@ -5055,8 +5059,12 @@ public void NestedType_MethodDeleteInsert() "Delete [public void goo() {}]@17", "Delete [()]@32"); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "public class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))); + edits.VerifySemantics( + new[] { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.D")), + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.goo"), deletedSymbolContainerProvider: c => c.GetMember("C")) + }, + capabilities: EditAndContinueCapabilities.Baseline); } [Fact] @@ -5307,11 +5315,15 @@ public void NestedPartialTypeInPartialType_InsertDeleteAndChange() DocumentResults(), DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.Delete, "partial struct S", DeletedSymbolDisplay(FeaturesResources.method, "F2(byte x)")) }), + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("S.C.F2").FirstOrDefault(m => m.GetParameterTypes().Any(t => t.SpecialType == SpecialType.System_Byte))?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("S.C")) + }), DocumentResults( - semanticEdits: new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("S").GetMember("C").GetMember("F2")) }) - }); + semanticEdits: new[] { SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("S.C.F2").FirstOrDefault(m => m.GetParameterTypes().Any(t => t.SpecialType == SpecialType.System_Int32))?.ISymbol) }) + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] @@ -6600,15 +6612,16 @@ public void PartialMember_DeleteInsert_MethodAddParameter() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("S.F")) + SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("S.F").FirstOrDefault(m => m.GetParameterCount() == 1)?.ISymbol) }), DocumentResults( - diagnostics: new[] + semanticEdits: new[] { - Diagnostic(RudeEditKind.Delete, "partial struct S", DeletedSymbolDisplay(FeaturesResources.method, "F()")) - }) - }); + SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("S.F").FirstOrDefault(m => m.GetParameterCount() == 0)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("S")) + }), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] @@ -6626,15 +6639,16 @@ public void PartialMember_DeleteInsert_UpdateMethodParameterType() DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("S.F")) + SemanticEdit(SemanticEditKind.Insert, c => c.GetMembers("S.F").FirstOrDefault(m => m.GetParameterTypes().Any(t => t.SpecialType == SpecialType.System_Byte))?.ISymbol) }), DocumentResults( - diagnostics: new[] + semanticEdits: new[] { - Diagnostic(RudeEditKind.Delete, "partial struct S", DeletedSymbolDisplay(FeaturesResources.method, "F(int x)")) - }) - }); + SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("S.F").FirstOrDefault(m => m.GetParameterTypes().Any(t => t.SpecialType == SpecialType.System_Int32))?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("S")) + }), + }, + capabilities: EditAndContinueCapabilities.AddMethodToExistingType); } [Fact] @@ -6656,10 +6670,10 @@ public void PartialMember_DeleteInsert_MethodAddTypeParameter() }), DocumentResults( - diagnostics: new[] + semanticEdits: new[] { - Diagnostic(RudeEditKind.Delete, "partial struct S", DeletedSymbolDisplay(FeaturesResources.method, "F()")) - }) + SemanticEdit(SemanticEditKind.Delete, c => c.GetMembers("S.F").FirstOrDefault(m => m.GetMemberTypeParameters().Length == 0)?.ISymbol, deletedSymbolContainerProvider: c => c.GetMember("S")) + }), }); } @@ -7117,8 +7131,47 @@ class C "Delete [void goo() { }]@18", "Delete [()]@26"); + edits.VerifySemantics( + new[] { SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.goo"), deletedSymbolContainerProvider: c => c.GetMember("C")) }, + capabilities: EditAndContinueCapabilities.Baseline); + } + + [Theory] + [InlineData("virtual")] + [InlineData("abstract")] + [InlineData("override")] + public void Method_Delete_Modifiers(string modifier) + { + /* TODO: https://github.com/dotnet/roslyn/issues/59264 + + This should be a supported edit. Consider the following inheritance chain: + + public class C { public virtual void M() => Console.WriteLine("C"); } + public class D : C { public override void M() { base.M(); Console.WriteLine("D"); } } + public class E : D { public override void M() { base.M(); Console.WriteLine("E"); } } + + If D.M is deleted we expect E.M to print "C E" and not throw. + + */ + var src1 = $$""" + class C + { + {{modifier}} void goo() { } + } + """; + var src2 = """ + class C + { + } + """; + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + $"Delete [{modifier} void goo() {{ }}]@16", + $"Delete [()]@{25 + modifier.Length}"); + edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))); + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))); } [Fact] @@ -7140,8 +7193,9 @@ class C "Delete [int goo() => 1;]@18", "Delete [()]@25"); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))); + edits.VerifySemantics( + new[] { SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.goo"), deletedSymbolContainerProvider: c => c.GetMember("C")) }, + capabilities: EditAndContinueCapabilities.Baseline); } [WorkItem(754853, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/754853")] @@ -7167,8 +7221,9 @@ void goo(int a) { }]@18", "Delete [(int a)]@42", "Delete [int a]@43"); - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.method, "goo(int a)"))); + edits.VerifySemantics( + new[] { SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C.goo"), deletedSymbolContainerProvider: c => c.GetMember("C")) }, + capabilities: EditAndContinueCapabilities.Baseline); } [WorkItem(754853, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/754853")] @@ -8778,7 +8833,10 @@ public void Method_Partial_DeleteImplementation() DocumentResults(), DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.Delete, "partial class C", DeletedSymbolDisplay(FeaturesResources.method, "F()")) }) + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").GetMember("F"), deletedSymbolContainerProvider: c => c.GetMember("C")) + }), }); } @@ -8798,7 +8856,10 @@ public void Method_Partial_DeleteBoth() DocumentResults(), DocumentResults( - diagnostics: new[] { Diagnostic(RudeEditKind.Delete, "partial class C", DeletedSymbolDisplay(FeaturesResources.method, "F()")) }) + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Delete, c => c.GetMember("C").GetMember("F")?.PartialImplementationPart, deletedSymbolContainerProvider: c => c.GetMember("C")) + }), }); } diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs index ce2af807f06ff..6a968c778d006 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/EditAndContinueTestHelpers.cs @@ -236,7 +236,17 @@ internal void VerifySemantics(EditScript[] editScripts, TargetFramew AssertEx.Empty(duplicateNonPartial, "Duplicate non-partial symbols"); // check if we can merge edits without throwing: - EditSession.MergePartialEdits(oldProject.GetCompilationAsync().Result!, newProject.GetCompilationAsync().Result!, allEdits, out var _, out var _, CancellationToken.None); + EditSession.MergePartialEdits(oldProject.GetCompilationAsync().Result!, newProject.GetCompilationAsync().Result!, allEdits, out var mergedEdits, out _, CancellationToken.None); + + // merging is where we fill in NewSymbol for deletes, so make sure that happened too + foreach (var edit in mergedEdits) + { + if (edit.Kind is SemanticEditKind.Delete && + edit.OldSymbol is IMethodSymbol) + { + Assert.True(edit.NewSymbol is not null); + } + } } public static void VerifyDiagnostics(IEnumerable expected, IEnumerable actual, SourceText newSource) @@ -277,7 +287,7 @@ private static void VerifySemanticEdits( Assert.Equal(editKind, actualSemanticEdit.Kind); - var expectedOldSymbol = (editKind == SemanticEditKind.Update) ? expectedSemanticEdit.SymbolProvider(oldCompilation) : null; + var expectedOldSymbol = (editKind is SemanticEditKind.Update or SemanticEditKind.Delete) ? expectedSemanticEdit.SymbolProvider(oldCompilation) : null; var expectedNewSymbol = expectedSemanticEdit.SymbolProvider(newCompilation); var symbolKey = actualSemanticEdit.Symbol; @@ -286,13 +296,32 @@ private static void VerifySemanticEdits( Assert.Equal(expectedOldSymbol, symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true).Symbol); Assert.Equal(expectedNewSymbol, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); } + else if (editKind == SemanticEditKind.Delete) + { + // Symbol key will happily resolve to a definition part that has no implementation, so we validate that + // differently + if (expectedOldSymbol is IMethodSymbol { IsPartialDefinition: true } && + symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true).Symbol is IMethodSymbol resolvedMethod) + { + Assert.Equal(expectedOldSymbol, resolvedMethod.PartialDefinitionPart); + Assert.Equal(null, resolvedMethod.PartialImplementationPart); + } + else + { + Assert.Equal(expectedOldSymbol, symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true).Symbol); + Assert.Equal(null, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); + } + + var deletedSymbolContainer = actualSemanticEdit.DeletedSymbolContainer?.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol; + Assert.Equal(deletedSymbolContainer, expectedSemanticEdit.DeletedSymbolContainerProvider?.Invoke(newCompilation)); + } else if (editKind is SemanticEditKind.Insert or SemanticEditKind.Replace) { Assert.Equal(expectedNewSymbol, symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true).Symbol); } else { - Assert.False(true, "Only Update, Insert or Replace allowed"); + Assert.False(true, "Only Update, Delete, Insert or Replace allowed"); } // Partial types must match: diff --git a/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs b/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs index 840e9377ec21a..135d9bb311306 100644 --- a/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs +++ b/src/EditorFeatures/TestUtilities/EditAndContinue/SemanticEditDescription.cs @@ -14,6 +14,7 @@ public sealed class SemanticEditDescription public readonly SemanticEditKind Kind; public readonly Func SymbolProvider; public readonly Func? PartialType; + public readonly Func? DeletedSymbolContainerProvider; /// /// If specified the node mappings will be validated against the actual syntax map function. @@ -27,13 +28,15 @@ public SemanticEditDescription( Func symbolProvider, Func? partialType, IEnumerable>? syntaxMap, - bool hasSyntaxMap) + bool hasSyntaxMap, + Func? deletedSymbolContainerProvider) { Kind = kind; SymbolProvider = symbolProvider; SyntaxMap = syntaxMap; PartialType = partialType; HasSyntaxMap = hasSyntaxMap; + DeletedSymbolContainerProvider = deletedSymbolContainerProvider; } } } diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb index 9853fc1001351..f5aee999fa0a1 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/ActiveStatementTests.vb @@ -344,8 +344,11 @@ End Class Dim edits = GetTopEdits(src1, src2) Dim active = GetActiveStatements(src1, src2) - edits.VerifySemanticDiagnostics(active, - Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "Goo(a As Integer)"))) + edits.VerifySemantics(active, + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.Goo"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + }) End Sub diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb index e00f5d9d99966..968c52d21ce66 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/Helpers/EditingTestBase.vb @@ -66,25 +66,29 @@ End Namespace Friend Shared Function SemanticEdit(kind As SemanticEditKind, symbolProvider As Func(Of Compilation, ISymbol), syntaxMap As IEnumerable(Of KeyValuePair(Of TextSpan, TextSpan)), - Optional partialType As String = Nothing) As SemanticEditDescription + Optional partialType As String = Nothing, + Optional deletedSymbolContainerProvider As Func(Of Compilation, ISymbol) = Nothing) As SemanticEditDescription Return New SemanticEditDescription( kind, symbolProvider, If(partialType Is Nothing, Nothing, Function(c As Compilation) CType(c.GetMember(partialType), ITypeSymbol)), syntaxMap, - hasSyntaxMap:=syntaxMap IsNot Nothing) + hasSyntaxMap:=syntaxMap IsNot Nothing, + deletedSymbolContainerProvider) End Function Friend Shared Function SemanticEdit(kind As SemanticEditKind, symbolProvider As Func(Of Compilation, ISymbol), Optional partialType As String = Nothing, - Optional preserveLocalVariables As Boolean = False) As SemanticEditDescription + Optional preserveLocalVariables As Boolean = False, + Optional deletedSymbolContainerProvider As Func(Of Compilation, ISymbol) = Nothing) As SemanticEditDescription Return New SemanticEditDescription( kind, symbolProvider, If(partialType Is Nothing, Nothing, Function(c As Compilation) CType(c.GetMember(partialType), ITypeSymbol)), syntaxMap:=Nothing, - hasSyntaxMap:=preserveLocalVariables) + hasSyntaxMap:=preserveLocalVariables, + deletedSymbolContainerProvider) End Function Friend Shared Function DeletedSymbolDisplay(kind As String, displayName As String) As String diff --git a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb index 0a401c92116cc..f9f0a42d59f3d 100644 --- a/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EditAndContinue/TopLevelEditingTests.vb @@ -1429,8 +1429,10 @@ End Class" EditAndContinueValidation.VerifySemantics( {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2)}, { - DocumentResults( - diagnostics:={Diagnostic(RudeEditKind.Delete, Nothing, DeletedSymbolDisplay(FeaturesResources.method, "C.F()"))}), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.F"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + }), DocumentResults( semanticEdits:={ SemanticEdit(SemanticEditKind.Update, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember("M")) @@ -2906,8 +2908,12 @@ End Class "Delete [Public Sub goo()]@15", "Delete [()]@29") - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Public Class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))) + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.D")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.Goo"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + }) End Sub @@ -3091,11 +3097,16 @@ End Structure {GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2), GetTopEdits(srcC1, srcC2)}, { DocumentResults(), - DocumentResults( - diagnostics:={Diagnostic(RudeEditKind.Delete, "Partial Structure S", DeletedSymbolDisplay(FeaturesResources.method, "F2(x As Byte)"))}), - DocumentResults( - semanticEdits:={SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("S").GetMember(Of NamedTypeSymbol)("C").GetMember("F2"))}) - }) + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("S.C.F2").FirstOrDefault(Function(m) m.GetParameters().Any(Function(p) p.Type.SpecialType = SpecialType.System_Byte)), deletedSymbolContainerProvider:=Function(c) c.GetMember("S.C")) + }), + DocumentResults(semanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember(Of NamedTypeSymbol)("S").GetMember(Of NamedTypeSymbol)("C").GetMembers("F2").FirstOrDefault(Function(m) m.GetParameters().Any(Function(p) p.Type.SpecialType = SpecialType.System_Int32))) + }) + }, + capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -4153,13 +4164,14 @@ End Structure { DocumentResults(semanticEdits:= { - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("S.F")) + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMembers("S.F").FirstOrDefault(Function(m) m.GetParameters().Length = 1)) }), - DocumentResults(diagnostics:= + DocumentResults(semanticEdits:= { - Diagnostic(RudeEditKind.Delete, "Partial Structure S", DeletedSymbolDisplay(FeaturesResources.method, "F()")) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("S.F").FirstOrDefault(Function(m) m.GetParameters().Length = 0), deletedSymbolContainerProvider:=Function(c) c.GetMember("S")) }) - }) + }, + capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -4191,13 +4203,14 @@ End Structure { DocumentResults(semanticEdits:= { - SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("S.F")) + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMembers("S.F").FirstOrDefault(Function(m) m.GetParameters().Any(Function(p) p.Type.SpecialType = SpecialType.System_Byte))) }), - DocumentResults(diagnostics:= + DocumentResults(semanticEdits:= { - Diagnostic(RudeEditKind.Delete, "Partial Structure S", DeletedSymbolDisplay(FeaturesResources.method, "F(a As Integer)")) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("S.F").FirstOrDefault(Function(m) m.GetParameters().Any(Function(p) p.Type.SpecialType = SpecialType.System_Int32)), deletedSymbolContainerProvider:=Function(c) c.GetMember("S")) }) - }) + }, + capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -4231,9 +4244,9 @@ End Structure { Diagnostic(RudeEditKind.InsertGenericMethod, "Sub F(Of T)()", FeaturesResources.method) }), - DocumentResults(diagnostics:= + DocumentResults(semanticEdits:= { - Diagnostic(RudeEditKind.Delete, "Partial Structure S", DeletedSymbolDisplay(FeaturesResources.method, "F()")) + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMembers("S.F").FirstOrDefault(Function(m) m.GetTypeParameters().Length = 0), deletedSymbolContainerProvider:=Function(c) c.GetMember("S")) }) }) End Sub @@ -4417,8 +4430,11 @@ End Structure "Delete [Sub goo()]@8", "Delete [()]@15") - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "goo()"))) + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.Goo"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + }) End Sub @@ -4446,8 +4462,11 @@ End Structure "Delete [a As Integer]@16", "Delete [a]@16") - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "goo(a As Integer)"))) + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.goo"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + }) End Sub @@ -4464,8 +4483,11 @@ End Structure "Delete [a As Integer]@30", "Delete [a]@30") - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "goo(a As Integer)"))) + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.Goo"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + }) End Sub @@ -4646,8 +4668,13 @@ Imports System.Runtime.InteropServices "Delete [b As Integer]@53", "Delete [b]@53") - edits.VerifySemanticDiagnostics( - Diagnostic(RudeEditKind.Delete, "Class C", DeletedSymbolDisplay(FeaturesResources.method, "f(a As Integer, b As Integer)"))) + edits.VerifySemantics( + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Insert, Function(c) c.GetMember("C.D.f")), + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember("C.f"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + }, + capabilities:=EditAndContinueCapabilities.AddMethodToExistingType) End Sub @@ -5337,7 +5364,10 @@ End Interface { DocumentResults(), DocumentResults( - diagnostics:={Diagnostic(RudeEditKind.Delete, "Partial Class C", DeletedSymbolDisplay(FeaturesResources.method, "F()"))}) + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F"), deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + }) }) End Sub @@ -5354,7 +5384,11 @@ End Interface { DocumentResults(), DocumentResults( - diagnostics:={Diagnostic(RudeEditKind.Delete, "Partial Class C", DeletedSymbolDisplay(FeaturesResources.method, "F()"))}) + semanticEdits:= + { + SemanticEdit(SemanticEditKind.Delete, Function(c) c.GetMember(Of NamedTypeSymbol)("C").GetMember(Of MethodSymbol)("F")?.PartialImplementationPart, deletedSymbolContainerProvider:=Function(c) c.GetMember("C")) + } + ) }) End Sub diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index efde5e994b5d4..57d735c4ab410 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -2672,6 +2672,16 @@ private async Task> AnalyzeSemanticsAsync( { continue; } + + // Deleting an ordinary method is allowed, and we store the newContainingSymbol in NewSymbol for later use + // We don't currently allow deleting virtual or abstract methods, because if those are in the middle of + // an inheritance chain then throwing a missing method exception is not expected + if (oldSymbol is IMethodSymbol { MethodKind: MethodKind.Ordinary, IsExtern: false, ContainingType.TypeKind: TypeKind.Class or TypeKind.Struct } && + oldSymbol.GetSymbolModifiers() is { IsVirtual: false, IsAbstract: false, IsOverride: false }) + { + semanticEdits.Add(new SemanticEditInfo(editKind, symbolKey, syntaxMap, syntaxMapTree: null, partialType: null, deletedSymbolContainer: containingSymbolKey)); + continue; + } } // deleting symbol is not allowed diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index 4139f6cd18536..6c77a7e1dda9d 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -679,6 +679,12 @@ internal static void MergePartialEdits( newResolution = edit.Symbol.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken); Contract.ThrowIfNull(newResolution.Symbol); } + else if (edit.Kind == SemanticEditKind.Delete && edit.DeletedSymbolContainer is not null) + { + // For deletes, we use NewSymbol to reference the containing type of the deleted member + newResolution = edit.DeletedSymbolContainer.Value.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken); + Contract.ThrowIfNull(newResolution.Symbol); + } else { newResolution = default; diff --git a/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs b/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs index 53e343d4c60ef..ead56879b0778 100644 --- a/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs +++ b/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs @@ -10,13 +10,14 @@ namespace Microsoft.CodeAnalysis.EditAndContinue internal readonly struct SemanticEditInfo { /// - /// or . + /// or or . /// public SemanticEditKind Kind { get; } /// /// If is represents the inserted symbol in the new compilation. /// If is represents the updated symbol in both compilations. + /// If is represents the deleted symbol in the old compilation. /// /// We use to represent the symbol rather then , /// since different semantic edits might have been calculated against different solution snapshot and thus symbols are not directly comparable. @@ -24,6 +25,15 @@ internal readonly struct SemanticEditInfo /// public SymbolKey Symbol { get; } + /// + /// If is represents the containing symbol in the new compilation. + /// + /// We use to represent the symbol rather then , + /// 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; } + /// /// 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. @@ -48,13 +58,15 @@ public SemanticEditInfo( SymbolKey symbol, Func? syntaxMap, SyntaxTree? syntaxMapTree, - SymbolKey? partialType) + SymbolKey? partialType, + SymbolKey? deletedSymbolContainer = null) { Kind = kind; Symbol = symbol; SyntaxMap = syntaxMap; SyntaxMapTree = syntaxMapTree; PartialType = partialType; + DeletedSymbolContainer = deletedSymbolContainer; } } }