Skip to content

Commit d0f57bf

Browse files
committed
Hot Reload: Handle document deletes
1 parent c1b5cf1 commit d0f57bf

23 files changed

+582
-347
lines changed

src/Compilers/Test/Core/FX/EncodingUtilities.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
#nullable disable
6-
75
using System.Text;
86

97
namespace Roslyn.Test.Utilities

src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,25 @@ public async ValueTask<ManagedHotReloadUpdates> GetUpdatesAsync(ImmutableArray<s
398398
break;
399399
}
400400

401-
UpdateApplyChangesDiagnostics(result.Diagnostics);
401+
ArrayBuilder<DiagnosticData>? deletedDocumentRudeEdits = null;
402+
foreach (var rudeEdit in result.RudeEdits)
403+
{
404+
if (await solution.GetDocumentAsync(rudeEdit.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false) == null)
405+
{
406+
deletedDocumentRudeEdits ??= ArrayBuilder<DiagnosticData>.GetInstance();
407+
deletedDocumentRudeEdits.Add(rudeEdit);
408+
}
409+
}
410+
411+
if (deletedDocumentRudeEdits != null)
412+
{
413+
deletedDocumentRudeEdits.AddRange(result.Diagnostics);
414+
UpdateApplyChangesDiagnostics(deletedDocumentRudeEdits.ToImmutableAndFree());
415+
}
416+
else
417+
{
418+
UpdateApplyChangesDiagnostics(result.Diagnostics);
419+
}
402420

403421
return new ManagedHotReloadUpdates(
404422
result.ModuleUpdates.Updates.FromContract(),

src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,14 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution
166166
var projectDiagnostic = CodeAnalysis.Diagnostic.Create(diagnosticDescriptor1, Location.None, ["proj", "error 2"]);
167167
var syntaxError = CodeAnalysis.Diagnostic.Create(diagnosticDescriptor1, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "syntax error 3"]);
168168
var rudeEditDiagnostic = new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(2, 3), arguments: ["x"]).ToDiagnostic(syntaxTree);
169+
var deletedDocumentRudeEdit = new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(2, 3), arguments: ["<deleted>"]).ToDiagnostic(tree: null);
169170

170171
return new()
171172
{
172173
Solution = solution,
173174
ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Ready, []),
174175
Diagnostics = [new ProjectDiagnostics(project.Id, [documentDiagnostic, projectDiagnostic])],
175-
RudeEdits = [new ProjectDiagnostics(project.Id, [rudeEditDiagnostic])],
176+
RudeEdits = [new ProjectDiagnostics(project.Id, [rudeEditDiagnostic, deletedDocumentRudeEdit])],
176177
SyntaxError = syntaxError,
177178
ProjectsToRebuild = [project.Id],
178179
ProjectsToRestart = ImmutableDictionary<ProjectId, ImmutableArray<ProjectId>>.Empty.Add(project.Id, [])
@@ -185,6 +186,7 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution
185186

186187
AssertEx.Equal(
187188
[
189+
$"Error ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "<deleted>")}",
188190
$"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}",
189191
$"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}"
190192
], sessionState.ApplyChangesDiagnostics.Select(Inspect));
@@ -194,7 +196,8 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution
194196
$"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}",
195197
$"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}",
196198
$"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "syntax error 3")}",
197-
$"RestartRequired ENC0033: {document.FilePath}(0, 2, 0, 3): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "x")}"
199+
$"RestartRequired ENC0033: {document.FilePath}(0, 2, 0, 3): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "x")}",
200+
$"RestartRequired ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "<deleted>")}",
198201
], updates.Diagnostics.Select(Inspect));
199202

200203
Assert.True(sessionState.IsSessionActive);

