diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index bdb4e90debbca..9c208127637c4 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -1160,7 +1160,7 @@ internal override bool HasBackingField(SyntaxNode propertyOrIndexerDeclaration) => propertyOrIndexerDeclaration.IsKind(SyntaxKind.PropertyDeclaration, out PropertyDeclarationSyntax? propertyDecl) && SyntaxUtilities.HasBackingField(propertyDecl); - internal override bool TryGetAssociatedMemberDeclaration(SyntaxNode node, [NotNullWhen(true)] out SyntaxNode? declaration) + internal override bool TryGetAssociatedMemberDeclaration(SyntaxNode node, EditKind editKind, [NotNullWhen(true)] out SyntaxNode? declaration) { if (node.IsKind(SyntaxKind.Parameter, SyntaxKind.TypeParameter)) { @@ -1169,7 +1169,8 @@ internal override bool TryGetAssociatedMemberDeclaration(SyntaxNode node, [NotNu return true; } - if (node.Parent.IsParentKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration, SyntaxKind.EventDeclaration)) + // For deletes, we don't associate accessors with their parents, as deleting accessors is allowed + if (editKind != EditKind.Delete && node.Parent.IsParentKind(SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration, SyntaxKind.EventDeclaration)) { declaration = node.Parent.Parent!; return true; diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 777a8ee5ba8b6..6c3f05a325b5e 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -465,7 +465,7 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind /// - a method, an indexer or a type (delegate) if the is a parameter, /// - a method or an type if the is a type parameter. /// - internal abstract bool TryGetAssociatedMemberDeclaration(SyntaxNode node, [NotNullWhen(true)] out SyntaxNode? declaration); + internal abstract bool TryGetAssociatedMemberDeclaration(SyntaxNode node, EditKind editKind, [NotNullWhen(true)] out SyntaxNode? declaration); internal abstract bool HasBackingField(SyntaxNode propertyDeclaration); @@ -2788,7 +2788,7 @@ private async Task> AnalyzeSemanticsAsync( // Associated member declarations must be in the same document as the symbol, so we don't need to resolve their symbol. // In some cases the symbol even can't be resolved unambiguously. Consider e.g. resolving a method with its parameter deleted - // we wouldn't know which overload to resolve to. - if (TryGetAssociatedMemberDeclaration(oldDeclaration, out var oldAssociatedMemberDeclaration)) + if (TryGetAssociatedMemberDeclaration(oldDeclaration, EditKind.Delete, out var oldAssociatedMemberDeclaration)) { if (HasEdit(editMap, oldAssociatedMemberDeclaration, EditKind.Delete)) { @@ -2806,14 +2806,43 @@ 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 }) + if (oldSymbol.GetSymbolModifiers() is { IsVirtual: false, IsAbstract: false, IsOverride: false } && + oldSymbol.ContainingType is { TypeKind: TypeKind.Class or TypeKind.Struct } && + !oldSymbol.IsExtern) { - semanticEdits.Add(new SemanticEditInfo(editKind, symbolKey, syntaxMap, syntaxMapTree: null, partialType: null, deletedSymbolContainer: containingSymbolKey)); - 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 or + MethodKind.Constructor or + MethodKind.EventAdd or + MethodKind.EventRemove or + MethodKind.EventRaise or + MethodKind.Conversion or + MethodKind.UserDefinedOperator or + MethodKind.PropertyGet or + MethodKind.PropertySet + }) + { + AddDeleteSemanticEdit(semanticEdits, oldSymbol, containingSymbolKey, syntaxMap, cancellationToken); + continue; + } + else if (oldSymbol is IPropertySymbol propertySymbol) + { + AddDeleteSemanticEdit(semanticEdits, propertySymbol.GetMethod, containingSymbolKey, syntaxMap, cancellationToken); + AddDeleteSemanticEdit(semanticEdits, propertySymbol.SetMethod, containingSymbolKey, syntaxMap, cancellationToken); + continue; + } + else if (oldSymbol is IEventSymbol eventSymbol) + { + AddDeleteSemanticEdit(semanticEdits, eventSymbol.AddMethod, containingSymbolKey, syntaxMap, cancellationToken); + AddDeleteSemanticEdit(semanticEdits, eventSymbol.RemoveMethod, containingSymbolKey, syntaxMap, cancellationToken); + AddDeleteSemanticEdit(semanticEdits, eventSymbol.RaiseMethod, containingSymbolKey, syntaxMap, cancellationToken); + continue; + } } } @@ -2961,7 +2990,7 @@ private async Task> AnalyzeSemanticsAsync( editKind = SemanticEditKind.Update; } } - else if (TryGetAssociatedMemberDeclaration(newDeclaration, out var newAssociatedMemberDeclaration) && + else if (TryGetAssociatedMemberDeclaration(newDeclaration, EditKind.Insert, out var newAssociatedMemberDeclaration) && HasEdit(editMap, newAssociatedMemberDeclaration, EditKind.Insert)) { // If the symbol is an accessor and the containing property/indexer/event declaration has also been inserted skip @@ -3315,6 +3344,15 @@ private async Task> AnalyzeSemanticsAsync( } } + private static void AddDeleteSemanticEdit(ArrayBuilder semanticEdits, ISymbol? symbol, SymbolKey containingSymbolKey, Func? syntaxMap, CancellationToken cancellationToken) + { + if (symbol is null) + return; + + var symbolKey = SymbolKey.Create(symbol, cancellationToken); + semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Delete, symbolKey, syntaxMap, syntaxMapTree: null, partialType: null, deletedSymbolContainer: containingSymbolKey)); + } + private ImmutableArray<(ISymbol? oldSymbol, ISymbol? newSymbol, EditKind editKind)> GetNamespaceSymbolEdits( SemanticModel oldModel, SemanticModel newModel, diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index d0d54f9c54fe5..d11b1e16a4a32 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -1043,14 +1043,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return node.Parent.FirstAncestorOrSelf(Of TypeBlockSyntax)() ' TODO: EnbumBlock? End Function - Friend Overrides Function TryGetAssociatedMemberDeclaration(node As SyntaxNode, ByRef declaration As SyntaxNode) As Boolean + Friend Overrides Function TryGetAssociatedMemberDeclaration(node As SyntaxNode, editKind As EditKind, ByRef declaration As SyntaxNode) As Boolean If node.IsKind(SyntaxKind.Parameter, SyntaxKind.TypeParameter) Then Contract.ThrowIfFalse(node.IsParentKind(SyntaxKind.ParameterList, SyntaxKind.TypeParameterList)) declaration = node.Parent.Parent Return True End If - If node.IsParentKind(SyntaxKind.PropertyBlock, SyntaxKind.EventBlock) Then + ' We allow deleting event and property accessors, so don't associate them + If editKind <> EditKind.Delete AndAlso node.IsParentKind(SyntaxKind.PropertyBlock, SyntaxKind.EventBlock) Then declaration = node.Parent Return True End If