Skip to content

Commit 0fd64c3

Browse files
authored
EnC: Simplify diagnostic reporting (#78708)
1 parent 5f01fe7 commit 0fd64c3

23 files changed

+639
-580
lines changed

src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -398,26 +398,26 @@ public async ValueTask<ManagedHotReloadUpdates> GetUpdatesAsync(ImmutableArray<s
398398
break;
399399
}
400400

401-
ArrayBuilder<DiagnosticData>? deletedDocumentRudeEdits = null;
402-
foreach (var rudeEdit in result.RudeEdits)
401+
ArrayBuilder<DiagnosticData>? applyChangesDiagnostics = null;
402+
foreach (var diagnostic in result.Diagnostics)
403403
{
404-
if (await solution.GetDocumentAsync(rudeEdit.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false) == null)
404+
// Report warnings and errors that are not reported when analyzing documents or are reported for deleted documents.
405+
406+
if (diagnostic.Severity is not (DiagnosticSeverity.Error or DiagnosticSeverity.Warning))
405407
{
406-
deletedDocumentRudeEdits ??= ArrayBuilder<DiagnosticData>.GetInstance();
407-
deletedDocumentRudeEdits.Add(rudeEdit);
408+
continue;
408409
}
409-
}
410410

411-
if (deletedDocumentRudeEdits != null)
412-
{
413-
deletedDocumentRudeEdits.AddRange(result.Diagnostics);
414-
UpdateApplyChangesDiagnostics(deletedDocumentRudeEdits.ToImmutableAndFree());
415-
}
416-
else
417-
{
418-
UpdateApplyChangesDiagnostics(result.Diagnostics);
411+
if ((!EditAndContinueDiagnosticDescriptors.IsRudeEdit(diagnostic.Id)) ||
412+
await solution.GetDocumentAsync(diagnostic.DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false) == null)
413+
{
414+
applyChangesDiagnostics ??= ArrayBuilder<DiagnosticData>.GetInstance();
415+
applyChangesDiagnostics.Add(diagnostic);
416+
}
419417
}
420418

419+
UpdateApplyChangesDiagnostics(applyChangesDiagnostics.ToImmutableOrEmptyAndFree());
420+
421421
return new ManagedHotReloadUpdates(
422422
result.ModuleUpdates.Updates.FromContract(),
423423
result.GetAllDiagnostics().FromContract(),

src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public async Task Test(bool commitChanges)
118118

119119
await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution
120120
.AddTestProject("proj", out var projectId)
121-
.AddTestDocument("test.cs", "class C { }", out var documentId).Project.Solution);
121+
.AddTestDocument("class C { }", "test.cs", out var documentId).Project.Solution);
122122

123123
var solution = localWorkspace.CurrentSolution;
124124
var project = solution.GetRequiredProject(projectId);
@@ -156,24 +156,57 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution
156156

157157
// EmitSolutionUpdate
158158

159-
var diagnosticDescriptor1 = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile);
159+
var errorReadingFileDescriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile);
160+
var moduleErrorDescriptor = EditAndContinueDiagnosticDescriptors.GetModuleDiagnosticDescriptor(Contracts.EditAndContinue.ManagedHotReloadAvailabilityStatus.Optimized);
161+
var syntaxErrorDescriptor = new DiagnosticDescriptor("CS0001", "Syntax error", "Syntax error", "Compiler", DiagnosticSeverity.Error, isEnabledByDefault: true);
162+
var compilerHiddenDescriptor = new DiagnosticDescriptor("CS0002", "Hidden", "Emit Hidden", "Compiler", DiagnosticSeverity.Hidden, isEnabledByDefault: true);
163+
var compilerInfoDescriptor = new DiagnosticDescriptor("CS0003", "Info", "Emit Info", "Compiler", DiagnosticSeverity.Info, isEnabledByDefault: true);
164+
var compilerWarningDescriptor = new DiagnosticDescriptor("CS0004", "Emit Warning", "Emit Warning", "Compiler", DiagnosticSeverity.Warning, isEnabledByDefault: true);
165+
var compilerErrorDescriptor = new DiagnosticDescriptor("CS0005", "Emit Error", "Emit Error", "Compiler", DiagnosticSeverity.Error, isEnabledByDefault: true);
160166