src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -179,22 +179,6 @@ internal override bool IsDeclarationWithSharedBody(SyntaxNode declaration, ISymb
179179
protected override bool AreHandledEventsEqual(IMethodSymbol oldMethod, IMethodSymbol newMethod)
180180
=> true;
181181

182-
protected override IEnumerable<SyntaxNode> GetVariableUseSites(IEnumerable<SyntaxNode> roots, ISymbol localOrParameter, SemanticModel model, CancellationToken cancellationToken)
183-
{
184-
Debug.Assert(localOrParameter is IParameterSymbol or ILocalSymbol or IRangeVariableSymbol);
185-
186-
// not supported (it's non trivial to find all places where "this" is used):
187-
Debug.Assert(!localOrParameter.IsThisParameter());
188-
189-
return from root in roots
190-
from node in root.DescendantNodesAndSelf()
191-
where node.IsKind(SyntaxKind.IdentifierName)
192-
let nameSyntax = (IdentifierNameSyntax)node
193-
where (string?)nameSyntax.Identifier.Value == localOrParameter.Name &&
194-
(model.GetSymbolInfo(nameSyntax, cancellationToken).Symbol?.Equals(localOrParameter) ?? false)
195-
select node;
196-
}
197-
198182
internal static SyntaxNode FindStatementAndPartner(
199183
TextSpan span,
200184
SyntaxNode body,
@@ -583,10 +567,6 @@ protected override IEnumerable<SequenceEdit> GetSyntaxSequenceEdits(ImmutableArr
583567
internal override SyntaxNode EmptyCompilationUnit
584568
=> SyntaxFactory.CompilationUnit();
585569

586-
// there are no experimental features at this time.
587-
internal override bool ExperimentalFeaturesEnabled(SyntaxTree tree)
588-
=> false;
589-
590570
protected override bool StatementLabelEquals(SyntaxNode node1, SyntaxNode node2)
591571
=> SyntaxComparer.Statement.GetLabel(node1) == SyntaxComparer.Statement.GetLabel(node2);
592572

@@ -1042,8 +1022,8 @@ WellKnownMemberNames.ObjectToString or
10421022
EditKind editKind,
10431023
SyntaxNode? oldNode,
10441024
SyntaxNode? newNode,
1045-
SemanticModel? oldModel,
1046-
SemanticModel newModel,
1025+
DocumentSemanticModel oldModel,
1026+
DocumentSemanticModel newModel,
10471027
CancellationToken cancellationToken)
10481028
{
10491029
// Chnage in type of a field affects all its variable declarations.
@@ -1060,21 +1040,19 @@ WellKnownMemberNames.ObjectToString or
10601040

10611041
OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol)> AddFieldSymbolUpdates(SeparatedSyntaxList<VariableDeclaratorSyntax> oldVariables, SeparatedSyntaxList<VariableDeclaratorSyntax> newVariables)
10621042
{
1063-
Debug.Assert(oldModel != null);
1064-
10651043
if (oldVariables.Count == 1 && newVariables.Count == 1)
10661044
{
1067-
return OneOrMany.Create((GetDeclaredSymbol(oldModel, oldVariables[0], cancellationToken), GetDeclaredSymbol(newModel, newVariables[0], cancellationToken)));
1045+
return OneOrMany.Create((GetDeclaredSymbol(oldModel.RequiredModel, oldVariables[0], cancellationToken), GetDeclaredSymbol(newModel.RequiredModel, newVariables[0], cancellationToken)));
10681046
}
10691047

10701048
return OneOrMany.Create(
10711049
(from oldVariable in oldVariables
10721050
join newVariable in newVariables on oldVariable.Identifier.Text equals newVariable.Identifier.Text
1073-
select (GetDeclaredSymbol(oldModel, oldVariable, cancellationToken), GetDeclaredSymbol(newModel, newVariable, cancellationToken))).ToImmutableArray());
1051+
select (GetDeclaredSymbol(oldModel.RequiredModel, oldVariable, cancellationToken), GetDeclaredSymbol(newModel.RequiredModel, newVariable, cancellationToken))).ToImmutableArray());
10741052
}
10751053

1076-
var oldSymbol = (oldNode != null) ? GetSymbolForEdit(oldNode, oldModel!, cancellationToken) : null;
1077-
var newSymbol = (newNode != null) ? GetSymbolForEdit(newNode, newModel, cancellationToken) : null;
1054+
var oldSymbol = (oldNode != null) ? GetSymbolForEdit(oldNode, oldModel.RequiredModel, cancellationToken) : null;
1055+
var newSymbol = (newNode != null) ? GetSymbolForEdit(newNode, newModel.RequiredModel, cancellationToken) : null;
10781056

