diff --git a/eng/Directory.Packages.props b/eng/Directory.Packages.props index 8b0c5a473de71..50cb2e3b2cb15 100644 --- a/eng/Directory.Packages.props +++ b/eng/Directory.Packages.props @@ -117,7 +117,7 @@ - + diff --git a/src/EditorFeatures/Core/EditAndContinue/Contracts/ContractWrappers.cs b/src/EditorFeatures/Core/EditAndContinue/Contracts/ContractWrappers.cs index 5118e11a7d526..872b044c6ed80 100644 --- a/src/EditorFeatures/Core/EditAndContinue/Contracts/ContractWrappers.cs +++ b/src/EditorFeatures/Core/EditAndContinue/Contracts/ContractWrappers.cs @@ -2,7 +2,9 @@ // 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.Linq; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; using Microsoft.VisualStudio.Debugger.Contracts.HotReload; using InternalContracts = Microsoft.CodeAnalysis.Contracts.EditAndContinue; @@ -23,6 +25,12 @@ public static InternalContracts.ManagedMethodId ToContract(this ManagedMethodId public static InternalContracts.SourceSpan ToContract(this SourceSpan id) => new(id.StartLine, id.StartColumn, id.EndLine, id.EndColumn); + public static InternalContracts.ProjectInstanceId ToContract(this ProjectInstanceId id) + => new(id.ProjectFilePath, id.TargetFramework); + + public static InternalContracts.RunningProjectInfo ToContract(this RunningProjectInfo id) + => new(id.ProjectInstanceId.ToContract(), id.RestartAutomatically); + public static InternalContracts.ManagedHotReloadAvailability ToContract(this ManagedHotReloadAvailability value) => new((InternalContracts.ManagedHotReloadAvailabilityStatus)value.Status, value.LocalizedMessage); @@ -41,7 +49,7 @@ public static ManagedHotReloadUpdate FromContract(this InternalContracts.Managed exceptionRegions: update.ExceptionRegions.SelectAsArray(FromContract)); public static ManagedHotReloadUpdates FromContract(this InternalContracts.ManagedHotReloadUpdates updates) - => new(updates.Updates.FromContract(), updates.Diagnostics.FromContract(), updates.ProjectsToRebuild, updates.ProjectsToRestart); + => new(updates.Updates.FromContract(), updates.Diagnostics.FromContract(), updates.ProjectsToRebuild.SelectAsArray(FromContract), updates.ProjectsToRestart.SelectAsArray(FromContract)); public static ImmutableArray FromContract(this ImmutableArray diagnostics) => diagnostics.SelectAsArray(FromContract); @@ -61,6 +69,9 @@ public static ManagedModuleMethodId FromContract(this InternalContracts.ManagedM public static SourceSpan FromContract(this InternalContracts.SourceSpan id) => new(id.StartLine, id.StartColumn, id.EndLine, id.EndColumn); + public static ProjectInstanceId FromContract(this InternalContracts.ProjectInstanceId id) + => new(id.ProjectFilePath, id.TargetFramework); + public static ManagedExceptionRegionUpdate FromContract(this InternalContracts.ManagedExceptionRegionUpdate update) => new(FromContract(update.Method), update.Delta, FromContract(update.NewSpan)); diff --git a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs index 81115532f4766..f08f3aa82a796 100644 --- a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs +++ b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageService.cs @@ -21,8 +21,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; [Shared] -[Export(typeof(IManagedHotReloadLanguageService))] -[Export(typeof(IManagedHotReloadLanguageService2))] +[Export(typeof(IManagedHotReloadLanguageService3))] [Export(typeof(IEditAndContinueSolutionProvider))] [Export(typeof(EditAndContinueLanguageService))] [ExportMetadata("UIContext", EditAndContinueUIContext.EncCapableProjectExistsInWorkspaceUIContextString)] @@ -34,7 +33,7 @@ internal sealed class EditAndContinueLanguageService( Lazy debuggerService, PdbMatchingSourceTextProvider sourceTextProvider, IEditAndContinueLogReporter logReporter, - IDiagnosticsRefresher diagnosticRefresher) : IManagedHotReloadLanguageService2, IEditAndContinueSolutionProvider + IDiagnosticsRefresher diagnosticRefresher) : IManagedHotReloadLanguageService3 { private sealed class NoSessionException : InvalidOperationException { @@ -236,59 +235,9 @@ public async ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) } } - public async ValueTask UpdateBaselinesAsync(ImmutableArray projectPaths, CancellationToken cancellationToken) - { - if (_disabled) - { - return; - } - - var currentDesignTimeSolution = GetCurrentDesignTimeSolution(); - var currentCompileTimeSolution = GetCurrentCompileTimeSolution(currentDesignTimeSolution); - - try - { - SolutionCommitted?.Invoke(currentDesignTimeSolution); - } - catch (Exception e) when (FatalError.ReportAndCatch(e)) - { - } - - _committedDesignTimeSolution = currentDesignTimeSolution; - var projectIds = GetProjectIds(projectPaths, currentCompileTimeSolution); - - try - { - await GetDebuggingSession().UpdateBaselinesAsync(currentCompileTimeSolution, projectIds, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) - { - } - - foreach (var projectId in projectIds) - { - workspaceProvider.Value.Workspace.EnqueueUpdateSourceGeneratorVersion(projectId, forceRegeneration: false); - } - } - - private ImmutableArray GetProjectIds(ImmutableArray projectPaths, Solution solution) - { - using var _ = ArrayBuilder.GetInstance(out var projectIds); - foreach (var path in projectPaths) - { - var projectId = solution.Projects.FirstOrDefault(project => project.FilePath == path)?.Id; - if (projectId != null) - { - projectIds.Add(projectId); - } - else - { - logReporter.Report($"Project with path '{path}' not found in the current solution.", LogMessageSeverity.Info); - } - } - - return projectIds.ToImmutable(); - } + [Obsolete] + public ValueTask UpdateBaselinesAsync(ImmutableArray projectPaths, CancellationToken cancellationToken) + => throw new NotImplementedException(); public async ValueTask EndSessionAsync(CancellationToken cancellationToken) { @@ -359,9 +308,13 @@ public async ValueTask HasChangesAsync(string? sourceFilePath, Cancellatio [Obsolete] public ValueTask GetUpdatesAsync(CancellationToken cancellationToken) - => GetUpdatesAsync(runningProjects: [], cancellationToken); + => throw new NotImplementedException(); - public async ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) + [Obsolete] + public ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) + => throw new NotImplementedException(); + + public async ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) { if (_disabled) { @@ -371,22 +324,13 @@ public async ValueTask GetUpdatesAsync(ImmutableArray (info.ProjectInstanceId.ProjectFilePath, info.ProjectInstanceId.TargetFramework, info.RestartAutomatically)); - using var _ = PooledHashSet.GetInstance(out var runningProjectPaths); - runningProjectPaths.AddAll(runningProjects); - - // TODO: Update once implemented: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2449700 - var runningProjectInfos = solution.Projects.Where(p => p.FilePath != null && runningProjectPaths.Contains(p.FilePath)).ToImmutableDictionary( - keySelector: static p => p.Id, - elementSelector: static p => new RunningProjectInfo { RestartWhenChangesHaveNoEffect = false, AllowPartialUpdate = false }); - - var result = await GetDebuggingSession().EmitSolutionUpdateAsync(solution, runningProjectInfos, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); + var result = await GetDebuggingSession().EmitSolutionUpdateAsync(solution, runningProjectOptions, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); switch (result.ModuleUpdates.Status) { - case ModuleUpdateStatus.Ready when result.ProjectsToRebuild.IsEmpty: - // We have updates to be applied and no rude edits. - // + case ModuleUpdateStatus.Ready: // The debugger will call Commit/Discard on the solution // based on whether the updates will be applied successfully or not. _pendingUpdatedDesignTimeSolution = designTimeSolution; @@ -422,10 +366,14 @@ await solution.GetDocumentAsync(diagnostic.DocumentId, includeSourceGenerated: t return new ManagedHotReloadUpdates( result.ModuleUpdates.Updates.FromContract(), result.GetAllDiagnostics().FromContract(), - GetProjectPaths(result.ProjectsToRebuild), - GetProjectPaths(result.ProjectsToRestart.Keys)); + ToProjectIntanceIds(result.ProjectsToRebuild), + ToProjectIntanceIds(result.ProjectsToRestart.Keys)); - ImmutableArray GetProjectPaths(IEnumerable ids) - => ids.SelectAsArray(id => solution.GetRequiredProject(id).FilePath!); + ImmutableArray ToProjectIntanceIds(IEnumerable ids) + => ids.SelectAsArray(id => + { + var project = solution.GetRequiredProject(id); + return new ProjectInstanceId(project.FilePath!, project.State.NameAndFlavor.flavor ?? ""); + }); } } diff --git a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageServiceBridge.cs b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageServiceBridge.cs index be9aadc407671..a58f500f26ddb 100644 --- a/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageServiceBridge.cs +++ b/src/EditorFeatures/Core/EditAndContinue/EditAndContinueLanguageServiceBridge.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; /// TODO (https://github.com/dotnet/roslyn/issues/72713): /// Once debugger is updated to use the brokered service, this class should be removed and should be exported directly. /// -internal sealed partial class ManagedEditAndContinueLanguageServiceBridge(EditAndContinueLanguageService service) : IManagedHotReloadLanguageService2 +internal sealed partial class ManagedEditAndContinueLanguageServiceBridge(EditAndContinueLanguageService service) : IManagedHotReloadLanguageService3 { public ValueTask StartSessionAsync(CancellationToken cancellationToken) => service.StartSessionAsync(cancellationToken); @@ -34,16 +34,21 @@ public ValueTask OnCapabilitiesChangedAsync(CancellationToken cancellationToken) [Obsolete] public ValueTask GetUpdatesAsync(CancellationToken cancellationToken) - => service.GetUpdatesAsync(cancellationToken); + => throw new NotImplementedException(); + [Obsolete] public ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) + => throw new NotImplementedException(); + + public ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) => service.GetUpdatesAsync(runningProjects, cancellationToken); public ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) => service.CommitUpdatesAsync(cancellationToken); + [Obsolete] public ValueTask UpdateBaselinesAsync(ImmutableArray projectPaths, CancellationToken cancellationToken) - => service.UpdateBaselinesAsync(projectPaths, cancellationToken); + => throw new NotImplementedException(); public ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) => service.DiscardUpdatesAsync(cancellationToken); diff --git a/src/EditorFeatures/ExternalAccess/Debugger/GlassTestsHotReloadService.cs b/src/EditorFeatures/ExternalAccess/Debugger/GlassTestsHotReloadService.cs index d8b299ea817ca..fb5981590e18c 100644 --- a/src/EditorFeatures/ExternalAccess/Debugger/GlassTestsHotReloadService.cs +++ b/src/EditorFeatures/ExternalAccess/Debugger/GlassTestsHotReloadService.cs @@ -84,7 +84,7 @@ public void EndDebuggingSession() public async ValueTask GetUpdatesAsync(Solution solution, CancellationToken cancellationToken) { - var results = (await _encService.EmitSolutionUpdateAsync(GetSessionId(), solution, runningProjects: ImmutableDictionary.Empty, s_noActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false)).Dehydrate(); - return new ManagedHotReloadUpdates(results.ModuleUpdates.Updates.FromContract(), results.GetAllDiagnostics().FromContract(), [], []); + var results = (await _encService.EmitSolutionUpdateAsync(GetSessionId(), solution, runningProjects: ImmutableDictionary.Empty, s_noActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false)).Dehydrate(); + return new ManagedHotReloadUpdates(results.ModuleUpdates.Updates.FromContract(), results.GetAllDiagnostics().FromContract(), projectInstancesToRebuild: [], projectInstancesToRestart: []); } } diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs index 389c8beb70085..f4f2baa187b5b 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueLanguageServiceTests.cs @@ -216,7 +216,13 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution }; }; - var updates = await localService.GetUpdatesAsync(runningProjects: [project.FilePath], CancellationToken.None); + var runningProjectInfo = new DebuggerContracts.RunningProjectInfo() + { + ProjectInstanceId = new DebuggerContracts.ProjectInstanceId(project.FilePath, "net9.0"), + RestartAutomatically = false, + }; + + var updates = await localService.GetUpdatesAsync(runningProjects: [runningProjectInfo], CancellationToken.None); Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); @@ -281,7 +287,7 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution }; }; - updates = await localService.GetUpdatesAsync(runningProjects: [project.FilePath], CancellationToken.None); + updates = await localService.GetUpdatesAsync(runningProjects: [runningProjectInfo], CancellationToken.None); Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); diff --git a/src/Features/Core/Portable/Contracts/EditAndContinue/IManagedHotReloadLanguageService.cs b/src/Features/Core/Portable/Contracts/EditAndContinue/IManagedHotReloadLanguageService.cs index 3ca98a0430989..0e4d61e6ac6ab 100644 --- a/src/Features/Core/Portable/Contracts/EditAndContinue/IManagedHotReloadLanguageService.cs +++ b/src/Features/Core/Portable/Contracts/EditAndContinue/IManagedHotReloadLanguageService.cs @@ -2,6 +2,7 @@ // 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.Immutable; using System.Threading; using System.Threading.Tasks; @@ -15,6 +16,7 @@ internal interface IManagedHotReloadLanguageService ValueTask EndSessionAsync(CancellationToken cancellationToken); ValueTask EnterBreakStateAsync(CancellationToken cancellationToken); ValueTask ExitBreakStateAsync(CancellationToken cancellationToken); + [Obsolete] ValueTask GetUpdatesAsync(CancellationToken cancellationToken); ValueTask HasChangesAsync(string? sourceFilePath, CancellationToken cancellationToken); ValueTask OnCapabilitiesChangedAsync(CancellationToken cancellationToken); @@ -23,6 +25,14 @@ internal interface IManagedHotReloadLanguageService internal interface IManagedHotReloadLanguageService2 : IManagedHotReloadLanguageService { + [Obsolete] ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken); + + [Obsolete] ValueTask UpdateBaselinesAsync(ImmutableArray projectPaths, CancellationToken cancellationToken); } + +internal interface IManagedHotReloadLanguageService3 : IManagedHotReloadLanguageService2 +{ + ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken); +} diff --git a/src/Features/Core/Portable/Contracts/EditAndContinue/ManagedHotReloadUpdates.cs b/src/Features/Core/Portable/Contracts/EditAndContinue/ManagedHotReloadUpdates.cs index 7fd7b7e09c751..e8f0b167f8815 100644 --- a/src/Features/Core/Portable/Contracts/EditAndContinue/ManagedHotReloadUpdates.cs +++ b/src/Features/Core/Portable/Contracts/EditAndContinue/ManagedHotReloadUpdates.cs @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue; [DataContract] -internal readonly struct ManagedHotReloadUpdates(ImmutableArray updates, ImmutableArray diagnostics, ImmutableArray projectsToRebuild, ImmutableArray projectsToRestart) +internal readonly struct ManagedHotReloadUpdates(ImmutableArray updates, ImmutableArray diagnostics, ImmutableArray projectsToRebuild, ImmutableArray projectsToRestart) { [DataMember(Name = "updates")] public ImmutableArray Updates { get; } = updates; @@ -17,8 +17,8 @@ internal readonly struct ManagedHotReloadUpdates(ImmutableArray Diagnostics { get; } = diagnostics; [DataMember(Name = "projectsToRebuild")] - public ImmutableArray ProjectsToRebuild { get; } = projectsToRebuild; + public ImmutableArray ProjectsToRebuild { get; } = projectsToRebuild; [DataMember(Name = "projectsToRestart")] - public ImmutableArray ProjectsToRestart { get; } = projectsToRestart; + public ImmutableArray ProjectsToRestart { get; } = projectsToRestart; } diff --git a/src/Features/Core/Portable/Contracts/EditAndContinue/ProjectInstanceId.cs b/src/Features/Core/Portable/Contracts/EditAndContinue/ProjectInstanceId.cs new file mode 100644 index 0000000000000..2797481f7cb1b --- /dev/null +++ b/src/Features/Core/Portable/Contracts/EditAndContinue/ProjectInstanceId.cs @@ -0,0 +1,17 @@ +// 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.Runtime.Serialization; + +namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue; + +[DataContract] +internal readonly struct ProjectInstanceId(string projectFilePath, string targetFramework) +{ + [DataMember(Name = "projectFilePath")] + public string ProjectFilePath { get; } = projectFilePath; + + [DataMember(Name = "targetFramework")] + public string TargetFramework { get; } = targetFramework; +} diff --git a/src/Features/Core/Portable/Contracts/EditAndContinue/RunningProjectInfo.cs b/src/Features/Core/Portable/Contracts/EditAndContinue/RunningProjectInfo.cs new file mode 100644 index 0000000000000..9e11ee9dbd9c8 --- /dev/null +++ b/src/Features/Core/Portable/Contracts/EditAndContinue/RunningProjectInfo.cs @@ -0,0 +1,17 @@ +// 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.Runtime.Serialization; + +namespace Microsoft.CodeAnalysis.Contracts.EditAndContinue; + +[DataContract] +internal readonly struct RunningProjectInfo(ProjectInstanceId projectInstanceId, bool restartAutomatically) +{ + [DataMember(Name = "projectInstanceId")] + public ProjectInstanceId ProjectInstanceId { get; } = projectInstanceId; + + [DataMember(Name = "restartAutomatically")] + public bool RestartAutomatically { get; } = restartAutomatically; +} diff --git a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs index e73d4cea23cd6..76408ddc1a5fd 100644 --- a/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs +++ b/src/Features/Core/Portable/EditAndContinue/CommittedSolution.cs @@ -455,16 +455,12 @@ await TryGetMatchingSourceTextAsync(log, sourceText, sourceFilePath, currentDocu } } - public void CommitChanges(Solution solution, ImmutableDictionary? staleProjects, ImmutableArray? projectsToUnstale = null) + public void CommitChanges(Solution solution, ImmutableDictionary staleProjects) { - Contract.ThrowIfTrue(staleProjects is null && projectsToUnstale is null); - lock (_guard) { _solution = solution; - staleProjects ??= _staleProjects.RemoveRange(projectsToUnstale!); - var oldStaleProjects = _staleProjects; _staleProjects = staleProjects; diff --git a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs index 64f8410edf233..251c5220aa989 100644 --- a/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/DebuggingSession.cs @@ -523,7 +523,7 @@ CommittedSolution.DocumentState.Indeterminate or public async ValueTask EmitSolutionUpdateAsync( Solution solution, - ImmutableDictionary runningProjects, + ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) { @@ -536,8 +536,6 @@ public async ValueTask EmitSolutionUpdateAsync( var solutionUpdate = await EditSession.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, updateId, runningProjects, cancellationToken).ConfigureAwait(false); - var allowPartialUpdates = runningProjects.Any(p => p.Value.AllowPartialUpdate); - solutionUpdate.Log(SessionLog, updateId); _lastModuleUpdatesLog = solutionUpdate.ModuleUpdates.Updates; @@ -549,29 +547,13 @@ public async ValueTask EmitSolutionUpdateAsync( // We have updates to be applied or processes to restart. The debugger will call Commit/Discard on the solution // based on whether the updates will be applied successfully or not. - if (allowPartialUpdates) - { - StorePendingUpdate(new PendingSolutionUpdate( - solution, - solutionUpdate.StaleProjects, - solutionUpdate.ProjectsToRebuild, - solutionUpdate.ProjectBaselines, - solutionUpdate.ModuleUpdates.Updates, - solutionUpdate.NonRemappableRegions)); - } - else if (solutionUpdate.ProjectsToRebuild.IsEmpty) - { - // no rude edits - - StorePendingUpdate(new PendingSolutionUpdate( - solution, - solutionUpdate.StaleProjects, - // if partial updates are not allowed we don't treat rebuild as part of solution update: - projectsToRebuild: [], - solutionUpdate.ProjectBaselines, - solutionUpdate.ModuleUpdates.Updates, - solutionUpdate.NonRemappableRegions)); - } + StorePendingUpdate(new PendingSolutionUpdate( + solution, + solutionUpdate.StaleProjects, + solutionUpdate.ProjectsToRebuild, + solutionUpdate.ProjectBaselines, + solutionUpdate.ModuleUpdates.Updates, + solutionUpdate.NonRemappableRegions)); break; @@ -594,10 +576,7 @@ public async ValueTask EmitSolutionUpdateAsync( return new EmitSolutionUpdateResults() { Solution = solution, - // If partial updates are disabled the debugger does not expect module updates when rude edits are reported: - ModuleUpdates = allowPartialUpdates || solutionUpdate.ProjectsToRebuild.IsEmpty - ? solutionUpdate.ModuleUpdates - : new ModuleUpdates(solutionUpdate.ModuleUpdates.Status, []), + ModuleUpdates = solutionUpdate.ModuleUpdates, Diagnostics = solutionUpdate.Diagnostics, SyntaxError = solutionUpdate.SyntaxError, ProjectsToRestart = solutionUpdate.ProjectsToRestart, @@ -704,36 +683,6 @@ private void DiscardProjectBaselinesNoLock(Solution solution, IEnumerable rebuiltProjects) - { - ThrowIfDisposed(); - - // Make sure the solution snapshot has all source-generated documents up-to-date. - solution = solution.WithUpToDateSourceGeneratorDocuments(solution.ProjectIds); - - LastCommittedSolution.CommitChanges(solution, staleProjects: null, projectsToUnstale: rebuiltProjects); - - // Wait for all operations on baseline to finish before we dispose the readers. - - _baselineContentAccessLock.EnterWriteLock(); - - lock (_projectEmitBaselinesGuard) - { - DiscardProjectBaselinesNoLock(solution, rebuiltProjects); - } - - _baselineContentAccessLock.ExitWriteLock(); - - foreach (var projectId in rebuiltProjects) - { - _editSessionTelemetry.LogUpdatedBaseline(solution.GetRequiredProject(projectId).State.ProjectInfo.Attributes.TelemetryId); - } - - // Restart edit session reusing previous non-remappable regions and break state: - RestartEditSession(nonRemappableRegions: null, inBreakState: null); - } - /// /// Returns s for each document of , /// or default if not in a break state. diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs index a27aaaa5141a3..029f020263026 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueService.cs @@ -220,7 +220,7 @@ public ValueTask> GetDocumentDiagnosticsAsync(Documen public ValueTask EmitSolutionUpdateAsync( DebuggingSessionId sessionId, Solution solution, - ImmutableDictionary runningProjects, + ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) { @@ -249,14 +249,6 @@ public void DiscardSolutionUpdate(DebuggingSessionId sessionId) debuggingSession.DiscardSolutionUpdate(); } - public void UpdateBaselines(DebuggingSessionId sessionId, Solution solution, ImmutableArray rebuiltProjects) - { - var debuggingSession = TryGetDebuggingSession(sessionId); - Contract.ThrowIfNull(debuggingSession); - - debuggingSession.UpdateBaselines(solution, rebuiltProjects); - } - public ValueTask>> GetBaseActiveStatementSpansAsync(DebuggingSessionId sessionId, Solution solution, ImmutableArray documentIds, CancellationToken cancellationToken) { var debuggingSession = TryGetDebuggingSession(sessionId); diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index 11c4f4b7895e9..03ff251d0d2ff 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -957,7 +957,7 @@ public async ValueTask EmitSolutionUpdateAsync( Solution solution, ActiveStatementSpanProvider solutionActiveStatementSpanProvider, UpdateId updateId, - ImmutableDictionary runningProjects, + ImmutableDictionary runningProjects, CancellationToken cancellationToken) { var projectDiagnostics = ArrayBuilder.GetInstance(); diff --git a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs index 8cbcc14f815f2..e4ae6e69d3cd9 100644 --- a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs @@ -78,7 +78,7 @@ internal ImmutableArray GetAllDiagnostics() return builder.ToImmutableAndClear(); } - public static Data CreateFromInternalError(Solution solution, string errorMessage, ImmutableDictionary runningProjects) + public static Data CreateFromInternalError(Solution solution, string errorMessage, ImmutableDictionary runningProjects) { ImmutableArray diagnostics = []; var firstProject = solution.GetProject(runningProjects.FirstOrDefault().Key) ?? solution.Projects.First(); @@ -200,7 +200,7 @@ internal static void GetProjectsToRebuildAndRestart( ImmutableArray moduleUpdates, ImmutableArray diagnostics, IReadOnlyCollection addedUnbuiltProjects, - ImmutableDictionary runningProjects, + ImmutableDictionary runningProjects, out ImmutableDictionary> projectsToRestart, out ImmutableArray projectsToRebuild) { @@ -279,26 +279,7 @@ internal static void GetProjectsToRebuildAndRestart( // At this point the restart set contains all running projects transitively affected by rude edits. // Next, find projects that were successfully updated and affect running projects. - // Remove once https://github.com/dotnet/roslyn/issues/78244 is implemented. - if (!runningProjects.Any(static p => p.Value.AllowPartialUpdate)) - { - // Partial solution update not supported. - if (projectsToRestartBuilder.Any()) - { - foreach (var update in moduleUpdates) - { - foreach (var ancestor in GetAncestorsAndSelf(update.ProjectId)) - { - if (runningProjects.ContainsKey(ancestor)) - { - projectsToRebuildBuilder.TryAdd(ancestor, []); - projectsToRestartBuilder.Add(ancestor); - } - } - } - } - } - else if (!moduleUpdates.IsEmpty && projectsToRebuildBuilder.Count > 0) + if (!moduleUpdates.IsEmpty && projectsToRebuildBuilder.Count > 0) { // The set of updated projects is usually much smaller than the number of all projects in the solution. // We iterate over this set updating the restart set until no new project is added to the restart set. diff --git a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs index 77384c0fc39fe..46d34c03a6ae3 100644 --- a/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/IEditAndContinueService.cs @@ -19,11 +19,10 @@ internal interface IEditAndContinueWorkspaceService : IWorkspaceService internal interface IEditAndContinueService { ValueTask> GetDocumentDiagnosticsAsync(Document document, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken); - ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken); + ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken); void CommitSolutionUpdate(DebuggingSessionId sessionId); void DiscardSolutionUpdate(DebuggingSessionId sessionId); - void UpdateBaselines(DebuggingSessionId sessionId, Solution solution, ImmutableArray rebuiltProjects); ValueTask StartDebuggingSessionAsync(Solution solution, IManagedHotReloadService debuggerService, IPdbMatchingSourceTextProvider sourceTextProvider, ImmutableArray captureMatchingDocuments, bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken); void BreakStateOrCapabilitiesChanged(DebuggingSessionId sessionId, bool? inBreakState); diff --git a/src/Features/Core/Portable/EditAndContinue/ModuleUpdateStatus.cs b/src/Features/Core/Portable/EditAndContinue/ModuleUpdateStatus.cs index 9eb9f048428cd..503303b6d5b13 100644 --- a/src/Features/Core/Portable/EditAndContinue/ModuleUpdateStatus.cs +++ b/src/Features/Core/Portable/EditAndContinue/ModuleUpdateStatus.cs @@ -11,17 +11,21 @@ internal enum ModuleUpdateStatus { /// /// No change made. + /// Pending solution update is not created. + /// Committed solution snapshot is advanced. /// None = 0, /// /// Changes can be applied (project might need rebuild in presence of transient errors). + /// A pending solution update is created and has to be committed (commited solution snapshot is advanced at that point) or discarded. /// Ready = 1, /// /// Some changes are errors that block rebuild of the module. /// This means that the code is in a broken state that cannot be resolved by restarting the application. + /// Pending solution update is not created. /// Blocked = 2 } diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs index 5f6b14203f2e5..86232da3050b7 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs @@ -27,14 +27,13 @@ internal interface ICallback } ValueTask> GetDocumentDiagnosticsAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken); - ValueTask EmitSolutionUpdateAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, ImmutableDictionary runningProjects, CancellationToken cancellationToken); + ValueTask EmitSolutionUpdateAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, ImmutableDictionary runningProjects, CancellationToken cancellationToken); /// /// Returns ids of documents for which diagnostics need to be refreshed in-proc. /// ValueTask CommitSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken); ValueTask DiscardSolutionUpdateAsync(DebuggingSessionId sessionId, CancellationToken cancellationToken); - ValueTask UpdateBaselinesAsync(Checksum solutionInfo, DebuggingSessionId sessionId, ImmutableArray rebuiltProjects, CancellationToken cancellationToken); ValueTask StartDebuggingSessionAsync(Checksum solutionChecksum, RemoteServiceCallbackId callbackId, ImmutableArray captureMatchingDocuments, bool captureAllMatchingDocuments, bool reportDiagnostics, CancellationToken cancellationToken); diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs index a460d799beef7..a789e81577fd9 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteDebuggingSessionProxy.cs @@ -54,7 +54,7 @@ await client.TryInvokeAsync( public async ValueTask EmitSolutionUpdateAsync( Solution solution, - ImmutableDictionary runningProjects, + ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) { @@ -111,22 +111,6 @@ await client.TryInvokeAsync( cancellationToken).ConfigureAwait(false); } - public async ValueTask UpdateBaselinesAsync(Solution solution, ImmutableArray rebuiltProjects, CancellationToken cancellationToken) - { - var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); - if (client == null) - { - GetLocalService().UpdateBaselines(sessionId, solution, rebuiltProjects); - } - else - { - var result = await client.TryInvokeAsync( - solution, - (service, solutionInfo, cancellationToken) => service.UpdateBaselinesAsync(solutionInfo, sessionId, rebuiltProjects, cancellationToken), - cancellationToken).ConfigureAwait(false); - } - } - public async ValueTask>> GetBaseActiveStatementSpansAsync(Solution solution, ImmutableArray documentIds, CancellationToken cancellationToken) { var client = await RemoteHostClient.TryGetClientAsync(services, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/EditAndContinue/RunningProjectInfo.cs b/src/Features/Core/Portable/EditAndContinue/RunningProjectInfo.cs deleted file mode 100644 index deafbf97e3e06..0000000000000 --- a/src/Features/Core/Portable/EditAndContinue/RunningProjectInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.Serialization; - -namespace Microsoft.CodeAnalysis.EditAndContinue; - -[DataContract] -internal readonly struct RunningProjectInfo -{ - /// - /// Required restart of the project when an edit that has no effect until the app is restarted is made to any dependent project. - /// - [DataMember] - public required bool RestartWhenChangesHaveNoEffect { get; init; } - - /// - /// TODO: remove when implemented: https://github.com/dotnet/roslyn/issues/78244 - /// Indicates that the info has been passed from debugger. - /// - [DataMember] - public required bool AllowPartialUpdate { get; init; } -} diff --git a/src/Features/Core/Portable/EditAndContinue/RunningProjectOptions.cs b/src/Features/Core/Portable/EditAndContinue/RunningProjectOptions.cs new file mode 100644 index 0000000000000..b2839f27ecbbf --- /dev/null +++ b/src/Features/Core/Portable/EditAndContinue/RunningProjectOptions.cs @@ -0,0 +1,74 @@ +// 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.Linq; +using System.Runtime.Serialization; + +namespace Microsoft.CodeAnalysis.EditAndContinue; + +[DataContract] +internal readonly struct RunningProjectOptions +{ + /// + /// Required restart of the project when an edit that has no effect until the app is restarted is made to any dependent project. + /// + [DataMember] + public required bool RestartWhenChangesHaveNoEffect { get; init; } +} + +internal static class RunningProjectOptionsFactory +{ + public static ImmutableDictionary ToRunningProjectOptions( + this ImmutableArray runningProjects, + Solution solution, + Func translator) + { + // Invariants guaranteed by the debugger: + // - Running projects does nto contain duplicate ids. + // - TFM is always specified for SDK projects event if the project doesn't multi-target, it is empty for legacy projects. + + var runningProjectsByPathAndTfm = runningProjects + .Select(info => + { + var (filePath, targetFramework, restartAutomatically) = translator(info); + return KeyValuePair.Create((filePath, targetFramework is { Length: > 0 } tfm ? tfm : null), restartAutomatically); + }) + .ToImmutableDictionary(PathAndTfmComparer.Instance); + + var result = ImmutableDictionary.CreateBuilder(); + + foreach (var project in solution.Projects) + { + if (project.FilePath == null) + { + continue; + } + + // Roslyn project name does not include TFM if the project is not multi-targeted (flavor is null). + // The key comparer ignores TFM if null and therefore returns a random entry that has the same file path. + // Since projects without TFM can only have at most one entry in the dictionary the random entry is that single value. + if (runningProjectsByPathAndTfm.TryGetValue((project.FilePath, project.State.NameAndFlavor.flavor), out var restartAutomatically)) + { + result.Add(project.Id, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = restartAutomatically }); + continue; + } + } + + return result.ToImmutableDictionary(); + } + + private sealed class PathAndTfmComparer : IEqualityComparer<(string path, string? tfm)> + { + public static readonly PathAndTfmComparer Instance = new(); + + public int GetHashCode((string path, string? tfm) obj) + => obj.path.GetHashCode(); // only hash path, all tfms need to fall to the same bucket + + public bool Equals((string path, string? tfm) x, (string path, string? tfm) y) + => x.path == y.path && (x.tfm == null || y.tfm == null || x.tfm == y.tfm); + } +} diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs index 89e3cb0857ac4..e46a0a1034d2a 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/UnitTestingHotReloadService.cs @@ -51,9 +51,6 @@ public readonly struct Update( private static readonly ActiveStatementSpanProvider s_solutionActiveStatementSpanProvider = (_, _, _) => ValueTask.FromResult(ImmutableArray.Empty); - private static readonly ImmutableArray EmptyUpdate = []; - private static readonly ImmutableArray EmptyDiagnostic = []; - private readonly IEditAndContinueService _encService = services.GetRequiredService().Service; private DebuggingSessionId _sessionId; @@ -95,10 +92,10 @@ public async Task StartSessionAsync(Solution solution, ImmutableArray ca Contract.ThrowIfFalse(sessionId != default, "Session has not started"); var results = await _encService - .EmitSolutionUpdateAsync(sessionId, solution, runningProjects: ImmutableDictionary.Empty, s_solutionActiveStatementSpanProvider, cancellationToken) + .EmitSolutionUpdateAsync(sessionId, solution, runningProjects: ImmutableDictionary.Empty, s_solutionActiveStatementSpanProvider, cancellationToken) .ConfigureAwait(false); - if (!results.ModuleUpdates.Updates.IsEmpty) + if (results.ModuleUpdates.Status == ModuleUpdateStatus.Ready) { if (commitUpdates) { @@ -110,11 +107,10 @@ public async Task StartSessionAsync(Solution solution, ImmutableArray ca } } - if (results.SyntaxError is not null) + var diagnostics = results.GetAllDiagnostics(); + if (diagnostics.HasAnyErrors()) { - // We do not need to acquire any updates or other - // diagnostics if there is a syntax error. - return (EmptyUpdate, EmptyDiagnostic.Add(results.SyntaxError)); + return ([], diagnostics); } var updates = results.ModuleUpdates.Updates.SelectAsArray( @@ -126,8 +122,6 @@ public async Task StartSessionAsync(Solution solution, ImmutableArray ca update.UpdatedMethods, update.UpdatedTypes)); - var diagnostics = results.GetAllDiagnostics(); - return (updates, diagnostics); } diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index 7244152f9ef33..b19e4fc5a6b9a 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -191,9 +191,6 @@ public void CapabilitiesChanged() public static string? GetTargetFramework(Project project) => project.State.NameAndFlavor.flavor; - // TODO: remove, for backwards compat only - public static bool RequireCommit { get; set; } - /// /// Emits updates for all projects that differ between the given snapshot and the one given to the previous successful call or /// the one passed to for the first invocation. @@ -211,21 +208,13 @@ public async Task GetUpdatesAsync(Solution solution, ImmutableDictiona var runningProjectsImpl = runningProjects.ToImmutableDictionary( static e => e.Key, - static e => new EditAndContinue.RunningProjectInfo() + static e => new RunningProjectOptions() { - RestartWhenChangesHaveNoEffect = e.Value.RestartWhenChangesHaveNoEffect, - AllowPartialUpdate = RequireCommit + RestartWhenChangesHaveNoEffect = e.Value.RestartWhenChangesHaveNoEffect }); var results = await _encService.EmitSolutionUpdateAsync(sessionId, solution, runningProjectsImpl, s_solutionActiveStatementSpanProvider, cancellationToken).ConfigureAwait(false); - // If the changes fail to apply dotnet-watch fails. - // We don't support discarding the changes and letting the user retry. - if (!RequireCommit && results.ModuleUpdates.Status is ModuleUpdateStatus.Ready && results.ProjectsToRebuild.IsEmpty) - { - _encService.CommitSolutionUpdate(sessionId); - } - return new Updates2 { Status = results.ModuleUpdates.Status switch @@ -263,12 +252,6 @@ public void DiscardUpdate() _encService.DiscardSolutionUpdate(sessionId); } - public void UpdateBaselines(Solution solution, ImmutableArray projectIds) - { - var sessionId = GetDebuggingSession(); - _encService.UpdateBaselines(sessionId, solution, projectIds); - } - public void EndSession() { _encService.EndDebuggingSession(GetDebuggingSession()); diff --git a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index c3b7810df4307..b3132866960e5 100644 --- a/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/Features/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -152,12 +152,12 @@ public async Task StartDebuggingSession_CapturingDocuments(bool captureAllDocume EnterBreakState(debuggingSession); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); // TODO: warning reported https://github.com/dotnet/roslyn/issues/78125 - // AssertEx.Equal([$"P.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFileB.Path)}"], InspectDiagnostics(emitDiagnostics)); + // AssertEx.Equal([$"P.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFileB.Path)}"], InspectDiagnostics(results.Diagnostics)); EndDebuggingSession(debuggingSession); } @@ -182,13 +182,13 @@ public async Task ProjectNotBuilt() Assert.Empty(diagnostics); // changes in the project are ignored: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); AssertEx.Equal( [ - $"{document1.Project.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, document1.FilePath)}" - ], InspectDiagnostics(emitDiagnostics)); + $"proj: {document1.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, document1.FilePath)}" + ], InspectDiagnostics(results.Diagnostics)); EndDebuggingSession(debuggingSession); } @@ -219,8 +219,8 @@ public async Task DifferentDocumentWithSameContent() Assert.Empty(diagnostics2); // validate solution update status and emit - changes made during run mode are ignored: - var (updates, _) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); EndDebuggingSession(debuggingSession); @@ -253,10 +253,10 @@ public async Task ProjectThatDoesNotSupportEnC_Language(bool breakMode) solution = solution.WithDocumentText(document1.Id, CreateText("dummy2")); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + Assert.Empty(results.Diagnostics); var document2 = solution.GetDocument(document1.Id); diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); @@ -296,7 +296,7 @@ public void F() {} } """)); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); @@ -335,10 +335,10 @@ class C { int Y => 2; } var diagnostics1 = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics1); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + Assert.Empty(results.Diagnostics); EndDebuggingSession(debuggingSession); } @@ -415,7 +415,7 @@ End Class var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); @@ -500,7 +500,7 @@ End Class var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(isWarning ? ModuleUpdateStatus.None : ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); @@ -576,7 +576,7 @@ End Class var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); @@ -629,7 +629,7 @@ class A return null; }; - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); Assert.Empty(results.Diagnostics); @@ -680,7 +680,7 @@ internal async Task Project_MetadataReferences() var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); @@ -722,7 +722,7 @@ internal async Task Project_MetadataReferences_RemoveAdd() // remove dependency: solution = project.RemoveMetadataReference(libV1).Solution; - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); Assert.Empty(results.Diagnostics); @@ -730,7 +730,7 @@ internal async Task Project_MetadataReferences_RemoveAdd() // add newer version: solution = project.AddMetadataReference(libV2).Solution; - results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); @@ -770,7 +770,7 @@ internal async Task Project_MetadataReferences_Add() // add dependency: solution = project.AddMetadataReference(libV1).Solution; - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); Assert.Empty(results.Diagnostics); @@ -805,7 +805,7 @@ internal async Task Project_MetadataReferences_MultipleVersions() // add version 3: solution = project.AddMetadataReference(libV3).Solution; - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: true); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); @@ -847,8 +847,8 @@ public async Task DesignTimeOnlyDocument() Assert.Empty(diagnostics); // validate solution update status and emit - changes made in design-time-only documents are ignored: - var (updates, _) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); EndDebuggingSession(debuggingSession); @@ -884,15 +884,15 @@ public async Task DesignTimeOnlyDocument_Dynamic() solution = solution.WithDocumentText(document1.Id, CreateText("class E {}")); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); - - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + Assert.Empty(results.Diagnostics); + + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + Assert.Empty(results.Diagnostics); } [Theory, CombinatorialData] @@ -978,18 +978,18 @@ public async Task DesignTimeOnlyDocument_Wpf([CombinatorialValues(LanguageNames. Assert.Empty(await service.GetDocumentDiagnosticsAsync(designTimeOnlyDocument2, s_noActiveSpans, CancellationToken.None)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); if (delayLoad) { LoadLibraryToDebuggee(moduleId); // validate solution update status and emit: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); } EndDebuggingSession(debuggingSession); @@ -1036,19 +1036,21 @@ public async Task ErrorReadingModuleFile(bool breakMode) var docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(docDiagnostics); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.Equal( [$"proj: : Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, moduleFile.Path, expectedErrorMessage)}"], InspectDiagnostics(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); + // correct the error: EmitLibrary(projectId, source2); - var (updates2, diagnostics2) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates2.Status); - Assert.Empty(diagnostics2); + var results2 = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results2.ModuleUpdates.Status); + Assert.Empty(results2.Diagnostics); CommitSolutionUpdate(debuggingSession); @@ -1117,13 +1119,14 @@ public async Task ErrorReadingPdbFile() Assert.Empty(docDiagnostics); // an error occurred so we need to call update to determine whether we have changes to apply or not: - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.Equal( [$"proj: {document2.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); AssertEx.Equal( @@ -1165,17 +1168,18 @@ public async Task ErrorReadingSourceFile() Assert.Empty(docDiagnostics); // an error occurred so we need to call update to determine whether we have changes to apply or not: - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.Equal( [$"test: {document1.FilePath}: (0,0)-(0,0): Error ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); fileLock.Dispose(); // try apply changes again: - results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.NotEmpty(results.ModuleUpdates.Updates); Assert.Empty(results.Diagnostics); @@ -1227,8 +1231,8 @@ public async Task Document_Add(bool breakMode) var diagnostics2 = await service.GetDocumentDiagnosticsAsync(documentB, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics2); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); debuggingSession.DiscardSolutionUpdate(); if (breakMode) @@ -1279,9 +1283,9 @@ public async Task Document_Delete() // delete B: solution = solution.RemoveDocument(documentBId); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.NotEmpty(updates.Updates); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.NotEmpty(results.ModuleUpdates.Updates); debuggingSession.DiscardSolutionUpdate(); @@ -1316,9 +1320,9 @@ public async Task Document_RenameAndUpdate() var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(document2Id), s_noActiveSpans, CancellationToken.None); AssertEx.Empty(diagnostics); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.NotEmpty(updates.Updates); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.NotEmpty(results.ModuleUpdates.Updates); debuggingSession.DiscardSolutionUpdate(); @@ -1381,10 +1385,10 @@ public async Task ModuleDisallowsEditAndContinue_NoChanges(bool breakMode) // workspace is updated to new version after build completed and the session started: solution = solution.WithDocumentText(document0.Id, CreateText(source1)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + Assert.Empty(results.Diagnostics); if (breakMode) { @@ -1419,9 +1423,9 @@ public async Task ModuleDisallowsEditAndContinue_SourceGenerator_NoChanges() var document1 = solution.Projects.Single().Documents.Single(); solution = solution.WithDocumentText(document1.Id, CreateText(source2)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); EndDebuggingSession(debuggingSession); } @@ -1476,13 +1480,14 @@ void M() AssertEx.Empty(docDiagnostics); // validate solution update status and emit: - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.Equal( [$"proj: {document2.FilePath}: (5,0)-(5,32): Error ENC2016: {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, document2.Project.Name, "*message*")}"], InspectDiagnostics(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate()); @@ -1575,11 +1580,13 @@ public async Task RudeEdits(bool breakMode) InspectDiagnostics(docDiagnostics)); // validate solution update status and emit: - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); + if (breakMode) { ExitBreakState(debuggingSession); @@ -1663,10 +1670,10 @@ public async Task DeferredApplyChangeWithActiveStatementRudeEdits() ExitBreakState(debuggingSession); // apply the change: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.NotEmpty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.NotEmpty(results.ModuleUpdates.Updates); + Assert.Empty(results.Diagnostics); CommitSolutionUpdate(debuggingSession); @@ -1717,15 +1724,17 @@ class C { int Y => 2; } [$"{generatedDocument.FilePath}: (0,17)-(0,18): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"], InspectDiagnostics(docDiagnostics)); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.Equal(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); } - [Theory, CombinatorialData] + [Theory(Skip = "https://github.com/dotnet/roslyn/issues/79589")] + [CombinatorialData] public async Task RudeEdits_DocumentOutOfSync(bool breakMode) { var source0 = "class C1 { void M() { System.Console.WriteLine(0); } }"; @@ -1766,68 +1775,53 @@ public async Task RudeEdits_DocumentOutOfSync(bool breakMode) Assert.Empty(docDiagnostics); // the document is out-of-sync, so no rude edits reported: - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); + // project is stale: + Assert.True(debuggingSession.LastCommittedSolution.StaleProjects.ContainsKey(projectId)); + // TODO: warning reported https://github.com/dotnet/roslyn/issues/78125 - // AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics)); + // AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(results.Diagnostics)); // We do not reload the content of out-of-sync file for analyzer query. // We don't check if the content on disk has been updated to match either. - // Document state can only be reset via UpdateBaselines. sourceFile.WriteAllText(source0, Encoding.UTF8); docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); Assert.Empty(docDiagnostics); // rebuild triggers reload of out-of-sync file content: moduleId = EmitAndLoadLibraryToDebuggee(projectId, source0, sourceFilePath: sourceFile.Path); - debuggingSession.UpdateBaselines(solution.WithDocumentText(documentId, CreateText(source0)), [projectId]); - results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); - Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); - Assert.Empty(results.ModuleUpdates.Updates); - AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); - // now we see the rude edit: - docDiagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None); - AssertEx.Equal( - [$"{document2.FilePath}: (0,11)-(0,22): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"], - InspectDiagnostics(docDiagnostics)); + // project has been unstaled: + Assert.False(debuggingSession.LastCommittedSolution.StaleProjects.ContainsKey(projectId)); - results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); - Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); - AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); + Assert.Empty(results.Diagnostics); if (breakMode) { ExitBreakState(debuggingSession); - EndDebuggingSession(debuggingSession); - } - else - { - EndDebuggingSession(debuggingSession); } - AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate()); + EndDebuggingSession(debuggingSession); if (breakMode) { AssertEx.SequenceEqual( [ - "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=1|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2", - "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=|ProjectIdsWithUpdatedBaselines=", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={6A6F7270-0000-4000-8000-000000000000}" + "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=1|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2" ], _telemetryLog); } else { AssertEx.SequenceEqual( [ - "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount=1", - "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges=|ProjectIdsWithUpdatedBaselines=", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={6A6F7270-0000-4000-8000-000000000000}" + "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1" ], _telemetryLog); } } @@ -1867,11 +1861,12 @@ public async Task RudeEdits_DocumentWithoutSequencePoints() InspectDiagnostics(docDiagnostics)); // validate solution update status and emit: - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.SequenceEqual(["ENC0023"], InspectDiagnosticIds(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); } @@ -1909,11 +1904,13 @@ public async Task RudeEdits_DelayLoadedModule() [$"{document2.FilePath}: (0,24)-(0,25): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"], InspectDiagnostics(docDiagnostics)); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); + // load library to the debuggee: LoadLibraryToDebuggee(moduleId); @@ -1923,17 +1920,18 @@ public async Task RudeEdits_DelayLoadedModule() [$"{document2.FilePath}: (0,24)-(0,25): Error ENC0110: {string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)}"], InspectDiagnostics(docDiagnostics)); - results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.SequenceEqual(["ENC0110"], InspectDiagnosticIds(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); } [Theory] [CombinatorialData] - public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit, bool allowPartialUpdates) + public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit) { var source3 = "abstract class C { void F() {} public abstract void G(); }"; var projectDir = Temp.CreateDirectory(); @@ -1956,7 +1954,7 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit, bool { solution = solution.WithDocumentText(documentId, CreateText("abstract class C { void F() {} }")); - results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdates); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ProjectsToRebuild); Assert.Empty(results.ProjectsToRestart); @@ -1985,29 +1983,21 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit, bool InspectDiagnostics(docDiagnostics)); // validate solution update status and emit: - results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdates); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); AssertEx.SequenceEqual(["ENC0023"], InspectDiagnosticIds(results.GetAllDiagnostics())); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); Assert.Empty(results.ModuleUpdates.Updates); AssertEx.Equal([projectId], results.ProjectsToRebuild); AssertEx.Equal([projectId], results.ProjectsToRestart.Keys); - if (allowPartialUpdates) - { - // assuming user approved restart and rebuild: - CommitSolutionUpdate(debuggingSession); - } + // assuming user approved restart and rebuild: + CommitSolutionUpdate(debuggingSession); // rebuild and restart: _debuggerService.LoadedModules.Remove(moduleId); File.WriteAllText(sourceFilePath, source3, Encoding.UTF8); moduleId = EmitAndLoadLibraryToDebuggee(solution.GetRequiredDocument(documentId)); - if (!allowPartialUpdates) - { - debuggingSession.UpdateBaselines(solution, results.ProjectsToRebuild); - } - if (validChangeBeforeRudeEdit) { // baseline should be removed: @@ -2027,7 +2017,7 @@ public async Task RudeEdits_UpdateBaseline(bool validChangeBeforeRudeEdit, bool Assert.Empty(await service.GetDocumentDiagnosticsAsync(solution.GetRequiredDocument(documentId), s_noActiveSpans, CancellationToken.None)); // apply valid change: - results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdates); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); @@ -2075,10 +2065,10 @@ public async Task SyntaxError() AssertEx.Empty(diagnostics1); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Blocked, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + Assert.Empty(results.Diagnostics); EndDebuggingSession(debuggingSession); @@ -2116,14 +2106,14 @@ public async Task SemanticError() // The EnC analyzer does not check for and block on all semantic errors as they are already reported by diagnostic analyzer. // Blocking update on semantic errors would be possible, but the status check is only an optimization to avoid emitting. - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status); - Assert.Empty(updates.Updates); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Blocked, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); // TODO: https://github.com/dotnet/roslyn/issues/36061 // Semantic errors should not be reported in emit diagnostics. - AssertEx.Equal([$"{document2.FilePath}: (0,30)-(0,32): Error CS0266: {string.Format(CSharpResources.ERR_NoImplicitConvCast, "long", "int")}"], InspectDiagnostics(emitDiagnostics)); + AssertEx.Equal([$"proj: {document2.FilePath}: (0,30)-(0,32): Error CS0266: {string.Format(CSharpResources.ERR_NoImplicitConvCast, "long", "int")}"], InspectDiagnostics(results.Diagnostics)); EndDebuggingSession(debuggingSession); @@ -2186,8 +2176,8 @@ public async Task HasChanges() Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: "NonexistentFile.cs", CancellationToken.None)); // All projects must have no errors. - var (updates, _) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Blocked, results.ModuleUpdates.Status); // add a project: @@ -2552,8 +2542,8 @@ public async Task Project_Add() .AddTestDocument(sourceB1, path: sourceFileB.Path, out var documentBId).Project.Solution .AddProjectReference(projectAId, new ProjectReference(projectBId)); - var runningProjects = ImmutableDictionary.Empty - .Add(projectAId, new RunningProjectInfo() { AllowPartialUpdate = true, RestartWhenChangesHaveNoEffect = false }); + var runningProjects = ImmutableDictionary.Empty + .Add(projectAId, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false }); var results = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); @@ -2593,8 +2583,8 @@ public async Task ProjectReference_Add() // Add project reference A -> B solution = solution.AddProjectReference(projectAId, new ProjectReference(projectBId)); - var runningProjects = ImmutableDictionary.Empty - .Add(projectAId, new RunningProjectInfo() { AllowPartialUpdate = true, RestartWhenChangesHaveNoEffect = false }); + var runningProjects = ImmutableDictionary.Empty + .Add(projectAId, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false }); var results = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); @@ -2687,11 +2677,11 @@ public async Task Project_Add_BinaryAlreadyLoaded() // update document with a valid change: solution = solution.WithDocumentText(documentB2.Id, CreateText("class B { int F() => 2; }")); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); // TODO: https://github.com/dotnet/roslyn/issues/1204 // verify valid update - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); ExitBreakState(debuggingSession); @@ -2832,12 +2822,12 @@ int M() """)); // validate solution update status and emit - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check that no types have been updated. this used to throw - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.UpdatedTypes); debuggingSession.DiscardSolutionUpdate(); @@ -2871,12 +2861,13 @@ public async Task Capabilities_SynthesizedNewType() AssertEx.Empty(diagnostics); // They are reported as emit diagnostics - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - AssertEx.Equal([$"{project.FilePath}: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics)); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + AssertEx.Equal([$"proj: : Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(results.Diagnostics)); // no emitted delta: - Assert.Empty(updates.Updates); + Assert.Empty(results.ModuleUpdates.Updates); + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); } @@ -2903,11 +2894,11 @@ public async Task ValidSignificantChange_EmitError() AssertEx.Empty(diagnostics1); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - AssertEx.Equal([$"{document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(emitDiagnostics)); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + AssertEx.Equal([$"proj: {document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(results.Diagnostics)); // no emitted delta: - Assert.Empty(updates.Updates); + Assert.Empty(results.ModuleUpdates.Updates); // no pending update: Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate()); @@ -2919,9 +2910,9 @@ public async Task ValidSignificantChange_EmitError() Assert.Empty(debuggingSession.EditSession.NonRemappableRegions); // solution update status after discarding an update (still has update ready): - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status); - AssertEx.Equal([$"{document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(emitDiagnostics)); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Blocked, results.ModuleUpdates.Status); + AssertEx.Equal([$"proj: {document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(results.Diagnostics)); EndDebuggingSession(debuggingSession); @@ -2996,10 +2987,10 @@ public async Task ValidSignificantChange_ApplyBeforeFileWatcherEvent(bool saveDo } // EnC service queries for a document, which triggers read of the source file from disk. - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); ExitBreakState(debuggingSession); @@ -3010,16 +3001,16 @@ public async Task ValidSignificantChange_ApplyBeforeFileWatcherEvent(bool saveDo solution = solution.WithDocumentText(documentId, CreateTextFromFile(sourceFile.Path)); var document3 = solution.Projects.Single().Documents.Single(); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); if (saveDocument) { - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); } else { - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); debuggingSession.DiscardSolutionUpdate(); } @@ -3070,11 +3061,11 @@ public async Task ValidSignificantChange_FileUpdateNotObservedBeforeDebuggingSes AssertEx.Empty(diagnostics); // since the document is out-of-sync we need to call update to determine whether we have changes to apply or not: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); // TODO: warning reported https://github.com/dotnet/roslyn/issues/78125 - // AssertEx.Equal([$"test.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics)); + // AssertEx.Equal([$"test.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(results.Diagnostics)); // undo: solution = solution.WithDocumentText(documentId, CreateText(source1)); @@ -3088,14 +3079,14 @@ public async Task ValidSignificantChange_FileUpdateNotObservedBeforeDebuggingSes Assert.Equal(CommittedSolution.DocumentState.OutOfSync, state); sourceFile.WriteAllText(source1, Encoding.UTF8); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); AssertEx.Equal( [ - $"{project.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourceFile.Path)}" - ], InspectDiagnostics(emitDiagnostics)); + $"test: {document3.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourceFile.Path)}" + ], InspectDiagnostics(results.Diagnostics)); // the content actually hasn't changed: - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); EndDebuggingSession(debuggingSession); } @@ -3158,10 +3149,10 @@ public async Task ValidSignificantChange_AddedFileNotObservedBeforeDebuggingSess // AssertEx.Equal(new[] { $"({activeLineSpan1}, LeafFrame)" }, spans.Single().Select(s => s.ToString())); // No changes. - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); - AssertEx.Empty(emitDiagnostics); + AssertEx.Empty(results.Diagnostics); EndDebuggingSession(debuggingSession); } @@ -3197,10 +3188,10 @@ public async Task ValidSignificantChange_DocumentOutOfSync(bool delayLoad) EnterBreakState(debuggingSession); // no changes have been made to the project - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.ModuleUpdates.Updates); + Assert.Empty(results.Diagnostics); // a file watcher observed a change and updated the document, so it now reflects the content on disk (the code that we compiled): solution = solution.WithDocumentText(document1.Id, CreateText(sourceOnDisk)); @@ -3210,9 +3201,9 @@ public async Task ValidSignificantChange_DocumentOutOfSync(bool delayLoad) Assert.Empty(diagnostics); // the content of the file is now exactly the same as the compiled document, so there is no change to be applied: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); EndDebuggingSession(debuggingSession); @@ -3243,10 +3234,10 @@ public async Task ValidSignificantChange_EmitSuccessful(bool breakMode, bool com AssertEx.Empty(diagnostics1); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - ValidateDelta(updates.Updates.Single()); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + ValidateDelta(results.ModuleUpdates.Updates.Single()); void ValidateDelta(ManagedHotReloadUpdate delta) { @@ -3265,7 +3256,7 @@ void ValidateDelta(ManagedHotReloadUpdate delta) // the update should be stored on the service: var pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate(); var newBaseline = pendingUpdate.ProjectBaselines.Single(); - AssertEx.Equal(updates.Updates, pendingUpdate.Deltas); + AssertEx.Equal(results.ModuleUpdates.Updates, pendingUpdate.Deltas); Assert.Equal(document2.Project.Id, newBaseline.ProjectId); Assert.Equal(moduleId, newBaseline.EmitBaseline.OriginalMetadata.GetModuleVersionId()); @@ -3293,9 +3284,9 @@ void ValidateDelta(ManagedHotReloadUpdate delta) Assert.Same(newBaseline.EmitBaseline, debuggingSession.GetTestAccessor().GetProjectBaselines(document2.Project.Id).Single().EmitBaseline); // solution update status after committing an update: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); } else { @@ -3305,11 +3296,11 @@ void ValidateDelta(ManagedHotReloadUpdate delta) Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate()); // solution update status after committing an update: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); - ValidateDelta(updates.Updates.Single()); + ValidateDelta(results.ModuleUpdates.Updates.Single()); debuggingSession.DiscardSolutionUpdate(); } @@ -3381,12 +3372,12 @@ public async Task ValidSignificantChange_EmitSuccessful_UpdateDeferred(bool comm var document2 = solution.GetDocument(document1.Id); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); // delta to apply: - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.ActiveStatements); Assert.NotEmpty(delta.ILDelta); Assert.NotEmpty(delta.MetadataDelta); @@ -3432,9 +3423,9 @@ public async Task ValidSignificantChange_EmitSuccessful_UpdateDeferred(bool comm var document3 = solution.GetDocument(document1.Id); solution = solution.WithDocumentText(document3.Id, CreateText("class C1 { void M1() { int a = 3; System.Console.WriteLine(a); } void M2() { System.Console.WriteLine(2); } }")); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); debuggingSession.DiscardSolutionUpdate(); } else @@ -3500,12 +3491,12 @@ partial class C { int Y = 2; } """)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check emitted delta: - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.ActiveStatements); Assert.NotEmpty(delta.ILDelta); Assert.NotEmpty(delta.MetadataDelta); @@ -3517,10 +3508,9 @@ partial class C { int Y = 2; } EndDebuggingSession(debuggingSession); } - [Theory] - [CombinatorialData] + [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/78244")] - public async Task MultiProjectUpdates_ValidSignificantChange_RudeEdit(bool allowPartialUpdate) + public async Task MultiProjectUpdates_ValidSignificantChange_RudeEdit() { using var _ = CreateWorkspace(out var solution, out var service); @@ -3580,31 +3570,21 @@ void F() {} InspectDiagnostics(docDiagnostics)); // validate solution update status and emit: - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); AssertEx.SequenceEqual(["ENC0023"], InspectDiagnosticIds(results.Diagnostics)); - if (allowPartialUpdate) - { - AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRebuild); - AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRestart.Keys); - - var delta = results.ModuleUpdates.Updates.Single(); - Assert.NotEmpty(delta.ILDelta); - Assert.NotEmpty(delta.MetadataDelta); - Assert.NotEmpty(delta.PdbDelta); - Assert.Equal(1, delta.UpdatedMethods.Length); + AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRebuild); + AssertEx.SetEqual([documentBId.ProjectId], results.ProjectsToRestart.Keys); - debuggingSession.DiscardSolutionUpdate(); - } - else - { - AssertEx.SetEqual([documentAId.ProjectId, documentBId.ProjectId], results.ProjectsToRebuild); - AssertEx.SetEqual([documentAId.ProjectId, documentBId.ProjectId], results.ProjectsToRestart.Keys); + var delta = results.ModuleUpdates.Updates.Single(); + Assert.NotEmpty(delta.ILDelta); + Assert.NotEmpty(delta.MetadataDelta); + Assert.NotEmpty(delta.PdbDelta); + Assert.Equal(1, delta.UpdatedMethods.Length); - Assert.Empty(results.ModuleUpdates.Updates); - } + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); } @@ -3672,21 +3652,19 @@ static B() // TODO: Set RestartWhenChangesHaveNoEffect=true and AllowPartialUpdate=true // https://github.com/dotnet/roslyn/issues/78244 - var runningProjects = ImmutableDictionary.Empty - .Add(projectAId, new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = false, AllowPartialUpdate = false }) - .Add(projectBId, new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = false, AllowPartialUpdate = false }); + var runningProjects = ImmutableDictionary.Empty + .Add(projectAId, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false }) + .Add(projectBId, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false }); // emit updates: - var result = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); - - AssertEx.SetEqual([], result.ProjectsToRestart.Select(p => p.Key.DebugName)); + var results = await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects, s_noActiveSpans, CancellationToken.None); - var updates = result.ModuleUpdates; - AssertEx.SequenceEqual(["ENC0118"], InspectDiagnosticIds(result.GetAllDiagnostics())); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + AssertEx.SetEqual([], results.ProjectsToRestart.Select(p => p.Key.DebugName)); + AssertEx.SequenceEqual(["ENC0118"], InspectDiagnosticIds(results.GetAllDiagnostics())); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check emitted delta: - Assert.Equal(2, updates.Updates.Length); + Assert.Equal(2, results.ModuleUpdates.Updates.Length); // Process will be restarted, so discard all updates: debuggingSession.DiscardSolutionUpdate(); @@ -3737,12 +3715,12 @@ class C { int Y => 2; } """)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check emitted delta: - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.ActiveStatements); Assert.NotEmpty(delta.ILDelta); Assert.NotEmpty(delta.MetadataDelta); @@ -3792,7 +3770,7 @@ class C { int Y => 2; } """)); // validate solution update status and emit: - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); var generatedFilePath = Path.Combine( TempRoot.Root, @@ -3804,6 +3782,7 @@ class C { int Y => 2; } [$"proj: {generatedFilePath}: (0,0)-(0,56): Error ENC0021: {string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.attribute)}"], InspectDiagnostics(results.Diagnostics)); + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); } @@ -3852,12 +3831,12 @@ int M() """)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check emitted delta: - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.ActiveStatements); var lineUpdate = delta.SequencePoints.Single(); @@ -3902,12 +3881,12 @@ partial class C { int X = 1; } """)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check emitted delta: - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.ActiveStatements); Assert.NotEmpty(delta.ILDelta); Assert.NotEmpty(delta.MetadataDelta); @@ -3954,12 +3933,12 @@ class C { int Y => 1; } """)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check emitted delta: - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.ActiveStatements); Assert.NotEmpty(delta.ILDelta); Assert.NotEmpty(delta.MetadataDelta); @@ -4000,12 +3979,12 @@ class C { int Y => 1; } solution = solution.WithAnalyzerConfigDocumentText(configDocument1.Id, GetAnalyzerConfigText(configV2)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check emitted delta: - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.ActiveStatements); Assert.NotEmpty(delta.ILDelta); Assert.NotEmpty(delta.MetadataDelta); @@ -4041,12 +4020,12 @@ public async Task ValidSignificantChange_SourceGenerators_DocumentRemove() solution = document1.Project.Solution.RemoveDocument(document1.Id); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check emitted delta: - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.ActiveStatements); Assert.NotEmpty(delta.ILDelta); Assert.NotEmpty(delta.MetadataDelta); @@ -4079,9 +4058,9 @@ public async Task ValidInsignificantChange() AssertEx.Empty(diagnostics1); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); // solution has been updated: var text = await debuggingSession.LastCommittedSolution.GetRequiredProject(document1.Project.Id).GetRequiredDocument(document1.Id).GetTextAsync(); @@ -4123,12 +4102,13 @@ public async Task RudeEdit() AssertEx.Empty(diagnostics); // They are reported as emit diagnostics - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - AssertEx.Equal([$"{project.FilePath}: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics)); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + AssertEx.Equal([$"proj: : Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(results.Diagnostics)); // no emitted delta: - Assert.Empty(updates.Updates); + Assert.Empty(results.ModuleUpdates.Updates); + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); } @@ -4186,13 +4166,13 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() solution = solution.WithDocumentText(projectB.Documents.Single().Id, CreateText(source2)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.Empty(emitDiagnostics); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); - var deltaA = updates.Updates.Single(d => d.Module == moduleIdA); - var deltaB = updates.Updates.Single(d => d.Module == moduleIdB); - Assert.Equal(2, updates.Updates.Length); + var deltaA = results.ModuleUpdates.Updates.Single(d => d.Module == moduleIdA); + var deltaB = results.ModuleUpdates.Updates.Single(d => d.Module == moduleIdB); + Assert.Equal(2, results.ModuleUpdates.Updates.Length); // the update should be stored on the service: var pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate(); @@ -4219,9 +4199,9 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() Assert.Same(newBaselineA1, debuggingSession.GetTestAccessor().GetProjectBaselines(projectA.Id).Single().EmitBaseline); Assert.Same(newBaselineB1, debuggingSession.GetTestAccessor().GetProjectBaselines(projectB.Id).Single().EmitBaseline); - // solution update status after committing an update:(updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + // solution update status after committing an update:results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); ExitBreakState(debuggingSession); EnterBreakState(debuggingSession); @@ -4234,13 +4214,13 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() solution = solution.WithDocumentText(projectB.Documents.Single().Id, CreateText(source3)); // validate solution update status and emit: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); - deltaA = updates.Updates.Single(d => d.Module == moduleIdA); - deltaB = updates.Updates.Single(d => d.Module == moduleIdB); - Assert.Equal(2, updates.Updates.Length); + deltaA = results.ModuleUpdates.Updates.Single(d => d.Module == moduleIdA); + deltaB = results.ModuleUpdates.Updates.Single(d => d.Module == moduleIdB); + Assert.Equal(2, results.ModuleUpdates.Updates.Length); // the update should be stored on the service: pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate(); @@ -4273,9 +4253,9 @@ public async Task TwoUpdatesWithLoadedAndUnloadedModule() Assert.Same(newBaselineB2, debuggingSession.GetTestAccessor().GetProjectBaselines(projectB.Id).Single().EmitBaseline); // solution update status after committing an update: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); ExitBreakState(debuggingSession); EndDebuggingSession(debuggingSession); @@ -4330,10 +4310,10 @@ public async Task MultiTargetedPartiallyBuiltProjects() solution = solution.WithDocumentText(documentA.Id, text2).WithDocumentText(documentB.Id, text2); // delta emitted only for up-to-date project - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.Empty(emitDiagnostics); - AssertEx.SequenceEqual([mvidA], updates.Updates.Select(u => u.Module)); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); + AssertEx.SequenceEqual([mvidA], results.ModuleUpdates.Updates.Select(u => u.Module)); CommitSolutionUpdate(debuggingSession); @@ -4346,10 +4326,10 @@ public async Task MultiTargetedPartiallyBuiltProjects() solution = solution.WithDocumentText(documentA.Id, text0).WithDocumentText(documentB.Id, text0); // both projects are up-to-date now, but B hasn't changed w.r.t. baseline: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.Empty(emitDiagnostics); - AssertEx.SetEqual([mvidA], updates.Updates.Select(u => u.Module)); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); + AssertEx.SetEqual([mvidA], results.ModuleUpdates.Updates.Select(u => u.Module)); CommitSolutionUpdate(debuggingSession); @@ -4357,10 +4337,10 @@ public async Task MultiTargetedPartiallyBuiltProjects() solution = solution.WithDocumentText(documentA.Id, text2).WithDocumentText(documentB.Id, text2); // project B is considered stale until rebuilt (even though the document content matches now): - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.Empty(emitDiagnostics); - AssertEx.SetEqual([mvidA], updates.Updates.Select(u => u.Module)); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); + AssertEx.SetEqual([mvidA], results.ModuleUpdates.Updates.Select(u => u.Module)); CommitSolutionUpdate(debuggingSession); @@ -4371,19 +4351,19 @@ public async Task MultiTargetedPartiallyBuiltProjects() // no changes have been made: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); - Assert.Empty(emitDiagnostics); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); // update source file in the editor: var text3 = CreateText(source3); solution = solution.WithDocumentText(documentA.Id, text3).WithDocumentText(documentB.Id, text3); // both modules updated now: - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); - Assert.Empty(emitDiagnostics); - AssertEx.SetEqual([mvidA, mvidB2], updates.Updates.Select(u => u.Module)); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + Assert.Empty(results.Diagnostics); + AssertEx.SetEqual([mvidA, mvidB2], results.ModuleUpdates.Updates.Select(u => u.Module)); CommitSolutionUpdate(debuggingSession); EndDebuggingSession(debuggingSession); @@ -4430,13 +4410,13 @@ public async Task MultiTargeted_AllTargetsStale() solution = solution.WithDocumentText(documentA.Id, text2).WithDocumentText(documentB.Id, text2); // no updates - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Equal(ModuleUpdateStatus.None, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Equal(ModuleUpdateStatus.None, results.ModuleUpdates.Status); AssertEx.Equal( [ - $"{documentA.Project.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourcePath)}", - $"{documentB.Project.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourcePath)}" - ], InspectDiagnostics(emitDiagnostics)); + $"A: {documentA.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourcePath)}", + $"B: {documentB.FilePath}: (0,0)-(0,0): Warning ENC1008: {string.Format(FeaturesResources.Changing_source_file_0_in_a_stale_project_has_no_effect_until_the_project_is_rebuit, sourcePath)}" + ], InspectDiagnostics(results.Diagnostics)); EndDebuggingSession(debuggingSession); } @@ -4461,7 +4441,7 @@ public async Task ValidSignificantChange_BaselineCreationFailed_NoStream() // change the source (valid edit): solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }")); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); AssertEx.Equal( [$"proj: : Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-pdb", new FileNotFoundException().Message)}"], InspectDiagnostics(results.Diagnostics)); @@ -4497,12 +4477,13 @@ public async Task ValidSignificantChange_BaselineCreationFailed_AssemblyReadErro var document1 = solution.Projects.Single().Documents.Single(); solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }")); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); AssertEx.Equal( [$"proj: : Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-assembly", "*message*")}"], InspectDiagnostics(results.Diagnostics)); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + debuggingSession.DiscardSolutionUpdate(); EndDebuggingSession(debuggingSession); @@ -4961,12 +4942,12 @@ void F() solution = solution.WithDocumentText(document1.Id, CreateText(source2)); // validate solution update status and emit: - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); // check emitted delta: - var delta = updates.Updates.Single(); + var delta = results.ModuleUpdates.Updates.Single(); Assert.Empty(delta.ActiveStatements); Assert.NotEmpty(delta.ILDelta); Assert.NotEmpty(delta.MetadataDelta); @@ -5051,10 +5032,12 @@ int F() [$"{document.FilePath}: (9,8)-(9,13): Error ENC0063: {string.Format(FeaturesResources.Updating_a_0_around_an_active_statement_requires_restarting_the_application, CSharpFeaturesResources.catch_clause)}"], InspectDiagnostics(docDiagnostics)); - var results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); AssertEx.SequenceEqual(["ENC0063"], InspectDiagnosticIds(results.Diagnostics)); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); + debuggingSession.DiscardSolutionUpdate(); + // undo the change solution = solution.WithDocumentText(document.Id, CreateText(source1)); document = solution.GetDocument(document.Id); @@ -5068,7 +5051,7 @@ int F() Assert.Empty(docDiagnostics); // validate solution update status and emit (Hot Reload change): - results = await EmitSolutionUpdateAsync(debuggingSession, solution, allowPartialUpdate: false); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(results.Diagnostics); Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); @@ -5134,11 +5117,11 @@ static void F() solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV2))); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); - Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single()); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); @@ -5154,11 +5137,11 @@ static void F() solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV3))); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); - Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single()); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); @@ -5190,11 +5173,11 @@ static void F() solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV4))); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); - Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single()); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); @@ -5309,11 +5292,11 @@ static void F() var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None); Assert.Empty(diagnostics); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); - Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single()); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); @@ -5371,11 +5354,11 @@ static void F() } """))); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); - Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(0x06000003, results.ModuleUpdates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, results.ModuleUpdates.Updates.Single().UpdatedTypes.Single()); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); @@ -5478,10 +5461,10 @@ static void H() Assert.Equal(1, oldProject.DocumentIds.Count); Assert.Equal(2, newProject.DocumentIds.Count); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, modifiedSolution); - Assert.Empty(emitDiagnostics); - Assert.False(updates.Updates.IsEmpty); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, modifiedSolution); + Assert.Empty(results.Diagnostics); + Assert.False(results.ModuleUpdates.Updates.IsEmpty); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); CommitSolutionUpdate(debuggingSession); EndDebuggingSession(debuggingSession); @@ -5524,14 +5507,14 @@ public async Task MultiSession() var solution1 = solution.WithDocumentText(documentIdA, CreateText("class C { void M() { System.Console.WriteLine(" + i + "); } }")); - var result1 = await encService.EmitSolutionUpdateAsync(sessionId, solution1, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None); + var result1 = await encService.EmitSolutionUpdateAsync(sessionId, solution1, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None); Assert.Empty(result1.Diagnostics); Assert.Equal(1, result1.ModuleUpdates.Updates.Length); encService.DiscardSolutionUpdate(sessionId); var solution2 = solution1.WithDocumentText(documentIdA, CreateText(source3)); - var result2 = await encService.EmitSolutionUpdateAsync(sessionId, solution2, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None); + var result2 = await encService.EmitSolutionUpdateAsync(sessionId, solution2, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None); Assert.Equal("CS0103", result2.Diagnostics.Single().Diagnostics.Single().Id); Assert.Empty(result2.ModuleUpdates.Updates); @@ -5554,7 +5537,7 @@ public async Task Disposal() EndDebuggingSession(debuggingSession); // The folling methods shall not be called after the debugging session ended. - await Assert.ThrowsAsync(async () => await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None)); + await Assert.ThrowsAsync(async () => await debuggingSession.EmitSolutionUpdateAsync(solution, runningProjects: ImmutableDictionary.Empty, s_noActiveSpans, CancellationToken.None)); Assert.Throws(() => debuggingSession.BreakStateOrCapabilitiesChanged(inBreakState: true)); Assert.Throws(() => debuggingSession.DiscardSolutionUpdate()); Assert.Throws(() => debuggingSession.CommitSolutionUpdate()); @@ -5605,11 +5588,11 @@ public class C // lib source is updated: solution = solution.WithDocumentText(documentId, CreateText(libSource2)); - var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + var results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); - var update = updates.Updates.Single(); + var update = results.ModuleUpdates.Updates.Single(); GetModuleIds(update.MetadataDelta, out var updateModuleId, out var baseId, out var genId, out var gen); Assert.Equal(libMvid1, updateModuleId); Assert.Equal(1, gen); @@ -5629,13 +5612,13 @@ public class C } """)); - (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); - Assert.Empty(emitDiagnostics); - Assert.Equal(ModuleUpdateStatus.Ready, updates.Status); + results = await EmitSolutionUpdateAsync(debuggingSession, solution); + Assert.Empty(results.Diagnostics); + Assert.Equal(ModuleUpdateStatus.Ready, results.ModuleUpdates.Status); - Assert.Equal(2, updates.Updates.Length); - var libUpdate1 = updates.Updates.Single(u => u.Module == libMvid1); - var libUpdate2 = updates.Updates.Single(u => u.Module == libMvid2); + Assert.Equal(2, results.ModuleUpdates.Updates.Length); + var libUpdate1 = results.ModuleUpdates.Updates.Single(u => u.Module == libMvid1); + var libUpdate2 = results.ModuleUpdates.Updates.Single(u => u.Module == libMvid2); // update of the original library should chain to its previous delta: GetModuleIds(libUpdate1.MetadataDelta, out var updateModuleId1, out var baseId1, out var genId1, out var gen1); diff --git a/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs b/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs index 0e93680c4a32b..0b3120a89fa18 100644 --- a/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs +++ b/src/Features/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs @@ -53,8 +53,8 @@ private static ImmutableArray CreateProjectRudeEdits(IEnumer .OrderBy(g => g.Key) .Select(g => new ProjectDiagnostics(g.Key, [.. g.Select(e => Diagnostic.Create(EditAndContinueDiagnosticDescriptors.GetDescriptor(e.kind), Location.None))]))]; - private static ImmutableDictionary CreateRunningProjects(IEnumerable<(ProjectId id, bool noEffectRestarts)> projectIds, bool allowPartialUpdate = true) - => projectIds.ToImmutableDictionary(keySelector: e => e.id, elementSelector: e => new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = e.noEffectRestarts, AllowPartialUpdate = allowPartialUpdate }); + private static ImmutableDictionary CreateRunningProjects(IEnumerable<(ProjectId id, bool noEffectRestarts)> projectIds) + => projectIds.ToImmutableDictionary(keySelector: e => e.id, elementSelector: e => new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = e.noEffectRestarts }); private static IEnumerable Inspect(ImmutableDictionary> projectsToRestart) => projectsToRestart @@ -382,9 +382,8 @@ public void RunningProjects_NoEffectEditAndRudeEdit_SameProject() AssertEx.SetEqual([a, b], projectsToRebuild); } - [Theory] - [CombinatorialData] - public void RunningProjects_NoEffectEditAndRudeEdit_DifferentProjects(bool allowPartialUpdate) + [Fact] + public void RunningProjects_NoEffectEditAndRudeEdit_DifferentProjects() { using var _ = CreateWorkspace(out var solution); @@ -402,7 +401,7 @@ public void RunningProjects_NoEffectEditAndRudeEdit_DifferentProjects(bool allow CreateValidUpdates(p0, q), CreateProjectRudeEdits(blocking: [p1, p2], noEffect: [q]), addedUnbuiltProjects: [], - CreateRunningProjects([(r0, noEffectRestarts: false), (r1, noEffectRestarts: false), (r2, noEffectRestarts: false)], allowPartialUpdate), + CreateRunningProjects([(r0, noEffectRestarts: false), (r1, noEffectRestarts: false), (r2, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); @@ -413,24 +412,12 @@ public void RunningProjects_NoEffectEditAndRudeEdit_DifferentProjects(bool allow // ==> R0 has to restart due to rude edits in P1 and P2 // Q has update // ==> R0 has to restart due to rude edits in P1 and P2 - if (allowPartialUpdate) - { - AssertEx.Equal( - [ - "R0: [P1,P2]", - "R1: [P1]", - "R2: [P2]", - ], Inspect(projectsToRestart)); - } - else - { - AssertEx.Equal( - [ - "R0: []", - "R1: [P1]", - "R2: [P2]", - ], Inspect(projectsToRestart)); - } + AssertEx.Equal( + [ + "R0: [P1,P2]", + "R1: [P1]", + "R2: [P2]", + ], Inspect(projectsToRestart)); AssertEx.SetEqual([r0, r1, r2], projectsToRebuild); } @@ -482,7 +469,7 @@ public void RunningProjects_RudeEditAndUpdate_DependentOnRebuiltProject() CreateValidUpdates(c), CreateProjectRudeEdits(blocking: [b], noEffect: []), addedUnbuiltProjects: [], - CreateRunningProjects([(a, noEffectRestarts: false)], allowPartialUpdate: true), + CreateRunningProjects([(a, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); @@ -513,7 +500,7 @@ public void RunningProjects_AddedProject_NotImpactingRunningProject() CreateValidUpdates(c), CreateProjectRudeEdits(blocking: [], noEffect: []), addedUnbuiltProjects: [b], - CreateRunningProjects([(a, noEffectRestarts: false)], allowPartialUpdate: true), + CreateRunningProjects([(a, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); @@ -540,7 +527,7 @@ public void RunningProjects_AddedProject_ImpactingRunningProject() CreateValidUpdates(c), CreateProjectRudeEdits(blocking: [], noEffect: []), addedUnbuiltProjects: [b], - CreateRunningProjects([(a, noEffectRestarts: false), (e, noEffectRestarts: false)], allowPartialUpdate: true), + CreateRunningProjects([(a, noEffectRestarts: false), (e, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); @@ -556,9 +543,8 @@ public void RunningProjects_AddedProject_ImpactingRunningProject() AssertEx.SetEqual([a, b, e], projectsToRebuild); } - [Theory] - [CombinatorialData] - public void RunningProjects_RudeEditAndUpdate_Independent(bool allowPartialUpdate) + [Fact] + public void RunningProjects_RudeEditAndUpdate_Independent() { using var _ = CreateWorkspace(out var solution); @@ -573,31 +559,17 @@ public void RunningProjects_RudeEditAndUpdate_Independent(bool allowPartialUpdat CreateValidUpdates(c), CreateProjectRudeEdits(blocking: [d], noEffect: []), addedUnbuiltProjects: [], - CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)], allowPartialUpdate), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: false)]), out var projectsToRestart, out var projectsToRebuild); - if (allowPartialUpdate) - { - // D has rude edit => B has to restart - AssertEx.Equal(["B: [D]"], Inspect(projectsToRestart)); - AssertEx.SetEqual([b], projectsToRebuild); - } - else - { - AssertEx.Equal( - [ - "A: []", - "B: [D]", - ], Inspect(projectsToRestart)); - - AssertEx.SetEqual([a, b], projectsToRebuild); - } + // D has rude edit => B has to restart + AssertEx.Equal(["B: [D]"], Inspect(projectsToRestart)); + AssertEx.SetEqual([b], projectsToRebuild); } - [Theory] - [CombinatorialData] - public void RunningProjects_NoEffectEditAndUpdate(bool allowPartialUpdate) + [Fact] + public void RunningProjects_NoEffectEditAndUpdate() { using var _ = CreateWorkspace(out var solution); @@ -612,7 +584,7 @@ public void RunningProjects_NoEffectEditAndUpdate(bool allowPartialUpdate) CreateValidUpdates(c, d), CreateProjectRudeEdits(blocking: [], noEffect: [d]), addedUnbuiltProjects: [], - CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: true)], allowPartialUpdate), + CreateRunningProjects([(a, noEffectRestarts: false), (b, noEffectRestarts: true)]), out var projectsToRestart, out var projectsToRebuild); @@ -620,22 +592,11 @@ public void RunningProjects_NoEffectEditAndUpdate(bool allowPartialUpdate) // ==> B has to restart // C has update, A -> C, B -> C, B restarting // ==> A has to restart even though it does not restart on no-effect edits - if (allowPartialUpdate) - { - AssertEx.Equal( - [ - "A: [D]", - "B: [D]", - ], Inspect(projectsToRestart)); - } - else - { - AssertEx.Equal( - [ - "A: []", - "B: [D]", - ], Inspect(projectsToRestart)); - } + AssertEx.Equal( + [ + "A: [D]", + "B: [D]", + ], Inspect(projectsToRestart)); AssertEx.SetEqual([a, b], projectsToRebuild); } diff --git a/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs index 40e1521986f9d..0001339a8a846 100644 --- a/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/Features/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -176,9 +176,9 @@ await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution var diagnosticDescriptor1 = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile); - var runningProjects1 = new Dictionary + var runningProjects1 = new Dictionary { - { project.Id, new RunningProjectInfo() { RestartWhenChangesHaveNoEffect = true, AllowPartialUpdate = true} } + { project.Id, new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = true } } }.ToImmutableDictionary(); mockEncService.EmitSolutionUpdateImpl = (solution, runningProjects, activeStatementSpanProvider) => diff --git a/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs b/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs index 028161f3b8df0..8b1c1e1c1c1ab 100644 --- a/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs +++ b/src/Features/Test/EditAndContinue/WatchHotReloadServiceTests.cs @@ -37,15 +37,9 @@ private static Task GetCommittedDocumentTextAsync(WatchHotReloadServ .GetRequiredDocument(documentId) .GetTextAsync(); - [Theory] - [CombinatorialData] - public async Task Test(bool requireCommit) + [Fact] + public async Task Test() { - // See https://github.com/dotnet/sdk/blob/main/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs#L125 - - // Note that xUnit does not run test case of a theory in parallel, so we can set global state here: - WatchHotReloadService.RequireCommit = requireCommit; - var source1 = "class C { void M() { System.Console.WriteLine(1); } }"; var source2 = "class C { void M() { System.Console.WriteLine(2); /*2*/} }"; var source3 = "class C { void M() { System.Console.WriteLine(2); /*3*/} }"; @@ -89,10 +83,7 @@ public async Task Test(bool requireCommit) Assert.Equal(1, result.ProjectUpdates.Length); AssertEx.Equal([0x02000002], result.ProjectUpdates[0].UpdatedTypes); - if (requireCommit) - { - hotReload.CommitUpdate(); - } + hotReload.CommitUpdate(); var updatedText = await GetCommittedDocumentTextAsync(hotReload, documentIdA); Assert.Equal(source2, updatedText.ToString()); @@ -124,12 +115,9 @@ public async Task Test(bool requireCommit) AssertEx.SetEqual(["P"], result.ProjectsToRestart.Select(p => solution.GetRequiredProject(p.Key).Name)); AssertEx.SetEqual(["P"], result.ProjectsToRebuild.Select(p => solution.GetRequiredProject(p).Name)); - if (requireCommit) - { - // Emulate the user making choice to not restart. - // dotnet-watch then waits until Ctrl+R forces restart. - hotReload.DiscardUpdate(); - } + // Emulate the user making choice to not restart. + // dotnet-watch then waits until Ctrl+R forces restart. + hotReload.DiscardUpdate(); updatedText = await GetCommittedDocumentTextAsync(hotReload, documentIdA); Assert.Equal(source3, updatedText.ToString()); diff --git a/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs b/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs index 308fc11242d2c..fe27c3d83ce62 100644 --- a/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs +++ b/src/Features/TestUtilities/EditAndContinue/EditAndContinueWorkspaceTestBase.cs @@ -219,28 +219,14 @@ internal static void DiscardSolutionUpdate(DebuggingSession session) internal static void EndDebuggingSession(DebuggingSession session) => session.EndSession(out _); - internal static async Task<(ModuleUpdates updates, ImmutableArray diagnostics)> EmitSolutionUpdateAsync( - DebuggingSession session, - Solution solution, - ActiveStatementSpanProvider? activeStatementSpanProvider = null) - { - var runningProjects = solution.ProjectIds.ToImmutableDictionary( - keySelector: id => id, - elementSelector: id => new RunningProjectInfo() { AllowPartialUpdate = false, RestartWhenChangesHaveNoEffect = false }); - - var result = await session.EmitSolutionUpdateAsync(solution, runningProjects, activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None); - return (result.ModuleUpdates, result.Diagnostics.OrderBy(d => d.ProjectId.DebugName).ToImmutableArray().ToDiagnosticData(solution)); - } - internal static async ValueTask EmitSolutionUpdateAsync( DebuggingSession session, Solution solution, - bool allowPartialUpdate, ActiveStatementSpanProvider? activeStatementSpanProvider = null) { var runningProjects = solution.ProjectIds.ToImmutableDictionary( keySelector: id => id, - elementSelector: id => new RunningProjectInfo() { AllowPartialUpdate = allowPartialUpdate, RestartWhenChangesHaveNoEffect = false }); + elementSelector: id => new RunningProjectOptions() { RestartWhenChangesHaveNoEffect = false }); var results = await session.EmitSolutionUpdateAsync(solution, runningProjects, activeStatementSpanProvider ?? s_noActiveSpans, CancellationToken.None); @@ -249,26 +235,14 @@ internal static async ValueTask EmitSolutionUpdateAsy Assert.Equal(hasTransientError, results.ProjectsToRestart.Any()); Assert.Equal(hasTransientError, results.ProjectsToRebuild.Any()); - if (!allowPartialUpdate) - { - // No updates should be produced if transient error is reported: - Assert.True(!hasTransientError || results.ModuleUpdates.Updates.IsEmpty); - } - return results; } - internal static IEnumerable InspectDiagnostics(ImmutableArray actual) - => actual.Select(InspectDiagnostic); - - internal static string InspectDiagnostic(DiagnosticData diagnostic) - => $"{(string.IsNullOrWhiteSpace(diagnostic.DataLocation.MappedFileSpan.Path) ? diagnostic.ProjectId.ToString() : diagnostic.DataLocation.MappedFileSpan.ToString())}: {diagnostic.Severity} {diagnostic.Id}: {diagnostic.Message}"; - internal static IEnumerable InspectDiagnostics(ImmutableArray actual) - => actual.SelectMany(pd => pd.Diagnostics.Select(d => $"{pd.ProjectId.DebugName}: {InspectDiagnostic(d)}")); + => actual.SelectMany(pd => pd.Diagnostics.Select(d => $"{pd.ProjectId.DebugName}: {InspectDiagnostic(d)}")).Order(); internal static IEnumerable InspectDiagnostics(ImmutableArray<(ProjectId project, ImmutableArray diagnostics)> diagnostics) - => diagnostics.SelectMany(pd => pd.diagnostics.Select(d => $"{pd.project.DebugName}: {InspectDiagnostic(d)}")); + => diagnostics.SelectMany(pd => pd.diagnostics.Select(d => $"{pd.project.DebugName}: {InspectDiagnostic(d)}")).Order(); internal static string InspectDiagnostic(Diagnostic actual) => $"{Inspect(actual.Location)}: {actual.Severity} {actual.Id}: {actual.GetMessage()}"; diff --git a/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs b/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs index 4a90b5ecf172f..0d384f2ac1eee 100644 --- a/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs +++ b/src/Features/TestUtilities/EditAndContinue/MockEditAndContinueService.cs @@ -23,10 +23,9 @@ internal sealed class MockEditAndContinueService() : IEditAndContinueService public Func, bool, bool, DebuggingSessionId>? StartDebuggingSessionImpl; public Action? EndDebuggingSessionImpl; - public Func, ActiveStatementSpanProvider, EmitSolutionUpdateResults>? EmitSolutionUpdateImpl; + public Func, ActiveStatementSpanProvider, EmitSolutionUpdateResults>? EmitSolutionUpdateImpl; public Action? OnSourceFileUpdatedImpl; public Action? CommitSolutionUpdateImpl; - public Action>? UpdateBaselinesImpl; public Action? BreakStateOrCapabilitiesChangedImpl; public Action? DiscardSolutionUpdateImpl; public Func>? GetDocumentDiagnosticsImpl; @@ -40,10 +39,7 @@ public void CommitSolutionUpdate(DebuggingSessionId sessionId) public void DiscardSolutionUpdate(DebuggingSessionId sessionId) => DiscardSolutionUpdateImpl?.Invoke(); - public void UpdateBaselines(DebuggingSessionId sessionId, Solution solution, ImmutableArray rebuiltProjects) - => UpdateBaselinesImpl?.Invoke(solution, rebuiltProjects); - - public ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) + public ValueTask EmitSolutionUpdateAsync(DebuggingSessionId sessionId, Solution solution, ImmutableDictionary runningProjects, ActiveStatementSpanProvider activeStatementSpanProvider, CancellationToken cancellationToken) => new((EmitSolutionUpdateImpl ?? throw new NotImplementedException()).Invoke(solution, runningProjects, activeStatementSpanProvider)); public void EndDebuggingSession(DebuggingSessionId sessionId) diff --git a/src/VisualStudio/DevKit/Impl/EditAndContinue/ManagedHotReloadLanguageServiceBridge.cs b/src/VisualStudio/DevKit/Impl/EditAndContinue/ManagedHotReloadLanguageServiceBridge.cs index 21f848e25a5ab..a015293e27d8f 100644 --- a/src/VisualStudio/DevKit/Impl/EditAndContinue/ManagedHotReloadLanguageServiceBridge.cs +++ b/src/VisualStudio/DevKit/Impl/EditAndContinue/ManagedHotReloadLanguageServiceBridge.cs @@ -18,7 +18,7 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; [ExportBrokeredService(ManagedHotReloadLanguageServiceDescriptor.MonikerName, ManagedHotReloadLanguageServiceDescriptor.ServiceVersion, Audience = ServiceAudience.Local)] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed partial class ManagedHotReloadLanguageServiceBridge(InternalContracts.IManagedHotReloadLanguageService2 service) : IManagedHotReloadLanguageService2, IExportedBrokeredService +internal sealed partial class ManagedHotReloadLanguageServiceBridge(InternalContracts.IManagedHotReloadLanguageService3 service) : IManagedHotReloadLanguageService3, IExportedBrokeredService { ServiceRpcDescriptor IExportedBrokeredService.Descriptor => ManagedHotReloadLanguageServiceDescriptor.Descriptor; @@ -41,17 +41,23 @@ public ValueTask ExitBreakStateAsync(CancellationToken cancellationToken) public ValueTask OnCapabilitiesChangedAsync(CancellationToken cancellationToken) => service.OnCapabilitiesChangedAsync(cancellationToken); - public async ValueTask GetUpdatesAsync(CancellationToken cancellationToken) - => (await service.GetUpdatesAsync(cancellationToken).ConfigureAwait(false)).FromContract(); + [Obsolete] + public ValueTask GetUpdatesAsync(CancellationToken cancellationToken) + => throw new NotImplementedException(); - public async ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) - => (await service.GetUpdatesAsync(runningProjects, cancellationToken).ConfigureAwait(false)).FromContract(); + [Obsolete] + public ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) + => throw new NotImplementedException(); + + public async ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) + => (await service.GetUpdatesAsync(runningProjects.SelectAsArray(static info => info.ToContract()), cancellationToken).ConfigureAwait(false)).FromContract(); public ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) => service.CommitUpdatesAsync(cancellationToken); + [Obsolete] public ValueTask UpdateBaselinesAsync(ImmutableArray projectPaths, CancellationToken cancellationToken) - => service.UpdateBaselinesAsync(projectPaths, cancellationToken); + => throw new NotImplementedException(); public ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) => service.DiscardUpdatesAsync(cancellationToken); diff --git a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs index 70df9645d436f..6c21fb5f82d94 100644 --- a/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs +++ b/src/Workspaces/Remote/Core/EditAndContinue/ManagedHotReloadLanguageService.cs @@ -14,7 +14,6 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -23,12 +22,13 @@ namespace Microsoft.CodeAnalysis.EditAndContinue; [Export(typeof(IManagedHotReloadLanguageService))] [Export(typeof(IManagedHotReloadLanguageService2))] +[Export(typeof(IManagedHotReloadLanguageService3))] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed partial class ManagedHotReloadLanguageService( IServiceBrokerProvider serviceBrokerProvider, IEditAndContinueService encService, - SolutionSnapshotRegistry solutionSnapshotRegistry) : IManagedHotReloadLanguageService2 + SolutionSnapshotRegistry solutionSnapshotRegistry) : IManagedHotReloadLanguageService3 { private sealed class PdbMatchingSourceTextProvider : IPdbMatchingSourceTextProvider { @@ -156,34 +156,9 @@ public ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) return ValueTask.CompletedTask; } - public async ValueTask UpdateBaselinesAsync(ImmutableArray projectPaths, CancellationToken cancellationToken) - { - if (_disabled) - { - return; - } - - try - { - Contract.ThrowIfNull(_debuggingSession); - - var currentDesignTimeSolution = await GetCurrentDesignTimeSolutionAsync(cancellationToken).ConfigureAwait(false); - var currentCompileTimeSolution = GetCurrentCompileTimeSolution(currentDesignTimeSolution); - - _committedDesignTimeSolution = currentDesignTimeSolution; - - var projectIds = from path in projectPaths - let projectId = currentCompileTimeSolution.Projects.FirstOrDefault(project => project.FilePath == path)?.Id - where projectId != null - select projectId; - - encService.UpdateBaselines(_debuggingSession.Value, currentCompileTimeSolution, [.. projectIds]); - } - catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) - { - Disable(); - } - } + [Obsolete] + public ValueTask UpdateBaselinesAsync(ImmutableArray projectPaths, CancellationToken cancellationToken) + => throw new NotImplementedException(); public ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) { @@ -269,10 +244,15 @@ public async ValueTask HasChangesAsync(string? sourceFilePath, Cancellatio } } + [Obsolete] public ValueTask GetUpdatesAsync(CancellationToken cancellationToken) - => GetUpdatesAsync(runningProjects: [], cancellationToken); + => throw new NotImplementedException(); + + [Obsolete] + public ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) + => throw new NotImplementedException(); - public async ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) + public async ValueTask GetUpdatesAsync(ImmutableArray runningProjects, CancellationToken cancellationToken) { if (_disabled) { @@ -285,24 +265,17 @@ public async ValueTask GetUpdatesAsync(ImmutableArray.GetInstance(out var runningProjectPaths); - runningProjectPaths.AddAll(runningProjects); - - // TODO: Update once implemented: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2449700 - var runningProjectInfos = solution.Projects.Where(p => p.FilePath != null && runningProjectPaths.Contains(p.FilePath)).ToImmutableDictionary( - keySelector: static p => p.Id, - elementSelector: static p => new RunningProjectInfo { RestartWhenChangesHaveNoEffect = false, AllowPartialUpdate = false }); + var runningProjectOptions = runningProjects.ToRunningProjectOptions(solution, static info => (info.ProjectInstanceId.ProjectFilePath, info.ProjectInstanceId.TargetFramework, info.RestartAutomatically)); EmitSolutionUpdateResults.Data results; try { - results = (await encService.EmitSolutionUpdateAsync(_debuggingSession.Value, solution, runningProjectInfos, s_emptyActiveStatementProvider, cancellationToken).ConfigureAwait(false)).Dehydrate(); + results = (await encService.EmitSolutionUpdateAsync(_debuggingSession.Value, solution, runningProjectOptions, s_emptyActiveStatementProvider, cancellationToken).ConfigureAwait(false)).Dehydrate(); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { - results = EmitSolutionUpdateResults.Data.CreateFromInternalError(solution, e.Message, runningProjectInfos); + results = EmitSolutionUpdateResults.Data.CreateFromInternalError(solution, e.Message, runningProjectOptions); } // Only store the solution if we have any changes to apply, otherwise CommitUpdatesAsync/DiscardUpdatesAsync won't be called. @@ -314,11 +287,15 @@ public async ValueTask GetUpdatesAsync(ImmutableArray GetProjectPaths(IEnumerable ids) - => ids.SelectAsArray(id => solution.GetRequiredProject(id).FilePath!); + ToProjectIntanceIds(results.ProjectsToRebuild), + ToProjectIntanceIds(results.ProjectsToRestart.Keys)); + + ImmutableArray ToProjectIntanceIds(IEnumerable ids) + => ids.SelectAsArray(id => + { + var project = solution.GetRequiredProject(id); + return new ProjectInstanceId(project.FilePath!, project.State.NameAndFlavor.flavor ?? ""); + }); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { diff --git a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs index 7d5be3e678b67..e9594d527b3cf 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs @@ -141,7 +141,7 @@ public ValueTask> GetDocumentDiagnosticsAsync(Che /// Remote API. /// public ValueTask EmitSolutionUpdateAsync( - Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, ImmutableDictionary runningProjects, CancellationToken cancellationToken) + Checksum solutionChecksum, RemoteServiceCallbackId callbackId, DebuggingSessionId sessionId, ImmutableDictionary runningProjects, CancellationToken cancellationToken) { return RunServiceAsync(solutionChecksum, async solution => { @@ -182,18 +182,6 @@ public ValueTask DiscardSolutionUpdateAsync(DebuggingSessionId sessionId, Cancel }, cancellationToken); } - /// - /// Remote API. - /// - public ValueTask UpdateBaselinesAsync(Checksum solutionChecksum, DebuggingSessionId sessionId, ImmutableArray rebuiltProjects, CancellationToken cancellationToken) - { - return RunServiceAsync(solutionChecksum, solution => - { - GetService().UpdateBaselines(sessionId, solution, rebuiltProjects); - return default; - }, cancellationToken); - } - /// /// Remote API. ///