161167
mockEncService.EmitSolutionUpdateImpl = (solution, _, _) =>
162168
{
163169
var syntaxTree = solution.GetRequiredDocument(documentId).GetSyntaxTreeSynchronously(CancellationToken.None)!;
164170

165-
var documentDiagnostic = CodeAnalysis.Diagnostic.Create(diagnosticDescriptor1, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "error 1"]);
166-
var projectDiagnostic = CodeAnalysis.Diagnostic.Create(diagnosticDescriptor1, Location.None, ["proj", "error 2"]);
167-
var syntaxError = CodeAnalysis.Diagnostic.Create(diagnosticDescriptor1, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "syntax error 3"]);
171+
var documentDiagnostic = CodeAnalysis.Diagnostic.Create(errorReadingFileDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "error 1"]);
172+
var projectDiagnostic = CodeAnalysis.Diagnostic.Create(errorReadingFileDescriptor, Location.None, ["proj", "error 2"]);
173+
var moduleError = CodeAnalysis.Diagnostic.Create(moduleErrorDescriptor, Location.None, ["proj", "module error"]);
174+
var syntaxError = CodeAnalysis.Diagnostic.Create(syntaxErrorDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)));
175+
var compilerDocHidden = CodeAnalysis.Diagnostic.Create(compilerHiddenDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)));
176+
var compilerDocInfo = CodeAnalysis.Diagnostic.Create(compilerInfoDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)));
177+
var compilerDocWarning = CodeAnalysis.Diagnostic.Create(compilerWarningDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)));
178+
var compilerDocError = CodeAnalysis.Diagnostic.Create(compilerErrorDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)));
179+
var compilerProjectHidden = CodeAnalysis.Diagnostic.Create(compilerHiddenDescriptor, Location.None);
180+
var compilerProjectInfo = CodeAnalysis.Diagnostic.Create(compilerInfoDescriptor, Location.None);
181+
var compilerProjectWarning = CodeAnalysis.Diagnostic.Create(compilerWarningDescriptor, Location.None);
182+
var compilerProjectError = CodeAnalysis.Diagnostic.Create(compilerErrorDescriptor, Location.None);
168183
var rudeEditDiagnostic = new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(2, 3), arguments: ["x"]).ToDiagnostic(syntaxTree);
169184
var deletedDocumentRudeEdit = new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(2, 3), arguments: ["<deleted>"]).ToDiagnostic(tree: null);
170185

171186
return new()
172187
{
173188
Solution = solution,
174189
ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Ready, []),
175-
Diagnostics = [new ProjectDiagnostics(project.Id, [documentDiagnostic, projectDiagnostic])],
176-
RudeEdits = [new ProjectDiagnostics(project.Id, [rudeEditDiagnostic, deletedDocumentRudeEdit])],
190+
Diagnostics =
191+
[
192+
new ProjectDiagnostics(
193+
project.Id,
194+
[
195+
documentDiagnostic,
196+
projectDiagnostic,
197+
moduleError,
198+
rudeEditDiagnostic,
199+
deletedDocumentRudeEdit,
200+
compilerDocError,
201+
compilerDocWarning,
202+
compilerDocHidden,
203+
compilerDocInfo,
204+
compilerProjectError,
205+
compilerProjectWarning,
206+
compilerProjectHidden,
207+
compilerProjectInfo,
208+
])
209+
],
177210
SyntaxError = syntaxError,
178211
ProjectsToRebuild = [project.Id],
179212
ProjectsToRestart = ImmutableDictionary<ProjectId, ImmutableArray<ProjectId>>.Empty.Add(project.Id, [])
@@ -186,18 +219,28 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution
186219

187220
AssertEx.Equal(
188221
[
189-
$"Error ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "<deleted>")}",
190222
$"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}",
191-
$"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}"
223+
$"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}",
224+
$"Error ENC2012: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, "proj", "module error")}",
225+
$"Error ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "<deleted>")}",
226+
$"Error CS0005: {document.FilePath}(0, 1, 0, 2): Emit Error",
227+
$"Warning CS0004: {document.FilePath}(0, 1, 0, 2): Emit Warning",
228+
$"Error CS0005: {project.FilePath}(0, 0, 0, 0): Emit Error",
229+
$"Warning CS0004: {project.FilePath}(0, 0, 0, 0): Emit Warning",
192230
], sessionState.ApplyChangesDiagnostics.Select(Inspect));
193231

194232
AssertEx.Equal(
195233
[
196-
$"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}",
197-
$"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}",
198-
$"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "syntax error 3")}",
234+
$"RestartRequired ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}",
235+
$"RestartRequired ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}",
236+
$"RestartRequired ENC2012: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, "proj", "module error")}",
199237
$"RestartRequired ENC0033: {document.FilePath}(0, 2, 0, 3): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "x")}",
200238
$"RestartRequired ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "<deleted>")}",
239+
$"Error CS0005: {document.FilePath}(0, 1, 0, 2): Emit Error",
240+
$"Warning CS0004: {document.FilePath}(0, 1, 0, 2): Emit Warning",
241+
$"Error CS0005: {project.FilePath}(0, 0, 0, 0): Emit Error",
242+
$"Warning CS0004: {project.FilePath}(0, 0, 0, 0): Emit Warning",
243+
$"Error CS0001: {document.FilePath}(0, 1, 0, 2): Syntax error",
201244
], updates.Diagnostics.Select(Inspect));
202245

203246
Assert.True(sessionState.IsSessionActive);