10791057
return (oldSymbol == null && newSymbol == null)
10801058
? OneOrMany<(ISymbol?, ISymbol?)>.Empty
@@ -1088,8 +1066,8 @@ protected override void AddSymbolEdits(
10881066
ISymbol? oldSymbol,
10891067
SyntaxNode? newNode,
10901068
ISymbol? newSymbol,
1091-
SemanticModel? oldModel,
1092-
SemanticModel newModel,
1069+
DocumentSemanticModel oldModel,
1070+
DocumentSemanticModel newModel,
10931071
Match<SyntaxNode> topMatch,
10941072
IReadOnlyDictionary<SyntaxNode, EditKind> editMap,
10951073
SymbolInfoCache symbolCache,
@@ -1101,7 +1079,7 @@ protected override void AddSymbolEdits(
11011079
var oldContainingMemberOrType = GetParameterContainingMemberOrType(oldNode, newNode, oldModel, topMatch.ReverseMatches, cancellationToken);
11021080
var newContainingMemberOrType = GetParameterContainingMemberOrType(newNode, oldNode, newModel, topMatch.Matches, cancellationToken);
11031081

1104-
var matchingNewContainingMemberOrType = GetSemanticallyMatchingNewSymbol(oldContainingMemberOrType, newContainingMemberOrType, newModel, symbolCache, cancellationToken);
1082+
var matchingNewContainingMemberOrType = GetSemanticallyMatchingNewSymbol(oldContainingMemberOrType, newContainingMemberOrType, newModel.Compilation, symbolCache, cancellationToken);
11051083

11061084
// Create candidate symbol edits to analyze:
11071085
// 1) An update of the containing member or type
@@ -1189,7 +1167,6 @@ newContainingMemberOrType is IPropertySymbol newPropertySymbol &&
11891167
case EditKind.Update:
11901168
Contract.ThrowIfNull(oldNode);
11911169
Contract.ThrowIfNull(newNode);
1192-
Contract.ThrowIfNull(oldModel);
11931170

11941171
// Updates of a property/indexer/event node might affect its accessors.
11951172
// Return all affected symbols for these updates so that the changes in the accessor bodies get analyzed.
@@ -1372,7 +1349,6 @@ newNode is TypeDeclarationSyntax newTypeDeclaration &&
13721349
case EditKind.Move:
13731350
Contract.ThrowIfNull(oldNode);
13741351
Contract.ThrowIfNull(newNode);
1375-
Contract.ThrowIfNull(oldModel);
13761352

13771353
Debug.Assert(oldNode.RawKind == newNode.RawKind);
13781354
Debug.Assert(SupportsMove(oldNode));
@@ -1432,7 +1408,7 @@ newNode is TypeDeclarationSyntax newTypeDeclaration &&
14321408
return GetDeclaredSymbol(model, node, cancellationToken);
14331409
}
14341410

1435-
private ISymbol? GetParameterContainingMemberOrType(SyntaxNode? node, SyntaxNode? otherNode, SemanticModel? model, IReadOnlyDictionary<SyntaxNode, SyntaxNode> fromOtherMap, CancellationToken cancellationToken)
1411+
private ISymbol? GetParameterContainingMemberOrType(SyntaxNode? node, SyntaxNode? otherNode, DocumentSemanticModel model, IReadOnlyDictionary<SyntaxNode, SyntaxNode> fromOtherMap, CancellationToken cancellationToken)
14361412
{
14371413
Debug.Assert(node is null or ParameterSyntax or TypeParameterSyntax or TypeParameterConstraintClauseSyntax);
14381414

@@ -1457,7 +1433,7 @@ newNode is TypeDeclarationSyntax newTypeDeclaration &&
14571433
declaration = declaration.Parent;
14581434
}
14591435

1460-
return (declaration != null) ? GetDeclaredSymbol(model!, declaration, cancellationToken) : null;
1436+
return (declaration != null) ? GetDeclaredSymbol(model.RequiredModel, declaration, cancellationToken) : null;
14611437

14621438
static SyntaxNode GetContainingDeclaration(SyntaxNode node)
14631439
=> node is TypeParameterSyntax ? node.Parent!.Parent! : node!.Parent!;
@@ -2857,10 +2833,10 @@ internal override void ReportOtherRudeEditsAroundActiveStatement(
28572833
IReadOnlyDictionary<SyntaxNode, SyntaxNode> reverseMap,
28582834
SyntaxNode oldActiveStatement,
28592835
DeclarationBody oldBody,
2860-
SemanticModel oldModel,
2836+
DocumentSemanticModel oldModel,
28612837
SyntaxNode newActiveStatement,
28622838
DeclarationBody newBody,
2863-
SemanticModel newModel,
2839+
DocumentSemanticModel newModel,
28642840
bool isNonLeaf,
28652841
CancellationToken cancellationToken)
28662842
{
@@ -2991,10 +2967,10 @@ private void ReportRudeEditsForAncestorsDeclaringInterStatementTemps(
29912967
IReadOnlyDictionary<SyntaxNode, SyntaxNode> reverseMap,
29922968
SyntaxNode oldActiveStatement,
29932969
SyntaxNode oldEncompassingAncestor,
2994-
SemanticModel oldModel,
2970+
DocumentSemanticModel oldModel,
29952971
SyntaxNode newActiveStatement,
29962972
SyntaxNode newEncompassingAncestor,
2997-
SemanticModel newModel,
2973+
DocumentSemanticModel newModel,
29982974
CancellationToken cancellationToken)
29992975
{
30002976
// Rude Edits for fixed/using/lock/foreach statements that are added/updated around an active statement.

src/Features/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ private static async Task<DocumentAnalysisResults> AnalyzeDocumentAsync(
126126
var baseActiveStatements = AsyncLazy.Create(activeStatementMap ?? ActiveStatementsMap.Empty);
127127
var lazyCapabilities = AsyncLazy.Create(capabilities);
128128
var log = new TraceLog("Test");
129-
return await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, newActiveStatementSpans.NullToEmpty(), lazyCapabilities, log, CancellationToken.None);
129+
return await analyzer.AnalyzeDocumentAsync(newDocument.Id, oldProject, newDocument.Project, baseActiveStatements, newActiveStatementSpans.NullToEmpty(), lazyCapabilities, log, CancellationToken.None);
130130
}
131131

132132
#endregion
@@ -760,7 +760,7 @@ public async Task AnalyzeDocumentAsync_InternalError(bool outOfMemory)
760760
};
761761

762762
var log = new TraceLog("Test");
763-
var result = await analyzer.AnalyzeDocumentAsync(oldProject, baseActiveStatements, newDocument, [], capabilities, log, CancellationToken.None);
763+
var result = await analyzer.AnalyzeDocumentAsync(newDocument.Id, oldProject, newDocument.Project, baseActiveStatements, [], capabilities, log, CancellationToken.None);
764764

765765
var expectedDiagnostic = outOfMemory
766766
? $"ENC0089: {string.Format(FeaturesResources.Modifying_source_file_0_requires_restarting_the_application_because_the_file_is_too_big, filePath)}"

src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.ActiveMembersBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal partial class AbstractEditAndContinueAnalyzer
1818
/// Constructor to which field/property initializers are emitted is active if its body or any of the field/property initializers has an active statement outside of a lambda.
1919
/// Fields and properties with initializers are considered active if any of the constructors that these initializers are emitted to are active.
2020
/// </summary>
21-
private readonly struct ActiveMembersBuilder(AbstractEditAndContinueAnalyzer analyzer, SemanticModel? oldModel, SemanticModel newModel, CancellationToken cancellationToken) : IDisposable
21+
private readonly struct ActiveMembersBuilder(AbstractEditAndContinueAnalyzer analyzer, DocumentSemanticModel oldModel, DocumentSemanticModel newModel, CancellationToken cancellationToken) : IDisposable
2222
{
2323
private readonly PooledHashSet<IMethodSymbol> _methods = PooledHashSet<IMethodSymbol>.GetInstance();
2424
private readonly PooledDictionary<SyntaxNode, SyntaxNode> _declarations = PooledDictionary<SyntaxNode, SyntaxNode>.GetInstance();

0 commit comments

Comments
 (0)