src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ internal ImmutableList<ProjectBaseline> GetOrCreateEmitBaselines(
309309
Guid moduleId,
310310
Project baselineProject,
311311
Compilation baselineCompilation,
312-
out ImmutableArray<Diagnostic> errors,
312+
ArrayBuilder<Diagnostic> diagnostics,
313313
out ReaderWriterLockSlim baselineAccessLock)
314314
{
315315
baselineAccessLock = _baselineAccessLock;
@@ -319,17 +319,16 @@ internal ImmutableList<ProjectBaseline> GetOrCreateEmitBaselines(
319319
{
320320
if (TryGetBaselinesContainingModuleVersion(moduleId, out existingBaselines))
321321
{
322-
errors = [];
323322
return existingBaselines;
324323
}
325324
}
326325

327326
var outputs = GetCompilationOutputs(baselineProject);
328-
if (!TryCreateInitialBaseline(baselineCompilation, outputs, baselineProject.Id, out errors, out var initialBaseline, out var debugInfoReaderProvider, out var metadataReaderProvider))
327+
if (!TryCreateInitialBaseline(baselineCompilation, outputs, baselineProject.Id, diagnostics, out var initialBaseline, out var debugInfoReaderProvider, out var metadataReaderProvider))
329328
{
330329
// Unable to read the DLL/PDB at this point (it might be open by another process).
331330
// Don't cache the failure so that the user can attempt to apply changes again.
332-
return existingBaselines ?? [];
331+
return [];
333332
}
334333

335334
lock (_projectEmitBaselinesGuard)
@@ -359,7 +358,7 @@ private unsafe bool TryCreateInitialBaseline(
359358
Compilation compilation,
360359
CompilationOutputs compilationOutputs,
361360
ProjectId projectId,
362-
out ImmutableArray<Diagnostic> errors,
361+
ArrayBuilder<Diagnostic> diagnostics,
363362
[NotNullWhen(true)] out EmitBaseline? baseline,
364363
[NotNullWhen(true)] out DebugInformationReaderProvider? debugInfoReaderProvider,
365364
[NotNullWhen(true)] out MetadataReaderProvider? metadataReaderProvider)
@@ -370,7 +369,6 @@ private unsafe bool TryCreateInitialBaseline(
370369
// Alternatively, we could drop the data once we are done with emitting the delta and re-emit the baseline again
371370
// when we need it next time and the module is loaded.
372371

373-
errors = [];
374372
baseline = null;
375373
debugInfoReaderProvider = null;
376374
metadataReaderProvider = null;
@@ -413,7 +411,7 @@ private unsafe bool TryCreateInitialBaseline(
413411
SessionLog.Write($"Failed to create baseline for '{projectId.DebugName}': {e.Message}", LogMessageSeverity.Error);
414412

415413
var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile);
416-
errors = [Diagnostic.Create(descriptor, Location.None, [fileBeingRead, e.Message])];
414+
diagnostics.Add(Diagnostic.Create(descriptor, Location.None, [fileBeingRead, e.Message]));
417415
}
418416
finally
419417
{
@@ -552,21 +550,10 @@ public async ValueTask<EmitSolutionUpdateResults> EmitSolutionUpdateAsync(
552550
break;
553551
}
554552

555-
using var _ = ArrayBuilder<ProjectDiagnostics>.GetInstance(out var rudeEditDiagnostics);
556-
foreach (var (projectId, documentsWithRudeEdits) in solutionUpdate.DocumentsWithRudeEdits.GroupBy(static e => e.Id.ProjectId).OrderBy(static id => id))
557-
{
558-
foreach (var documentWithRudeEdits in documentsWithRudeEdits)
559-
{
560-
var document = await solution.GetDocumentAsync(documentWithRudeEdits.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
561-
var tree = (document != null) ? await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false) : null;
562-
rudeEditDiagnostics.Add(new(projectId, documentWithRudeEdits.RudeEdits.SelectAsArray(static (rudeEdit, tree) => rudeEdit.ToDiagnostic(tree), tree)));
563-
}
564-
}
565-
566553
EmitSolutionUpdateResults.GetProjectsToRebuildAndRestart(
567554
solution,
568555
solutionUpdate.ModuleUpdates,
569-
rudeEditDiagnostics,
556+
solutionUpdate.Diagnostics,
570557
runningProjects,
571558
out var projectsToRestart,
572559
out var projectsToRebuild);
@@ -578,7 +565,6 @@ public async ValueTask<EmitSolutionUpdateResults> EmitSolutionUpdateAsync(
578565
Solution = solution,
579566
ModuleUpdates = solutionUpdate.ModuleUpdates,
580567
Diagnostics = solutionUpdate.Diagnostics,
581-
RudeEdits = rudeEditDiagnostics.ToImmutable(),
582568
SyntaxError = solutionUpdate.SyntaxError,
583569
ProjectsToRestart = projectsToRestart,
584570
ProjectsToRebuild = projectsToRebuild

src/Features/Core/Portable/EditAndContinue/DebuggingSessionTelemetry.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public static void Log(Data data, Action<FunctionId, LogMessage> log, Func<int>
106106

107107
map["RudeEditsCount"] = editSessionData.RudeEdits.Length;
108108

109-
// Number of emit errors.
109+
// Number of emit errors. These are any errors only produced during emitting deltas and do not include document analysis errors.
110110
map["EmitDeltaErrorIdCount"] = editSessionData.EmitErrorIds.Length;
111111

112112
// False for Hot Reload session, true or missing for EnC session (missing in older data that did not have this property).

src/Features/Core/Portable/EditAndContinue/DocumentWithRudeEdits.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)